From 7acd103226726d848bae81aabd564d21dea1ff1c Mon Sep 17 00:00:00 2001 From: Efe Baran Durmaz Date: Wed, 6 May 2026 21:11:25 +0300 Subject: [PATCH] fix(trust-graph): map write identity lookup errors --- docs/api/trust-graph.yaml | 10 +++ services/trust-graph/src/routes/trust.ts | 21 ++++-- services/trust-graph/test/routes.test.ts | 88 ++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/docs/api/trust-graph.yaml b/docs/api/trust-graph.yaml index 2834e71..7809020 100644 --- a/docs/api/trust-graph.yaml +++ b/docs/api/trust-graph.yaml @@ -230,6 +230,12 @@ paths: id: type: string format: uuid + "400": + $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "503": + $ref: "#/components/responses/ServiceUnavailable" "500": $ref: "#/components/responses/InternalError" @@ -288,6 +294,10 @@ paths: type: integer "400": $ref: "#/components/responses/BadRequest" + "404": + $ref: "#/components/responses/NotFound" + "503": + $ref: "#/components/responses/ServiceUnavailable" "500": $ref: "#/components/responses/InternalError" diff --git a/services/trust-graph/src/routes/trust.ts b/services/trust-graph/src/routes/trust.ts index a57835e..a6e08bd 100644 --- a/services/trust-graph/src/routes/trust.ts +++ b/services/trust-graph/src/routes/trust.ts @@ -82,8 +82,8 @@ export function createTrustRoutes(db: DbClient, discoveryUrl?: string) { const id = await trustService.recordIncident(db, body) return c.json({ id }, 201) } catch (error) { - const message = error instanceof Error ? error.message : 'Unknown error' - return c.json({ error: message }, 400) + const mapped = mapTrustWriteError(error) + return c.json({ error: mapped.message }, mapped.status) } }) @@ -94,8 +94,8 @@ export function createTrustRoutes(db: DbClient, discoveryUrl?: string) { const result = await trustService.recordRevocation(db, body) return c.json(result, 201) } catch (error) { - const message = error instanceof Error ? error.message : 'Unknown error' - return c.json({ error: message }, 400) + const mapped = mapTrustWriteError(error) + return c.json({ error: mapped.message }, mapped.status) } }) @@ -140,3 +140,16 @@ function mapTrustLookupError(error: unknown): { status: 404 | 500 | 503; message return { status: 500, message: 'Internal server error' } } + +function mapTrustWriteError(error: unknown): { status: 400 | 404 | 500 | 503; message: string } { + const lookup = mapTrustLookupError(error) + if (lookup.status !== 500) { + return lookup + } + + if (error instanceof TrustError || error instanceof Error) { + return { status: 400, message: error.message } + } + + return { status: 400, message: 'Unknown error' } +} diff --git a/services/trust-graph/test/routes.test.ts b/services/trust-graph/test/routes.test.ts index 68076f5..6600970 100644 --- a/services/trust-graph/test/routes.test.ts +++ b/services/trust-graph/test/routes.test.ts @@ -453,6 +453,50 @@ describe('HTTP Routes', () => { expect(json.error).toContain('signed revocation record') }) + it('POST /v1/revocations should return 404 when revoker identity is absent from discovery', async () => { + const record = await signedRevocationRecord() + vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('not found', { status: 404 })))) + mockDb.select = vi.fn(() => ({ + from: vi.fn(() => ({ + where: vi.fn(() => ({ + limit: vi.fn(() => Promise.resolve([])), + })), + })), + })) + + const app = createTrustRoutes(mockDb, 'http://discovery.test') + const res = await app.request('/v1/revocations', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ record }), + }) + + expect(res.status).toBe(404) + expect((await res.json()).error).toContain('Identity not found: did:fides:principal') + }) + + it('POST /v1/revocations should return 503 when discovery is unavailable for revoker identity', async () => { + const record = await signedRevocationRecord() + vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('unavailable', { status: 503 })))) + mockDb.select = vi.fn(() => ({ + from: vi.fn(() => ({ + where: vi.fn(() => ({ + limit: vi.fn(() => Promise.resolve([])), + })), + })), + })) + + const app = createTrustRoutes(mockDb, 'http://discovery.test') + const res = await app.request('/v1/revocations', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ record }), + }) + + expect(res.status).toBe(503) + expect((await res.json()).error).toContain('Discovery service unavailable') + }) + it('GET /v1/revocations/:did should return latest revocation state', async () => { const record = await signedRevocationRecord() mockDb.select = vi.fn(() => ({ @@ -522,6 +566,50 @@ describe('HTTP Routes', () => { expect(json.error).toContain('signed incident record') }) + it('POST /v1/incidents should return 404 when reporter identity is absent from discovery', async () => { + const record = await signedIncidentRecord() + vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('not found', { status: 404 })))) + mockDb.select = vi.fn(() => ({ + from: vi.fn(() => ({ + where: vi.fn(() => ({ + limit: vi.fn(() => Promise.resolve([])), + })), + })), + })) + + const app = createTrustRoutes(mockDb, 'http://discovery.test') + const res = await app.request('/v1/incidents', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ actorDid: record.actor, record }), + }) + + expect(res.status).toBe(404) + expect((await res.json()).error).toContain('Identity not found: did:fides:principal') + }) + + it('POST /v1/incidents should return 503 when discovery is unavailable for reporter identity', async () => { + const record = await signedIncidentRecord() + vi.stubGlobal('fetch', vi.fn(() => Promise.resolve(new Response('unavailable', { status: 503 })))) + mockDb.select = vi.fn(() => ({ + from: vi.fn(() => ({ + where: vi.fn(() => ({ + limit: vi.fn(() => Promise.resolve([])), + })), + })), + })) + + const app = createTrustRoutes(mockDb, 'http://discovery.test') + const res = await app.request('/v1/incidents', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ actorDid: record.actor, record }), + }) + + expect(res.status).toBe(503) + expect((await res.json()).error).toContain('Discovery service unavailable') + }) + it('POST /v1/incidents should accept agentd incident payloads', async () => { const app = createTrustRoutes(mockDb) const record = await signedAgentdIncidentRecord()