diff --git a/documentation.js b/documentation.js index df5ec08..2fe7c15 100644 --- a/documentation.js +++ b/documentation.js @@ -63,6 +63,8 @@ export default { '$.paths.*.*.responses.links.*', '$.*.securitySchemes.*', '$.securityDefinitions.*', + '$.components.schemas.[*].properties.*', + '$.components.schemas.*', ], 'then': { 'function': ensureField, diff --git a/test/description-for-every-attribute.spec.js b/test/description-for-every-attribute.spec.js new file mode 100644 index 0000000..31367e3 --- /dev/null +++ b/test/description-for-every-attribute.spec.js @@ -0,0 +1,131 @@ +/** + * 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 '../documentation'; +const ruleName = 'description-for-every-attribute'; +const resPath = path.join(__dirname, `resources/${ ruleName }`); + +describe(ruleName, () => { + let spectral; + + beforeAll(() => { + spectral = prepLinter(ruleset, ruleName); + }); + test('should throw an error if missing description', async () => { + const spec = await fsPromises.readFile(`${ resPath }/negative.json`); + const res = await spectral.run(spec.toString()); + + expect(res).toEqual([ + { + code: ruleName, + message: 'For every attribute that is present in the OAS document, if a description is proposed as optional to complement that attribute, then yes it must be present; description is missing in the object', + path: [ + 'paths', + '/test', + 'get', + 'parameters', + '0', + ], + range: { + end: { + character: 29, + line: 36, + }, + start: { + character: 11, + line: 29, + }, + }, + severity: 0, + }, + { + code: ruleName, + message: 'For every attribute that is present in the OAS document, if a description is proposed as optional to complement that attribute, then yes it must be present; description is missing in the object', + path: [ + 'paths', + '/test', + 'get', + 'responses', + '200', + ], + range: { + end: { + character: 32, + line: 72, + }, + start: { + character: 16, + line: 41, + }, + }, + severity: 0, + }, + { + code: ruleName, + message: 'For every attribute that is present in the OAS document, if a description is proposed as optional to complement that attribute, then yes it must be present; description is missing in the object', + path: [ + 'components', + 'schemas', + 'Pet', + ], + range: { + end: { + character: 28, + line: 160, + }, + start: { + character: 12, + line: 143, + }, + }, + severity: 0, + }, + { + code: ruleName, + message: 'For every attribute that is present in the OAS document, if a description is proposed as optional to complement that attribute, then yes it must be present; description is missing in the object', + path: [ + 'components', + 'schemas', + 'Error', + 'properties', + 'message', + ], + range: { + end: { + character: 28, + line: 184, + }, + start: { + character: 20, + line: 183, + }, + }, + severity: 0, + }, + ]); + }); + test('should pass with provided description', async () => { + const spec = await fsPromises.readFile(`${ resPath }/positive.json`); + const res = await spectral.run(spec.toString()); + + expect(res).toEqual([]); + }); +}); diff --git a/test/resources/description-for-every-attribute/negative.json b/test/resources/description-for-every-attribute/negative.json new file mode 100644 index 0000000..541ee4b --- /dev/null +++ b/test/resources/description-for-every-attribute/negative.json @@ -0,0 +1,203 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Sample API", + "description": "This is a sample API.", + "version": "1.0", + "contact": { + "name": "Dan Hayduk" + } + }, + "servers": [ + { + "url": "http://api.example.com" + } + ], + "tags": [ + { + "name": "Sample", + "description": "This is a sample tag." + } + ], + "paths": { + "/test": { + "get": { + "operationId": "getTestData", + "tags": [ + "Sample" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "example": 1 + } + } + ], + "responses": { + "200": { + "headers": { + "X-RateLimit-Limit": { + "schema": { + "type": "integer", + "example": 1 + } + } + }, + "content": { + "application/json": { + "schema": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "example": { + "id": 1, + "name": "tom", + "tag": "man" + } + } + } + } + }, + "500": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "description": "Modify some test data.", + "operationId": "patchTestData", + "tags": [ + "Sample" + ], + "requestBody": { + "description": "descllla", + "content": { + "application/merge-patch+json": { + "schema": { + "type": "string", + "example": "s" + } + }, + "application/json": { + "schema": { + "type": "string", + "example": "s" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "s" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + + }, + + "components": { + "schemas": { + "Pet": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "description": "desc", + "type": "integer", + "format": "int64" + }, + "name": { + "description": "desc", + "type": "string" + }, + "tag": { + "description": "desc", + "type": "string" + } + } + }, + "Pets": { + "description": "desc", + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + }, + "Error": { + "description": "desc", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "description": "desc", + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + }, + "example": { + "code": 2, + "message": "m" + } + } + }, + "securitySchemes": { + "api_key": { + "description": "desc", + "in": "header", + "name": "api_key", + "type": "apiKey" + } + } + } +} diff --git a/test/resources/description-for-every-attribute/positive.json b/test/resources/description-for-every-attribute/positive.json new file mode 100644 index 0000000..7f097fe --- /dev/null +++ b/test/resources/description-for-every-attribute/positive.json @@ -0,0 +1,207 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Sample API", + "description": "This is a sample API.", + "version": "1.0", + "contact": { + "name": "Dan Hayduk" + } + }, + "servers": [ + { + "url": "http://api.example.com" + } + ], + "tags": [ + { + "name": "Sample" + } + ], + "paths": { + "/test": { + "get": { + "description": "Get some test data.", + "operationId": "getTestData", + "tags": [ + "Sample" + ], + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "How many items to return at one time (max 100)", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "example": 1 + } + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "X-RateLimit-Limit": { + "schema": { + "type": "integer", + "example": 1 + } + } + }, + "content": { + "application/json": { + "schema": { + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "example": { + "id": 1, + "name": "tom", + "tag": "man" + } + } + } + } + }, + "500": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + }, + "patch": { + "description": "Modify some test data.", + "operationId": "patchTestData", + "tags": [ + "Sample" + ], + "requestBody": { + "description": "descllla", + "content": { + "application/merge-patch+json": { + "schema": { + "type": "string", + "example": "s" + } + }, + "application/json": { + "schema": { + "type": "string", + "example": "s" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string", + "example": "s" + } + } + } + }, + "default": { + "description": "unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + } + + }, + + "components": { + "schemas": { + "Pet": { + "description": "desc", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "description": "desc", + "type": "integer", + "format": "int64" + }, + "name": { + "description": "desc", + "type": "string" + }, + "tag": { + "description": "desc", + "type": "string" + } + } + }, + "Pets": { + "description": "desc", + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + }, + "Error": { + "description": "desc", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "description": "desc", + "type": "integer", + "format": "int32" + }, + "message": { + "description": "desc", + "type": "string" + } + }, + "example": { + "code": 2, + "message": "m" + } + } + }, + "securitySchemes": { + "api_key": { + "description": "desc", + "in": "header", + "name": "api_key", + "type": "apiKey" + } + } + } +}