Skip to content

Commit 0e988e5

Browse files
IPA 106: Create : A Request object must include only input fields
1 parent 24794df commit 0e988e5

File tree

5 files changed

+254
-6
lines changed

5 files changed

+254
-6
lines changed

tools/spectral/ipa/__tests__/createMethodRequestHasNoReadOnlyFields.test.js renamed to tools/spectral/ipa/__tests__/createMethodRequestHasNoReadonlyFields.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [
134134
errors: [
135135
{
136136
code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields',
137-
message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: id http://go/ipa/106',
137+
message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: id http://go/ipa/106',
138138
path: ['paths', '/invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'],
139139
severity: DiagnosticSeverity.Warning,
140140
}
@@ -163,7 +163,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [
163163
errors: [
164164
{
165165
code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields',
166-
message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId http://go/ipa/106',
166+
message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: user.userId http://go/ipa/106',
167167
path: ['paths', '/nested-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'],
168168
severity: DiagnosticSeverity.Warning,
169169
}
@@ -192,7 +192,7 @@ testRule('xgen-IPA-106-create-method-request-has-no-readonly-fields', [
192192
errors: [
193193
{
194194
code: 'xgen-IPA-106-create-method-request-has-no-readonly-fields',
195-
message: 'The create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId http://go/ipa/106',
195+
message: 'The Create method request object must not include input fields (readOnly properties). Found readOnly property at: items.items.itemId http://go/ipa/106',
196196
path: ['paths', '/array-invalid-resource', 'post', 'requestBody', 'content', 'application/vnd.atlas.2023-01-01+json'],
197197
severity: DiagnosticSeverity.Warning,
198198
}

tools/spectral/ipa/__tests__/getMethodResponseHasNoInputFields.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [
131131
{
132132
code: 'xgen-IPA-104-get-method-response-has-no-input-fields',
133133
message:
134-
'The get method response object must not include output fields (writeOnly properties). http://go/ipa/104',
134+
'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name http://go/ipa/104',
135135
path: [
136136
'paths',
137137
'/resource/{id}',
@@ -146,7 +146,7 @@ testRule('xgen-IPA-104-get-method-response-has-no-input-fields', [
146146
{
147147
code: 'xgen-IPA-104-get-method-response-has-no-input-fields',
148148
message:
149-
'The get method response object must not include output fields (writeOnly properties). http://go/ipa/104',
149+
'The get method response object must not include output fields (writeOnly properties). Found writeOnly property at: name http://go/ipa/104',
150150
path: [
151151
'paths',
152152
'/resource/{id}/singleton',
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// schemaUtils.test.js
2+
import { findPropertiesByAttribute } from '../../rulesets/functions/utils/schemaUtils';
3+
import { describe, expect, it } from '@jest/globals';
4+
5+
describe('findPropertiesByAttribute', () => {
6+
const mockPath = ['paths', '/resources', 'get', 'responses', '200', 'content', 'application/json'];
7+
const errorMessage = 'Test error message';
8+
9+
it('handles primitive values', () => {
10+
expect(findPropertiesByAttribute(null, 'readOnly', mockPath, [], errorMessage)).toEqual([]);
11+
expect(findPropertiesByAttribute(undefined, 'readOnly', mockPath, [], errorMessage)).toEqual([]);
12+
expect(findPropertiesByAttribute('string', 'readOnly', mockPath, [], errorMessage)).toEqual([]);
13+
expect(findPropertiesByAttribute(123, 'readOnly', mockPath, [], errorMessage)).toEqual([]);
14+
expect(findPropertiesByAttribute(true, 'readOnly', mockPath, [], errorMessage)).toEqual([]);
15+
});
16+
17+
it('detects direct attribute match', () => {
18+
const schema = {
19+
type: 'string',
20+
readOnly: true
21+
};
22+
23+
const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
24+
25+
expect(errors).toHaveLength(1);
26+
expect(errors[0]).toEqual({
27+
path: mockPath,
28+
message: `${errorMessage} Found readOnly property at: `
29+
});
30+
});
31+
32+
it('detects properties with the specified attribute', () => {
33+
const schema = {
34+
type: 'object',
35+
properties: {
36+
id: {
37+
type: 'string',
38+
readOnly: true
39+
},
40+
name: {
41+
type: 'string'
42+
},
43+
password: {
44+
type: 'string',
45+
writeOnly: true
46+
}
47+
}
48+
};
49+
50+
// Testing readOnly detection
51+
let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
52+
expect(errors).toHaveLength(1);
53+
expect(errors[0].message).toContain('Found readOnly property at: id');
54+
55+
// Testing writeOnly detection
56+
errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage);
57+
expect(errors).toHaveLength(1);
58+
expect(errors[0].message).toContain('Found writeOnly property at: password');
59+
});
60+
61+
it('detects nested properties with the specified attribute', () => {
62+
const schema = {
63+
type: 'object',
64+
properties: {
65+
user: {
66+
type: 'object',
67+
properties: {
68+
id: {
69+
type: 'string',
70+
readOnly: true
71+
},
72+
credentials: {
73+
type: 'object',
74+
properties: {
75+
password: {
76+
type: 'string',
77+
writeOnly: true
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
};
85+
86+
// Testing deep readOnly detection
87+
let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
88+
expect(errors).toHaveLength(1);
89+
expect(errors[0].message).toContain('Found readOnly property at: user.id');
90+
91+
// Testing deep writeOnly detection
92+
errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage);
93+
expect(errors).toHaveLength(1);
94+
expect(errors[0].message).toContain('Found writeOnly property at: user.credentials.password');
95+
});
96+
97+
it('detects properties in array items', () => {
98+
const schema = {
99+
type: 'object',
100+
properties: {
101+
items: {
102+
type: 'array',
103+
items: {
104+
type: 'object',
105+
properties: {
106+
id: {
107+
type: 'string',
108+
readOnly: true
109+
},
110+
secret: {
111+
type: 'string',
112+
writeOnly: true
113+
}
114+
}
115+
}
116+
}
117+
}
118+
};
119+
120+
// Testing readOnly in array items
121+
let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
122+
expect(errors).toHaveLength(1);
123+
expect(errors[0].message).toContain('Found readOnly property at: items.items.id');
124+
125+
// Testing writeOnly in array items
126+
errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage);
127+
expect(errors).toHaveLength(1);
128+
expect(errors[0].message).toContain('Found writeOnly property at: items.items.secret');
129+
});
130+
131+
it('detects properties in schema combiners', () => {
132+
const schema = {
133+
allOf: [
134+
{
135+
type: 'object',
136+
properties: {
137+
id: {
138+
type: 'string',
139+
readOnly: true
140+
}
141+
}
142+
}
143+
],
144+
anyOf: [
145+
{
146+
type: 'object',
147+
properties: {
148+
key: {
149+
type: 'string',
150+
writeOnly: true
151+
}
152+
}
153+
}
154+
],
155+
oneOf: [
156+
{
157+
type: 'object'
158+
},
159+
{
160+
type: 'object',
161+
properties: {
162+
token: {
163+
type: 'string',
164+
readOnly: true
165+
}
166+
}
167+
}
168+
]
169+
};
170+
171+
// Testing readOnly in combiners
172+
let errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
173+
expect(errors).toHaveLength(2);
174+
expect(errors[0].message).toContain('Found readOnly property at: allOf.0.id');
175+
expect(errors[1].message).toContain('Found readOnly property at: oneOf.1.token');
176+
177+
// Testing writeOnly in combiners
178+
errors = findPropertiesByAttribute(schema, 'writeOnly', mockPath, [], errorMessage);
179+
expect(errors).toHaveLength(1);
180+
expect(errors[0].message).toContain('Found writeOnly property at: anyOf.0.key');
181+
});
182+
183+
it('correctly accumulates multiple errors', () => {
184+
const schema = {
185+
type: 'object',
186+
properties: {
187+
id: {
188+
type: 'string',
189+
readOnly: true
190+
},
191+
nested: {
192+
type: 'object',
193+
properties: {
194+
innerId: {
195+
type: 'string',
196+
readOnly: true
197+
}
198+
}
199+
},
200+
items: {
201+
type: 'array',
202+
items: {
203+
readOnly: true
204+
}
205+
}
206+
}
207+
};
208+
209+
const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
210+
211+
expect(errors).toHaveLength(3);
212+
expect(errors[0].message).toContain('Found readOnly property at: id');
213+
expect(errors[1].message).toContain('Found readOnly property at: nested.innerId');
214+
expect(errors[2].message).toContain('Found readOnly property at: items.items');
215+
});
216+
217+
it('handles empty objects', () => {
218+
const schema = {};
219+
const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
220+
expect(errors).toHaveLength(0);
221+
});
222+
223+
it('handles schemas with no matching attributes', () => {
224+
const schema = {
225+
type: 'object',
226+
properties: {
227+
id: { type: 'string' },
228+
name: { type: 'string' },
229+
nested: {
230+
type: 'object',
231+
properties: {
232+
value: { type: 'number' }
233+
}
234+
},
235+
items: {
236+
type: 'array',
237+
items: {
238+
type: 'string'
239+
}
240+
}
241+
}
242+
};
243+
244+
const errors = findPropertiesByAttribute(schema, 'readOnly', mockPath, [], errorMessage);
245+
expect(errors).toHaveLength(0);
246+
});
247+
});

tools/spectral/ipa/rulesets/IPA-106.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ functions:
55
- createMethodRequestBodyIsRequestSuffixedObject
66
- createMethodShouldNotHaveQueryParameters
77
- createMethodRequestBodyIsGetResponse
8-
- createMethodRequestHasNoReadOnlyFields
8+
- createMethodRequestHasNoReadonlyFields
99

1010
rules:
1111
xgen-IPA-106-create-method-request-body-is-request-suffixed-object:

tools/spectral/ipa/rulesets/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ For rule definitions, see [IPA-106.yaml](https://github.com/mongodb/openapi/blob
6060
| xgen-IPA-106-create-method-request-body-is-get-method-response | Request body content of the Create method and response content of the Get method should refer to the same resource.
6161
readOnly/writeOnly properties will be ignored. http://go/ipa/106
6262
| warn |
63+
| xgen-IPA-106-create-method-request-has-no-readonly-fields | Create method Request object must not include fields with readOnly:true. http://go/ipa/106 | warn |
6364

6465
### IPA-108
6566

0 commit comments

Comments
 (0)