diff --git a/packages/cli/src/modules/mcp-registry/__tests__/mcp-registry-test.controller.test.ts b/packages/cli/src/modules/mcp-registry/__tests__/mcp-registry-test.controller.test.ts index 3a8de4b6ec9..7b4abe3c657 100644 --- a/packages/cli/src/modules/mcp-registry/__tests__/mcp-registry-test.controller.test.ts +++ b/packages/cli/src/modules/mcp-registry/__tests__/mcp-registry-test.controller.test.ts @@ -3,13 +3,34 @@ import { mock } from 'jest-mock-extended'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { McpRegistryTestController } from '../mcp-registry-test.controller'; +import { McpRegistryServerEntity } from '../registry/mcp-registry-server.entity'; import type { McpRegistryServerRepository } from '../registry/mcp-registry-server.repository'; import type { McpRegistryService } from '../registry/mcp-registry.service'; import { toEntity } from '../registry/mcp-registry.types'; import { notionMockServer, linearMockServer } from '../registry/mock-servers'; describe('McpRegistryTestController', () => { + const deleteQueryBuilder = { + delete: jest.fn().mockReturnThis(), + from: jest.fn().mockReturnThis(), + execute: jest.fn().mockResolvedValue({}), + }; + + const transactionManager = { + createQueryBuilder: jest.fn().mockReturnValue(deleteQueryBuilder), + insert: jest.fn().mockResolvedValue({}), + }; + + const manager = { + transaction: jest.fn( + async (runInTransaction: (m: typeof transactionManager) => Promise) => + await runInTransaction(transactionManager), + ), + }; + const repository = mock(); + Object.assign(repository, { manager }); + const service = mock(); const controller = new McpRegistryTestController(repository, service); @@ -25,15 +46,17 @@ describe('McpRegistryTestController', () => { }); describe('seed', () => { - it('should upsert mock servers and trigger registry reload', async () => { - repository.upsert.mockResolvedValue({} as never); + it('should replace mock servers and trigger registry reload', async () => { service.handleReloadMcpRegistry.mockResolvedValue(); const result = await controller.seed(); - expect(repository.upsert).toHaveBeenCalledWith( + expect(manager.transaction).toHaveBeenCalled(); + expect(deleteQueryBuilder.from).toHaveBeenCalledWith(McpRegistryServerEntity); + expect(deleteQueryBuilder.execute).toHaveBeenCalled(); + expect(transactionManager.insert).toHaveBeenCalledWith( + McpRegistryServerEntity, [notionMockServer, linearMockServer].map(toEntity), - ['id'], ); expect(service.handleReloadMcpRegistry).toHaveBeenCalled(); expect(result).toEqual({ ok: true, count: 2 }); diff --git a/packages/cli/src/modules/mcp-registry/mcp-registry-test.controller.ts b/packages/cli/src/modules/mcp-registry/mcp-registry-test.controller.ts index 306055626cd..5eca498c496 100644 --- a/packages/cli/src/modules/mcp-registry/mcp-registry-test.controller.ts +++ b/packages/cli/src/modules/mcp-registry/mcp-registry-test.controller.ts @@ -2,6 +2,7 @@ import { Post, RestController } from '@n8n/decorators'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; +import { McpRegistryServerEntity } from './registry/mcp-registry-server.entity'; import { McpRegistryServerRepository } from './registry/mcp-registry-server.repository'; import { McpRegistryService } from './registry/mcp-registry.service'; import { toEntity } from './registry/mcp-registry.types'; @@ -23,7 +24,12 @@ export class McpRegistryTestController { this.assertE2ETestsEnabled(); const entities = [notionMockServer, linearMockServer].map(toEntity); - await this.repository.upsert(entities, ['id']); + + // Replace rather than upsert: a startup refresh can leave rows whose slug collides with our mocks at a different id, which `ON CONFLICT (id) DO UPDATE` does not cover. + await this.repository.manager.transaction(async (manager) => { + await manager.createQueryBuilder().delete().from(McpRegistryServerEntity).execute(); + await manager.insert(McpRegistryServerEntity, entities); + }); await this.service.handleReloadMcpRegistry(); return { ok: true, count: entities.length };