diff --git a/app/config/openapi.js b/app/config/openapi.js index 15084c2..870406a 100644 --- a/app/config/openapi.js +++ b/app/config/openapi.js @@ -629,8 +629,32 @@ const spec = { { name: 'id', in: 'path', required: true, schema: { type: 'integer' } }, ], responses: { - 200: { description: 'Found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Customer' } } } }, + // The controller wraps the row in a `{message, customer, + // customers}` envelope. The historical `customers` + // (plural) key stays for backward compat; the singular + // `customer` was added in #292 to match the + // singular-for-single-row shape every other entity + // GET uses. Surface both in the spec so SDK + // generators can reach either field — the previous + // `$ref: Customer` declaration was misleading + // (the body is the envelope, not the raw row). + 200: { + description: 'Found — wraps the row in a {message, customer, customers} envelope.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + customer: { $ref: '#/components/schemas/Customer' }, + customers: { $ref: '#/components/schemas/Customer' }, + }, + }, + }, + }, + }, 403: { description: 'Missing or invalid authKey', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, + 404: { description: 'Not found', content: { 'application/json': { schema: { $ref: '#/components/schemas/Error' } } } }, }, }, }, diff --git a/tests/api/openapi.test.js b/tests/api/openapi.test.js index 21f1432..0ba3053 100644 --- a/tests/api/openapi.test.js +++ b/tests/api/openapi.test.js @@ -70,6 +70,24 @@ describe('OpenAPI spec', () => { expect(schemas.TimeEntry.properties.teStartedAt).toBeDefined(); }); + test('GET /v1/customer/{id} 200 declares the {message, customer, customers} envelope', async () => { + // Pre-#292 the spec said the body was a bare Customer. The + // controller actually returns a `{message, customer, customers}` + // envelope (the dual key is the backward-compat wart documented + // in customercontroller.js). SDK code-gen builds the wrong type + // unless the spec mirrors the runtime shape. + const res = await request(app).get('/openapi.json'); + const r200 = res.body.paths['/v1/customer/{id}'].get.responses['200']; + const schema = r200.content['application/json'].schema; + expect(schema.type).toBe('object'); + expect(schema.properties.message).toBeDefined(); + expect(schema.properties.customer.$ref).toBe('#/components/schemas/Customer'); + expect(schema.properties.customers.$ref).toBe('#/components/schemas/Customer'); + // 404 is documented now too (controller short-circuits on missing rows). + expect(r200).toBeDefined(); + expect(res.body.paths['/v1/customer/{id}'].get.responses['404']).toBeDefined(); + }); + test('VersionInfo.viVersion pins the 1..255 bound from the validator', async () => { // Mirrors versioninfo.schema.js. SDK generators expose viVersion // as `string`; without minLength/maxLength they can't catch a