diff --git a/packages/rapidoc/src/utils/spec-parser.js b/packages/rapidoc/src/utils/spec-parser.js index a3d6a350..b2d2c840 100644 --- a/packages/rapidoc/src/utils/spec-parser.js +++ b/packages/rapidoc/src/utils/spec-parser.js @@ -593,7 +593,7 @@ function groupByTags(openApiSpec, sortEndpointsBy = 'none', generateMissingTags responses: pathOrHookObj.responses, callbacks: pathOrHookObj.callbacks, deprecated: pathOrHookObj.deprecated, - security: pathOrHookObj.security, + security: pathOrHookObj.security ?? openApiSpec.security, // commonSummary: commonPathProp.summary, // commonDescription: commonPathProp.description, xBadges: pathOrHookObj['x-badges'] || undefined, diff --git a/tests/unit/spec-parser-security.test.js b/tests/unit/spec-parser-security.test.js new file mode 100644 index 00000000..124b29b3 --- /dev/null +++ b/tests/unit/spec-parser-security.test.js @@ -0,0 +1,41 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import ProcessSpec from '../../packages/rapidoc/src/utils/spec-parser.js'; + +describe('operation security inheritance', () => { + const cases = [ + ['inherits root security', [{ bearerAuth: [] }], undefined, [{ bearerAuth: [] }]], + ['preserves explicit public access', [{ bearerAuth: [] }], [], []], + ['preserves operation overrides', [{ bearerAuth: [] }], [{ otherAuth: [] }], [{ otherAuth: [] }]], + ['preserves optional authentication', [{ bearerAuth: [] }], [{}, { bearerAuth: [] }], [{}, { bearerAuth: [] }]], + ['has no requirements when neither level declares security', undefined, undefined, undefined], + ]; + + for (const [name, rootSecurity, operationSecurity, expected] of cases) { + it(name, async () => { + const operation = { responses: { 200: { description: 'OK' } } }; + const spec = { + openapi: '3.0.3', + info: { title: 'Security inheritance', version: '1.0.0' }, + paths: { '/example': { get: operation } }, + components: { + securitySchemes: { + bearerAuth: { type: 'http', scheme: 'bearer' }, + otherAuth: { type: 'http', scheme: 'basic' }, + }, + }, + }; + + if (rootSecurity !== undefined) { + spec.security = rootSecurity; + } + if (operationSecurity !== undefined) { + operation.security = operationSecurity; + } + + const result = await ProcessSpec.call({ requestUpdate() {}, dispatchEvent() {} }, spec); + assert.strictEqual(result.specLoadError, false); + assert.deepStrictEqual(result.tags[0].paths[0].security, expected); + }); + } +});