Skip to content

Commit 1b1899f

Browse files
feat(openapi): add bearerAuth description + apiKeyAuth alias + schema descriptions
- Unquote type/scheme/bearerFormat (YAML canonical form) - Add description to bearerAuth security scheme - Add apiKeyAuth as legacy-alias security scheme (points to bearerAuth) - Add description to SuccessResponse and ErrorResponse schema components - 6 new unit tests covering all additions Closes #3780. References infra#38 (T13 Fix B sub-step).
1 parent 4f505c1 commit 1b1899f

2 files changed

Lines changed: 161 additions & 3 deletions

File tree

lib/services/tests/express.docs.unit.tests.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,155 @@
33
*/
44
import { jest, beforeEach, afterEach, describe, test, expect } from '@jest/globals';
55

6+
/**
7+
* Unit tests for core/doc/index.yml OpenAPI baseline — bearerAuth description,
8+
* apiKeyAuth alias, SuccessResponse + ErrorResponse schema descriptions.
9+
* (T13 Fix B — infra#38)
10+
*/
11+
describe('express initSwagger — core/doc/index.yml OpenAPI baseline (T13 Fix B):', () => {
12+
const mockCoreYamlDoc = {
13+
openapi: '3.0.0',
14+
info: { version: '1.0.0' },
15+
components: {
16+
securitySchemes: {
17+
bearerAuth: {
18+
type: 'http',
19+
scheme: 'bearer',
20+
bearerFormat: 'JWT',
21+
description: 'Authenticate by sending an HTTP `Authorization: Bearer <token>` header.',
22+
},
23+
apiKeyAuth: {
24+
type: 'http',
25+
scheme: 'bearer',
26+
description: 'Legacy alias for `bearerAuth`. New integrations should use `bearerAuth`.',
27+
},
28+
},
29+
schemas: {
30+
SuccessResponse: {
31+
type: 'object',
32+
description: 'Standard success envelope. data type varies by endpoint.',
33+
properties: {
34+
type: { type: 'string', enum: ['success'] },
35+
message: { type: 'string' },
36+
data: { description: 'Response payload (type varies by endpoint)' },
37+
},
38+
},
39+
ErrorResponse: {
40+
type: 'object',
41+
description: 'Standard error envelope returned on 4xx/5xx.',
42+
properties: {
43+
type: { type: 'string', enum: ['error'] },
44+
message: { type: 'string' },
45+
code: { type: 'integer' },
46+
status: { type: 'integer' },
47+
errorCode: { type: 'string' },
48+
description: { type: 'string' },
49+
error: { type: 'string', description: 'JSON-stringified error details included only in non-production environments.' },
50+
},
51+
},
52+
},
53+
},
54+
};
55+
56+
const baseConfig = {
57+
swagger: { enable: true },
58+
files: { swagger: ['/fake/core.yaml'], guides: [] },
59+
app: { title: 'Test API', description: 'Test', url: 'https://example.com' },
60+
domain: 'https://example.com',
61+
};
62+
63+
const buildMockApp = () => {
64+
const routes = {};
65+
return { get: (path, handler) => { routes[path] = handler; }, _routes: routes };
66+
};
67+
68+
const callSpecRoute = (app) => {
69+
const handler = app._routes['/api/spec.json'];
70+
if (!handler) throw new Error('/api/spec.json route not registered');
71+
let spec = null;
72+
const res = { json: (body) => { spec = body; } };
73+
handler({}, res);
74+
return spec;
75+
};
76+
77+
beforeEach(() => {
78+
jest.resetModules();
79+
jest.unstable_mockModule('fs', () => ({
80+
default: { readFileSync: jest.fn().mockReturnValue('mocked') },
81+
readFileSync: jest.fn().mockReturnValue('mocked'),
82+
}));
83+
jest.unstable_mockModule('js-yaml', () => ({
84+
default: { load: jest.fn().mockReturnValue(mockCoreYamlDoc) },
85+
load: jest.fn().mockReturnValue(mockCoreYamlDoc),
86+
}));
87+
jest.unstable_mockModule('../../helpers/guides.js', () => ({
88+
default: { loadGuides: jest.fn().mockReturnValue([]), mergeGuidesIntoSpec: jest.fn() },
89+
}));
90+
jest.unstable_mockModule('../logger.js', () => ({
91+
default: { warn: jest.fn(), info: jest.fn(), error: jest.fn() },
92+
}));
93+
});
94+
95+
afterEach(() => jest.restoreAllMocks());
96+
97+
test('bearerAuth security scheme exists in merged spec', async () => {
98+
jest.unstable_mockModule('../../../config/index.js', () => ({ default: baseConfig }));
99+
const { default: expressService } = await import('../express.js');
100+
const app = buildMockApp();
101+
expressService.initSwagger(app);
102+
const spec = callSpecRoute(app);
103+
expect(spec.components.securitySchemes.bearerAuth).toBeDefined();
104+
});
105+
106+
test('bearerAuth has a description field', async () => {
107+
jest.unstable_mockModule('../../../config/index.js', () => ({ default: baseConfig }));
108+
const { default: expressService } = await import('../express.js');
109+
const app = buildMockApp();
110+
expressService.initSwagger(app);
111+
const spec = callSpecRoute(app);
112+
expect(typeof spec.components.securitySchemes.bearerAuth.description).toBe('string');
113+
expect(spec.components.securitySchemes.bearerAuth.description.length).toBeGreaterThan(0);
114+
});
115+
116+
test('apiKeyAuth security scheme exists as legacy alias', async () => {
117+
jest.unstable_mockModule('../../../config/index.js', () => ({ default: baseConfig }));
118+
const { default: expressService } = await import('../express.js');
119+
const app = buildMockApp();
120+
expressService.initSwagger(app);
121+
const spec = callSpecRoute(app);
122+
expect(spec.components.securitySchemes.apiKeyAuth).toBeDefined();
123+
});
124+
125+
test('apiKeyAuth description mentions bearerAuth as the canonical scheme', async () => {
126+
jest.unstable_mockModule('../../../config/index.js', () => ({ default: baseConfig }));
127+
const { default: expressService } = await import('../express.js');
128+
const app = buildMockApp();
129+
expressService.initSwagger(app);
130+
const spec = callSpecRoute(app);
131+
expect(spec.components.securitySchemes.apiKeyAuth.description).toMatch(/bearerAuth/i);
132+
});
133+
134+
test('SuccessResponse schema has a description', async () => {
135+
jest.unstable_mockModule('../../../config/index.js', () => ({ default: baseConfig }));
136+
const { default: expressService } = await import('../express.js');
137+
const app = buildMockApp();
138+
expressService.initSwagger(app);
139+
const spec = callSpecRoute(app);
140+
expect(typeof spec.components.schemas.SuccessResponse.description).toBe('string');
141+
expect(spec.components.schemas.SuccessResponse.description.length).toBeGreaterThan(0);
142+
});
143+
144+
test('ErrorResponse schema has a description', async () => {
145+
jest.unstable_mockModule('../../../config/index.js', () => ({ default: baseConfig }));
146+
const { default: expressService } = await import('../express.js');
147+
const app = buildMockApp();
148+
expressService.initSwagger(app);
149+
const spec = callSpecRoute(app);
150+
expect(typeof spec.components.schemas.ErrorResponse.description).toBe('string');
151+
expect(spec.components.schemas.ErrorResponse.description.length).toBeGreaterThan(0);
152+
});
153+
});
154+
6155
/**
7156
* Unit tests for express.js initSwagger — Redoc theme polish (issue #3686).
8157
*

modules/core/doc/index.yml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ info:
44
components:
55
securitySchemes:
66
bearerAuth:
7-
type: "http"
8-
scheme: "bearer"
9-
bearerFormat: "JWT"
7+
type: http
8+
scheme: bearer
9+
bearerFormat: JWT
10+
description: |
11+
Authenticate by sending an HTTP `Authorization: Bearer <token>` header.
12+
apiKeyAuth:
13+
type: http
14+
scheme: bearer
15+
description: |
16+
Legacy alias for `bearerAuth`. New integrations should use `bearerAuth`.
1017
1118
schemas:
1219
SuccessResponse:
1320
type: object
21+
description: Standard success envelope. data type varies by endpoint.
1422
properties:
1523
type:
1624
type: string
@@ -23,6 +31,7 @@ components:
2331

2432
ErrorResponse:
2533
type: object
34+
description: Standard error envelope returned on 4xx/5xx.
2635
properties:
2736
type:
2837
type: string

0 commit comments

Comments
 (0)