Skip to content

Commit 4fd7cfa

Browse files
Merge pull request #36 from CiscoDevNet/descriptions_not_required_for_requestBody
Fix requestBody description requirement in completeness analyzer
2 parents 395282e + 8387f76 commit 4fd7cfa

6 files changed

Lines changed: 457 additions & 1 deletion

File tree

completeness.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ export default {
155155
'$.server',
156156
'$.externalDocs',
157157
'$.paths.*.*.parameters.*',
158-
'$.paths.*.*.requestBody',
159158
'$.paths.*.*.responses.*',
160159
'$.paths.*.*.examples.*',
161160
'$.paths.*.*.responses.links.*',
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright 2022 Cisco Systems, Inc. and its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
19+
import fsPromises from 'fs/promises';
20+
import path from 'path';
21+
import { prepLinter } from '../util/testUtils';
22+
import ruleset from '../completeness';
23+
24+
const ruleName = 'description-for-every-attribute';
25+
const resPath = path.join(__dirname, `resources/completeness-${ ruleName }`);
26+
27+
describe(`completeness: ${ruleName}`, () => {
28+
let spectral;
29+
30+
beforeAll(() => {
31+
spectral = prepLinter(ruleset, ruleName);
32+
});
33+
34+
test('should NOT require description on requestBody (requestBody.description is optional per OpenAPI spec)', async () => {
35+
const spec = await fsPromises.readFile(`${ resPath }/requestBody-no-description.json`);
36+
const res = await spectral.run(spec.toString());
37+
38+
// Filter results to only check for requestBody-related errors
39+
const requestBodyErrors = res.filter(r =>
40+
r.path && r.path.includes('requestBody') && !r.path.includes('content')
41+
);
42+
43+
// There should be NO errors for missing requestBody description
44+
expect(requestBodyErrors).toEqual([]);
45+
});
46+
47+
test('should still require description on parameters', async () => {
48+
const spec = await fsPromises.readFile(`${ resPath }/parameter-no-description.json`);
49+
const res = await spectral.run(spec.toString());
50+
51+
// Should have error for parameter without description
52+
const paramErrors = res.filter(r =>
53+
r.path && r.path.includes('parameters')
54+
);
55+
56+
expect(paramErrors.length).toBeGreaterThan(0);
57+
expect(paramErrors[0].code).toBe(ruleName);
58+
});
59+
60+
test('should still require description on responses', async () => {
61+
const spec = await fsPromises.readFile(`${ resPath }/response-no-description.json`);
62+
const res = await spectral.run(spec.toString());
63+
64+
// Should have error for response without description
65+
const responseErrors = res.filter(r =>
66+
r.path && r.path.includes('responses')
67+
);
68+
69+
expect(responseErrors.length).toBeGreaterThan(0);
70+
expect(responseErrors[0].code).toBe(ruleName);
71+
});
72+
73+
test('should pass when all required descriptions are present', async () => {
74+
const spec = await fsPromises.readFile(`${ resPath }/positive.json`);
75+
const res = await spectral.run(spec.toString());
76+
77+
expect(res).toEqual([]);
78+
});
79+
});
80+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"openapi": "3.0.3",
3+
"info": {
4+
"title": "Sample API",
5+
"description": "This is a sample API to test parameter description requirement.",
6+
"version": "1.0"
7+
},
8+
"servers": [
9+
{
10+
"url": "https://api.example.com/v1"
11+
}
12+
],
13+
"tags": [
14+
{
15+
"name": "Users",
16+
"description": "User management operations"
17+
}
18+
],
19+
"paths": {
20+
"/users/{userId}": {
21+
"get": {
22+
"summary": "Get a user by ID",
23+
"description": "Retrieves a user by their unique identifier",
24+
"operationId": "getUserById",
25+
"tags": ["Users"],
26+
"parameters": [
27+
{
28+
"name": "userId",
29+
"in": "path",
30+
"required": true,
31+
"schema": {
32+
"type": "string"
33+
}
34+
}
35+
],
36+
"responses": {
37+
"200": {
38+
"description": "User found",
39+
"content": {
40+
"application/json": {
41+
"schema": {
42+
"type": "object",
43+
"properties": {
44+
"id": {
45+
"type": "string"
46+
},
47+
"name": {
48+
"type": "string"
49+
}
50+
}
51+
}
52+
}
53+
}
54+
},
55+
"404": {
56+
"description": "User not found"
57+
}
58+
}
59+
}
60+
}
61+
},
62+
"components": {
63+
"securitySchemes": {
64+
"bearerAuth": {
65+
"type": "http",
66+
"scheme": "bearer",
67+
"description": "JWT Bearer token authentication"
68+
}
69+
}
70+
}
71+
}
72+
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
{
2+
"openapi": "3.0.3",
3+
"info": {
4+
"title": "Sample API",
5+
"description": "This is a sample API with all required descriptions present.",
6+
"version": "1.0"
7+
},
8+
"servers": [
9+
{
10+
"url": "https://api.example.com/v1"
11+
}
12+
],
13+
"tags": [
14+
{
15+
"name": "Users",
16+
"description": "User management operations"
17+
}
18+
],
19+
"paths": {
20+
"/users": {
21+
"get": {
22+
"summary": "List all users",
23+
"description": "Retrieves a list of all users",
24+
"operationId": "listUsers",
25+
"tags": ["Users"],
26+
"parameters": [
27+
{
28+
"name": "limit",
29+
"in": "query",
30+
"description": "Maximum number of users to return",
31+
"required": false,
32+
"schema": {
33+
"type": "integer"
34+
}
35+
}
36+
],
37+
"responses": {
38+
"200": {
39+
"description": "List of users retrieved successfully",
40+
"content": {
41+
"application/json": {
42+
"schema": {
43+
"type": "array",
44+
"items": {
45+
"type": "object",
46+
"properties": {
47+
"id": {
48+
"type": "string"
49+
},
50+
"name": {
51+
"type": "string"
52+
}
53+
}
54+
}
55+
}
56+
}
57+
}
58+
},
59+
"500": {
60+
"description": "Internal server error"
61+
}
62+
}
63+
},
64+
"post": {
65+
"summary": "Create a new user",
66+
"description": "Creates a new user in the system",
67+
"operationId": "createUser",
68+
"tags": ["Users"],
69+
"parameters": [
70+
{
71+
"name": "X-Request-ID",
72+
"in": "header",
73+
"description": "Unique request identifier for tracing",
74+
"required": false,
75+
"schema": {
76+
"type": "string"
77+
}
78+
}
79+
],
80+
"requestBody": {
81+
"required": true,
82+
"content": {
83+
"application/json": {
84+
"schema": {
85+
"type": "object",
86+
"properties": {
87+
"name": {
88+
"type": "string"
89+
},
90+
"email": {
91+
"type": "string"
92+
}
93+
}
94+
},
95+
"example": {
96+
"name": "John Doe",
97+
"email": "john@example.com"
98+
}
99+
}
100+
}
101+
},
102+
"responses": {
103+
"201": {
104+
"description": "User created successfully",
105+
"content": {
106+
"application/json": {
107+
"schema": {
108+
"type": "object",
109+
"properties": {
110+
"id": {
111+
"type": "string"
112+
},
113+
"name": {
114+
"type": "string"
115+
}
116+
}
117+
}
118+
}
119+
}
120+
},
121+
"400": {
122+
"description": "Invalid request"
123+
}
124+
}
125+
}
126+
}
127+
},
128+
"components": {
129+
"securitySchemes": {
130+
"bearerAuth": {
131+
"type": "http",
132+
"scheme": "bearer",
133+
"description": "JWT Bearer token authentication"
134+
}
135+
}
136+
}
137+
}
138+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"openapi": "3.0.3",
3+
"info": {
4+
"title": "Sample API",
5+
"description": "This is a sample API to test requestBody description requirement.",
6+
"version": "1.0"
7+
},
8+
"servers": [
9+
{
10+
"url": "https://api.example.com/v1"
11+
}
12+
],
13+
"tags": [
14+
{
15+
"name": "Users",
16+
"description": "User management operations"
17+
}
18+
],
19+
"paths": {
20+
"/users": {
21+
"post": {
22+
"summary": "Create a new user",
23+
"description": "Creates a new user in the system",
24+
"operationId": "createUser",
25+
"tags": ["Users"],
26+
"parameters": [
27+
{
28+
"name": "X-Request-ID",
29+
"in": "header",
30+
"description": "Unique request identifier for tracing",
31+
"required": false,
32+
"schema": {
33+
"type": "string"
34+
}
35+
}
36+
],
37+
"requestBody": {
38+
"required": true,
39+
"content": {
40+
"application/json": {
41+
"schema": {
42+
"type": "object",
43+
"properties": {
44+
"name": {
45+
"type": "string"
46+
},
47+
"email": {
48+
"type": "string"
49+
}
50+
}
51+
},
52+
"example": {
53+
"name": "John Doe",
54+
"email": "john@example.com"
55+
}
56+
}
57+
}
58+
},
59+
"responses": {
60+
"201": {
61+
"description": "User created successfully",
62+
"content": {
63+
"application/json": {
64+
"schema": {
65+
"type": "object",
66+
"properties": {
67+
"id": {
68+
"type": "string"
69+
},
70+
"name": {
71+
"type": "string"
72+
}
73+
}
74+
}
75+
}
76+
}
77+
},
78+
"400": {
79+
"description": "Invalid request"
80+
}
81+
}
82+
}
83+
}
84+
},
85+
"components": {
86+
"securitySchemes": {
87+
"bearerAuth": {
88+
"type": "http",
89+
"scheme": "bearer",
90+
"description": "JWT Bearer token authentication"
91+
}
92+
}
93+
}
94+
}
95+

0 commit comments

Comments
 (0)