feat(mvp): phase 8 - integration, testing & deployment
Fix all build/type/lint errors (zod 3.25 compat wrapper, Svelte 5 fixes), write 115 unit tests across 10 test files, expand seed script with demo data, update Docker config with migration on startup.
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
vi.mock('../../prisma.js', () => ({
|
||||
prisma: {
|
||||
app: {
|
||||
findMany: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn()
|
||||
},
|
||||
appStatus: {
|
||||
create: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
findMany: vi.fn()
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
import { prisma } from '../../prisma.js';
|
||||
import * as appService from '../appService.js';
|
||||
|
||||
const mockApp = prisma.app as unknown as {
|
||||
findMany: ReturnType<typeof vi.fn>;
|
||||
findUnique: ReturnType<typeof vi.fn>;
|
||||
create: ReturnType<typeof vi.fn>;
|
||||
update: ReturnType<typeof vi.fn>;
|
||||
delete: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
const mockAppStatus = prisma.appStatus as unknown as {
|
||||
create: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
describe('appService', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('returns all apps', async () => {
|
||||
const apps = [
|
||||
{ id: '1', name: 'App1', statuses: [] },
|
||||
{ id: '2', name: 'App2', statuses: [] }
|
||||
];
|
||||
mockApp.findMany.mockResolvedValue(apps);
|
||||
|
||||
const result = await appService.findAll();
|
||||
|
||||
expect(result).toEqual(apps);
|
||||
expect(mockApp.findMany).toHaveBeenCalledWith({
|
||||
where: {},
|
||||
orderBy: { name: 'asc' },
|
||||
include: { statuses: { orderBy: { checkedAt: 'desc' }, take: 1 } }
|
||||
});
|
||||
});
|
||||
|
||||
it('filters by category', async () => {
|
||||
mockApp.findMany.mockResolvedValue([]);
|
||||
|
||||
await appService.findAll({ category: 'media' });
|
||||
|
||||
expect(mockApp.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { category: 'media' }
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('filters by search term', async () => {
|
||||
mockApp.findMany.mockResolvedValue([]);
|
||||
|
||||
await appService.findAll({ search: 'grafana' });
|
||||
|
||||
const call = mockApp.findMany.mock.calls[0][0];
|
||||
expect(call.where.OR).toBeDefined();
|
||||
expect(call.where.OR).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('returns app when found', async () => {
|
||||
const app = { id: '1', name: 'App', statuses: [], createdBy: null };
|
||||
mockApp.findUnique.mockResolvedValue(app);
|
||||
|
||||
const result = await appService.findById('1');
|
||||
expect(result).toEqual(app);
|
||||
});
|
||||
|
||||
it('throws when not found', async () => {
|
||||
mockApp.findUnique.mockResolvedValue(null);
|
||||
await expect(appService.findById('missing')).rejects.toThrow('App not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('creates app with required fields', async () => {
|
||||
const input = { name: 'New App', url: 'https://app.local' };
|
||||
const created = { id: '1', ...input };
|
||||
mockApp.create.mockResolvedValue(created);
|
||||
|
||||
const result = await appService.create(input);
|
||||
|
||||
expect(result.id).toBe('1');
|
||||
expect(mockApp.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({
|
||||
name: 'New App',
|
||||
url: 'https://app.local',
|
||||
healthcheckEnabled: false,
|
||||
healthcheckInterval: 300
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
it('updates specified fields', async () => {
|
||||
mockApp.findUnique.mockResolvedValue({ id: '1' });
|
||||
mockApp.update.mockResolvedValue({ id: '1', name: 'Updated' });
|
||||
|
||||
const result = await appService.update('1', { name: 'Updated' });
|
||||
|
||||
expect(mockApp.update).toHaveBeenCalledWith({
|
||||
where: { id: '1' },
|
||||
data: { name: 'Updated' }
|
||||
});
|
||||
expect(result.name).toBe('Updated');
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', () => {
|
||||
it('deletes app', async () => {
|
||||
mockApp.findUnique.mockResolvedValue({ id: '1' });
|
||||
mockApp.delete.mockResolvedValue({});
|
||||
|
||||
await appService.remove('1');
|
||||
|
||||
expect(mockApp.delete).toHaveBeenCalledWith({ where: { id: '1' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordStatus', () => {
|
||||
it('creates a status record', async () => {
|
||||
const status = { id: 's1', appId: '1', status: 'online', responseTime: 150 };
|
||||
mockAppStatus.create.mockResolvedValue(status);
|
||||
|
||||
const result = await appService.recordStatus('1', 'online', 150);
|
||||
|
||||
expect(result).toEqual(status);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCategories', () => {
|
||||
it('returns unique categories', async () => {
|
||||
mockApp.findMany.mockResolvedValue([
|
||||
{ category: 'Media' },
|
||||
{ category: 'Monitoring' }
|
||||
]);
|
||||
|
||||
const result = await appService.getCategories();
|
||||
|
||||
expect(result).toEqual(['Media', 'Monitoring']);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user