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; findUnique: ReturnType; create: ReturnType; update: ReturnType; delete: ReturnType; }; const mockAppStatus = prisma.appStatus as unknown as { create: ReturnType; }; 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: { links: { orderBy: { order: 'asc' } }, 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']); }); }); });