diff --git a/app/config/openapi.js b/app/config/openapi.js index 42a1412..9dbc7f5 100644 --- a/app/config/openapi.js +++ b/app/config/openapi.js @@ -843,20 +843,72 @@ const spec = { summary: 'Get one time entry', security: [{ authKey: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }], - responses: { 200: { description: 'Found' }, 404: { description: 'Not found' }, 403: { description: 'Auth failure' } }, + responses: { + 200: { + description: 'Found — {message, timeEntry} envelope', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + timeEntry: { $ref: '#/components/schemas/TimeEntry' }, + }, + }, + }, + }, + }, + 404: { description: 'Not found' }, + 403: { description: 'Auth failure' }, + }, }, patch: { summary: 'Partial update of a time entry', security: [{ authKey: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }], requestBody: { content: { 'application/json': { schema: { $ref: '#/components/schemas/TimeEntry' } } } }, - responses: { 200: { description: 'Updated' }, 400: { description: 'No updatable fields supplied' }, 404: { description: 'Not found' }, 403: { description: 'Auth failure' } }, + responses: { + 200: { + description: 'Updated — {message, timeEntry} envelope', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + timeEntry: { $ref: '#/components/schemas/TimeEntry' }, + }, + }, + }, + }, + }, + 400: { description: 'No updatable fields supplied' }, + 404: { description: 'Not found' }, + 403: { description: 'Auth failure' }, + }, }, delete: { summary: 'Soft-delete a time entry', security: [{ authKey: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }], - responses: { 200: { description: 'Archived' }, 404: { description: 'Not found' }, 403: { description: 'Auth failure' } }, + responses: { + 200: { + description: 'Archived — {message, id} envelope (id echoes the deleted row\'s teId)', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + id: { type: 'integer' }, + }, + }, + }, + }, + }, + 404: { description: 'Not found' }, + 403: { description: 'Auth failure' }, + }, }, }, '/v1/timeentry/export.csv': { diff --git a/tests/api/openapi.test.js b/tests/api/openapi.test.js index 55833c7..442ed0d 100644 --- a/tests/api/openapi.test.js +++ b/tests/api/openapi.test.js @@ -129,6 +129,35 @@ describe('OpenAPI spec', () => { } }); + test('/v1/timeentry/{id} GET / PATCH / DELETE 200 declare their envelopes', async () => { + // Same missing-content-schema pattern as #312 (customer GET), + // #326 (timeentry POST), #340 (customer bycompany), #348 + // (timeentry bycompany). The single-row endpoints + // historically had only `description: ...` on the 200s. Pin + // the controller-emitted envelopes: + // GET → {message, timeEntry} + // PATCH → {message, timeEntry} + // DELETE → {message, id} (no entity body — the row is archived) + const res = await request(app).get('/openapi.json'); + const ops = res.body.paths['/v1/timeentry/{id}']; + + const getSchema = ops.get.responses['200'].content['application/json'].schema; + expect(getSchema.properties.message.type).toBe('string'); + expect(getSchema.properties.timeEntry.$ref).toBe('#/components/schemas/TimeEntry'); + + const patchSchema = ops.patch.responses['200'].content['application/json'].schema; + expect(patchSchema.properties.message.type).toBe('string'); + expect(patchSchema.properties.timeEntry.$ref).toBe('#/components/schemas/TimeEntry'); + + const deleteSchema = ops.delete.responses['200'].content['application/json'].schema; + expect(deleteSchema.properties.message.type).toBe('string'); + expect(deleteSchema.properties.id.type).toBe('integer'); + // DELETE responds with the row's id, NOT the full entity — + // pin the absence so a future "echo the deleted row back" + // refactor surfaces here. + expect(deleteSchema.properties.timeEntry).toBeUndefined(); + }); + test('GET /v1/timeentry/bycompany/{id} 200 declares the {message, count, limit, offset, timeEntries} envelope', async () => { // Parallel to the customer/bycompany declaration in #340. // Pre-fix the spec said only `description: 'OK'`; SDK code-gen