diff --git a/app/config/openapi.js b/app/config/openapi.js index 3fcd408..05006d9 100644 --- a/app/config/openapi.js +++ b/app/config/openapi.js @@ -405,7 +405,28 @@ const spec = { }, }, }, - 503: { description: 'Degraded — DB unreachable' }, + 503: { + description: 'Degraded — DB unreachable', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + status: { type: 'string', enum: ['degraded'] }, + db: { type: 'string', enum: ['down'] }, + uptime_s: { type: 'integer' }, + version: { type: 'string' }, + elapsed_ms: { type: 'number' }, + migration: { type: 'string', nullable: true }, + db_error: { + type: 'string', + description: 'The underlying DB connection error message (e.g. "ECONNREFUSED 127.0.0.1:5432"). Useful for operator debugging; never includes credentials. Present only on the 503 path.', + }, + }, + }, + }, + }, + }, }, }, }, diff --git a/tests/api/openapi.test.js b/tests/api/openapi.test.js index d0ccb38..4c97365 100644 --- a/tests/api/openapi.test.js +++ b/tests/api/openapi.test.js @@ -160,6 +160,25 @@ describe('OpenAPI spec', () => { expect(m.get.responses['200']).toBeDefined(); }); + test('/healthz 503 response documents the db_error field', async () => { + // The healthz controller (app/controllers/healthcontroller.js) + // appends a `db_error` string field to the 503 body when the + // SELECT 1 probe throws. Operators rely on this for debugging; + // SDK generators read it from the spec. Pin the schema so a + // future controller refactor that drops the field also fails + // here and the docs stay in sync with reality. + const res = await request(app).get('/openapi.json'); + const h = res.body.paths['/healthz']; + const r503 = h.get.responses['503']; + expect(r503).toBeDefined(); + const schema = r503.content + && r503.content['application/json'] + && r503.content['application/json'].schema; + expect(schema, '/healthz 503 should declare a body schema').toBeDefined(); + expect(schema.properties.db_error, 'db_error should appear in the 503 body').toBeDefined(); + expect(schema.properties.db_error.type).toBe('string'); + }); + test('GET /docs serves Swagger UI HTML', async () => { const res = await request(app).get('/docs/'); // swagger-ui-express serves HTML; we don't pin the exact body