diff --git a/app/config/openapi.js b/app/config/openapi.js index f0e3151..87006fa 100644 --- a/app/config/openapi.js +++ b/app/config/openapi.js @@ -734,6 +734,22 @@ const spec = { // Declare the replay header so SDK generators surface // the field on single-create writes. headers: idempotencyReplayResponseHeader, + // Controller returns `{message, timeEntry}` — same + // envelope drift fixed for /v1/customer in #316. + // Pin the shape so SDK code-gen builds the right + // client type instead of leaving the body + // unspecified. + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + timeEntry: { $ref: '#/components/schemas/TimeEntry' }, + }, + }, + }, + }, }, 400: { description: 'Bad request — missing teCustId or teStartedAt' }, 403: { description: 'Missing or invalid authKey' }, diff --git a/tests/api/openapi.test.js b/tests/api/openapi.test.js index 2d2f90f..7c33af3 100644 --- a/tests/api/openapi.test.js +++ b/tests/api/openapi.test.js @@ -70,6 +70,21 @@ describe('OpenAPI spec', () => { expect(schemas.TimeEntry.properties.teStartedAt).toBeDefined(); }); + test('POST /v1/timeentry 201 declares the {message, timeEntry} envelope', async () => { + // Same envelope-drift fix as #316 (customer POST) and #312 + // (customer GET). Pre-fix the timeentry POST 201 had no + // content schema at all, so SDK code-gen modeled the body + // as a bare untyped 201 — strictly worse than the customer + // case because the row inside the envelope was unreachable + // from generated client types. + const res = await request(app).get('/openapi.json'); + const r201 = res.body.paths['/v1/timeentry'].post.responses['201']; + const schema = r201.content['application/json'].schema; + expect(schema.type).toBe('object'); + expect(schema.properties.message).toBeDefined(); + expect(schema.properties.timeEntry.$ref).toBe('#/components/schemas/TimeEntry'); + }); + test('POST /v1/customer 201 declares the {message, customer} envelope', async () => { // Same envelope-drift fix as the GET endpoint below (#312) // but for the create path. The controller responds with