From 036d17d8f0de25f6b152c3539008a2163a39a961 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 20 Mar 2026 01:02:36 -0700 Subject: [PATCH] fix(agents): use NotFoundError for missing resources, add deprecate+published tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - routes.ts: use NotFoundError (404) instead of BadRequestError (400) for missing agent by key and missing published version - routes.test.ts: fix expectation for unknown key (400→404), add 4 new tests: deprecate success, deprecate already-deprecated guard, GET published success, GET published 404 when none - Total: 13 agent tests (was 8) --- .../src/modules/agents/routes.test.ts | 59 ++++++++++++++++++- .../src/modules/agents/routes.ts | 6 +- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/services/platform-service/src/modules/agents/routes.test.ts b/services/platform-service/src/modules/agents/routes.test.ts index 636e8fb5..8555037c 100644 --- a/services/platform-service/src/modules/agents/routes.test.ts +++ b/services/platform-service/src/modules/agents/routes.test.ts @@ -111,12 +111,12 @@ describe('agentRoutes', () => { expect(JSON.parse(res.body).key).toBe('incident-responder'); }); - it('GET /agents/by-key/:key returns 400 for unknown key', async () => { + it('GET /agents/by-key/:key returns 404 for unknown key', async () => { repoMock.getAgentByKey.mockResolvedValue(null); const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); const res = await app.inject({ method: 'GET', url: '/api/agents/by-key/nonexistent' }); - expect(res.statusCode).toBe(400); + expect(res.statusCode).toBe(404); }); it('POST /agents/:id/versions/:versionId/publish publishes and deprecates previous', async () => { @@ -154,6 +154,61 @@ describe('agentRoutes', () => { expect(res.statusCode).toBe(400); }); + it('POST /agents/:id/versions/:versionId/deprecate marks version deprecated', async () => { + repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' }); + repoMock.getAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'published' }); + repoMock.updateAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'deprecated' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ + method: 'POST', + url: '/api/agents/agt_1/versions/agt_1:v1/deprecate', + }); + + expect(res.statusCode).toBe(200); + expect(repoMock.updateAgentVersion).toHaveBeenCalledWith('agt_1:v1', 'agt_1', { + status: 'deprecated', + }); + }); + + it('POST /agents/:id/versions/:versionId/deprecate rejects already deprecated', async () => { + repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' }); + repoMock.getAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'deprecated' }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ + method: 'POST', + url: '/api/agents/agt_1/versions/agt_1:v1/deprecate', + }); + + expect(res.statusCode).toBe(400); + }); + + it('GET /agents/:id/published returns the published version', async () => { + repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' }); + repoMock.getPublishedVersion.mockResolvedValue({ + id: 'agt_1:v2', + status: 'published', + version: 2, + }); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ method: 'GET', url: '/api/agents/agt_1/published' }); + + expect(res.statusCode).toBe(200); + expect(JSON.parse(res.body).id).toBe('agt_1:v2'); + }); + + it('GET /agents/:id/published returns 404 when none published', async () => { + repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' }); + repoMock.getPublishedVersion.mockResolvedValue(null); + + const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); + const res = await app.inject({ method: 'GET', url: '/api/agents/agt_1/published' }); + + expect(res.statusCode).toBe(404); + }); + it('DELETE /agents/:id only allows deleting draft agents', async () => { repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai', status: 'active' }); const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' }); diff --git a/services/platform-service/src/modules/agents/routes.ts b/services/platform-service/src/modules/agents/routes.ts index cce49a5f..f778c54b 100644 --- a/services/platform-service/src/modules/agents/routes.ts +++ b/services/platform-service/src/modules/agents/routes.ts @@ -1,6 +1,6 @@ import { randomUUID } from 'node:crypto'; import type { FastifyInstance } from 'fastify'; -import { BadRequestError, ForbiddenError } from '../../lib/errors.js'; +import { BadRequestError, ForbiddenError, NotFoundError } from '../../lib/errors.js'; import { AgentDefinitionDoc, AgentVersionDoc, @@ -148,7 +148,7 @@ export async function agentRoutes(app: FastifyInstance) { const access = requireAdmin(req); const { key } = req.params as { key: string }; const agent = await repo.getAgentByKey(access.productId, key); - if (!agent) throw new BadRequestError(`Agent with key '${key}' not found`); + if (!agent) throw new NotFoundError(`Agent with key '${key}' not found`); return agent; }); @@ -158,7 +158,7 @@ export async function agentRoutes(app: FastifyInstance) { const { id } = req.params as { id: string }; await repo.getAgent(id, access.productId); const published = await repo.getPublishedVersion(id); - if (!published) throw new BadRequestError('No published version found'); + if (!published) throw new NotFoundError('No published version found'); return published; });