Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 55 additions & 3 deletions app/config/openapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -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': {
Expand Down
29 changes: 29 additions & 0 deletions tests/api/openapi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down