feat: add MCP server and profile management API
Add validation schemas (Zod), repository pattern with Prisma, service layer with business logic (NotFoundError, ConflictError), and REST routes for MCP server and profile CRUD. 86 mcpd tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
110
src/mcpd/tests/mcp-server-service.test.ts
Normal file
110
src/mcpd/tests/mcp-server-service.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
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<typeof mockRepo>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user