diff --git a/package.json b/package.json index c819d9f..8c7068d 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,12 @@ "update-lock-file": "update-lock-file @netcracker" }, "dependencies": { - "@netcracker/qubership-apihub-api-unifier": "2.3.0", + "@netcracker/qubership-apihub-api-unifier": "dev", "@netcracker/qubership-apihub-json-crawl": "1.0.4", "fast-equals": "4.0.3" }, "devDependencies": { - "@netcracker/qubership-apihub-compatibility-suites": "2.2.0", + "@netcracker/qubership-apihub-compatibility-suites": "dev", "@netcracker/qubership-apihub-graphapi": "1.0.8", "@netcracker/qubership-apihub-npm-gitflow": "3.1.0", "@types/jest": "29.5.11", diff --git a/src/openapi/openapi3.rules.ts b/src/openapi/openapi3.rules.ts index 667c943..51652f5 100644 --- a/src/openapi/openapi3.rules.ts +++ b/src/openapi/openapi3.rules.ts @@ -388,6 +388,20 @@ export const openApi3Rules = (options: OpenApi3RulesOptions): CompareRules => { '/*': operationAnnotationRule, } + const pathItemObjectRules = (options: OpenApi3RulesOptions): CompareRules => ({ + $: pathChangeClassifyRule, + mapping: options.mode === COMPARE_MODE_OPERATION ? singleOperationPathMappingResolver : pathMappingResolver, + '/description': { $: allAnnotation }, + '/parameters': { + $: [nonBreaking, breaking, breaking], + mapping: paramMappingResolver(1), + ...parametersRules, + }, + '/servers': serversRules, + '/summary': { $: allAnnotation }, + '/*': operationRule, + }) + const componentsRule: CompareRules = { $: allNonBreaking, [START_NEW_COMPARE_SCOPE_RULE]: COMPARE_SCOPE_COMPONENTS, @@ -412,6 +426,10 @@ export const openApi3Rules = (options: OpenApi3RulesOptions): CompareRules => { ...requestSchemaRules, }), }, + '/pathItems': { + $: [nonBreaking, breaking, breaking], + '/*': pathItemObjectRules(options), + }, '/securitySchemes': { $: [breaking, nonBreaking, breaking], '/*': { @@ -438,19 +456,7 @@ export const openApi3Rules = (options: OpenApi3RulesOptions): CompareRules => { '/paths': { $: allUnclassified, mapping: options.mode === COMPARE_MODE_OPERATION ? singleOperationPathMappingResolver : pathMappingResolver, - '/*': { - $: pathChangeClassifyRule, - mapping: options.mode === COMPARE_MODE_OPERATION ? singleOperationPathMappingResolver : pathMappingResolver, - '/description': { $: allAnnotation }, - '/parameters': { - $: [nonBreaking, breaking, breaking], - mapping: paramMappingResolver(1), - ...parametersRules, - }, - '/servers': serversRules, - '/summary': { $: allAnnotation }, - '/*': operationRule, - }, + '/*': pathItemObjectRules(options), }, '/components': componentsRule, '/security': { diff --git a/test/compatibility-suites/openapi/general-operation-parameters.test.ts b/test/compatibility-suites/openapi/general-operation-parameters.test.ts index 2714bb7..7c4c4df 100644 --- a/test/compatibility-suites/openapi/general-operation-parameters.test.ts +++ b/test/compatibility-suites/openapi/general-operation-parameters.test.ts @@ -767,3 +767,59 @@ describe('Openapi3 General Operation Parameters', () => { ])) }) }) + +const PATH_ITEM_PATH = [ + 'components', + 'pathItems', + 'UserOps', +] + +describe('Openapi3.1 PathItems', () => { + test('Add method in path item', async () => { + const testId = 'add-method-in-path-item' + const result = await compareFiles(SUITE_ID, testId) + expect(result).toEqual(diffsMatcher([ + expect.objectContaining({ + action: DiffAction.add, + afterDeclarationPaths: [[...PATH_ITEM_PATH, 'post']], + type: nonBreaking, + }), + ])) + }) + + test('Remove unused method in path item', async () => { + const testId = 'remove-unused-method-in-path-item' + const result = await compareFiles(SUITE_ID, testId) + expect(result).toEqual([]) + }) + + test('Add unused method in path item', async () => { + const testId = 'add-unused-method-in-path-item' + const result = await compareFiles(SUITE_ID, testId) + expect(result).toEqual([]) + }) + + test('Remove method in path item', async () => { + const testId = 'remove-method-in-path-item' + const result = await compareFiles(SUITE_ID, testId) + expect(result).toEqual(diffsMatcher([ + expect.objectContaining({ + action: DiffAction.remove, + beforeDeclarationPaths: [[...PATH_ITEM_PATH, 'post']], + type: breaking, + }), + ])) + }) + + test('Replace inline path item to ref', async () => { + const testId = 'replace-inline-path-item-to-ref' + const result = await compareFiles(SUITE_ID, testId) + expect(result).toEqual([]) + }) + + test('Replace ref path item to inline', async () => { + const testId = 'replace-ref-path-item-to-inline' + const result = await compareFiles(SUITE_ID, testId) + expect(result).toEqual([]) + }) +}) diff --git a/test/pathItems.rules.test.ts b/test/pathItems.rules.test.ts new file mode 100644 index 0000000..8e57a69 --- /dev/null +++ b/test/pathItems.rules.test.ts @@ -0,0 +1,85 @@ +import { apiDiff, ClassifierType, DiffAction } from '../src' +import { diffsMatcher } from './helper/matchers' + +const COMPONENTS_RESPONSE_PATH = [ + 'components', + 'pathItems', + 'componentsPathItem', + 'post', + 'responses', + '200', +] + +describe('Openapi3.1 Components PathItems Rules', () => { + test('reports changes to path items in components', async () => { + const before = { + 'openapi': '3.1.0', + 'paths': { + '/path1': { + 'post': { + 'responses': { + '200': {}, + }, + }, + }, + }, + 'components': { + 'pathItems': { + 'componentsPathItem': { + 'post': { + 'responses': { + '200': { + 'description': 'Pet successfully added', + }, + }, + }, + }, + }, + }, + } + + const after = { + 'openapi': '3.1.0', + 'paths': { + '/path1': { + 'post': { + 'responses': { + '200': {}, + }, + }, + }, + }, + 'components': { + 'pathItems': { + 'componentsPathItem': { + 'post': { + 'responses': { + '200': { + 'description': 'new value', + }, + }, + }, + }, + }, + }, + } + + const result = apiDiff(before, after) + expect(result.diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + action: DiffAction.replace, + beforeValue: 'Pet successfully added', + afterValue: 'new value', + beforeDeclarationPaths: [[ + ...COMPONENTS_RESPONSE_PATH, + 'description', + ]], + afterDeclarationPaths: [[ + ...COMPONENTS_RESPONSE_PATH, + 'description', + ]], + type: ClassifierType.annotation, + }), + ])) + }) +})