From 8387f76d602818b388e8af9c6b324caa628c45ac Mon Sep 17 00:00:00 2001 From: Prasanth Janardhanan Date: Mon, 5 Jan 2026 16:22:27 +0530 Subject: [PATCH] fix: remove requestBody description requirement from completeness analyzer The requestBody.description field is optional per OpenAPI spec. Removed '$.paths.*.*.requestBody' from description-for-every-attribute rule in completeness.js. Added tests to verify the fix. --- completeness.js | 1 - ...ss-description-for-every-attribute.spec.js | 80 ++++++++++ .../parameter-no-description.json | 72 +++++++++ .../positive.json | 138 ++++++++++++++++++ .../requestBody-no-description.json | 95 ++++++++++++ .../response-no-description.json | 72 +++++++++ 6 files changed, 457 insertions(+), 1 deletion(-) create mode 100644 test/completeness-description-for-every-attribute.spec.js create mode 100644 test/resources/completeness-description-for-every-attribute/parameter-no-description.json create mode 100644 test/resources/completeness-description-for-every-attribute/positive.json create mode 100644 test/resources/completeness-description-for-every-attribute/requestBody-no-description.json create mode 100644 test/resources/completeness-description-for-every-attribute/response-no-description.json diff --git a/completeness.js b/completeness.js index cbaad58..78f871a 100644 --- a/completeness.js +++ b/completeness.js @@ -155,7 +155,6 @@ export default { '$.server', '$.externalDocs', '$.paths.*.*.parameters.*', - '$.paths.*.*.requestBody', '$.paths.*.*.responses.*', '$.paths.*.*.examples.*', '$.paths.*.*.responses.links.*', diff --git a/test/completeness-description-for-every-attribute.spec.js b/test/completeness-description-for-every-attribute.spec.js new file mode 100644 index 0000000..8650dca --- /dev/null +++ b/test/completeness-description-for-every-attribute.spec.js @@ -0,0 +1,80 @@ +/** + * Copyright 2022 Cisco Systems, Inc. and its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import fsPromises from 'fs/promises'; +import path from 'path'; +import { prepLinter } from '../util/testUtils'; +import ruleset from '../completeness'; + +const ruleName = 'description-for-every-attribute'; +const resPath = path.join(__dirname, `resources/completeness-${ ruleName }`); + +describe(`completeness: ${ruleName}`, () => { + let spectral; + + beforeAll(() => { + spectral = prepLinter(ruleset, ruleName); + }); + + test('should NOT require description on requestBody (requestBody.description is optional per OpenAPI spec)', async () => { + const spec = await fsPromises.readFile(`${ resPath }/requestBody-no-description.json`); + const res = await spectral.run(spec.toString()); + + // Filter results to only check for requestBody-related errors + const requestBodyErrors = res.filter(r => + r.path && r.path.includes('requestBody') && !r.path.includes('content') + ); + + // There should be NO errors for missing requestBody description + expect(requestBodyErrors).toEqual([]); + }); + + test('should still require description on parameters', async () => { + const spec = await fsPromises.readFile(`${ resPath }/parameter-no-description.json`); + const res = await spectral.run(spec.toString()); + + // Should have error for parameter without description + const paramErrors = res.filter(r => + r.path && r.path.includes('parameters') + ); + + expect(paramErrors.length).toBeGreaterThan(0); + expect(paramErrors[0].code).toBe(ruleName); + }); + + test('should still require description on responses', async () => { + const spec = await fsPromises.readFile(`${ resPath }/response-no-description.json`); + const res = await spectral.run(spec.toString()); + + // Should have error for response without description + const responseErrors = res.filter(r => + r.path && r.path.includes('responses') + ); + + expect(responseErrors.length).toBeGreaterThan(0); + expect(responseErrors[0].code).toBe(ruleName); + }); + + test('should pass when all required descriptions are present', async () => { + const spec = await fsPromises.readFile(`${ resPath }/positive.json`); + const res = await spectral.run(spec.toString()); + + expect(res).toEqual([]); + }); +}); + diff --git a/test/resources/completeness-description-for-every-attribute/parameter-no-description.json b/test/resources/completeness-description-for-every-attribute/parameter-no-description.json new file mode 100644 index 0000000..3099fab --- /dev/null +++ b/test/resources/completeness-description-for-every-attribute/parameter-no-description.json @@ -0,0 +1,72 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Sample API", + "description": "This is a sample API to test parameter description requirement.", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "tags": [ + { + "name": "Users", + "description": "User management operations" + } + ], + "paths": { + "/users/{userId}": { + "get": { + "summary": "Get a user by ID", + "description": "Retrieves a user by their unique identifier", + "operationId": "getUserById", + "tags": ["Users"], + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "User found", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "JWT Bearer token authentication" + } + } + } +} + diff --git a/test/resources/completeness-description-for-every-attribute/positive.json b/test/resources/completeness-description-for-every-attribute/positive.json new file mode 100644 index 0000000..e3b466f --- /dev/null +++ b/test/resources/completeness-description-for-every-attribute/positive.json @@ -0,0 +1,138 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Sample API", + "description": "This is a sample API with all required descriptions present.", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "tags": [ + { + "name": "Users", + "description": "User management operations" + } + ], + "paths": { + "/users": { + "get": { + "summary": "List all users", + "description": "Retrieves a list of all users", + "operationId": "listUsers", + "tags": ["Users"], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Maximum number of users to return", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "List of users retrieved successfully", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + }, + "500": { + "description": "Internal server error" + } + } + }, + "post": { + "summary": "Create a new user", + "description": "Creates a new user in the system", + "operationId": "createUser", + "tags": ["Users"], + "parameters": [ + { + "name": "X-Request-ID", + "in": "header", + "description": "Unique request identifier for tracing", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "example": { + "name": "John Doe", + "email": "john@example.com" + } + } + } + }, + "responses": { + "201": { + "description": "User created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid request" + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "JWT Bearer token authentication" + } + } + } +} + diff --git a/test/resources/completeness-description-for-every-attribute/requestBody-no-description.json b/test/resources/completeness-description-for-every-attribute/requestBody-no-description.json new file mode 100644 index 0000000..9fe92d1 --- /dev/null +++ b/test/resources/completeness-description-for-every-attribute/requestBody-no-description.json @@ -0,0 +1,95 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Sample API", + "description": "This is a sample API to test requestBody description requirement.", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "tags": [ + { + "name": "Users", + "description": "User management operations" + } + ], + "paths": { + "/users": { + "post": { + "summary": "Create a new user", + "description": "Creates a new user in the system", + "operationId": "createUser", + "tags": ["Users"], + "parameters": [ + { + "name": "X-Request-ID", + "in": "header", + "description": "Unique request identifier for tracing", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "example": { + "name": "John Doe", + "email": "john@example.com" + } + } + } + }, + "responses": { + "201": { + "description": "User created successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + }, + "400": { + "description": "Invalid request" + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "JWT Bearer token authentication" + } + } + } +} + diff --git a/test/resources/completeness-description-for-every-attribute/response-no-description.json b/test/resources/completeness-description-for-every-attribute/response-no-description.json new file mode 100644 index 0000000..4a8cbde --- /dev/null +++ b/test/resources/completeness-description-for-every-attribute/response-no-description.json @@ -0,0 +1,72 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Sample API", + "description": "This is a sample API to test response description requirement.", + "version": "1.0" + }, + "servers": [ + { + "url": "https://api.example.com/v1" + } + ], + "tags": [ + { + "name": "Users", + "description": "User management operations" + } + ], + "paths": { + "/users": { + "get": { + "summary": "List all users", + "description": "Retrieves a list of all users", + "operationId": "listUsers", + "tags": ["Users"], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Maximum number of users to return", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "description": "JWT Bearer token authentication" + } + } + } +} +