diff --git a/src/apitypes/rest/rest.changes.ts b/src/apitypes/rest/rest.changes.ts index 96d5c89..ccf3ece 100644 --- a/src/apitypes/rest/rest.changes.ts +++ b/src/apitypes/rest/rest.changes.ts @@ -59,10 +59,13 @@ import { calculateObjectHash } from '../../utils/hashes' import { REST_API_TYPE } from './rest.consts' import { OpenAPIV3 } from 'openapi-types' import { + extractOperationSecurityDiffs, extractOpenapiVersionDiff, extractPathParamRenameDiff, extractRootSecurityDiffs, extractRootServersDiffs, + extractSecuritySchemesDiffs, + extractSecuritySchemesNames, validateGroupPrefix, } from './rest.utils' import { createOperationChange, getOperationTags, OperationsMap } from '../../components' @@ -167,13 +170,18 @@ export const compareDocuments = async ( let operationDiffs: Diff[] = [] if (operationPotentiallyChanged) { + const operationSecurityDiffs = extractOperationSecurityDiffs(methodData as OpenAPIV3.OperationObject) + const shouldTakeRootSecurityDiffs = operationSecurityDiffs.length === 0 && !methodData?.security + const relevantSecuritySchemesNames = shouldTakeRootSecurityDiffs ? extractSecuritySchemesNames(merged.security ?? []) : extractSecuritySchemesNames(methodData?.security ?? []) operationDiffs = [ ...(methodData as WithAggregatedDiffs)[DIFFS_AGGREGATED_META_KEY] ?? [], ...extractOpenapiVersionDiff(merged), ...extractRootServersDiffs(merged), - ...extractRootSecurityDiffs(merged), + ...shouldTakeRootSecurityDiffs ? extractRootSecurityDiffs(merged) : [], + ...extractSecuritySchemesDiffs(merged.components, relevantSecuritySchemesNames), ...extractPathParamRenameDiff(merged, path), // parameters, servers, summary, description and extensionKeys are moved from path to method in pathItemsUnification during normalization in apiDiff, so no need to aggregate them here + // note that operation security diffs are not aggregated here, because they are in aggregated diffs for operation object ] } if (operationAddedOrRemoved) { diff --git a/src/apitypes/rest/rest.operation.ts b/src/apitypes/rest/rest.operation.ts index 2a067c0..f5173d5 100644 --- a/src/apitypes/rest/rest.operation.ts +++ b/src/apitypes/rest/rest.operation.ts @@ -47,7 +47,7 @@ import { takeIfDefined, } from '../../utils' import { API_KIND, INLINE_REFS_FLAG, ORIGINS_SYMBOL, VERSION_STATUS } from '../../consts' -import { getCustomTags, resolveApiAudience } from './rest.utils' +import { extractSecuritySchemesNames, getCustomTags, resolveApiAudience } from './rest.utils' import { DebugPerformanceContext, syncDebugPerformance } from '../../utils/logs' import { calculateDeprecatedItems, @@ -142,6 +142,7 @@ export const buildRestOperation = ( const models: Record = {} const apiKind = effectiveOperationObject[REST_KIND_KEY] || document.apiKind || API_KIND.BWC const [specWithSingleOperation] = syncDebugPerformance('[ModelsAndOperationHashing]', () => { + const operationSecurity = effectiveOperationObject.security const specWithSingleOperation = createSingleOperationSpec( document.data, path, @@ -149,6 +150,7 @@ export const buildRestOperation = ( openapi, servers, security, + operationSecurity, components?.securitySchemes, ) calculateSpecRefs(document.data, refsOnlySingleOperationSpec, specWithSingleOperation, [operationId], models, componentsHashMap) @@ -320,22 +322,32 @@ const isOperationPaths = (paths: JsonPath[]): boolean => { // todo output of this method disrupts document normalization. // origin symbols are not being transferred to the resulting spec. // DO NOT pass output of this method to apiDiff -const createSingleOperationSpec = ( +export const createSingleOperationSpec = ( document: OpenAPIV3.Document, path: string, method: OpenAPIV3.HttpMethods, openapi?: string, servers?: OpenAPIV3.ServerObject[], security?: OpenAPIV3.SecurityRequirementObject[], + operationSecurity?: OpenAPIV3.SecurityRequirementObject[], securitySchemes?: { [p: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.SecuritySchemeObject }, ): TYPE.RestOperationData => { const pathData = document.paths[path] as OpenAPIV3.PathItemObject + // Filter security schemes to only include used ones + const effectiveSecurity = operationSecurity ?? security ?? [] + const usedSecuritySchemeNames = extractSecuritySchemesNames(effectiveSecurity) + const effectiveSecuritySchemes = securitySchemes && usedSecuritySchemeNames.size > 0 + ? Object.fromEntries( + Object.entries(securitySchemes).filter(([name]) => usedSecuritySchemeNames.has(name)), + ) + : undefined + const isRefPathData = !!pathData.$ref return { openapi: openapi ?? '3.0.0', ...takeIfDefined({ servers }), - ...takeIfDefined({ security }), // TODO: remove duplicates in security + ...!operationSecurity ? takeIfDefined({ security }) : {},// Only add root security if operation security is not explicitly defined paths: { [path]: isRefPathData ? pathData @@ -345,9 +357,9 @@ const createSingleOperationSpec = ( ...extractSymbolProperty(pathData, INLINE_REFS_FLAG), }, }, - components: { - ...takeIfDefined({ securitySchemes }), - }, + ...takeIfDefined({ + components: effectiveSecuritySchemes ? { securitySchemes: effectiveSecuritySchemes } : undefined, + }), } } diff --git a/src/apitypes/rest/rest.utils.ts b/src/apitypes/rest/rest.utils.ts index 729dfbc..c67629b 100644 --- a/src/apitypes/rest/rest.utils.ts +++ b/src/apitypes/rest/rest.utils.ts @@ -89,17 +89,55 @@ export const extractRootServersDiffs = (doc: OpenAPIV3.Document): Diff[] => { ] } -export const extractRootSecurityDiffs = (doc: OpenAPIV3.Document): Diff[] => { - const addOrRemoveSecurityDiff = (doc as WithDiffMetaRecord)[DIFF_META_KEY]?.security - const securityInternalDiffs = (doc.security as WithAggregatedDiffs | undefined)?.[DIFFS_AGGREGATED_META_KEY] ?? [] - const componentsSecuritySchemesDiffs = (doc.components?.securitySchemes as WithAggregatedDiffs>)[DIFFS_AGGREGATED_META_KEY] ?? [] +const extractSecurityDiffs = (source: WithDiffMetaRecord<{ security?: OpenAPIV3.SecurityRequirementObject[] }>): Diff[] => { + const addOrRemoveSecurityDiff = source[DIFF_META_KEY]?.security + const securityInternalDiffs = (source.security as WithAggregatedDiffs | undefined)?.[DIFFS_AGGREGATED_META_KEY] ?? [] return [ ...(addOrRemoveSecurityDiff ? [addOrRemoveSecurityDiff] : []), ...securityInternalDiffs, - ...componentsSecuritySchemesDiffs, ] } +export const extractRootSecurityDiffs = (doc: OpenAPIV3.Document): Diff[] => { + return extractSecurityDiffs(doc as WithDiffMetaRecord) +} + +export const extractOperationSecurityDiffs = (operation: OpenAPIV3.OperationObject): Diff[] => { + return extractSecurityDiffs(operation as WithDiffMetaRecord) +} + +export const extractSecuritySchemesNames = (security: OpenAPIV3.SecurityRequirementObject[]): Set => { + return new Set(security.flatMap(securityRequirement => Object.keys(securityRequirement))) +} + +export const extractSecuritySchemesDiffs = (components: OpenAPIV3.ComponentsObject | undefined, securitySchemesNames: Set): Diff[] => { + if (!components || !components.securitySchemes) { + return [] + } + const result: Diff[] = [] + + const addRemoveSecuritySchemesDiffs = (components.securitySchemes as WithDiffMetaRecord>)?.[DIFF_META_KEY] + if (addRemoveSecuritySchemesDiffs) { + for (const schemeName of securitySchemesNames) { + const diff = addRemoveSecuritySchemesDiffs[schemeName] + if (diff) { + result.push(diff) + } + } + } + + for (const schemeName of securitySchemesNames) { + const securityScheme = components.securitySchemes[schemeName] + if (securityScheme) { + const aggregatedDiffs = (securityScheme as WithAggregatedDiffs)?.[DIFFS_AGGREGATED_META_KEY] ?? [] + result.push(...aggregatedDiffs) + } + } + + + return result +} + export function validateGroupPrefix(group: unknown, paramName: string): void { if (group === undefined) { return diff --git a/test/changelog.security.test.ts b/test/changelog.security.test.ts new file mode 100644 index 0000000..839dcdd --- /dev/null +++ b/test/changelog.security.test.ts @@ -0,0 +1,286 @@ +/** + * Copyright 2024-2025 NetCracker Technology Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, test, expect } from '@jest/globals' +import { buildChangelogPackage, changesSummaryMatcher, noChangesMatcher, numberOfImpactedOperationsMatcher, operationChangesMatcher } from './helpers' +import { BREAKING_CHANGE_TYPE, NON_BREAKING_CHANGE_TYPE, UNCLASSIFIED_CHANGE_TYPE } from '../src/types/external/comparison' + +describe('Security Diff Collection', () => { + describe('Operation Explicit Security Precedence', () => { + test('Operation with explicit security ignores global security changes', async () => { + const result = await buildChangelogPackage('changelog/security/operation-security-precedence/global-changes-ignored') + + expect(result).toEqual(noChangesMatcher()) + }) + + test('Operation security changes are reported for operation with explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/operation-security-precedence/operation-security-changes') + + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + [NON_BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Operation security changes reported, global changes ignored', async () => { + const result = await buildChangelogPackage('changelog/security/operation-security-precedence/both-change') + + // Both global and operation security change + // Expected: Only operation security diffs reported + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + [NON_BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Add explicit operation security', async () => { + const result = await buildChangelogPackage('changelog/security/operation-security-precedence/adds-explicit-security') + + // Operation adds explicit security + // Expected: Diffs for adding operation security + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + }) + }) + + describe('Global Security Fallback', () => { + test('Global security changes affect operations without explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/global-security-fallback/global-security-changes') + + // Global security changes, operation has no explicit security + // Expected: Diffs from global security change + // todo: fix the ER once api-diff classification is changed + expect(result).toEqual(changesSummaryMatcher({ + [UNCLASSIFIED_CHANGE_TYPE]: 2, + })) + }) + + test('Adding global security affects operations without explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/global-security-fallback/add-global-security') + + // Global security added + // Expected: Diffs for adding global security + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Removing global security affects operations without explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/global-security-fallback/remove-global-security') + + // Global security removed + // Expected: Diffs for removing global security + expect(result).toEqual(changesSummaryMatcher({ + [NON_BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Removing operation security shows change', async () => { + const result = await buildChangelogPackage('changelog/security/global-security-fallback/removes-explicit-security') + + // Operation removes explicit security, global exists + // Expected: Diffs for removing operation security + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + }) + }) + + describe('Empty Security Array', () => { + test('Operation with explicit empty array as security ignores global changes', async () => { + const result = await buildChangelogPackage('changelog/security/empty-security-array/empty-array-global-changes') + + expect(result).toEqual(noChangesMatcher()) + }) + + test('Adding empty security array to operation', async () => { + const result = await buildChangelogPackage('changelog/security/empty-security-array/add-empty-array') + + // Operation adds security: [] + // Expected: Diffs for adding empty security + expect(result).toEqual(changesSummaryMatcher({ + [NON_BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Removing empty security array from operation causes global security to be applied', async () => { + const result = await buildChangelogPackage('changelog/security/empty-security-array/remove-empty-array') + + // Operation removes security: [] + // Expected: Diffs for removing empty security + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Changing from real security to empty array', async () => { + const result = await buildChangelogPackage('changelog/security/empty-security-array/change-to-empty-array') + + // Operation security changes to [] + // Expected: Diffs for changing operation security + expect(result).toEqual(changesSummaryMatcher({ + [NON_BREAKING_CHANGE_TYPE]: 1, + })) + }) + }) + + describe('Security Scheme Relevance', () => { + test('Security scheme used in operation security changes', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/scheme-used-in-security-changes') + + // Operation uses ApiKeyAuth, ApiKeyAuth scheme changes + // Expected: Diffs for security scheme change + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Unused scheme removed from components', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/unused-scheme-removed') + + expect(result).toEqual(noChangesMatcher()) + }) + + test('Unused scheme added to components', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/unused-scheme-added') + + expect(result).toEqual(noChangesMatcher()) + }) + + test('Unused security scheme changes are not reported', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/unused-scheme-changes') + + expect(result).toEqual(noChangesMatcher()) + }) + + test('Security scheme used in global security is not reported if operation has explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/global-scheme-changes-ignored') + + expect(result).toEqual(noChangesMatcher()) + }) + + test('Operation uses multiple schemes, several schemes changes', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/multiple-schemes-changes') + + // Operation uses ApiKeyAuth and OAuth2, ApiKeyAuth and OAuth2 changes + // Expected: Diffs for ApiKeyAuth scheme change only + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + [UNCLASSIFIED_CHANGE_TYPE]: 1, // todo: fix the ER once api-diff classification is changed + })) + }) + + test('Security scheme used in global security changes are reported if operation has no explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/used-global-scheme-changes') + + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + }) + + test('Scheme added to components and used by operation', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/add-and-use-schema-in-operation') + + // ApiKeyAuth scheme added, operation adds security using it + // Expected: Operation has diffs for adding operation security and scheme + // todo: fix the ER once api-diff classification is changed, adding schema in components should not be breaking + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 2, + })) + }) + + test('Scheme removed from components, still referenced', async () => { + const result = await buildChangelogPackage('changelog/security/scheme-relevance/scheme-removed-still-referenced') + + // ApiKeyAuth scheme removed, still referenced by operation + // Expected: Operation has diffs for removing scheme + // todo: fix the ER once api-diff classification is changed + // removing schema from components should be annotation + // security scheme for which there is not definition should be removed by api-unifier during validation? + expect(result).toEqual(changesSummaryMatcher({ + [NON_BREAKING_CHANGE_TYPE]: 1, + })) + }) + }) + + describe('Multiple Operations - Different Security Configurations', () => { + test('One operation has explicit security, another uses global, global changes', async () => { + const result = await buildChangelogPackage('changelog/security/multiple-operations/op1-explicit-op2-global') + + //todo: fix the ER once api-diff classification is changed + // both changes to root security and security scheme definitions in components are reported + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + [NON_BREAKING_CHANGE_TYPE]: 1, + [UNCLASSIFIED_CHANGE_TYPE]: 2, + })) + + // only operation without explicit security is impacted + expect(result).toEqual(numberOfImpactedOperationsMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + [NON_BREAKING_CHANGE_TYPE]: 1, + [UNCLASSIFIED_CHANGE_TYPE]: 1, + })) + expect(result).toEqual(operationChangesMatcher([ + expect.objectContaining({ + operationId: 'test2-get', + previousOperationId: 'test2-get', + }), + ])) + }) + + test('Security scheme used by one operation only', async () => { + const result = await buildChangelogPackage('changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes') + + //todo: fix the ER once api-diff classification is changed + expect(result).toEqual(changesSummaryMatcher({ + [UNCLASSIFIED_CHANGE_TYPE]: 1, + })) + + // only operation which uses changed security scheme is reported + expect(result).toEqual(numberOfImpactedOperationsMatcher({ + [UNCLASSIFIED_CHANGE_TYPE]: 1, + })) + expect(result).toEqual(operationChangesMatcher([ + expect.objectContaining({ + operationId: 'test1-get', + previousOperationId: 'test1-get', + }), + ])) + }) + + test('Security scheme used by global, affects only operations without explicit security', async () => { + const result = await buildChangelogPackage('changelog/security/multiple-operations/scheme-global-affects-subset') + + expect(result).toEqual(changesSummaryMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + + // only operation which does not have explicit security is affected by security scheme change used in global security + expect(result).toEqual(numberOfImpactedOperationsMatcher({ + [BREAKING_CHANGE_TYPE]: 1, + })) + expect(result).toEqual(operationChangesMatcher([ + expect.objectContaining({ + operationId: 'test2-get', + previousOperationId: 'test2-get', + }), + ])) + }) + }) +}) + diff --git a/test/helpers/matchers.ts b/test/helpers/matchers.ts index ac03382..7930f34 100644 --- a/test/helpers/matchers.ts +++ b/test/helpers/matchers.ts @@ -31,7 +31,11 @@ import { } from '../../src' import { JsonPath } from 'json-crawl' import { ActionType } from '@netcracker/qubership-apihub-api-diff' -import { ArrayContaining, ObjectContaining, RecursiveMatcher } from '../../.jest/jasmin' +import { ArrayContaining, AsymmetricMatcher, ExpectedRecursive, ObjectContaining, RecursiveMatcher } from '../../.jest/jasmin' +import { extractSecuritySchemesNames } from '../../src/apitypes/rest/rest.utils' +import type { OpenAPIV3 } from 'openapi-types' + +type SecuritySchemesObject = OpenAPIV3.ComponentsObject['securitySchemes'] export type ApihubComparisonMatcher = ObjectContaining & VersionsComparison export type ApihubOperationChangesMatcher = ObjectContaining & OperationChanges @@ -62,6 +66,16 @@ export function apihubOperationChangesMatcher( ]) } +export function noChangesMatcher( + apiType: OperationsApiType = REST_API_TYPE, +): ApihubChangesSummaryMatcher { + return operationTypeMatcher({ + apiType: apiType, + changesSummary: EMPTY_CHANGE_SUMMARY, + numberOfImpactedOperations: EMPTY_CHANGE_SUMMARY, + }) +} + export function changesSummaryMatcher( expected: Partial, apiType: OperationsApiType = REST_API_TYPE, @@ -92,27 +106,27 @@ export function operationTypeMatcher( expected: RecursiveMatcher, ): ApihubChangesSummaryMatcher { return expect.objectContaining({ - comparisons: expect.arrayContaining([ - expect.objectContaining({ - operationTypes: expect.arrayContaining([ - expect.objectContaining(expected), - ]), - }), - ]), - }, + comparisons: expect.arrayContaining([ + expect.objectContaining({ + operationTypes: expect.arrayContaining([ + expect.objectContaining(expected), + ]), + }), + ]), + }, ) } export function operationChangesMatcher( - expected: Array>, + expected: Array>, ): ApihubOperationChangesMatcher { return expect.objectContaining({ - comparisons: expect.arrayContaining([ - expect.objectContaining({ - data: expect.toIncludeSameMembers(expected), - }), - ]), - }, + comparisons: expect.arrayContaining([ + expect.objectContaining({ + data: expect.toIncludeSameMembers(expected), + }), + ]), + }, ) } @@ -156,8 +170,8 @@ export function notificationsMatcher( expected: Array>, ): ApihubNotificationsMatcher { return expect.objectContaining({ - notifications: expect.toIncludeSameMembers(expected), - }, + notifications: expect.toIncludeSameMembers(expected), + }, ) } @@ -174,8 +188,8 @@ export function exportDocumentsMatcher( expected: Array>, ): ApihubExportDocumentsMatcher { return expect.objectContaining({ - exportDocuments: expect.toIncludeSameMembers(expected), - }, + exportDocuments: expect.toIncludeSameMembers(expected), + }, ) } @@ -186,3 +200,31 @@ export function exportDocumentMatcher( filename: filename, }) } + +/** + * Custom matcher to verify that security schemes in result match exactly the schemes used in security requirements + */ +export function securitySchemesFromRequirementsMatcher( + securityRequirements: OpenAPIV3.SecurityRequirementObject[], +): AsymmetricMatcher { + const expectedSchemes = Array.from(extractSecuritySchemesNames(securityRequirements)) + + return { + asymmetricMatch: (actual: SecuritySchemesObject): boolean => { + if (!actual || typeof actual !== 'object') { + return false + } + + const actualSchemes = Object.keys(actual) + + // Check that all expected schemes are present + const hasAllExpected = expectedSchemes.every(scheme => actualSchemes.includes(scheme)) + + // Check that no extra schemes are present + const hasOnlyExpected = actualSchemes.every(scheme => expectedSchemes.includes(scheme)) + + return hasAllExpected && hasOnlyExpected + }, + jasmineToString: () => `securitySchemesFromRequirements(${JSON.stringify(expectedSchemes)})`, + } +} diff --git a/test/projects/changelog/security/empty-security-array/add-empty-array/after.yaml b/test/projects/changelog/security/empty-security-array/add-empty-array/after.yaml new file mode 100644 index 0000000..63fa184 --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/add-empty-array/after.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/empty-security-array/add-empty-array/before.yaml b/test/projects/changelog/security/empty-security-array/add-empty-array/before.yaml new file mode 100644 index 0000000..67b84be --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/add-empty-array/before.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/empty-security-array/change-to-empty-array/after.yaml b/test/projects/changelog/security/empty-security-array/change-to-empty-array/after.yaml new file mode 100644 index 0000000..7e55f58 --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/change-to-empty-array/after.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/empty-security-array/change-to-empty-array/before.yaml b/test/projects/changelog/security/empty-security-array/change-to-empty-array/before.yaml new file mode 100644 index 0000000..7500d81 --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/change-to-empty-array/before.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/empty-security-array/empty-array-global-changes/after.yaml b/test/projects/changelog/security/empty-security-array/empty-array-global-changes/after.yaml new file mode 100644 index 0000000..5a97c26 --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/empty-array-global-changes/after.yaml @@ -0,0 +1,29 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - OAuth2: + - read +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/empty-security-array/empty-array-global-changes/before.yaml b/test/projects/changelog/security/empty-security-array/empty-array-global-changes/before.yaml new file mode 100644 index 0000000..7e55f58 --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/empty-array-global-changes/before.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/empty-security-array/remove-empty-array/after.yaml b/test/projects/changelog/security/empty-security-array/remove-empty-array/after.yaml new file mode 100644 index 0000000..67b84be --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/remove-empty-array/after.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/empty-security-array/remove-empty-array/before.yaml b/test/projects/changelog/security/empty-security-array/remove-empty-array/before.yaml new file mode 100644 index 0000000..63fa184 --- /dev/null +++ b/test/projects/changelog/security/empty-security-array/remove-empty-array/before.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/global-security-fallback/add-global-security/after.yaml b/test/projects/changelog/security/global-security-fallback/add-global-security/after.yaml new file mode 100644 index 0000000..67b84be --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/add-global-security/after.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/global-security-fallback/add-global-security/before.yaml b/test/projects/changelog/security/global-security-fallback/add-global-security/before.yaml new file mode 100644 index 0000000..684035d --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/add-global-security/before.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/global-security-fallback/global-security-changes/after.yaml b/test/projects/changelog/security/global-security-fallback/global-security-changes/after.yaml new file mode 100644 index 0000000..55915b7 --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/global-security-changes/after.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - OAuth2: + - read +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/global-security-fallback/global-security-changes/before.yaml b/test/projects/changelog/security/global-security-fallback/global-security-changes/before.yaml new file mode 100644 index 0000000..c1b0050 --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/global-security-changes/before.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/global-security-fallback/remove-global-security/after.yaml b/test/projects/changelog/security/global-security-fallback/remove-global-security/after.yaml new file mode 100644 index 0000000..684035d --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/remove-global-security/after.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/global-security-fallback/remove-global-security/before.yaml b/test/projects/changelog/security/global-security-fallback/remove-global-security/before.yaml new file mode 100644 index 0000000..67b84be --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/remove-global-security/before.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/global-security-fallback/removes-explicit-security/after.yaml b/test/projects/changelog/security/global-security-fallback/removes-explicit-security/after.yaml new file mode 100644 index 0000000..c1b0050 --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/removes-explicit-security/after.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/global-security-fallback/removes-explicit-security/before.yaml b/test/projects/changelog/security/global-security-fallback/removes-explicit-security/before.yaml new file mode 100644 index 0000000..7500d81 --- /dev/null +++ b/test/projects/changelog/security/global-security-fallback/removes-explicit-security/before.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes/after.yaml b/test/projects/changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes/after.yaml new file mode 100644 index 0000000..b9d0cfa --- /dev/null +++ b/test/projects/changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes/after.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test1: + get: + summary: Test operation 1 + security: + - OAuth2: + - read + responses: + '200': + description: OK + /test2: + get: + summary: Test operation 2 + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes/before.yaml b/test/projects/changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes/before.yaml new file mode 100644 index 0000000..1e37dbe --- /dev/null +++ b/test/projects/changelog/security/multiple-operations/explicit-security-schema-used-by-one-operation-changes/before.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test1: + get: + summary: Test operation 1 + security: + - OAuth2: + - read + responses: + '200': + description: OK + /test2: + get: + summary: Test operation 2 + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/multiple-operations/op1-explicit-op2-global/after.yaml b/test/projects/changelog/security/multiple-operations/op1-explicit-op2-global/after.yaml new file mode 100644 index 0000000..df12c9a --- /dev/null +++ b/test/projects/changelog/security/multiple-operations/op1-explicit-op2-global/after.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - BasicAuth: [] +paths: + /test1: + get: + summary: Test operation 1 + security: + - OAuth2: + - read + responses: + '200': + description: OK + /test2: + get: + summary: Test operation 2 + responses: + '200': + description: OK +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/multiple-operations/op1-explicit-op2-global/before.yaml b/test/projects/changelog/security/multiple-operations/op1-explicit-op2-global/before.yaml new file mode 100644 index 0000000..45860ff --- /dev/null +++ b/test/projects/changelog/security/multiple-operations/op1-explicit-op2-global/before.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test1: + get: + summary: Test operation 1 + security: + - OAuth2: + - read + responses: + '200': + description: OK + /test2: + get: + summary: Test operation 2 + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/multiple-operations/scheme-global-affects-subset/after.yaml b/test/projects/changelog/security/multiple-operations/scheme-global-affects-subset/after.yaml new file mode 100644 index 0000000..0fe4a48 --- /dev/null +++ b/test/projects/changelog/security/multiple-operations/scheme-global-affects-subset/after.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test1: + get: + summary: Test operation 1 + security: + - OAuth2: + - read + responses: + '200': + description: OK + /test2: + get: + summary: Test operation 2 + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Token + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/multiple-operations/scheme-global-affects-subset/before.yaml b/test/projects/changelog/security/multiple-operations/scheme-global-affects-subset/before.yaml new file mode 100644 index 0000000..45860ff --- /dev/null +++ b/test/projects/changelog/security/multiple-operations/scheme-global-affects-subset/before.yaml @@ -0,0 +1,36 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test1: + get: + summary: Test operation 1 + security: + - OAuth2: + - read + responses: + '200': + description: OK + /test2: + get: + summary: Test operation 2 + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/operation-security-precedence/adds-explicit-security/after.yaml b/test/projects/changelog/security/operation-security-precedence/adds-explicit-security/after.yaml new file mode 100644 index 0000000..7500d81 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/adds-explicit-security/after.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/operation-security-precedence/adds-explicit-security/before.yaml b/test/projects/changelog/security/operation-security-precedence/adds-explicit-security/before.yaml new file mode 100644 index 0000000..c1b0050 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/adds-explicit-security/before.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/operation-security-precedence/both-change/after.yaml b/test/projects/changelog/security/operation-security-precedence/both-change/after.yaml new file mode 100644 index 0000000..113ce57 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/both-change/after.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - BasicAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - write + responses: + '200': + description: OK +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/operation-security-precedence/both-change/before.yaml b/test/projects/changelog/security/operation-security-precedence/both-change/before.yaml new file mode 100644 index 0000000..bbfb5b9 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/both-change/before.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/operation-security-precedence/global-changes-ignored/after.yaml b/test/projects/changelog/security/operation-security-precedence/global-changes-ignored/after.yaml new file mode 100644 index 0000000..72b605f --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/global-changes-ignored/after.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - BasicAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + BasicAuth: + type: http + scheme: basic + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/operation-security-precedence/global-changes-ignored/before.yaml b/test/projects/changelog/security/operation-security-precedence/global-changes-ignored/before.yaml new file mode 100644 index 0000000..bbfb5b9 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/global-changes-ignored/before.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/operation-security-precedence/operation-security-changes/after.yaml b/test/projects/changelog/security/operation-security-precedence/operation-security-changes/after.yaml new file mode 100644 index 0000000..3735760 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/operation-security-changes/after.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - write + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/operation-security-precedence/operation-security-changes/before.yaml b/test/projects/changelog/security/operation-security-precedence/operation-security-changes/before.yaml new file mode 100644 index 0000000..bbfb5b9 --- /dev/null +++ b/test/projects/changelog/security/operation-security-precedence/operation-security-changes/before.yaml @@ -0,0 +1,31 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/scheme-relevance/add-and-use-schema-in-operation/after.yaml b/test/projects/changelog/security/scheme-relevance/add-and-use-schema-in-operation/after.yaml new file mode 100644 index 0000000..676bc6f --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/add-and-use-schema-in-operation/after.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/scheme-relevance/add-and-use-schema-in-operation/before.yaml b/test/projects/changelog/security/scheme-relevance/add-and-use-schema-in-operation/before.yaml new file mode 100644 index 0000000..cb3c84a --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/add-and-use-schema-in-operation/before.yaml @@ -0,0 +1,12 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK + diff --git a/test/projects/changelog/security/scheme-relevance/global-scheme-changes-ignored/after.yaml b/test/projects/changelog/security/scheme-relevance/global-scheme-changes-ignored/after.yaml new file mode 100644 index 0000000..18d7880 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/global-scheme-changes-ignored/after.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Token + diff --git a/test/projects/changelog/security/scheme-relevance/global-scheme-changes-ignored/before.yaml b/test/projects/changelog/security/scheme-relevance/global-scheme-changes-ignored/before.yaml new file mode 100644 index 0000000..63fa184 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/global-scheme-changes-ignored/before.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/scheme-relevance/multiple-schemes-changes/after.yaml b/test/projects/changelog/security/scheme-relevance/multiple-schemes-changes/after.yaml new file mode 100644 index 0000000..55f5705 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/multiple-schemes-changes/after.yaml @@ -0,0 +1,30 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Token + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + admin: Admin access + diff --git a/test/projects/changelog/security/scheme-relevance/multiple-schemes-changes/before.yaml b/test/projects/changelog/security/scheme-relevance/multiple-schemes-changes/before.yaml new file mode 100644 index 0000000..e92cc06 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/multiple-schemes-changes/before.yaml @@ -0,0 +1,29 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + - OAuth2: + - read + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/scheme-relevance/scheme-removed-still-referenced/after.yaml b/test/projects/changelog/security/scheme-relevance/scheme-removed-still-referenced/after.yaml new file mode 100644 index 0000000..3b196b0 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/scheme-removed-still-referenced/after.yaml @@ -0,0 +1,14 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK + diff --git a/test/projects/changelog/security/scheme-relevance/scheme-removed-still-referenced/before.yaml b/test/projects/changelog/security/scheme-relevance/scheme-removed-still-referenced/before.yaml new file mode 100644 index 0000000..676bc6f --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/scheme-removed-still-referenced/before.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/scheme-relevance/scheme-used-in-security-changes/after.yaml b/test/projects/changelog/security/scheme-relevance/scheme-used-in-security-changes/after.yaml new file mode 100644 index 0000000..1a0a8bb --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/scheme-used-in-security-changes/after.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Token + diff --git a/test/projects/changelog/security/scheme-relevance/scheme-used-in-security-changes/before.yaml b/test/projects/changelog/security/scheme-relevance/scheme-used-in-security-changes/before.yaml new file mode 100644 index 0000000..676bc6f --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/scheme-used-in-security-changes/before.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/changelog/security/scheme-relevance/unused-scheme-added/after.yaml b/test/projects/changelog/security/scheme-relevance/unused-scheme-added/after.yaml new file mode 100644 index 0000000..55915b7 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/unused-scheme-added/after.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - OAuth2: + - read +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/scheme-relevance/unused-scheme-added/before.yaml b/test/projects/changelog/security/scheme-relevance/unused-scheme-added/before.yaml new file mode 100644 index 0000000..666801d --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/unused-scheme-added/before.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - OAuth2: + - read +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/scheme-relevance/unused-scheme-changes/after.yaml b/test/projects/changelog/security/scheme-relevance/unused-scheme-changes/after.yaml new file mode 100644 index 0000000..87f3264 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/unused-scheme-changes/after.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + write: Write access + diff --git a/test/projects/changelog/security/scheme-relevance/unused-scheme-changes/before.yaml b/test/projects/changelog/security/scheme-relevance/unused-scheme-changes/before.yaml new file mode 100644 index 0000000..cece716 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/unused-scheme-changes/before.yaml @@ -0,0 +1,27 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: + - ApiKeyAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/scheme-relevance/unused-scheme-removed/after.yaml b/test/projects/changelog/security/scheme-relevance/unused-scheme-removed/after.yaml new file mode 100644 index 0000000..666801d --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/unused-scheme-removed/after.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - OAuth2: + - read +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/scheme-relevance/unused-scheme-removed/before.yaml b/test/projects/changelog/security/scheme-relevance/unused-scheme-removed/before.yaml new file mode 100644 index 0000000..55915b7 --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/unused-scheme-removed/before.yaml @@ -0,0 +1,28 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - OAuth2: + - read +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + OAuth2: + type: oauth2 + flows: + implicit: + authorizationUrl: https://example.com/oauth/authorize + scopes: + read: Read access + diff --git a/test/projects/changelog/security/scheme-relevance/used-global-scheme-changes/after.yaml b/test/projects/changelog/security/scheme-relevance/used-global-scheme-changes/after.yaml new file mode 100644 index 0000000..d0b99cf --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/used-global-scheme-changes/after.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Token + diff --git a/test/projects/changelog/security/scheme-relevance/used-global-scheme-changes/before.yaml b/test/projects/changelog/security/scheme-relevance/used-global-scheme-changes/before.yaml new file mode 100644 index 0000000..67b84be --- /dev/null +++ b/test/projects/changelog/security/scheme-relevance/used-global-scheme-changes/before.yaml @@ -0,0 +1,20 @@ +openapi: 3.0.3 +info: + title: Security Test + version: 1.0.0 +security: + - ApiKeyAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + diff --git a/test/projects/rest.operation/conditional-security-explicit/base.yaml b/test/projects/rest.operation/conditional-security-explicit/base.yaml new file mode 100644 index 0000000..8f5add3 --- /dev/null +++ b/test/projects/rest.operation/conditional-security-explicit/base.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.3 +info: + title: Conditional Security Test + version: 1.0.0 +security: + - bearerAuth: [] +paths: + /test: + get: + summary: Test operation with explicit security + security: + - apiKey: [] + responses: + '200': + description: OK +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + apiKey: + type: apiKey + in: header + name: X-API-Key diff --git a/test/projects/rest.operation/security-schemes-filtering-empty-operation/base.yaml b/test/projects/rest.operation/security-schemes-filtering-empty-operation/base.yaml new file mode 100644 index 0000000..853248a --- /dev/null +++ b/test/projects/rest.operation/security-schemes-filtering-empty-operation/base.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.3 +info: + title: Security Filtering Root Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation + security: [] + responses: + '200': + description: OK +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + unusedAuth: + type: http + scheme: basic diff --git a/test/projects/rest.operation/security-schemes-filtering-empty-root/base.yaml b/test/projects/rest.operation/security-schemes-filtering-empty-root/base.yaml new file mode 100644 index 0000000..2c27fd3 --- /dev/null +++ b/test/projects/rest.operation/security-schemes-filtering-empty-root/base.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.3 +info: + title: Security Filtering Root Test + version: 1.0.0 +security: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + unusedAuth: + type: http + scheme: basic diff --git a/test/projects/rest.operation/security-schemes-filtering-none-used/base.yaml b/test/projects/rest.operation/security-schemes-filtering-none-used/base.yaml new file mode 100644 index 0000000..c18eea0 --- /dev/null +++ b/test/projects/rest.operation/security-schemes-filtering-none-used/base.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.3 +info: + title: Security Filtering Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation with explicit security + responses: + '200': + description: OK +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + unusedAuth: + type: http + scheme: basic diff --git a/test/projects/rest.operation/security-schemes-filtering-operation/base.yaml b/test/projects/rest.operation/security-schemes-filtering-operation/base.yaml new file mode 100644 index 0000000..b883441 --- /dev/null +++ b/test/projects/rest.operation/security-schemes-filtering-operation/base.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.3 +info: + title: Security Filtering Test + version: 1.0.0 +paths: + /test: + get: + summary: Test operation with explicit security + security: + - bearerAuth: [] + responses: + '200': + description: OK +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + unusedAuth: + type: http + scheme: basic diff --git a/test/projects/rest.operation/security-schemes-filtering-root/base.yaml b/test/projects/rest.operation/security-schemes-filtering-root/base.yaml new file mode 100644 index 0000000..9a365cb --- /dev/null +++ b/test/projects/rest.operation/security-schemes-filtering-root/base.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.3 +info: + title: Security Filtering Root Test + version: 1.0.0 +security: + - bearerAuth: [] +paths: + /test: + get: + summary: Test operation + responses: + '200': + description: OK +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + apiKeyAuth: + type: apiKey + in: header + name: X-API-Key + unusedAuth: + type: http + scheme: basic diff --git a/test/rest.operation.test.ts b/test/rest.operation.test.ts new file mode 100644 index 0000000..9d0b70b --- /dev/null +++ b/test/rest.operation.test.ts @@ -0,0 +1,126 @@ +/** + * Copyright 2024-2025 NetCracker Technology Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OpenAPIV3 } from 'openapi-types' +import { createSingleOperationSpec } from '../src/apitypes/rest/rest.operation' + +import { describe, test, expect } from '@jest/globals' +import * as fs from 'fs/promises' +import * as path from 'path' +import YAML from 'js-yaml' +import { securitySchemesFromRequirementsMatcher } from './helpers/matchers' +import { RestOperationData } from '../src/apitypes/rest/rest.types' + +// Helper function to load YAML test files +const loadYamlFile = async (relativePath: string): Promise => { + const filePath = path.join(process.cwd(), 'test/projects', relativePath) + const content = await fs.readFile(filePath, 'utf8') + return YAML.load(content) as OpenAPIV3.Document +} + +describe('REST Operation Unit Tests', () => { + describe('createSingleOperationSpec', () => { + + const TEST_DATA_PATH = '/test' + const TEST_DATA_METHOD = 'get' as OpenAPIV3.HttpMethods + const TEST_DATA_OPENAPI = '3.0.0' + + const getOperationSecurity = (document: OpenAPIV3.Document, path: string, method: OpenAPIV3.HttpMethods): OpenAPIV3.SecurityRequirementObject[] | undefined => { + return document.paths?.[path]?.[method]?.security + } + + const createTestSingleOperationSpec = (document: OpenAPIV3.Document): RestOperationData => { + return createSingleOperationSpec( + document, + TEST_DATA_PATH, + TEST_DATA_METHOD, + TEST_DATA_OPENAPI, + document.servers, + document.security, + getOperationSecurity(document, TEST_DATA_PATH, TEST_DATA_METHOD), + document.components?.securitySchemes, + ) + } + + describe('Security Scheme Filtering', () => { + test('should include only used security schemes when operation security is defined', async () => { + const document = await loadYamlFile('rest.operation/security-schemes-filtering-operation/base.yaml') + + const operationSecurity = getOperationSecurity(document, TEST_DATA_PATH, TEST_DATA_METHOD) + + const result = createTestSingleOperationSpec(document) + + expect(result.components?.securitySchemes).toBeDefined() + expect(result.components!.securitySchemes!).toEqual( + securitySchemesFromRequirementsMatcher(operationSecurity || []), + ) + }) + + test('should include only used security schemes when root security is defined but no operation security', async () => { + const document = await loadYamlFile('rest.operation/security-schemes-filtering-root/base.yaml') + + const result = createTestSingleOperationSpec(document) + + expect(result.components?.securitySchemes).toBeDefined() + expect(result.components!.securitySchemes!).toEqual( + securitySchemesFromRequirementsMatcher(document.security || []), + ) + }) + + test('should not include security schemes when none are used', async () => { + const document = await loadYamlFile('rest.operation/security-schemes-filtering-none-used/base.yaml') + + const result = createTestSingleOperationSpec(document) + + expect(result.components?.securitySchemes).toBeUndefined() + }) + + test('should handle empty root security requirements', async () => { + const document = await loadYamlFile('rest.operation/security-schemes-filtering-empty-root/base.yaml') + + const result = createTestSingleOperationSpec(document) + + expect(result.components?.securitySchemes).toBeUndefined() + }) + + test('should handle empty operation security requirements', async () => { + const document = await loadYamlFile('rest.operation/security-schemes-filtering-empty-operation/base.yaml') + + const result = createTestSingleOperationSpec(document) + + expect(result.components?.securitySchemes).toBeUndefined() + }) + }) + + describe('Conditional Security Handling', () => { + test('should not include root security when operation security is explicitly defined', async () => { + const document = await loadYamlFile('rest.operation/conditional-security-explicit/base.yaml') + + const result = createTestSingleOperationSpec(document) + + expect(result.security).toBeUndefined() + }) + + test('should include root security when no operation security is defined', async () => { + const document = await loadYamlFile('rest.operation/security-schemes-filtering-root/base.yaml') + + const result = createTestSingleOperationSpec(document) + + expect(result.security).toEqual(document.security) + }) + }) + }) +})