|
3 | 3 | */ |
4 | 4 | import { jest, beforeEach, afterEach, describe, test, expect } from '@jest/globals'; |
5 | 5 |
|
| 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 | + |
6 | 155 | /** |
7 | 156 | * Unit tests for express.js initSwagger — Redoc theme polish (issue #3686). |
8 | 157 | * |
|
0 commit comments