-
Couldn't load subscription status.
- Fork 14
CLOUDP-304053: IPA-106:Create - The resource must be the request body (implement deepObjectComparison without third party dependencies) #522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
84f0579
5a98e13
cb8fcb4
6ba5e3b
62054dc
39462eb
2390d68
7b04e62
2017cd1
51cedde
b14115c
fb66eb4
7211337
63fd013
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| import { describe, expect, it } from '@jest/globals'; | ||
| import { isDeepEqual, omitDeep } from '../../rulesets/functions/utils/compareUtils'; | ||
|
|
||
| describe('isDeepEqual', () => { | ||
| it('handles primitive values', () => { | ||
| expect(isDeepEqual(1, 1)).toBe(true); | ||
| expect(isDeepEqual('hello', 'hello')).toBe(true); | ||
| expect(isDeepEqual(true, true)).toBe(true); | ||
| expect(isDeepEqual(null, null)).toBe(true); | ||
| expect(isDeepEqual(undefined, undefined)).toBe(true); | ||
|
|
||
| expect(isDeepEqual(1, 2)).toBe(false); | ||
| expect(isDeepEqual('hello', 'world')).toBe(false); | ||
| expect(isDeepEqual(true, false)).toBe(false); | ||
| expect(isDeepEqual(null, undefined)).toBe(false); | ||
| expect(isDeepEqual(1, '1')).toBe(false); | ||
| }); | ||
|
|
||
| it('handles simple objects', () => { | ||
| expect(isDeepEqual({}, {})).toBe(true); | ||
| expect(isDeepEqual({ a: 1 }, { a: 1 })).toBe(true); | ||
| expect(isDeepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true); | ||
|
|
||
| expect(isDeepEqual({ a: 1 }, { a: 2 })).toBe(false); | ||
| expect(isDeepEqual({ a: 1 }, { b: 1 })).toBe(false); | ||
| expect(isDeepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false); | ||
| }); | ||
|
|
||
| it('handles arrays', () => { | ||
| expect(isDeepEqual([], [])).toBe(true); | ||
| expect(isDeepEqual([1, 2], [1, 2])).toBe(true); | ||
|
|
||
| expect(isDeepEqual([1, 2], [2, 1])).toBe(false); | ||
| expect(isDeepEqual([1, 2], [1, 2, 3])).toBe(false); | ||
| }); | ||
|
|
||
| it('handles nested objects', () => { | ||
| expect(isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })).toBe(true); | ||
|
|
||
| expect(isDeepEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 3 } })).toBe(false); | ||
|
|
||
| expect(isDeepEqual({ a: 1, b: { c: 2, d: 3 } }, { a: 1, b: { c: 2 } })).toBe(false); | ||
| }); | ||
|
|
||
| it('handles nested arrays', () => { | ||
| expect(isDeepEqual({ a: [1, 2, { b: 3 }] }, { a: [1, 2, { b: 3 }] })).toBe(true); | ||
|
|
||
| expect(isDeepEqual({ a: [1, 2, { b: 3 }] }, { a: [1, 2, { b: 4 }] })).toBe(false); | ||
| }); | ||
|
|
||
| it('handles mixed types', () => { | ||
| expect(isDeepEqual({ a: 1 }, [1])).toBe(false); | ||
| expect(isDeepEqual({ a: 1 }, null)).toBe(false); | ||
| expect(isDeepEqual(null, { a: 1 })).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('omitDeep', () => { | ||
| it('handles primitives', () => { | ||
| expect(omitDeep(1, 'any')).toBe(1); | ||
| expect(omitDeep('hello', 'any')).toBe('hello'); | ||
| expect(omitDeep(null, 'any')).toBe(null); | ||
| expect(omitDeep(undefined, 'any')).toBe(undefined); | ||
| }); | ||
|
|
||
| it('handles shallow objects', () => { | ||
| expect(omitDeep({ a: 1, b: 2 }, 'a')).toEqual({ b: 2 }); | ||
| expect(omitDeep({ a: 1, b: 2 }, 'c')).toEqual({ a: 1, b: 2 }); | ||
| expect(omitDeep({ a: 1, b: 2 }, 'a', 'b')).toEqual({}); | ||
| }); | ||
|
|
||
| it('handles arrays', () => { | ||
| expect( | ||
| omitDeep( | ||
| [ | ||
| { a: 1, b: 2 }, | ||
| { a: 3, b: 4 }, | ||
| ], | ||
| 'a' | ||
| ) | ||
| ).toEqual([{ b: 2 }, { b: 4 }]); | ||
| }); | ||
|
|
||
| it('handles nested objects', () => { | ||
| const input = { | ||
| a: 1, | ||
| b: { | ||
| c: 2, | ||
| d: 3, | ||
| e: { | ||
| f: 4, | ||
| g: 5, | ||
| }, | ||
| }, | ||
| h: 6, | ||
| }; | ||
|
|
||
| const expected = { | ||
| a: 1, | ||
| b: { | ||
| d: 3, | ||
| e: { | ||
| g: 5, | ||
| }, | ||
| }, | ||
| h: 6, | ||
| }; | ||
|
|
||
| expect(omitDeep(input, 'c', 'f')).toEqual(expected); | ||
| }); | ||
|
|
||
| it('handles deeply nested arrays', () => { | ||
| const input = { | ||
| items: [ | ||
| { id: 1, name: 'item1', metadata: { created: '2023', readOnly: true } }, | ||
| { id: 2, name: 'item2', metadata: { created: '2023', readOnly: true } }, | ||
| ], | ||
| }; | ||
|
|
||
| const expected = { | ||
| items: [ | ||
| { id: 1, name: 'item1', metadata: { created: '2023' } }, | ||
| { id: 2, name: 'item2', metadata: { created: '2023' } }, | ||
| ], | ||
| }; | ||
|
|
||
| expect(omitDeep(input, 'readOnly')).toEqual(expected); | ||
| }); | ||
|
|
||
| it('handles complex schemas', () => { | ||
| const schema = { | ||
| type: 'object', | ||
| properties: { | ||
| name: { type: 'string' }, | ||
| id: { type: 'string', readOnly: true }, | ||
| details: { | ||
| type: 'object', | ||
| properties: { | ||
| createdAt: { type: 'string', readOnly: true }, | ||
| description: { type: 'string' }, | ||
| }, | ||
| }, | ||
| items: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| properties: { | ||
| itemId: { type: 'string', readOnly: true }, | ||
| itemName: { type: 'string' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| const expected = { | ||
| type: 'object', | ||
| properties: { | ||
| name: { type: 'string' }, | ||
| id: { type: 'string' }, | ||
| details: { | ||
| type: 'object', | ||
| properties: { | ||
| createdAt: { type: 'string' }, | ||
| description: { type: 'string' }, | ||
| }, | ||
| }, | ||
| items: { | ||
| type: 'array', | ||
| items: { | ||
| type: 'object', | ||
| properties: { | ||
| itemId: { type: 'string' }, | ||
| itemName: { type: 'string' }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| expect(omitDeep(schema, 'readOnly')).toEqual(expected); | ||
| }); | ||
|
|
||
| it('handles multiple keys to omit', () => { | ||
| const input = { | ||
| a: 1, | ||
| b: 2, | ||
| c: { | ||
| d: 3, | ||
| e: 4, | ||
| f: { | ||
| g: 5, | ||
| h: 6, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| expect(omitDeep(input, 'a', 'e', 'g')).toEqual({ | ||
| b: 2, | ||
| c: { | ||
| d: 3, | ||
| f: { | ||
| h: 6, | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,17 +16,21 @@ rules: | |
| field: '@key' | ||
| function: 'createMethodRequestBodyIsRequestSuffixedObject' | ||
| xgen-IPA-106-create-method-should-not-have-query-parameters: | ||
| description: 'Create operations should not use query parameters. http://go/ipa/xxx' | ||
| description: 'Create operations should not use query parameters. http://go/ipa/106' | ||
| message: '{{error}} http://go/ipa/106' | ||
| severity: warn | ||
| given: '$.paths[*].post' | ||
| then: | ||
| function: 'createMethodShouldNotHaveQueryParameters' | ||
| xgen-IPA-106-create-method-request-body-is-get-method-response: | ||
| description: 'The Create method request should be a Get method response. http://go/ipa/106' | ||
| description: | | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
| Request body content of the Create method and response content of the Get method should refer to the same resource. | ||
| readOnly/writeOnly properties will be ignored. http://go/ipa/106 | ||
| message: '{{error}} http://go/ipa/106' | ||
| severity: warn | ||
| given: '$.paths[*].post.requestBody.content' | ||
| then: | ||
| field: '@key' | ||
| function: 'createMethodRequestBodyIsGetResponse' | ||
| functionOptions: | ||
| ignoredValues: ['readOnly', 'writeOnly'] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,14 @@ | ||
| import { getResponseOfGetMethodByMediaType, isCustomMethodIdentifier } from './utils/resourceEvaluation.js'; | ||
| import { resolveObject } from './utils/componentUtils.js'; | ||
| import { isEqual } from 'lodash'; | ||
| import omitDeep from 'omit-deep-lodash'; | ||
| import { isDeepEqual, omitDeep } from './utils/compareUtils.js'; | ||
| import { hasException } from './utils/exceptions.js'; | ||
| import { collectAdoption, collectAndReturnViolation, collectException } from './utils/collectionUtils.js'; | ||
|
|
||
| const RULE_NAME = 'xgen-IPA-106-create-method-request-body-is-get-method-response'; | ||
| const ERROR_MESSAGE = | ||
| 'The request body schema properties must match the response body schema properties of the Get method.'; | ||
|
|
||
| export default (input, _, { path, documentInventory }) => { | ||
| export default (input, opts, { path, documentInventory }) => { | ||
| const oas = documentInventory.resolved; | ||
| const resourcePath = path[1]; | ||
| let mediaType = input; | ||
|
|
@@ -34,7 +33,8 @@ export default (input, _, { path, documentInventory }) => { | |
| const errors = checkViolationsAndReturnErrors( | ||
| path, | ||
| postMethodRequestContentPerMediaType, | ||
| getMethodResponseContentPerMediaType | ||
| getMethodResponseContentPerMediaType, | ||
| opts | ||
| ); | ||
|
|
||
| if (errors.length !== 0) { | ||
|
|
@@ -47,14 +47,16 @@ export default (input, _, { path, documentInventory }) => { | |
| function checkViolationsAndReturnErrors( | ||
| path, | ||
| postMethodRequestContentPerMediaType, | ||
| getMethodResponseContentPerMediaType | ||
| getMethodResponseContentPerMediaType, | ||
| opts | ||
| ) { | ||
| const errors = []; | ||
|
|
||
| const ignoredValues = opts?.ignoredValues || []; | ||
| if ( | ||
| !isEqual( | ||
| omitDeep(postMethodRequestContentPerMediaType.schema, 'readOnly', 'writeOnly'), | ||
| omitDeep(getMethodResponseContentPerMediaType.schema, 'readOnly', 'writeOnly') | ||
| !isDeepEqual( | ||
| omitDeep(postMethodRequestContentPerMediaType.schema, ...ignoredValues), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
| omitDeep(getMethodResponseContentPerMediaType.schema, ...ignoredValues) | ||
| ) | ||
| ) { | ||
| errors.push({ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️