import { describe, it, expect, vi, beforeEach } from 'vitest'; import { McpServerService, NotFoundError, ConflictError } from '../src/services/mcp-server.service.js'; import type { IMcpServerRepository } from '../src/repositories/interfaces.js'; function mockRepo(): IMcpServerRepository { return { findAll: vi.fn(async () => []), findById: vi.fn(async () => null), findByName: vi.fn(async () => null), create: vi.fn(async (data) => ({ id: 'new-id', name: data.name, description: data.description ?? '', packageName: data.packageName ?? null, dockerImage: null, transport: data.transport ?? 'STDIO', repositoryUrl: data.repositoryUrl ?? null, envTemplate: data.envTemplate ?? [], version: 1, createdAt: new Date(), updatedAt: new Date(), })), update: vi.fn(async (id, data) => ({ id, name: 'test', description: (data.description as string) ?? '', packageName: null, dockerImage: null, transport: 'STDIO' as const, repositoryUrl: null, envTemplate: [], version: 2, createdAt: new Date(), updatedAt: new Date(), })), delete: vi.fn(async () => {}), }; } describe('McpServerService', () => { let repo: ReturnType; let service: McpServerService; beforeEach(() => { repo = mockRepo(); service = new McpServerService(repo); }); describe('list', () => { it('returns all servers', async () => { const servers = await service.list(); expect(repo.findAll).toHaveBeenCalled(); expect(servers).toEqual([]); }); }); describe('getById', () => { it('returns server when found', async () => { const server = { id: '1', name: 'test' }; vi.mocked(repo.findById).mockResolvedValue(server as never); const result = await service.getById('1'); expect(result.id).toBe('1'); }); it('throws NotFoundError when not found', async () => { await expect(service.getById('missing')).rejects.toThrow(NotFoundError); }); }); describe('create', () => { it('creates a server with valid input', async () => { const result = await service.create({ name: 'my-server' }); expect(result.name).toBe('my-server'); expect(repo.create).toHaveBeenCalled(); }); it('throws ConflictError when name exists', async () => { vi.mocked(repo.findByName).mockResolvedValue({ id: '1', name: 'existing' } as never); await expect(service.create({ name: 'existing' })).rejects.toThrow(ConflictError); }); it('throws on invalid input', async () => { await expect(service.create({ name: '' })).rejects.toThrow(); }); }); describe('update', () => { it('updates an existing server', async () => { vi.mocked(repo.findById).mockResolvedValue({ id: '1', name: 'test' } as never); await service.update('1', { description: 'updated' }); expect(repo.update).toHaveBeenCalledWith('1', { description: 'updated' }); }); it('throws NotFoundError when server does not exist', async () => { await expect(service.update('missing', {})).rejects.toThrow(NotFoundError); }); }); describe('delete', () => { it('deletes an existing server', async () => { vi.mocked(repo.findById).mockResolvedValue({ id: '1', name: 'test' } as never); await service.delete('1'); expect(repo.delete).toHaveBeenCalledWith('1'); }); it('throws NotFoundError when server does not exist', async () => { await expect(service.delete('missing')).rejects.toThrow(NotFoundError); }); }); });