diff --git a/src/core/compare.ts b/src/core/compare.ts index 51cff70..502d36c 100644 --- a/src/core/compare.ts +++ b/src/core/compare.ts @@ -16,6 +16,7 @@ import { deepEqual } from 'fast-equals' import { AdapterContext, AdapterResolver, + BwcState, CompareContext, CompareResult, CompareRule, @@ -30,6 +31,7 @@ import { JsonNode, MergeState, NodeContext, + ApiCompatibilityKind, ValueTransformer, } from '../types' import { getObjectValue, isArray, isDiffAdd, isDiffRemove, isDiffReplace, isNumber, isObject, typeOf } from '../utils' @@ -75,6 +77,7 @@ export const createContext = (data: ContextInput, options: InternalCompareOption rules, compareScope, parentContext, + backwardCompatibility, } = data return { parentContext: parentContext, @@ -84,6 +87,7 @@ export const createContext = (data: ContextInput, options: InternalCompareOption mergeKey, rules, options, + backwardCompatibility, } } @@ -92,6 +96,7 @@ export const createChildContext = ( mergedKey: PropertyKey, beforeChildKey: PropertyKey | undefined, afterChildKey: PropertyKey | undefined, + backwardCompatibility: BwcState = ctx.backwardCompatibility, ): CompareContext => { const { before, after, rules, options, scope } = ctx let beforeContext: NodeContext @@ -133,6 +138,7 @@ export const createChildContext = ( ) ?? {}, options, scope: scope, + backwardCompatibility, } } @@ -156,7 +162,19 @@ const cleanUpRecursive = (ctx: NodeContext): NodeContext => { } export const getOrCreateChildDiffAdd = (diffUniquenessCache: EvaluationCacheService, childCtx: CompareContext) => { - const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[unknown, string, CompareScope, typeof DiffAction.add], DiffAdd>([childCtx.after.value, buildPathsIdentifier(childCtx.after.declarativePaths), childCtx.scope, DiffAction.add], () => { + const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[ + unknown, + string, + CompareScope, + typeof DiffAction.add, + BwcState + ], DiffAdd>([ + childCtx.after.value, + buildPathsIdentifier(childCtx.after.declarativePaths), + childCtx.scope, + DiffAction.add, + childCtx.backwardCompatibility, + ], () => { return diffFactory.added(childCtx) }, {} as DiffAdd, (result, guard) => { Object.assign(guard, result) @@ -167,7 +185,19 @@ export const getOrCreateChildDiffAdd = (diffUniquenessCache: EvaluationCacheServ } export const getOrCreateChildDiffRemove = (diffUniquenessCache: EvaluationCacheService, childCtx: CompareContext) => { - const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[unknown, string, CompareScope, typeof DiffAction.remove], DiffRemove>([childCtx.before.value, buildPathsIdentifier(childCtx.before.declarativePaths), childCtx.scope, DiffAction.remove], () => { + const diff = diffUniquenessCache.cacheEvaluationResultByFootprint<[ + unknown, + string, + CompareScope, + typeof DiffAction.remove, + BwcState + ], DiffRemove>([ + childCtx.before.value, + buildPathsIdentifier(childCtx.before.declarativePaths), + childCtx.scope, + DiffAction.remove, + childCtx.backwardCompatibility, + ], () => { return diffFactory.removed(childCtx) }, {} as DiffRemove, (result, guard) => { Object.assign(guard, result) @@ -203,7 +233,7 @@ const adaptValues = (beforeJso: JsonNode, beforeKey: PropertyKey, afterJso: Json } const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): SyncCrawlHook => { - const { metaKey } = options + const { metaKey, bwcScopeFunction } = options const diffs: Set = new Set() const addDiff: (diff: Diff) => void = (diff) => { const oldSize = diffs.size @@ -229,6 +259,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): diffUniquenessCache, createdMergedJso, compareScope, + backwardCompatibility: parentBwc, } = state if (typeof unsafeKey === 'symbol') { @@ -252,6 +283,8 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): afterValueAdapted, ] = adaptValues(beforeJso, beforeKey, afterJso, afterKey, adapter, options) + const computedBwc = bwcScopeFunction?.(crawlContext.path, beforeValueAdapted, afterValueAdapted) ?? parentBwc + const ctx = createContext({ ...state, beforeValue: beforeValueAdapted, @@ -261,12 +294,27 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): mergeKey, rules, compareScope: newCompareScope ?? compareScope, + backwardCompatibility: computedBwc, }, options) const beforeDeclarativePathsId = buildPathsIdentifier(cleanUpRecursive(ctx.before).declarativePaths) const afterDeclarativePathsId = buildPathsIdentifier(cleanUpRecursive(ctx.after).declarativePaths) - const reuseResult: ReusableMergeResult = mergedJsoCache.cacheEvaluationResultByFootprint<[typeof ctx.before.value, typeof ctx.after.value, typeof beforeDeclarativePathsId, typeof afterDeclarativePathsId, CompareScope], ReusableMergeResult>([ctx.before.value, ctx.after.value, beforeDeclarativePathsId, afterDeclarativePathsId, ctx.scope], ([beforeValue, afterValue]) => { + const reuseResult: ReusableMergeResult = mergedJsoCache.cacheEvaluationResultByFootprint<[ + typeof ctx.before.value, + typeof ctx.after.value, + typeof beforeDeclarativePathsId, + typeof afterDeclarativePathsId, + CompareScope, + BwcState + ], ReusableMergeResult>([ + ctx.before.value, + ctx.after.value, + beforeDeclarativePathsId, + afterDeclarativePathsId, + ctx.scope, + computedBwc, + ], ([beforeValue, afterValue]) => { if (!ignoreKeyDifference && beforeKey !== afterKey) { const diffEntry = createDiffEntry(ctx, diffFactory.renamed(ctx)) addDiff(diffEntry.diff) @@ -310,13 +358,19 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): once = true keyToRemove.forEach((keyToBefore) => { - const childCtx = createChildContext(ctx, keyToBefore, keyToBefore, undefined) + const removalBwc = computedBwc === ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE + ? computedBwc + : bwcScopeFunction?.(crawlContext.path, beforeValue[keyToBefore]) + const childCtx = createChildContext(ctx, keyToBefore, keyToBefore, undefined, removalBwc) jsoDiffEntries.push(getOrCreateChildDiffRemove(diffUniquenessCache, childCtx)) }) keysToAdd.forEach((keyInAfter) => { + const additionBwc = computedBwc === ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE + ? computedBwc + : bwcScopeFunction?.(crawlContext.path, undefined, afterJso[keyInAfter]) const keyInMerge = isArray(mergedJsoValue) ? mergedJsoValue.length : keyInAfter - const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter) + const childCtx = createChildContext(ctx, keyInMerge, undefined, keyInAfter, additionBwc) jsoDiffEntries.push(getOrCreateChildDiffAdd(diffUniquenessCache, childCtx)) mergedJsoValue[keyInMerge] = afterValue[keyInAfter] }) @@ -363,6 +417,7 @@ const useMergeFactory = (onDiff: DiffCallback, options: InternalCompareOptions): afterJso: afterValueAdapted as JsonNode/*safe cause it only happens for object*/, mergedJso: mergedValue, compareScope: newCompareScope ?? compareScope, + backwardCompatibility: computedBwc, } return { value: reuseResult.nextValue, state: childState, exitHook: reuseResult.exitHook } } else { @@ -548,6 +603,7 @@ const compareInternal = (before: unknown, after: unknown, onDiff: DiffCallback, diffUniquenessCache: options.diffUniquenessCache, createdMergedJso: options.createdMergedJso, compareScope: options.compareScope, + backwardCompatibility: ApiCompatibilityKind.BACKWARD_COMPATIBLE, } syncCrawl(before, [hook], { state: rootState, rules: options.rules }) return root.merged[JSO_ROOT] diff --git a/src/core/diff.ts b/src/core/diff.ts index cc76e47..d3ada72 100644 --- a/src/core/diff.ts +++ b/src/core/diff.ts @@ -1,7 +1,20 @@ import { JsonPath } from '@netcracker/qubership-apihub-json-crawl' -import { CompareContext, Diff, DiffAdd, DiffRemove, DiffReplace, DiffRename, DiffEntry, DiffFactory, DiffMetaRecord, NodeContext } from '../types' -import { allUnclassified, DiffAction, unclassified } from './constants' +import { + ApiCompatibilityKind, + CompareContext, + Diff, + DiffAdd, + DiffEntry, + DiffFactory, + DiffMetaRecord, + DiffRemove, + DiffRename, + DiffReplace, + DiffType, + NodeContext, +} from '../types' +import { allUnclassified, breaking, DiffAction, risky, unclassified } from './constants' import { getKeyValue, isFunc } from '../utils' import { calculateDefaultDiffDescription } from './description' @@ -18,7 +31,8 @@ export const createDiff = (diff: Omit, ctx: CompareCo const changeType = classifier[index] try { - mutableDiffCopy.type = isFunc(changeType) ? changeType(ctx) : changeType + const type = isFunc(changeType) ? changeType(ctx) : changeType + mutableDiffCopy.type = reclassifyBreakingToRisky(type, ctx) } catch (error) { ctx.options.onCreateDiffError?.(`Unable to find diff type. ${error instanceof Error ? error.message : ''}`, mutableDiffCopy, ctx) } @@ -31,6 +45,10 @@ export const createDiff = (diff: Omit, ctx: CompareCo return mutableDiffCopy } +export const reclassifyBreakingToRisky = (type: DiffType, ctx: CompareContext): DiffType => { + return type === breaking && ctx.backwardCompatibility === ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE ? risky : type +} + export function createDiffEntry(ctx: CompareContext, diff: Diff): DiffEntry { return ({ propertyKey: ctx.mergeKey, diff --git a/src/core/rules.ts b/src/core/rules.ts index 9b5bea0..8cd5928 100644 --- a/src/core/rules.ts +++ b/src/core/rules.ts @@ -80,7 +80,6 @@ export const transformClassifyRule = ([add, remove, replace, reverseAdd, reverse transformedRule(remove, DiffAction.remove), transformedRule(replace, DiffAction.replace), ] - } export const breakingIf = (v: boolean): DiffType => (v ? breaking : nonBreaking) diff --git a/src/index.ts b/src/index.ts index 11df071..39ce05d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,8 @@ export { apiDiff } from './api' export type { CompareResult, CompareOptions, + BwcScopeFunction, + BwcState, DiffType, ActionType, Diff, @@ -26,6 +28,7 @@ export type { DiffRename, DiffMetaRecord, } from './types' +export { ApiCompatibilityKind } from './types' export { isDiffAdd, diff --git a/src/openapi/openapi3.rules.ts b/src/openapi/openapi3.rules.ts index 1903667..5c8ac27 100644 --- a/src/openapi/openapi3.rules.ts +++ b/src/openapi/openapi3.rules.ts @@ -6,6 +6,7 @@ import { allUnclassified, breaking, breakingIfAfterTrue, + deepEqualsUniqueItemsArrayMappingResolver, diffDescription, GREP_TEMPLATE_PARAM_ENCODING_NAME, GREP_TEMPLATE_PARAM_EXAMPLE_NAME, @@ -27,7 +28,6 @@ import { TEMPLATE_PARAM_RESPONSE_PATH, TEMPLATE_PARAM_SCOPE, unclassified, - deepEqualsUniqueItemsArrayMappingResolver, } from '../core' import { COMPARE_MODE_OPERATION, @@ -62,7 +62,7 @@ import { import { isResponseSchema } from './openapi3.utils' import { apihubCaseInsensitiveKeyMappingResolver } from './mapping' import { nonBreakingIf } from '../utils' -import { COMPARE_SCOPE_COMPONENTS, COMPARE_SCOPE_RESPONSE, COMPARE_SCOPE_REQUEST } from './openapi3.const' +import { COMPARE_SCOPE_COMPONENTS, COMPARE_SCOPE_REQUEST, COMPARE_SCOPE_RESPONSE } from './openapi3.const' import { parameterParamsCalculator } from './openapi3.description.parameter' import { requestParamsCalculator } from './openapi3.description.request' import { responseParamsCalculator } from './openapi3.description.response' diff --git a/src/types/compare.ts b/src/types/compare.ts index c099a5d..5677f51 100644 --- a/src/types/compare.ts +++ b/src/types/compare.ts @@ -83,6 +83,15 @@ export const COMPARE_MODE_OPERATION = 'operation' export type CompareMode = typeof COMPARE_MODE_DEFAULT | typeof COMPARE_MODE_OPERATION +export enum ApiCompatibilityKind { + BACKWARD_COMPATIBLE = 'BACKWARD_COMPATIBLE', + NOT_BACKWARD_COMPATIBLE = 'NOT_BACKWARD_COMPATIBLE' +} + +export type BwcState = keyof typeof ApiCompatibilityKind + +export type BwcScopeFunction = (path?: JsonPath, beforeJso?: unknown, afterJso?: unknown) => BwcState | undefined + export interface CompareOptions extends Omit { mode?: CompareMode normalizedResult?: boolean @@ -92,6 +101,7 @@ export interface CompareOptions extends Omit { onCreateDiffError?: (message: string, diff: Diff, ctx: CompareContext) => void beforeValueNormalizedProperty?: symbol afterValueNormalizedProperty?: symbol + bwcScopeFunction?: BwcScopeFunction } export type DiffCallback = (diff: Diff/*, ctx: CompareContext*/) => void @@ -133,6 +143,7 @@ export interface MergeState { diffUniquenessCache: EvaluationCacheService, createdMergedJso: Set, compareScope: CompareScope + backwardCompatibility: BwcState } export type JsonNode = Key extends (string | symbol) ? Record : Record | Array diff --git a/src/types/rules.ts b/src/types/rules.ts index 5221c67..cef035d 100644 --- a/src/types/rules.ts +++ b/src/types/rules.ts @@ -1,7 +1,7 @@ import { CrawlRules, JsonPath } from '@netcracker/qubership-apihub-json-crawl' import type { CompareResult, Diff, DiffType } from './compare' -import { CompareScope, InternalCompareOptions } from './compare' +import { BwcState, CompareScope, InternalCompareOptions } from './compare' import { DiffAction } from '../core' import { OriginLeafs } from '@netcracker/qubership-apihub-api-unifier' @@ -44,6 +44,7 @@ export interface CompareContext { mergeKey: PropertyKey rules: CompareRules options: InternalCompareOptions + backwardCompatibility: BwcState } export interface AdapterContext { diff --git a/test/backward-compatibility.test.ts b/test/backward-compatibility.test.ts new file mode 100644 index 0000000..1b0a3c2 --- /dev/null +++ b/test/backward-compatibility.test.ts @@ -0,0 +1,246 @@ +import { ApiCompatibilityKind, apiDiff, breaking, BwcScopeFunction, DiffAction, risky } from '../src' + +import singleMethodResponseBefore from './helper/resources/backward-compatibility/single-method-response/before.json' +import singleMethodResponseAfter from './helper/resources/backward-compatibility/single-method-response/after.json' + +import singleMethodRequestResponseBefore + from './helper/resources/backward-compatibility/single-method-request-response/before.json' +import singleMethodRequestResponseAfter + from './helper/resources/backward-compatibility/single-method-request-response/after.json' + +import multipleMethodsResponseBefore + from './helper/resources/backward-compatibility/multiple-methods-response/before.json' +import multipleMethodsResponseAfter + from './helper/resources/backward-compatibility/multiple-methods-response/after.json' + +import multipleMethodsResponseRefBefore + from './helper/resources/backward-compatibility/multiple-methods-response-ref/before.json' +import multipleMethodsResponseRefAfter + from './helper/resources/backward-compatibility/multiple-methods-response-ref/after.json' + +import multipleMethodsRequestResponseRefBefore + from './helper/resources/backward-compatibility/multiple-methods-request-response-ref/before.json' +import multipleMethodsRequestResponseRefAfter + from './helper/resources/backward-compatibility/multiple-methods-request-response-ref/after.json' + +import { diffsMatcher } from './helper/matchers' + +type PATH_ENTRY = [string, string, string] + +const GET_PATH1: PATH_ENTRY = ['paths', '/path1', 'get'] +const POST_PATH1: PATH_ENTRY = ['paths', '/path1', 'post'] + +function createBwcScopeFunction(data: PATH_ENTRY[]): BwcScopeFunction { + return (path?: PropertyKey[]) => { + if (path?.length !== 3) { + return undefined + } + return data.some(entry => + entry.every((el, i) => path?.[i] === el), + ) ? ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE : undefined + } +} + +describe('Backward compatibility tests', () => { + it('should diff from get has risky type with not backward compatible', async () => { + const { diffs } = apiDiff( + singleMethodResponseBefore, + singleMethodResponseAfter, + { bwcScopeFunction: () => ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE }, + ) + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + ])) + }) + + it('should mark both request and response as risky for single method with not backward compatible', async () => { + const { diffs } = apiDiff( + singleMethodRequestResponseBefore, + singleMethodRequestResponseAfter, + { bwcScopeFunction: () => ApiCompatibilityKind.NOT_BACKWARD_COMPATIBLE }, + ) + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'request', + action: DiffAction.replace, + type: risky, + }), + ])) + }) + + it('should mark GET method as risky and POST as breaking when only GET is in scope', async () => { + const { diffs } = apiDiff( + multipleMethodsResponseBefore, + multipleMethodsResponseAfter, + { bwcScopeFunction: createBwcScopeFunction([GET_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: breaking, + }), + ])) + }) + + it('should mark POST method as breaking and GET as risky when only POST is in scope', async () => { + const { diffs } = apiDiff( + multipleMethodsResponseBefore, + multipleMethodsResponseAfter, + { bwcScopeFunction: createBwcScopeFunction([POST_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: breaking, + }), + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + ])) + }) + + it('should mark both GET and POST methods as risky when both are in scope', async () => { + const { diffs } = apiDiff( + multipleMethodsResponseBefore, + multipleMethodsResponseAfter, + { bwcScopeFunction: createBwcScopeFunction([GET_PATH1, POST_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + ])) + }) + + it('should have two response diffs and one components diff when only GET is in scope with refs', async () => { + const { diffs } = apiDiff( + multipleMethodsResponseRefBefore, + multipleMethodsResponseRefAfter, + { bwcScopeFunction: createBwcScopeFunction([GET_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + // post + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: breaking, + }), + // get + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'components', + action: DiffAction.replace, + type: breaking, + }), + ])) + }) + + it('should have one response diff and one components diff when both methods are in scope with refs', async () => { + const { diffs } = apiDiff( + multipleMethodsResponseRefBefore, + multipleMethodsResponseRefAfter, + { bwcScopeFunction: createBwcScopeFunction([GET_PATH1, POST_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + // get + post + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'components', + action: DiffAction.replace, + type: breaking, + }), + ])) + }) + + it('should mark POST as breaking and GET as risky for both request and response when only GET is in scope with refs', async () => { + const { diffs } = apiDiff( + multipleMethodsRequestResponseRefBefore, + multipleMethodsRequestResponseRefAfter, + { bwcScopeFunction: createBwcScopeFunction([GET_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + // post + expect.objectContaining({ + scope: 'request', + action: DiffAction.replace, + type: breaking, + }), + // post + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: breaking, + }), + // get + expect.objectContaining({ + scope: 'request', + action: DiffAction.replace, + type: risky, + }), + // get + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'components', + action: DiffAction.replace, + type: breaking, + }), + ])) + }) + + it('should mark both request and response as risky when both methods are in scope with refs', async () => { + const { diffs } = apiDiff( + multipleMethodsRequestResponseRefBefore, + multipleMethodsRequestResponseRefAfter, + { bwcScopeFunction: createBwcScopeFunction([GET_PATH1, POST_PATH1]) }) + expect(diffs).toEqual(diffsMatcher([ + // get + post + expect.objectContaining({ + scope: 'request', + action: DiffAction.replace, + type: risky, + }), + // get + post + expect.objectContaining({ + scope: 'response', + action: DiffAction.replace, + type: risky, + }), + expect.objectContaining({ + scope: 'components', + action: DiffAction.replace, + type: breaking, + }), + ])) + }) +}) diff --git a/test/helper/resources/backward-compatibility/multiple-methods-request-response-ref/after.json b/test/helper/resources/backward-compatibility/multiple-methods-request-response-ref/after.json new file mode 100644 index 0000000..d29eca1 --- /dev/null +++ b/test/helper/resources/backward-compatibility/multiple-methods-request-response-ref/after.json @@ -0,0 +1,73 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sample API", + "description": "API specification example", + "version": "1.0.0" + }, + "paths": { + "/path1": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + }, + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "componentsValue": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "currency": { + "type": "string" + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/multiple-methods-request-response-ref/before.json b/test/helper/resources/backward-compatibility/multiple-methods-request-response-ref/before.json new file mode 100644 index 0000000..da5eab3 --- /dev/null +++ b/test/helper/resources/backward-compatibility/multiple-methods-request-response-ref/before.json @@ -0,0 +1,73 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sample API", + "description": "API specification example", + "version": "1.0.0" + }, + "paths": { + "/path1": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + }, + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "componentsValue": { + "type": "object", + "properties": { + "value": { + "type": "number" + }, + "currency": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/helper/resources/backward-compatibility/multiple-methods-response-ref/after.json b/test/helper/resources/backward-compatibility/multiple-methods-response-ref/after.json new file mode 100644 index 0000000..3cb610d --- /dev/null +++ b/test/helper/resources/backward-compatibility/multiple-methods-response-ref/after.json @@ -0,0 +1,55 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sample API", + "description": "API specification example", + "version": "1.0.0" + }, + "paths": { + "/path1": { + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + }, + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "componentsValue": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "currency": { + "type": "string" + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/multiple-methods-response-ref/before.json b/test/helper/resources/backward-compatibility/multiple-methods-response-ref/before.json new file mode 100644 index 0000000..875a78d --- /dev/null +++ b/test/helper/resources/backward-compatibility/multiple-methods-response-ref/before.json @@ -0,0 +1,55 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Sample API", + "description": "API specification example", + "version": "1.0.0" + }, + "paths": { + "/path1": { + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + }, + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/componentsValue" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "componentsValue": { + "type": "object", + "properties": { + "value": { + "type": "number" + }, + "currency": { + "type": "string" + } + } + } + } + } +} \ No newline at end of file diff --git a/test/helper/resources/backward-compatibility/multiple-methods-response/after.json b/test/helper/resources/backward-compatibility/multiple-methods-response/after.json new file mode 100644 index 0000000..d775ffc --- /dev/null +++ b/test/helper/resources/backward-compatibility/multiple-methods-response/after.json @@ -0,0 +1,39 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "test", + "version": "0.1.0" + }, + "paths": { + "/path1": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + }, + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/multiple-methods-response/before.json b/test/helper/resources/backward-compatibility/multiple-methods-response/before.json new file mode 100644 index 0000000..5e99dbf --- /dev/null +++ b/test/helper/resources/backward-compatibility/multiple-methods-response/before.json @@ -0,0 +1,39 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "test", + "version": "0.1.0" + }, + "paths": { + "/path1": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + } + } + }, + "post": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/single-method-request-response/after.json b/test/helper/resources/backward-compatibility/single-method-request-response/after.json new file mode 100644 index 0000000..7af775f --- /dev/null +++ b/test/helper/resources/backward-compatibility/single-method-request-response/after.json @@ -0,0 +1,34 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "test", + "version": "0.1.0" + }, + "paths": { + "/path1": { + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/single-method-request-response/before.json b/test/helper/resources/backward-compatibility/single-method-request-response/before.json new file mode 100644 index 0000000..d72f57c --- /dev/null +++ b/test/helper/resources/backward-compatibility/single-method-request-response/before.json @@ -0,0 +1,34 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "test", + "version": "0.1.0" + }, + "paths": { + "/path1": { + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/single-method-response/after.json b/test/helper/resources/backward-compatibility/single-method-response/after.json new file mode 100644 index 0000000..fafe297 --- /dev/null +++ b/test/helper/resources/backward-compatibility/single-method-response/after.json @@ -0,0 +1,25 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "test", + "version": "0.1.0" + }, + "paths": { + "/path1": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + } +} diff --git a/test/helper/resources/backward-compatibility/single-method-response/before.json b/test/helper/resources/backward-compatibility/single-method-response/before.json new file mode 100644 index 0000000..6adac56 --- /dev/null +++ b/test/helper/resources/backward-compatibility/single-method-response/before.json @@ -0,0 +1,25 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "test", + "version": "0.1.0" + }, + "paths": { + "/path1": { + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + } + } + } + } + } +}