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
19 changes: 16 additions & 3 deletions app/config/openapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ const spec = {
post: {
summary: 'Create a customer',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: {
required: true,
content: {
Expand All @@ -566,6 +567,7 @@ const spec = {
post: {
summary: 'Create a time entry',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: {
required: true,
content: {
Expand Down Expand Up @@ -642,6 +644,7 @@ const spec = {
post: {
summary: 'Create a worker',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: {
required: true,
content: {
Expand Down Expand Up @@ -692,6 +695,7 @@ const spec = {
post: {
summary: 'Create a billing type',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/BillingType' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand Down Expand Up @@ -733,6 +737,7 @@ const spec = {
post: {
summary: 'Create an inventory item',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/InventoryItem' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand Down Expand Up @@ -774,6 +779,7 @@ const spec = {
post: {
summary: 'Create a company (master keys only)',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/Company' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Non-master key' } },
},
Expand Down Expand Up @@ -812,6 +818,7 @@ const spec = {
post: {
summary: 'Create a job',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/Job' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand All @@ -837,6 +844,7 @@ const spec = {
post: {
summary: 'Create an invoice',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/Invoice' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand All @@ -862,6 +870,7 @@ const spec = {
post: {
summary: 'Create a customer payment',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/CustomerPayment' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand All @@ -887,6 +896,7 @@ const spec = {
post: {
summary: 'Create an invoice line (job → invoice)',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/InvoiceJob' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand All @@ -912,6 +922,7 @@ const spec = {
post: {
summary: 'Create a product entry',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/ProductEntry' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand All @@ -937,6 +948,7 @@ const spec = {
post: {
summary: 'Create a version info record (master keys only)',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/VersionInfo' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Non-master key' } },
},
Expand All @@ -959,6 +971,7 @@ const spec = {
post: {
summary: 'Create a PO vendor',
security: [{ authKey: [] }],
parameters: [idempotencyKeyHeader],
requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PurchaseOrderVendor' } } } },
responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } },
},
Expand All @@ -981,7 +994,7 @@ const spec = {
},
},
'/v1/purchaseorderheader': {
post: { summary: 'Create a PO header', security: [{ authKey: [] }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PurchaseOrderHeader' } } } }, responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } } },
post: { summary: 'Create a PO header', security: [{ authKey: [] }], parameters: [idempotencyKeyHeader], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PurchaseOrderHeader' } } } }, responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } } },
},
'/v1/purchaseorderheader/{id}': {
get: { summary: 'Get one PO header', security: [{ authKey: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }], responses: { 200: { description: 'Found' }, 404: { description: 'Not found' }, 403: { description: 'Auth failure' } } },
Expand All @@ -1001,7 +1014,7 @@ const spec = {
},
},
'/v1/purchaseorderline': {
post: { summary: 'Create a PO line', security: [{ authKey: [] }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PurchaseOrderLine' } } } }, responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } } },
post: { summary: 'Create a PO line', security: [{ authKey: [] }], parameters: [idempotencyKeyHeader], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PurchaseOrderLine' } } } }, responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } } },
},
'/v1/purchaseorderline/{id}': {
get: { summary: 'Get one PO line', security: [{ authKey: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }], responses: { 200: { description: 'Found' }, 404: { description: 'Not found' }, 403: { description: 'Auth failure' } } },
Expand All @@ -1021,7 +1034,7 @@ const spec = {
},
},
'/v1/inventorytransaction': {
post: { summary: 'Create an inventory transaction', security: [{ authKey: [] }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/InventoryTransaction' } } } }, responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } } },
post: { summary: 'Create an inventory transaction', security: [{ authKey: [] }], parameters: [idempotencyKeyHeader], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/InventoryTransaction' } } } }, responses: { 201: { description: 'Created' }, 400: { description: 'Bad request' }, 403: { description: 'Auth failure' } } },
},
'/v1/inventorytransaction/{id}': {
get: { summary: 'Get one inventory transaction', security: [{ authKey: [] }], parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'integer' } }], responses: { 200: { description: 'Found' }, 404: { description: 'Not found' }, 403: { description: 'Auth failure' } } },
Expand Down
25 changes: 25 additions & 0 deletions tests/api/openapi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,31 @@ describe('OpenAPI spec', () => {
}
});

test('single-create POSTs document the Idempotency-Key header', async () => {
// The middleware applies to every /v1/* POST, so the spec
// should advertise the header on the single-create endpoints
// too — not just the bulk variants. We don't pin the 409
// response on single POSTs (the same-key-different-body case
// is rare enough that documenting just the request header is
// sufficient for SDK code-gen).
const res = await request(app).get('/openapi.json');
const targets = [
'/v1/customer', '/v1/timeentry', '/v1/worker', '/v1/billingtype',
'/v1/inventoryitem', '/v1/company', '/v1/job', '/v1/invoice',
'/v1/customerpayment', '/v1/invoicejob', '/v1/productentry',
'/v1/versioninfo', '/v1/purchaseordervendor',
'/v1/purchaseorderheader', '/v1/purchaseorderline',
'/v1/inventorytransaction',
];
for (const path of targets) {
const post = res.body.paths[path] && res.body.paths[path].post;
expect(post, `${path} POST should be documented`).toBeDefined();
const params = post.parameters || [];
const idem = params.find((p) => p.name === 'Idempotency-Key');
expect(idem, `${path} POST should document the Idempotency-Key header`).toBeDefined();
}
});

test('bulk endpoints document the Idempotency-Key header', async () => {
const res = await request(app).get('/openapi.json');
const customer = res.body.paths['/v1/customer/bulk'];
Expand Down
Loading