From e8e750bb7b142e972f5b39712d6b401318243f6b Mon Sep 17 00:00:00 2001 From: Anthony M MacAllister <2531816+ezenity@users.noreply.github.com> Date: Tue, 26 Dec 2023 19:26:01 -0500 Subject: [PATCH] API-30 FetchingData.FetchingResources (#55) * Initial Setup * Feat: Adding New Spectral Ruleset This commit introduces a new set of Spectral rules specifically tailored for validating JSON:API v1.0 Fetching Data - Fetching Resources within an OpenAPI Document. Key highlights include: - Creation of Spectral rules to ensure compliance with JSON:API v1.0 standards, focusing on Fetching Resources specifications. - Validating that a server MUST supprt fetching resource data for every URL that is provided to a strict format. These additions significantly improve our capability to automatically validate and ensure the consistency of API responses with the JSON:API v1.0 standard. * Enhance: refactored valid OpenAPI Template This document serves as a standardized template for verifying positive test scenarios in ruleset development. To ensure that the document can be tested against multiple rulesets, the design and endpoints were refactored. This document is now closer to adhering to JSON:API v1.0 specifications. Covered sections include: - ContentNegotiation.ClientResponsibilities - ContentNegotiation.ServerResponsibilities - DocumentStructure - DocumentStructure.TopLevel - DocumentStructure.ResourceObjects - DocumentStructure.ResourceObjects.Attributes - DocumentStructure.Links - DocumentStructure.MetaInformation - DocumentStructure.MemberNames - FetchingData.FetchingResources - FetchingData.Sorting - FetchingData.Pagination - FetchingData.Filtering - Errors.ProcessingErrors - Errors.ErrorObjects * Feat: Test Case Completion This commit marks the completion of detailed test cases for all of the JSON:API v1.0 Fetching Data - Fetching Resources ruleset. These updates significantly improve the robustness and relaibility of the JSON:API v1.0 specifications for Fetching Resources validation process. * Feat: Passing/Failing OpenAPI Documents for Spectral CLI Testing - These two files provides a way to test all the created rules for both positive and negative scenarios. - Created a failing-rules.yaml which generates a way to trigger all the rules to display the error message - Updated the passing-rules.yaml to adhere to the specifications of JSON:API v1.0 for Fetching Resources. --- .../fetchingResources/failing-rules.yaml | 1262 +++++++++++++++++ cliTest/passing-rules.yaml | 30 + ...sonapi-fetching-data-fetching-resources.js | 82 ++ rules/jsonapi-fetching-data-ruleset.yaml | 8 + rules/jsonapi-ruleset.yaml | 1 + .../invalidApiDocumentArrayLevelSelfLink.js | 846 +++++++++++ ...cumentArrayRelationshipLevelRelatedLink.js | 846 +++++++++++ .../invalidApiDocumentSingleLevelSelfLink.js | 846 +++++++++++ ...umentSingleRelationshipLevelRelatedLink.js | 846 +++++++++++ .../invalidApiDocumentTopLevelLinks.js | 837 +++++++++++ test/docs/validApiDocument.js | 57 +- ...i-fetching-data-fetching-resources.test.js | 551 +++++++ 12 files changed, 6210 insertions(+), 2 deletions(-) create mode 100644 cliTest/fetchingData/fetchingResources/failing-rules.yaml create mode 100644 rules/jsonapi-fetching-data-fetching-resources.js create mode 100644 rules/jsonapi-fetching-data-ruleset.yaml create mode 100644 test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayLevelSelfLink.js create mode 100644 test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayRelationshipLevelRelatedLink.js create mode 100644 test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleLevelSelfLink.js create mode 100644 test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleRelationshipLevelRelatedLink.js create mode 100644 test/docs/fetchingData/fetchingResources/invalidApiDocumentTopLevelLinks.js create mode 100644 test/jsonapi-fetching-data-fetching-resources.test.js diff --git a/cliTest/fetchingData/fetchingResources/failing-rules.yaml b/cliTest/fetchingData/fetchingResources/failing-rules.yaml new file mode 100644 index 0000000..14d8d15 --- /dev/null +++ b/cliTest/fetchingData/fetchingResources/failing-rules.yaml @@ -0,0 +1,1262 @@ +# This OpenAPI Document provides a way of testing rulesets using Spectrals CLI Command. Covered rulesets include: +# - `jsonapi-fetching-data-fetching-resources.js` +# +# Usage (From Repo Root Location): +# - spectral lint cliTest/fetchingData/fetchingResources/failing-rules.yaml --ruleset rules/jsonapi-fetching-data-fetching-resources.js --verbose +# +# This document will generate `5 errors` as expected for each ruleset ran +openapi: 3.1.0 +info: + title: OpenAPI Management Template + description: |- + This API manages information pertaining to users + which is adhereing to JSON:API v1.0 standards. The goal of this template is + to provide a universal temaplte for testing all of the JSON:API v1.0 + specifications. This is to generate a failing scenario for the following section: + - FetchingData.FetchingResources + version: 1.2.2 +servers: + - url: https://api.template.com/v1 +x-jsonapi-object: + type: object + properties: + version: + type: string + meta: + type: object + additionalProperties: false + additionalProperties: false +paths: + /users: + get: + tags: + - users + summary: List all users + description: >- + Retrieve a list of users with pagination and optional filters for + sorting and searching. + security: [] + responses: + '200': + description: A list of users + content: + application/vnd.api+json: + schema: + type: object + description: Response schema for a list of users with pagination details. + properties: + data: + type: array + items: + type: object + required: + - id + - type + properties: + id: + type: string + description: Unique identifier for the user + type: + type: string + description: Type of the resource (users) + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + links: + type: object + properties: + # Removing `self` member to trigger rule `fetching-data-fetching-resources-array-level-self-link` + # self: + # type: string + # format: uri + relationships: + type: object + properties: + posts: + type: object + properties: + self: + type: string + format: uri + # Removing `related` member to trigger rule `fetching-data-fetching-resources-array-relationship-level-related-link` + # related: + # type: string + # format: uri + links: + type: object + properties: + # Removing `self` member to trigger rule `fetching-data-fetching-resources-top-level-links` + # self: + # type: string + # format: uri + first: + type: string + format: uri + nullable: true + description: >- + The link to the first page of data. Null if not + available. + last: + type: string + format: uri + nullable: true + description: >- + The link to the last page of data. Null if not + available. + prev: + type: string + format: uri + nullable: true + description: >- + The link to the previous page of data. Null if not + available. + next: + type: string + format: uri + nullable: true + description: >- + The link to the next page of data. Null if not + available. + examples: + userListExample: + summary: Example response for user list + value: + data: + - type: users + id: '1' + attributes: + name: John Doe + email: john@example.com + '400': + description: Bad Request + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + badRequest: + summary: Example of a bad request error + value: + errors: + - id: error-102 + status: '400' + title: Bad Request + detail: The request is invalid. + '500': + description: Internal Server Error - Indicates a server-side error. + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + internalServerErrorExample: + summary: Example of an internal server error response + value: + errors: + - id: error-500 + status: '500' + title: Internal Server Error + detail: >- + The server encountered an unexpected condition that + prevented it from fulfilling the request. + parameters: + - name: page[number] + in: query + schema: + type: integer + minimum: 1 + default: 1 + description: Page number for pagination + - name: page[size] + in: query + schema: + type: integer + minimum: 1 + default: 20 + description: Number of items per page + - name: filter + in: query + schema: + type: string + description: Filter string to narrow down the search + - name: sort + in: query + schema: + type: string + description: >- + Sorting criteria. E.g., `name,-email` for ascending by name and + descending by email. + - name: fields + in: query + schema: + type: string + description: Comma-separated list of fields to include in the response. + post: + tags: + - users + summary: Create a new user + requestBody: + description: Payload to create a new user, containing user details. + required: true + content: + application/vnd.api+json: + schema: + type: object + description: Request schema for creating a new user. + properties: + data: + type: object + required: + - type + - attributes + properties: + type: + type: string + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + responses: + '201': + description: New user created + content: + application/vnd.api+json: + schema: + type: object + description: Response schema for a single user or a newly created user. + properties: + data: + type: object + required: + - id + - type + properties: + id: + type: string + description: Unique identifier for the user + type: + type: string + description: Type of the resource (users) + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + links: + type: object + properties: + # Removing `self` member to trigger rule `fetching-data-fetching-resources-single-level-self-link` + # self: + # type: string + # format: uri + relationships: + type: object + properties: + posts: + type: object + properties: + self: + type: string + format: uri + # Removing `related` member to trigger rule `fetching-data-fetching-resources-single-relationship-level-related-link` + # related: + # type: string + # format: uri + included: + type: array + items: + type: object + properties: + id: + type: string + type: + type: string + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + meta: + type: object + properties: + totalCount: + type: integer + description: Total number of resources available. + lastUpdated: + type: string + format: date-time + description: The timestamp of the last update. + '400': + description: >- + Bad Request - Indicates that the server cannot process the request + due to a client error. + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + badRequest: + summary: Example of a bad request error + value: + errors: + - id: error-701 + status: '400' + title: Bad Request + detail: >- + The request could not be processed due to malformed + syntax. + links: + about: https://api.usermanagement.com/docs/errors/400 + '500': + description: Internal server error - Indicates a server-side error. + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + badRequest: + summary: Example of a bad request error + value: + errors: + - id: error-902 + status: '500' + title: Internal Server Error + detail: The server encountered an unexpected condition. + links: + about: https://api.usermanagement.com/docs/errors/400 + /users/{userId}: + get: + tags: + - users + summary: Get User by ID + security: [] + description: Retrieves information for a specific user by their ID. + operationId: getUserById + parameters: + - name: userId + in: path + required: true + description: Unique identifier of the user + schema: + type: string + responses: + '200': + description: Details of a user + content: + application/vnd.api+json: + schema: + type: object + description: Response schema for a single user or a newly created user. + properties: + data: + type: object + required: + - id + - type + properties: + id: + type: string + description: Unique identifier for the user + type: + type: string + description: Type of the resource (users) + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + links: + type: object + properties: + self: + type: string + format: uri + relationships: + type: object + properties: + posts: + type: object + properties: + self: + type: string + format: uri + related: + type: string + format: uri + included: + type: array + items: + type: object + properties: + id: + type: string + type: + type: string + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + meta: + type: object + properties: + totalCount: + type: integer + description: Total number of resources available. + lastUpdated: + type: string + format: date-time + description: The timestamp of the last update. + examples: + user: + summary: User Example + value: + data: + id: '12345' + type: user + attributes: + name: John Doe + email: john.doe@example.com + '404': + description: User Not Found + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + notFound: + summary: Example of a not found error + value: + errors: + id: error-444 + status: '404' + title: Not Found + detail: The requested resource was not found. + links: + about: https://api.usermanagement.com/docs/errors/404 + '500': + description: Internal Server Error + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + badRequest: + summary: Example of a bad request error + value: + errors: + - id: error-032 + status: '500' + title: Internal Server Error + detail: The server encountered an unexpected condition. + links: + about: https://api.usermanagement.com/docs/errors/500 + put: + tags: + - users + summary: Update a user + parameters: + - name: userId + in: path + required: true + schema: + type: string + requestBody: + description: Payload to update an existing user. + required: true + content: + application/vnd.api+json: + schema: + type: object + description: Request schema for updating an existing user's details. + properties: + data: + type: object + required: + - id + - type + - attributes + properties: + id: + type: string + type: + type: string + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + responses: + '200': + description: User updated + content: + application/vnd.api+json: + schema: + type: object + description: Response schema for a single user or a newly created user. + properties: + data: + type: object + required: + - id + - type + properties: + id: + type: string + description: Unique identifier for the user + type: + type: string + description: Type of the resource (users) + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + links: + type: object + properties: + self: + type: string + format: uri + relationships: + type: object + properties: + posts: + type: object + properties: + self: + type: string + format: uri + related: + type: string + format: uri + included: + type: array + items: + type: object + properties: + id: + type: string + type: + type: string + attributes: + type: object + required: + - name + - email + properties: + name: + type: string + description: Name of the user + email: + type: string + format: email + description: >- + Email address of the user, must follow standard + email format. + role: + type: string + description: Role of the user in the system + meta: + type: object + properties: + totalCount: + type: integer + description: Total number of resources available. + lastUpdated: + type: string + format: date-time + description: The timestamp of the last update. + '400': + description: Bad Request + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + badRequestExample: + summary: Example of a Bad Request response + value: + errors: + - status: 400 + title: Bad Request + detail: >- + The request payload is invalid. Please check the + request data. + '500': + description: Internal Server Error + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + badRequest: + summary: Example of a bad request error + value: + errors: + - id: error-032 + status: '500' + title: Internal Server Error + detail: The server encountered an unexpected condition. + links: + about: https://api.usermanagement.com/docs/errors/500 + delete: + tags: + - users + summary: Delete a user + parameters: + - name: userId + in: path + required: true + schema: + type: string + responses: + '204': + description: The user was successfully deleted. + '404': + description: The specified user was not found. + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + notFoundError: + summary: Example of a 404 Not Found error + value: + errors: + - id: error-123 + status: '404' + title: Not Found + detail: The user with the specified ID was not found. + links: + about: https://api.usermanagement.com/docs/errors/404 + '500': + description: Internal Server Error - Indicates a server-side error. + content: + application/vnd.api+json: + schema: + type: object + required: + - errors + properties: + errors: + type: array + items: + type: object + properties: + id: + type: string + links: + type: object + properties: + about: + type: string + format: uri + status: + type: string + enum: + - '400' + - '401' + - '403' + - '404' + - '405' + - '406' + - '409' + - '422' + - '500' + - '502' + - '503' + description: >- + HTTP status code applicable to this error, given as + a string value. + code: + type: string + title: + type: string + detail: + type: string + source: + type: object + properties: + pointer: + type: string + parameter: + type: string + meta: + type: object + additionalProperties: true + examples: + internalServerErrorExample: + summary: Example of an internal server error response + value: + errors: + - id: error-500 + status: '500' + title: Internal Server Error + detail: >- + The server encountered an unexpected condition that + prevented it from fulfilling the request. +components: + schemas: {} + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: JWT Bearer token authentication + ApiKeyAuth: + type: apiKey + in: header + name: X-API-KEY + description: API Key based authentication +security: + - BearerAuth: [] + - ApiKeyAuth: [] diff --git a/cliTest/passing-rules.yaml b/cliTest/passing-rules.yaml index 31e13e5..8520896 100644 --- a/cliTest/passing-rules.yaml +++ b/cliTest/passing-rules.yaml @@ -1,12 +1,14 @@ # This OpenAPI Document provides a way of testing rulesets using Spectrals CLI Command. Covered rulesets include: # - `jsonapi-errors-error-object.js` # - `jsonapi-document-structure-resource-objects.js` +# - `jsonapi-fetching-data-fetching-resources.js` # - `jsonapi-document-structure-resource-attributes.js` # - `jsonapi-document-structure-resource-identification.js` # # Usage (From Repo Root Location): # - spectral lint cliTest/passing-rules.yaml --ruleset rules/jsonapi-errors-error-object.js --verbose # - spectral lint cliTest/passing-rules.yaml --ruleset rules/jsonapi-document-structure-resource-objects.js --verbose +# - spectral lint cliTest/passing-rules.yaml --ruleset rules/jsonapi-fetching-data-fetching-resources.js --verbose # - spectral lint cliTest/passing-rules.yaml --ruleset rules/jsonapi-document-structure-resource-attributes.js --verbose # - spectral lint cliTest/passing-rules.yaml --ruleset rules/jsonapi-document-structure-resource-identification.js --verbose # @@ -29,6 +31,7 @@ info: - DocumentStructure.Links - DocumentStructure.MetaInformation - DocumentStructure.MemberNames + - FetchingData.FetchingResources - FetchingData.Sorting - FetchingData.Pagination - FetchingData.Filtering @@ -97,6 +100,12 @@ paths: role: type: string description: Role of the user in the system + links: + type: object + properties: + self: + type: string + format: uri relationships: type: object properties: @@ -112,6 +121,9 @@ paths: links: type: object properties: + self: + type: string + format: uri first: type: string format: uri @@ -390,6 +402,12 @@ paths: role: type: string description: Role of the user in the system + links: + type: object + properties: + self: + type: string + format: uri relationships: type: object properties: @@ -629,6 +647,12 @@ paths: role: type: string description: Role of the user in the system + links: + type: object + properties: + self: + type: string + format: uri relationships: type: object properties: @@ -907,6 +931,12 @@ paths: role: type: string description: Role of the user in the system + links: + type: object + properties: + self: + type: string + format: uri relationships: type: object properties: diff --git a/rules/jsonapi-fetching-data-fetching-resources.js b/rules/jsonapi-fetching-data-fetching-resources.js new file mode 100644 index 0000000..9d68dd4 --- /dev/null +++ b/rules/jsonapi-fetching-data-fetching-resources.js @@ -0,0 +1,82 @@ +// Fetching Data - Fetching Resources - https://jsonapi.org/format/1.0/#fetching-resources + +// All rules in the file MUST have corresponding tests + +import { truthy } from '@stoplight/spectral-functions'; + +export default { + documentationUrl: 'https://jsonapi.org/format/1.0/#fetching-resources', + rules: { + + /** + * Ensure top-level resposnes include a `self` link in a `links` object + */ + 'fetching-data-fetching-resources-top-level-links': { + description: 'Ensure top-level resposnes include a `self` link in a `links` object', + message: `{{path}} - {{description}}`, + severity: 'error', + given: "$.paths.*.*.responses.*.content['application/vnd.api+json'].schema.properties.links.properties", + then: { + field: 'self', + function: truthy + } + }, + + /** + * Checks for the presence of a `self` link in each resource object within a single responses + */ + 'fetching-data-fetching-resources-single-level-self-link': { + description: 'Ensure single resource object responses include a `self` link in a `links` object', + message: `{{path}} - {{description}}`, + severity: 'error', + given: "$.paths.*.*.responses.*.content['application/vnd.api+json'].schema.properties.data.properties.links.properties", + then: { + field: 'self', + function: truthy + } + }, + + /** + * Ensures that relationship objects within each resource in responses has a `related` link + */ + 'fetching-data-fetching-resources-single-relationship-level-related-link': { + description: 'Ensure relationship objects in responses include a `related` link', + message: `{{path}} - {{description}}`, + severity: 'error', + given: "$.paths.*.*.responses.*.content['application/vnd.api+json'].schema.properties.data.properties.relationships.properties.*.properties", + then: { + field: 'related', + function: truthy + } + }, + + /** + * Checks for the presence of a `self` link in each resource object within array responses + */ + 'fetching-data-fetching-resources-array-level-self-link': { + description: 'Ensure single resource object responses include a `self` link in a `links` object', + message: `{{path}} - {{description}}`, + severity: 'error', + given: "$.paths.*.*.responses.*.content['application/vnd.api+json'].schema.properties.data.items.properties.links.properties", + then: { + field: 'self', + function: truthy + } + }, + + /** + * Ensures that relationship objects within each resource in array responses has a `related` link + */ + 'fetching-data-fetching-resources-array-relationship-level-related-link': { + description: 'Ensure relationship objects in each resource in array resposnes include a `related` link', + message: `{{path}} - {{description}}`, + severity: 'error', + given: "$.paths.*.*.responses.*.content['application/vnd.api+json'].schema.properties.data.items.properties.relationships.properties.*.properties", + then: { + field: 'related', + function: truthy + } + } + + } +}; diff --git a/rules/jsonapi-fetching-data-ruleset.yaml b/rules/jsonapi-fetching-data-ruleset.yaml new file mode 100644 index 0000000..8704164 --- /dev/null +++ b/rules/jsonapi-fetching-data-ruleset.yaml @@ -0,0 +1,8 @@ +# Fetching Data +# https://jsonapi.org/format/1.0/#fetching + +# *-ruleset.yaml files generally SHOULD NOT contain any testable rules. +# all rules in this file MUST have corresponding tests. + +extends: + - jsonapi-fetching-data-fetching-resources.js \ No newline at end of file diff --git a/rules/jsonapi-ruleset.yaml b/rules/jsonapi-ruleset.yaml index b28d704..45ec4bd 100644 --- a/rules/jsonapi-ruleset.yaml +++ b/rules/jsonapi-ruleset.yaml @@ -6,3 +6,4 @@ extends: - jsonapi-content-negotiation-ruleset.yaml - jsonapi-document-structure-ruleset.yaml - jsonapi-errors-ruleset.yaml + - jsonapi-fetching-data-ruleset.yaml diff --git a/test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayLevelSelfLink.js b/test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayLevelSelfLink.js new file mode 100644 index 0000000..f0f31c3 --- /dev/null +++ b/test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayLevelSelfLink.js @@ -0,0 +1,846 @@ +/* eslint-env mocha */ +/* eslint-disable quotes */ +const invalidApiDocumentArrayLevelSelfLink = { + "openapi": "3.1.0", + "info": { + "title": "OpenAPI Management Template", + "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", + "version": "1.2.2" + }, + "servers": [ + { + "url": "https://api.template.com/v1" + } + ], + "x-jsonapi-object": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "paths": { + "/users": { + "get": { + "tags": [ + "users" + ], + "summary": "List all users", + "description": "Retrieve a list of users with pagination and optional filters for sorting and searching.", + "security": [], + "responses": { + "200": { + "description": "A list of users", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + }, + "examples": { + "userListExample": { + "summary": "Example response for user list", + "value": { + "data": [ + { + "type": "users", + "id": "1", + "attributes": { + "name": "John Doe", + "email": "john@example.com" + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-102", + "status": "400", + "title": "Bad Request", + "detail": "The request is invalid." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "page[number]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "page[size]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + }, + "description": "Number of items per page" + }, + { + "name": "filter", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter string to narrow down the search" + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Sorting criteria. E.g., `name,-email` for ascending by name and descending by email." + }, + { + "name": "fields", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response." + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Create a new user", + "requestBody": { + "description": "Payload to create a new user, containing user details.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserRequest" + } + } + } + }, + "responses": { + "201": { + "description": "New user created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Indicates that the server cannot process the request due to a client error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-701", + "status": "400", + "title": "Bad Request", + "detail": "The request could not be processed due to malformed syntax.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal server error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-902", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "tags": [ + "users" + ], + "summary": "Get User by ID", + "security": [], + "description": "Retrieves information for a specific user by their ID.", + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "description": "Unique identifier of the user", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Details of a user", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "examples": { + "user": { + "summary": "User Example", + "value": { + "data": { + "id": "12345", + "type": "user", + "attributes": { + "name": "John Doe", + "email": "john.doe@example.com" + } + } + } + } + } + } + } + }, + "404": { + "description": "User Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFound": { + "summary": "Example of a not found error", + "value": { + "errors": { + "id": "error-444", + "status": "404", + "title": "Not Found", + "detail": "The requested resource was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "put": { + "tags": [ + "users" + ], + "summary": "Update a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Payload to update an existing user.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User updated", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequestExample": { + "summary": "Example of a Bad Request response", + "value": { + "errors": [ + { + "status": 400, + "title": "Bad Request", + "detail": "The request payload is invalid. Please check the request data." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Delete a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The user was successfully deleted." + }, + "404": { + "description": "The specified user was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFoundError": { + "summary": "Example of a 404 Not Found error", + "value": { + "errors": [ + { + "id": "error-123", + "status": "404", + "title": "Not Found", + "detail": "The user with the specified ID was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the user" + }, + "type": { + "type": "string", + "description": "Type of the resource (users)" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + }, + "links": { + "type": "object", + "properties": { + // Removing the `self` member to generate a failing scenario for rule `fetching-data-fetching-resources-array-level-self-link` + "fail": { + "type": "string", + "format": "uri" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "posts": { + "$ref": "#/components/schemas/RelationshipLinks" + } + } + } + } + }, + "UserResponse": { + "type": "object", + "description": "Response schema for a single user or a newly created user.", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RelatedResource" + } + }, + "meta": { + "$ref": "#/components/schemas/Meta" + } + } + }, + "UserListResponse": { + "type": "object", + "description": "Response schema for a list of users with pagination details.", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "links": { + "$ref": "#/components/schemas/PaginationLinks" + } + } + }, + "UserRequest": { + "type": "object", + "description": "Request schema for creating a new user.", + "properties": { + "data": { + "type": "object", + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserUpdateRequest": { + "type": "object", + "description": "Request schema for updating an existing user's details.", + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserAttributes": { + "type": "object", + "required": [ + "name", + "email" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the user" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user, must follow standard email format." + }, + "role": { + "type": "string", + "description": "Role of the user in the system" + } + } + }, + "RelationshipLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "related": { + "type": "string", + "format": "uri" + } + } + }, + "RelatedResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + }, + "Meta": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer", + "description": "Total number of resources available." + }, + "lastUpdated": { + "type": "string", + "format": "date-time", + "description": "The timestamp of the last update." + } + } + }, + "PaginationLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "first": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the first page of data. Null if not available." + }, + "last": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the last page of data. Null if not available." + }, + "prev": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the previous page of data. Null if not available." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the next page of data. Null if not available." + } + } + }, + "JsonApiError": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "ErrorObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "about": { + "type": "string", + "format": "uri" + } + } + }, + + /** + * Commonly used HTTP status codes: + * + * `400` Bad Request: The request was unacceptable, often due o missing a required parameter + * `401` Unauthorized: No valid authentication credentials provided. + * `403` Forbidden: The client does not have access rights to the content. + * `404` Not Found: The requested resource does not exist. + * `406` Not Acceptable: The requested format is not available. + * `409` Conflict: The request could not be completed due to a conflict. + * `422` Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. + * `500` Internal Server Error: A generic error message for unexpected server errors. + * `502` Bad Gateway: The server received an invalid response from the upstream server. + * `503` Service Unavailable: The server is currently unavailable (overloaded or down). + */ + "status": { + "type": "string", + "enum": [ + "400", + "401", + "403", + "404", + "405", + "406", + "409", + "422", + "500", + "502", + "503" + ], + "description": "HTTP status code applicable to this error, given as a string value." + }, + "code": { + "type": "string" + }, + "title": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "type": "string" + }, + "parameter": { + "type": "string" + } + } + }, + "meta": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Bearer token authentication" + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "API Key based authentication" + } + } + }, + "security": [ + { + "BearerAuth": [] + }, + { + "ApiKeyAuth": [] + } + ] +}; + +export default invalidApiDocumentArrayLevelSelfLink; diff --git a/test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayRelationshipLevelRelatedLink.js b/test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayRelationshipLevelRelatedLink.js new file mode 100644 index 0000000..5f19dbd --- /dev/null +++ b/test/docs/fetchingData/fetchingResources/invalidApiDocumentArrayRelationshipLevelRelatedLink.js @@ -0,0 +1,846 @@ +/* eslint-env mocha */ +/* eslint-disable quotes */ +const invalidApiDocumentArrayRelationshipLevelRelatedLink = { + "openapi": "3.1.0", + "info": { + "title": "OpenAPI Management Template", + "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", + "version": "1.2.2" + }, + "servers": [ + { + "url": "https://api.template.com/v1" + } + ], + "x-jsonapi-object": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "paths": { + "/users": { + "get": { + "tags": [ + "users" + ], + "summary": "List all users", + "description": "Retrieve a list of users with pagination and optional filters for sorting and searching.", + "security": [], + "responses": { + "200": { + "description": "A list of users", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + }, + "examples": { + "userListExample": { + "summary": "Example response for user list", + "value": { + "data": [ + { + "type": "users", + "id": "1", + "attributes": { + "name": "John Doe", + "email": "john@example.com" + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-102", + "status": "400", + "title": "Bad Request", + "detail": "The request is invalid." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "page[number]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "page[size]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + }, + "description": "Number of items per page" + }, + { + "name": "filter", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter string to narrow down the search" + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Sorting criteria. E.g., `name,-email` for ascending by name and descending by email." + }, + { + "name": "fields", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response." + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Create a new user", + "requestBody": { + "description": "Payload to create a new user, containing user details.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserRequest" + } + } + } + }, + "responses": { + "201": { + "description": "New user created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Indicates that the server cannot process the request due to a client error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-701", + "status": "400", + "title": "Bad Request", + "detail": "The request could not be processed due to malformed syntax.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal server error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-902", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "tags": [ + "users" + ], + "summary": "Get User by ID", + "security": [], + "description": "Retrieves information for a specific user by their ID.", + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "description": "Unique identifier of the user", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Details of a user", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "examples": { + "user": { + "summary": "User Example", + "value": { + "data": { + "id": "12345", + "type": "user", + "attributes": { + "name": "John Doe", + "email": "john.doe@example.com" + } + } + } + } + } + } + } + }, + "404": { + "description": "User Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFound": { + "summary": "Example of a not found error", + "value": { + "errors": { + "id": "error-444", + "status": "404", + "title": "Not Found", + "detail": "The requested resource was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "put": { + "tags": [ + "users" + ], + "summary": "Update a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Payload to update an existing user.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User updated", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequestExample": { + "summary": "Example of a Bad Request response", + "value": { + "errors": [ + { + "status": 400, + "title": "Bad Request", + "detail": "The request payload is invalid. Please check the request data." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Delete a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The user was successfully deleted." + }, + "404": { + "description": "The specified user was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFoundError": { + "summary": "Example of a 404 Not Found error", + "value": { + "errors": [ + { + "id": "error-123", + "status": "404", + "title": "Not Found", + "detail": "The user with the specified ID was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the user" + }, + "type": { + "type": "string", + "description": "Type of the resource (users)" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "posts": { + "$ref": "#/components/schemas/RelationshipLinks" + } + } + } + } + }, + "UserResponse": { + "type": "object", + "description": "Response schema for a single user or a newly created user.", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RelatedResource" + } + }, + "meta": { + "$ref": "#/components/schemas/Meta" + } + } + }, + "UserListResponse": { + "type": "object", + "description": "Response schema for a list of users with pagination details.", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "links": { + "$ref": "#/components/schemas/PaginationLinks" + } + } + }, + "UserRequest": { + "type": "object", + "description": "Request schema for creating a new user.", + "properties": { + "data": { + "type": "object", + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserUpdateRequest": { + "type": "object", + "description": "Request schema for updating an existing user's details.", + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserAttributes": { + "type": "object", + "required": [ + "name", + "email" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the user" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user, must follow standard email format." + }, + "role": { + "type": "string", + "description": "Role of the user in the system" + } + } + }, + "RelationshipLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + // Removing to generate a failing scenario for rule `fetching-data-fetching-resources-single-relationship-level-related-link` + // "related": { + // "type": "string", + // "format": "uri" + // } + } + }, + "RelatedResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + }, + "Meta": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer", + "description": "Total number of resources available." + }, + "lastUpdated": { + "type": "string", + "format": "date-time", + "description": "The timestamp of the last update." + } + } + }, + "PaginationLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "first": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the first page of data. Null if not available." + }, + "last": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the last page of data. Null if not available." + }, + "prev": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the previous page of data. Null if not available." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the next page of data. Null if not available." + } + } + }, + "JsonApiError": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "ErrorObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "about": { + "type": "string", + "format": "uri" + } + } + }, + + /** + * Commonly used HTTP status codes: + * + * `400` Bad Request: The request was unacceptable, often due o missing a required parameter + * `401` Unauthorized: No valid authentication credentials provided. + * `403` Forbidden: The client does not have access rights to the content. + * `404` Not Found: The requested resource does not exist. + * `406` Not Acceptable: The requested format is not available. + * `409` Conflict: The request could not be completed due to a conflict. + * `422` Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. + * `500` Internal Server Error: A generic error message for unexpected server errors. + * `502` Bad Gateway: The server received an invalid response from the upstream server. + * `503` Service Unavailable: The server is currently unavailable (overloaded or down). + */ + "status": { + "type": "string", + "enum": [ + "400", + "401", + "403", + "404", + "405", + "406", + "409", + "422", + "500", + "502", + "503" + ], + "description": "HTTP status code applicable to this error, given as a string value." + }, + "code": { + "type": "string" + }, + "title": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "type": "string" + }, + "parameter": { + "type": "string" + } + } + }, + "meta": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Bearer token authentication" + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "API Key based authentication" + } + } + }, + "security": [ + { + "BearerAuth": [] + }, + { + "ApiKeyAuth": [] + } + ] +}; + +export default invalidApiDocumentArrayRelationshipLevelRelatedLink; diff --git a/test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleLevelSelfLink.js b/test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleLevelSelfLink.js new file mode 100644 index 0000000..6fcf1d6 --- /dev/null +++ b/test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleLevelSelfLink.js @@ -0,0 +1,846 @@ +/* eslint-env mocha */ +/* eslint-disable quotes */ +const invalidApiDocumentSingleLevelSelfLink = { + "openapi": "3.1.0", + "info": { + "title": "OpenAPI Management Template", + "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", + "version": "1.2.2" + }, + "servers": [ + { + "url": "https://api.template.com/v1" + } + ], + "x-jsonapi-object": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "paths": { + "/users": { + "get": { + "tags": [ + "users" + ], + "summary": "List all users", + "description": "Retrieve a list of users with pagination and optional filters for sorting and searching.", + "security": [], + "responses": { + "200": { + "description": "A list of users", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + }, + "examples": { + "userListExample": { + "summary": "Example response for user list", + "value": { + "data": [ + { + "type": "users", + "id": "1", + "attributes": { + "name": "John Doe", + "email": "john@example.com" + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-102", + "status": "400", + "title": "Bad Request", + "detail": "The request is invalid." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "page[number]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "page[size]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + }, + "description": "Number of items per page" + }, + { + "name": "filter", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter string to narrow down the search" + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Sorting criteria. E.g., `name,-email` for ascending by name and descending by email." + }, + { + "name": "fields", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response." + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Create a new user", + "requestBody": { + "description": "Payload to create a new user, containing user details.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserRequest" + } + } + } + }, + "responses": { + "201": { + "description": "New user created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Indicates that the server cannot process the request due to a client error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-701", + "status": "400", + "title": "Bad Request", + "detail": "The request could not be processed due to malformed syntax.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal server error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-902", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "tags": [ + "users" + ], + "summary": "Get User by ID", + "security": [], + "description": "Retrieves information for a specific user by their ID.", + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "description": "Unique identifier of the user", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Details of a user", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "examples": { + "user": { + "summary": "User Example", + "value": { + "data": { + "id": "12345", + "type": "user", + "attributes": { + "name": "John Doe", + "email": "john.doe@example.com" + } + } + } + } + } + } + } + }, + "404": { + "description": "User Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFound": { + "summary": "Example of a not found error", + "value": { + "errors": { + "id": "error-444", + "status": "404", + "title": "Not Found", + "detail": "The requested resource was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "put": { + "tags": [ + "users" + ], + "summary": "Update a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Payload to update an existing user.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User updated", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequestExample": { + "summary": "Example of a Bad Request response", + "value": { + "errors": [ + { + "status": 400, + "title": "Bad Request", + "detail": "The request payload is invalid. Please check the request data." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Delete a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The user was successfully deleted." + }, + "404": { + "description": "The specified user was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFoundError": { + "summary": "Example of a 404 Not Found error", + "value": { + "errors": [ + { + "id": "error-123", + "status": "404", + "title": "Not Found", + "detail": "The user with the specified ID was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the user" + }, + "type": { + "type": "string", + "description": "Type of the resource (users)" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + }, + "links": { + "type": "object", + "properties": { + // Removing the `self` member to generate a failing scenario for rule `fetching-data-fetching-resources-single-level-self-link` + "fail": { + "type": "string", + "format": "uri" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "posts": { + "$ref": "#/components/schemas/RelationshipLinks" + } + } + } + } + }, + "UserResponse": { + "type": "object", + "description": "Response schema for a single user or a newly created user.", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RelatedResource" + } + }, + "meta": { + "$ref": "#/components/schemas/Meta" + } + } + }, + "UserListResponse": { + "type": "object", + "description": "Response schema for a list of users with pagination details.", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "links": { + "$ref": "#/components/schemas/PaginationLinks" + } + } + }, + "UserRequest": { + "type": "object", + "description": "Request schema for creating a new user.", + "properties": { + "data": { + "type": "object", + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserUpdateRequest": { + "type": "object", + "description": "Request schema for updating an existing user's details.", + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserAttributes": { + "type": "object", + "required": [ + "name", + "email" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the user" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user, must follow standard email format." + }, + "role": { + "type": "string", + "description": "Role of the user in the system" + } + } + }, + "RelationshipLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "related": { + "type": "string", + "format": "uri" + } + } + }, + "RelatedResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + }, + "Meta": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer", + "description": "Total number of resources available." + }, + "lastUpdated": { + "type": "string", + "format": "date-time", + "description": "The timestamp of the last update." + } + } + }, + "PaginationLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "first": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the first page of data. Null if not available." + }, + "last": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the last page of data. Null if not available." + }, + "prev": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the previous page of data. Null if not available." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the next page of data. Null if not available." + } + } + }, + "JsonApiError": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "ErrorObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "about": { + "type": "string", + "format": "uri" + } + } + }, + + /** + * Commonly used HTTP status codes: + * + * `400` Bad Request: The request was unacceptable, often due o missing a required parameter + * `401` Unauthorized: No valid authentication credentials provided. + * `403` Forbidden: The client does not have access rights to the content. + * `404` Not Found: The requested resource does not exist. + * `406` Not Acceptable: The requested format is not available. + * `409` Conflict: The request could not be completed due to a conflict. + * `422` Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. + * `500` Internal Server Error: A generic error message for unexpected server errors. + * `502` Bad Gateway: The server received an invalid response from the upstream server. + * `503` Service Unavailable: The server is currently unavailable (overloaded or down). + */ + "status": { + "type": "string", + "enum": [ + "400", + "401", + "403", + "404", + "405", + "406", + "409", + "422", + "500", + "502", + "503" + ], + "description": "HTTP status code applicable to this error, given as a string value." + }, + "code": { + "type": "string" + }, + "title": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "type": "string" + }, + "parameter": { + "type": "string" + } + } + }, + "meta": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Bearer token authentication" + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "API Key based authentication" + } + } + }, + "security": [ + { + "BearerAuth": [] + }, + { + "ApiKeyAuth": [] + } + ] +}; + +export default invalidApiDocumentSingleLevelSelfLink; diff --git a/test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleRelationshipLevelRelatedLink.js b/test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleRelationshipLevelRelatedLink.js new file mode 100644 index 0000000..552f18b --- /dev/null +++ b/test/docs/fetchingData/fetchingResources/invalidApiDocumentSingleRelationshipLevelRelatedLink.js @@ -0,0 +1,846 @@ +/* eslint-env mocha */ +/* eslint-disable quotes */ +const invalidApiDocumentSingleRelationshipLevelRelatedLink = { + "openapi": "3.1.0", + "info": { + "title": "OpenAPI Management Template", + "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", + "version": "1.2.2" + }, + "servers": [ + { + "url": "https://api.template.com/v1" + } + ], + "x-jsonapi-object": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "paths": { + "/users": { + "get": { + "tags": [ + "users" + ], + "summary": "List all users", + "description": "Retrieve a list of users with pagination and optional filters for sorting and searching.", + "security": [], + "responses": { + "200": { + "description": "A list of users", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + }, + "examples": { + "userListExample": { + "summary": "Example response for user list", + "value": { + "data": [ + { + "type": "users", + "id": "1", + "attributes": { + "name": "John Doe", + "email": "john@example.com" + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-102", + "status": "400", + "title": "Bad Request", + "detail": "The request is invalid." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "page[number]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "page[size]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + }, + "description": "Number of items per page" + }, + { + "name": "filter", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter string to narrow down the search" + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Sorting criteria. E.g., `name,-email` for ascending by name and descending by email." + }, + { + "name": "fields", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response." + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Create a new user", + "requestBody": { + "description": "Payload to create a new user, containing user details.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserRequest" + } + } + } + }, + "responses": { + "201": { + "description": "New user created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Indicates that the server cannot process the request due to a client error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-701", + "status": "400", + "title": "Bad Request", + "detail": "The request could not be processed due to malformed syntax.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal server error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-902", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "tags": [ + "users" + ], + "summary": "Get User by ID", + "security": [], + "description": "Retrieves information for a specific user by their ID.", + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "description": "Unique identifier of the user", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Details of a user", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "examples": { + "user": { + "summary": "User Example", + "value": { + "data": { + "id": "12345", + "type": "user", + "attributes": { + "name": "John Doe", + "email": "john.doe@example.com" + } + } + } + } + } + } + } + }, + "404": { + "description": "User Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFound": { + "summary": "Example of a not found error", + "value": { + "errors": { + "id": "error-444", + "status": "404", + "title": "Not Found", + "detail": "The requested resource was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "put": { + "tags": [ + "users" + ], + "summary": "Update a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Payload to update an existing user.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User updated", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequestExample": { + "summary": "Example of a Bad Request response", + "value": { + "errors": [ + { + "status": 400, + "title": "Bad Request", + "detail": "The request payload is invalid. Please check the request data." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Delete a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The user was successfully deleted." + }, + "404": { + "description": "The specified user was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFoundError": { + "summary": "Example of a 404 Not Found error", + "value": { + "errors": [ + { + "id": "error-123", + "status": "404", + "title": "Not Found", + "detail": "The user with the specified ID was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the user" + }, + "type": { + "type": "string", + "description": "Type of the resource (users)" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + } + }, + "relationships": { + "type": "object", + "properties": { + "posts": { + "$ref": "#/components/schemas/RelationshipLinks" + } + } + } + } + }, + "UserResponse": { + "type": "object", + "description": "Response schema for a single user or a newly created user.", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RelatedResource" + } + }, + "meta": { + "$ref": "#/components/schemas/Meta" + } + } + }, + "UserListResponse": { + "type": "object", + "description": "Response schema for a list of users with pagination details.", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "links": { + "$ref": "#/components/schemas/PaginationLinks" + } + } + }, + "UserRequest": { + "type": "object", + "description": "Request schema for creating a new user.", + "properties": { + "data": { + "type": "object", + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserUpdateRequest": { + "type": "object", + "description": "Request schema for updating an existing user's details.", + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserAttributes": { + "type": "object", + "required": [ + "name", + "email" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the user" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user, must follow standard email format." + }, + "role": { + "type": "string", + "description": "Role of the user in the system" + } + } + }, + "RelationshipLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + // Removing to generate a failing scenario for rule `fetching-data-fetching-resources-single-relationship-level-related-link` + // "related": { + // "type": "string", + // "format": "uri" + // } + } + }, + "RelatedResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + }, + "Meta": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer", + "description": "Total number of resources available." + }, + "lastUpdated": { + "type": "string", + "format": "date-time", + "description": "The timestamp of the last update." + } + } + }, + "PaginationLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "first": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the first page of data. Null if not available." + }, + "last": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the last page of data. Null if not available." + }, + "prev": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the previous page of data. Null if not available." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the next page of data. Null if not available." + } + } + }, + "JsonApiError": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "ErrorObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "about": { + "type": "string", + "format": "uri" + } + } + }, + + /** + * Commonly used HTTP status codes: + * + * `400` Bad Request: The request was unacceptable, often due o missing a required parameter + * `401` Unauthorized: No valid authentication credentials provided. + * `403` Forbidden: The client does not have access rights to the content. + * `404` Not Found: The requested resource does not exist. + * `406` Not Acceptable: The requested format is not available. + * `409` Conflict: The request could not be completed due to a conflict. + * `422` Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. + * `500` Internal Server Error: A generic error message for unexpected server errors. + * `502` Bad Gateway: The server received an invalid response from the upstream server. + * `503` Service Unavailable: The server is currently unavailable (overloaded or down). + */ + "status": { + "type": "string", + "enum": [ + "400", + "401", + "403", + "404", + "405", + "406", + "409", + "422", + "500", + "502", + "503" + ], + "description": "HTTP status code applicable to this error, given as a string value." + }, + "code": { + "type": "string" + }, + "title": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "type": "string" + }, + "parameter": { + "type": "string" + } + } + }, + "meta": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Bearer token authentication" + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "API Key based authentication" + } + } + }, + "security": [ + { + "BearerAuth": [] + }, + { + "ApiKeyAuth": [] + } + ] +}; + +export default invalidApiDocumentSingleRelationshipLevelRelatedLink; diff --git a/test/docs/fetchingData/fetchingResources/invalidApiDocumentTopLevelLinks.js b/test/docs/fetchingData/fetchingResources/invalidApiDocumentTopLevelLinks.js new file mode 100644 index 0000000..2e4ef20 --- /dev/null +++ b/test/docs/fetchingData/fetchingResources/invalidApiDocumentTopLevelLinks.js @@ -0,0 +1,837 @@ +/* eslint-env mocha */ +/* eslint-disable quotes */ +const invalidApiDocumentTopLevelLinks = { + "openapi": "3.1.0", + "info": { + "title": "OpenAPI Management Template", + "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", + "version": "1.2.2" + }, + "servers": [ + { + "url": "https://api.template.com/v1" + } + ], + "x-jsonapi-object": { + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "meta": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "paths": { + "/users": { + "get": { + "tags": [ + "users" + ], + "summary": "List all users", + "description": "Retrieve a list of users with pagination and optional filters for sorting and searching.", + "security": [], + "responses": { + "200": { + "description": "A list of users", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserListResponse" + }, + "examples": { + "userListExample": { + "summary": "Example response for user list", + "value": { + "data": [ + { + "type": "users", + "id": "1", + "attributes": { + "name": "John Doe", + "email": "john@example.com" + } + } + ] + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-102", + "status": "400", + "title": "Bad Request", + "detail": "The request is invalid." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + }, + "parameters": [ + { + "name": "page[number]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 1 + }, + "description": "Page number for pagination" + }, + { + "name": "page[size]", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1, + "default": 20 + }, + "description": "Number of items per page" + }, + { + "name": "filter", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Filter string to narrow down the search" + }, + { + "name": "sort", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Sorting criteria. E.g., `name,-email` for ascending by name and descending by email." + }, + { + "name": "fields", + "in": "query", + "schema": { + "type": "string" + }, + "description": "Comma-separated list of fields to include in the response." + } + ] + }, + "post": { + "tags": [ + "users" + ], + "summary": "Create a new user", + "requestBody": { + "description": "Payload to create a new user, containing user details.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserRequest" + } + } + } + }, + "responses": { + "201": { + "description": "New user created", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request - Indicates that the server cannot process the request due to a client error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-701", + "status": "400", + "title": "Bad Request", + "detail": "The request could not be processed due to malformed syntax.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal server error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-902", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/400" + } + } + ] + } + } + } + } + } + } + } + } + }, + "/users/{userId}": { + "get": { + "tags": [ + "users" + ], + "summary": "Get User by ID", + "security": [], + "description": "Retrieves information for a specific user by their ID.", + "operationId": "getUserById", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "description": "Unique identifier of the user", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Details of a user", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + }, + "examples": { + "user": { + "summary": "User Example", + "value": { + "data": { + "id": "12345", + "type": "user", + "attributes": { + "name": "John Doe", + "email": "john.doe@example.com" + } + } + } + } + } + } + } + }, + "404": { + "description": "User Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFound": { + "summary": "Example of a not found error", + "value": { + "errors": { + "id": "error-444", + "status": "404", + "title": "Not Found", + "detail": "The requested resource was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "put": { + "tags": [ + "users" + ], + "summary": "Update a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Payload to update an existing user.", + "required": true, + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "User updated", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/UserResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequestExample": { + "summary": "Example of a Bad Request response", + "value": { + "errors": [ + { + "status": 400, + "title": "Bad Request", + "detail": "The request payload is invalid. Please check the request data." + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "badRequest": { + "summary": "Example of a bad request error", + "value": { + "errors": [ + { + "id": "error-032", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/500" + } + } + ] + } + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "users" + ], + "summary": "Delete a user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The user was successfully deleted." + }, + "404": { + "description": "The specified user was not found.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "notFoundError": { + "summary": "Example of a 404 Not Found error", + "value": { + "errors": [ + { + "id": "error-123", + "status": "404", + "title": "Not Found", + "detail": "The user with the specified ID was not found.", + "links": { + "about": "https://api.usermanagement.com/docs/errors/404" + } + } + ] + } + } + } + } + } + }, + "500": { + "description": "Internal Server Error - Indicates a server-side error.", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/JsonApiError" + }, + "examples": { + "internalServerErrorExample": { + "summary": "Example of an internal server error response", + "value": { + "errors": [ + { + "id": "error-500", + "status": "500", + "title": "Internal Server Error", + "detail": "The server encountered an unexpected condition that prevented it from fulfilling the request." + } + ] + } + } + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "required": [ + "id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the user" + }, + "type": { + "type": "string", + "description": "Type of the resource (users)" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + }, + "relationships": { + "type": "object", + "properties": { + "posts": { + "$ref": "#/components/schemas/RelationshipLinks" + } + } + } + } + }, + "UserResponse": { + "type": "object", + "description": "Response schema for a single user or a newly created user.", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + }, + "included": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RelatedResource" + } + }, + "meta": { + "$ref": "#/components/schemas/Meta" + } + } + }, + "UserListResponse": { + "type": "object", + "description": "Response schema for a list of users with pagination details.", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + }, + "links": { + "$ref": "#/components/schemas/PaginationLinks" + } + } + }, + "UserRequest": { + "type": "object", + "description": "Request schema for creating a new user.", + "properties": { + "data": { + "type": "object", + "required": [ + "type", + "attributes" + ], + "properties": { + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserUpdateRequest": { + "type": "object", + "description": "Request schema for updating an existing user's details.", + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + } + } + }, + "UserAttributes": { + "type": "object", + "required": [ + "name", + "email" + ], + "properties": { + "name": { + "type": "string", + "description": "Name of the user" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email address of the user, must follow standard email format." + }, + "role": { + "type": "string", + "description": "Role of the user in the system" + } + } + }, + "RelationshipLinks": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + }, + "related": { + "type": "string", + "format": "uri" + } + } + }, + "RelatedResource": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "attributes": { + "$ref": "#/components/schemas/UserAttributes" + } + } + }, + "Meta": { + "type": "object", + "properties": { + "totalCount": { + "type": "integer", + "description": "Total number of resources available." + }, + "lastUpdated": { + "type": "string", + "format": "date-time", + "description": "The timestamp of the last update." + } + } + }, + "PaginationLinks": { + "type": "object", + "properties": { + // Removing this member to generate a failing scenario for rule `fetching-data-fetching-resources-top-level-links` + // "self": { + // "type": "string", + // "format": "uri" + // }, + "first": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the first page of data. Null if not available." + }, + "last": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the last page of data. Null if not available." + }, + "prev": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the previous page of data. Null if not available." + }, + "next": { + "type": "string", + "format": "uri", + "nullable": true, + "description": "The link to the next page of data. Null if not available." + } + } + }, + "JsonApiError": { + "type": "object", + "required": [ + "errors" + ], + "properties": { + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ErrorObject" + } + } + } + }, + "ErrorObject": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "links": { + "type": "object", + "properties": { + "about": { + "type": "string", + "format": "uri" + } + } + }, + + /** + * Commonly used HTTP status codes: + * + * `400` Bad Request: The request was unacceptable, often due o missing a required parameter + * `401` Unauthorized: No valid authentication credentials provided. + * `403` Forbidden: The client does not have access rights to the content. + * `404` Not Found: The requested resource does not exist. + * `406` Not Acceptable: The requested format is not available. + * `409` Conflict: The request could not be completed due to a conflict. + * `422` Unprocessable Entity: The request was well-formed but was unable to be followed due to semantic errors. + * `500` Internal Server Error: A generic error message for unexpected server errors. + * `502` Bad Gateway: The server received an invalid response from the upstream server. + * `503` Service Unavailable: The server is currently unavailable (overloaded or down). + */ + "status": { + "type": "string", + "enum": [ + "400", + "401", + "403", + "404", + "405", + "406", + "409", + "422", + "500", + "502", + "503" + ], + "description": "HTTP status code applicable to this error, given as a string value." + }, + "code": { + "type": "string" + }, + "title": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "source": { + "type": "object", + "properties": { + "pointer": { + "type": "string" + }, + "parameter": { + "type": "string" + } + } + }, + "meta": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "securitySchemes": { + "BearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "JWT Bearer token authentication" + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-KEY", + "description": "API Key based authentication" + } + } + }, + "security": [ + { + "BearerAuth": [] + }, + { + "ApiKeyAuth": [] + } + ] +}; + +export default invalidApiDocumentTopLevelLinks; diff --git a/test/docs/validApiDocument.js b/test/docs/validApiDocument.js index 972b4de..07be6f4 100644 --- a/test/docs/validApiDocument.js +++ b/test/docs/validApiDocument.js @@ -1,11 +1,51 @@ /* eslint-env mocha */ /* eslint-disable quotes */ +/** + * This validApiDocument is utilized for a centralized dereferencing document which is used in test suites as a global scope. + * This will provide performance optimiation and resolve any issues related to mutliple derferencing. + * + * Key Reason: + * 1. Performance Optimization: + * - As our API template grows, the number of test cases that require a dereferenced version of validApiDocument is increasing. + * - Previously, each test file was individually dereferencing this document, which is a resource-intensive process. + * By dereferencing it once globally, we reduce the computational overhead significantly. + * - This global approach ensures that all tests use the same dereferenced instance, improving the overall efficiency of our + * test execution. + * 2. Resolving Multiple Dereferencing Issues: + * - In our prior setup, attempting to dereference an already dereferenced document in different test files led to inconsistencies + * and potential errors. + * - By having a single, globally dereferenced document, we eliminate the risk of such issues. This ensures that all tests work + * with a consistent and stable version of the API document. + * 3. Maintainability and Consistency: + * - This refactor simplifies the test setup by removing redundant dereferencing logic in multiple files. + * - It also enhances the consistency across our test suites, making it easier for the team to write and maintain tests. + * + * Use Case: + * - Inside of your test case suite you will need to create a variable that will call the globally scoped document + * - @see {@link ../jsonapi-fetching-data-fetching-resources.test.js} for live usage + * - Below is a setup example: + * + * + * describe('your-custom-rule-file ruleset:', function yourCustomTestSuite() { + * + * let dereferenceValidApiDocument; + * + * before(function () { + * + * // Access the globally dereferenced document + * dereferenceValidApiDocument = global.dereferencedValidOpenApiDocument; + * + * }); + * + * }); + * + */ const validApiDocument = { "openapi": "3.1.0", "info": { "title": "OpenAPI Management Template", - "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", - "version": "1.2.0" + "description": "This API manages information pertaining to users\nwhich is adhereing to JSON:API v1.0 standards. The goal of this template is\nto provide a universal temaplte for testing all of the JSON:API v1.0\nspecifications. This document adheres to the following sections:\n - ContentNegotiation.ClientResponsibilities\n - ContentNegotiation.ServerResponsibilities\n - DocumentStructure\n - DocumentStructure.TopLevel\n - DocumentStructure.ResourceObjects\n - DocumentStructure.ResourceObjects.Attributes\n - DocumentStructure.Links\n - DocumentStructure.MetaInformation\n - DocumentStructure.MemberNames\n - FetchingData.FetchingResources\n - FetchingData.Sorting\n - FetchingData.Pagination\n - FetchingData.Filtering\n - Errors.ProcessingErrors\n - Errors.ErrorObjects", + "version": "1.2.2" }, "servers": [ { @@ -535,6 +575,15 @@ const validApiDocument = { "attributes": { "$ref": "#/components/schemas/UserAttributes" }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "string", + "format": "uri" + } + } + }, "relationships": { "type": "object", "properties": { @@ -690,6 +739,10 @@ const validApiDocument = { "PaginationLinks": { "type": "object", "properties": { + "self": { + "type": "string", + "format": "uri" + }, "first": { "type": "string", "format": "uri", diff --git a/test/jsonapi-fetching-data-fetching-resources.test.js b/test/jsonapi-fetching-data-fetching-resources.test.js new file mode 100644 index 0000000..79377af --- /dev/null +++ b/test/jsonapi-fetching-data-fetching-resources.test.js @@ -0,0 +1,551 @@ +/* eslint-env mocha */ +import { JSONPath } from 'jsonpath-plus'; + +// Rules to test +import ruleset from '../rules/jsonapi-fetching-data-fetching-resources.js'; + +// Helper Functions +import { resolveRef } from './utils/refResolver.js'; +import { handleSpectralResults } from './utils/handleSpectralResults.js'; +import { processErrors } from './utils/processErrors.js'; +import { debugLog, debugError, debugInfo, debugDebug } from './utils/debugUtils.js'; +import { setupSpectralBeforeEach } from './utils/setupSpectralBeforeEach.js'; + +// OpenAPI Documents +import invalidApiDocumentTopLevelLinks from './docs/fetchingData/fetchingResources/invalidApiDocumentTopLevelLinks.js'; +import invalidApiDocumentSingleLevelSelfLink from './docs/fetchingData/fetchingResources/invalidApiDocumentSingleLevelSelfLink.js'; +import invalidApiDocumentSingleRelationshipLevelRelatedLink from './docs/fetchingData/fetchingResources/invalidApiDocumentSingleRelationshipLevelRelatedLink.js'; +import invalidApiDocumentArrayLevelSelfLink from './docs/fetchingData/fetchingResources/invalidApiDocumentArrayLevelSelfLink.js'; +import invalidApiDocumentArrayRelationshipLevelRelatedLink from './docs/fetchingData/fetchingResources/invalidApiDocumentArrayRelationshipLevelRelatedLink.js'; + +/** + * @fileoverview This test suite validates the behavior of the JSON: API FetchingData.FetchingResources ruleset + * when given OpenAPI documents. It tests the different rules defined in jsonapi-fetching-data-fetching-resources.js + * against various OpenAPI documents that are valid based on JSON: API v1.0 standards + * + * The tests leverage several helper methods: + * - `setupSpectralBeforeEach`: Creates a beforeEach function for Mocha tests, setting up Spectral with a given ruleset + * and enabling specific rules. + * - `handleSpectralResults`: Filters and handles the results of the spectral run. + * - `processErrors`: Processes and logs errors, specifically handling AggregateErrors separately. This function checks if + * the provided error is an instance of AggregateError. If so, it iterates over each individual error within the aggregate + * and logs them separately. For all other types of errors, it logs them as unexpected errors. This utility is particularly + * useful for handling and debugging multiple errors that can occur during Spectral setup or execution. + * - `resolveRef`: Recursively resolves $ref references in an OpenAPI document. This function handles objects and arrays, + * resolving all $ref references found within. It supports nested structures and arrays, handles circular references, and + * keeps resolved references from the components section for when they are needed at a later time. + * + * The suite uses Mocha for test execution and Chai for assertions. + * + * Each ruleset has three test cases that focuses on the following: + * - Validate the JSONPath Expression for that rule + * - Generate a negative case scenario for that rule + * - Generate a positive case scenario for that rule + */ + +describe('jsonapi-fetching-data-fetching-resources ruleset:', function fetchingDataFetchingResourcesSuite() { + + let dereferenceValidApiDocument; + + before(function () { + + // Access the globally dereferenced document + dereferenceValidApiDocument = global.dereferencedValidOpenApiDocument; + + }); + + /** + * Ruleset: fetching-data-fetching-resources-top-level-links + */ + describe('fetching-data-fetching-resources-top-level-links:', function fetchingDataFetchingResourcesTopLevelLinks() { + + const testingRuleName = 'fetching-data-fetching-resources-top-level-links'; + + beforeEach(setupSpectralBeforeEach(ruleset, [testingRuleName])); + + it('the json path expression should find the correct paths from the given document', function fetchingDataFetchingResourcesTopLevelLinksPath() { + + try { + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferenceValidApiDocument, null, 2)}`); + + const jsonPathExpression = ruleset.rules[testingRuleName].given; + debugDebug(`JSONPath Expression: ${jsonPathExpression}`); + const expectedExpressionPaths = [ + { expected: dereferenceValidApiDocument.paths['/users'].get.responses['200'].content['application/vnd.api+json'].schema.properties.links.properties } + ]; + + expectedExpressionPaths.forEach((path, index) => { + + const result = JSONPath({ path: jsonPathExpression, + json: dereferenceValidApiDocument }); + + debugInfo(`Element ${index + 1} found from JSONPath Expression: \x1b[32m${JSON.stringify(result[index], null, 2)}`); + + // Check if the number of results matches the expected number + expect(result.length).to.equal(expectedExpressionPaths.length, `\x1b[31mExpected ${expectedExpressionPaths.length} elements to match in the OpenAPI Document.\x1b[0m\n`); + + // Check if each result matches the corresponding expected path + expect(result[index]).to.deep.equal(path.expected, `\x1b[31mThe wrong JSONPath Expression was provided in expected path: ${index + 1}\x1b[0m`); + + }); + + } catch (error) { + + processErrors(error); + + } + + }); + + it(`the rule should return "${testingRuleName}" errors, if ${ruleset.rules[testingRuleName].description} is false`, async function fetchingDataFetchingResourcesTopLevelLinksFailure() { + + try { + + const dereferencedOpenApiDocument = resolveRef(invalidApiDocumentTopLevelLinks, invalidApiDocumentTopLevelLinks); + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferencedOpenApiDocument,null,2)}`); + + const relevantResults = await handleSpectralResults(this.spectral, dereferencedOpenApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + relevantResults.forEach((result) => { + + debugError(`\x1b[32mResults for '\x1b[33m${testingRuleName}\x1b[32m':\x1b[36m ${JSON.stringify(result, ['message', 'path'], 2)} \x1b[0m\n`); + + }); + + const confirmedErrors = 1; + const errorMessage = `\x1b[31mError count should be ${confirmedErrors}.\x1b[0m`; + + expect(relevantResults.length).to.equal(confirmedErrors, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + it('the rule should pass with NO errors', async function fetchingDataFetchingResourcesTopLevelLinksPassing() { + + try { + + const relevantResults = await handleSpectralResults(this.spectral, dereferenceValidApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + const errorMessage = `\x1b[31mError count should be 0, ${ruleset.rules[testingRuleName].description}\x1b[0m`; + expect(relevantResults.length).to.equal(0, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + }); + + /** + * Ruleset: fetching-data-fetching-resources-single-level-self-link + */ + describe('fetching-data-fetching-resources-single-level-self-link:', function fetchingDataFetchingResourcesSingleLevelSelfLink() { + + const testingRuleName = 'fetching-data-fetching-resources-single-level-self-link'; + + beforeEach(setupSpectralBeforeEach(ruleset, [testingRuleName])); + + it('the json path expression should find the correct paths from the given document', function fetchingDataFetchingResourcesSingleLevelSelfLinkPath() { + + try { + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferenceValidApiDocument, null, 2)}`); + + const jsonPathExpression = ruleset.rules[testingRuleName].given; + debugDebug(`JSONPath Expression: ${jsonPathExpression}`); + const expectedExpressionPaths = [ + { expected: dereferenceValidApiDocument.paths['/users'].post.responses['201'].content['application/vnd.api+json'].schema.properties.data.properties.links.properties }, + { expected: dereferenceValidApiDocument.paths['/users/{userId}'].get.responses['200'].content['application/vnd.api+json'].schema.properties.data.properties.links.properties }, + { expected: dereferenceValidApiDocument.paths['/users/{userId}'].put.responses['200'].content['application/vnd.api+json'].schema.properties.data.properties.links.properties } + ]; + + expectedExpressionPaths.forEach((path, index) => { + + const result = JSONPath({ path: jsonPathExpression, + json: dereferenceValidApiDocument }); + + debugInfo(`Element ${index + 1} found from JSONPath Expression: \x1b[32m${JSON.stringify(result[index], null, 2)}`); + + // Check if the number of results matches the expected number + expect(result.length).to.equal(expectedExpressionPaths.length, `\x1b[31mExpected ${expectedExpressionPaths.length} elements to match in the OpenAPI Document.\x1b[0m\n`); + + // Check if each result matches the corresponding expected path + expect(result[index]).to.deep.equal(path.expected, `\x1b[31mThe wrong JSONPath Expression was provided in expected path: ${index + 1}\x1b[0m`); + + }); + + } catch (error) { + + processErrors(error); + + } + + }); + + it(`the rule should return "${testingRuleName}" errors, if ${ruleset.rules[testingRuleName].description} is false`, async function fetchingDataFetchingResourcesSingleLevelSelfLinkFailure() { + + try { + + const dereferencedOpenApiDocument = resolveRef(invalidApiDocumentSingleLevelSelfLink, invalidApiDocumentSingleLevelSelfLink); + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferencedOpenApiDocument,null,2)}`); + + const relevantResults = await handleSpectralResults(this.spectral, dereferencedOpenApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + relevantResults.forEach((result) => { + + debugError(`\x1b[32mResults for '\x1b[33m${testingRuleName}\x1b[32m':\x1b[36m ${JSON.stringify(result, ['message', 'path'], 2)} \x1b[0m\n`); + + }); + + const confirmedErrors = 3; + const errorMessage = `\x1b[31mError count should be ${confirmedErrors}.\x1b[0m`; + + expect(relevantResults.length).to.equal(confirmedErrors, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + it('the rule should pass with NO errors', async function fetchingDataFetchingResourcesSingleLevelSelfLinkPassing() { + + try { + + const relevantResults = await handleSpectralResults(this.spectral, dereferenceValidApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + const errorMessage = `\x1b[31mError count should be 0, ${ruleset.rules[testingRuleName].description}\x1b[0m`; + expect(relevantResults.length).to.equal(0, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + }); + + /** + * Ruleset: fetching-data-fetching-resources-single-relationship-level-related-link + */ + describe('fetching-data-fetching-resources-single-relationship-level-related-link:', function fetchingDataFetchingResourcesSingleRelationshipLevelRelatedLink() { + + const testingRuleName = 'fetching-data-fetching-resources-single-relationship-level-related-link'; + + beforeEach(setupSpectralBeforeEach(ruleset, [testingRuleName])); + + it('the json path expression should find the correct paths from the given document', function fetchingDataFetchingResourcesSingleRelationshipLevelRelatedLinkPath() { + + try { + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferenceValidApiDocument, null, 2)}`); + + const jsonPathExpression = ruleset.rules[testingRuleName].given; + debugDebug(`JSONPath Expression: ${jsonPathExpression}`); + const expectedExpressionPaths = [ + { expected: dereferenceValidApiDocument.paths['/users'].post.responses['201'].content['application/vnd.api+json'].schema.properties.data.properties.relationships.properties.posts.properties }, + { expected: dereferenceValidApiDocument.paths['/users/{userId}'].get.responses['200'].content['application/vnd.api+json'].schema.properties.data.properties.relationships.properties.posts.properties }, + { expected: dereferenceValidApiDocument.paths['/users/{userId}'].put.responses['200'].content['application/vnd.api+json'].schema.properties.data.properties.relationships.properties.posts.properties } + ]; + + expectedExpressionPaths.forEach((path, index) => { + + const result = JSONPath({ path: jsonPathExpression, + json: dereferenceValidApiDocument }); + + debugInfo(`Element ${index + 1} found from JSONPath Expression: \x1b[32m${JSON.stringify(result[index], null, 2)}`); + + // Check if the number of results matches the expected number + expect(result.length).to.equal(expectedExpressionPaths.length, `\x1b[31mExpected ${expectedExpressionPaths.length} elements to match in the OpenAPI Document.\x1b[0m\n`); + + // Check if each result matches the corresponding expected path + expect(result[index]).to.deep.equal(path.expected, `\x1b[31mThe wrong JSONPath Expression was provided in expected path: ${index + 1}\x1b[0m`); + + }); + + } catch (error) { + + processErrors(error); + + } + + }); + + it(`the rule should return "${testingRuleName}" errors, if ${ruleset.rules[testingRuleName].description} is false`, async function fetchingDataFetchingResourcesSingleRelationshipLevelRelatedLinkFailure() { + + try { + + const dereferencedOpenApiDocument = resolveRef(invalidApiDocumentSingleRelationshipLevelRelatedLink, invalidApiDocumentSingleRelationshipLevelRelatedLink); + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferencedOpenApiDocument,null,2)}`); + + const relevantResults = await handleSpectralResults(this.spectral, dereferencedOpenApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + relevantResults.forEach((result) => { + + debugError(`\x1b[32mResults for '\x1b[33m${testingRuleName}\x1b[32m':\x1b[36m ${JSON.stringify(result, ['message', 'path'], 2)} \x1b[0m\n`); + + }); + + const confirmedErrors = 3; + const errorMessage = `\x1b[31mError count should be ${confirmedErrors}.\x1b[0m`; + + expect(relevantResults.length).to.equal(confirmedErrors, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + it('the rule should pass with NO errors', async function fetchingDataFetchingResourcesSingleRelationshipLevelRelatedLinkPassing() { + + try { + + const relevantResults = await handleSpectralResults(this.spectral, dereferenceValidApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + const errorMessage = `\x1b[31mError count should be 0, ${ruleset.rules[testingRuleName].description}\x1b[0m`; + expect(relevantResults.length).to.equal(0, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + }); + + /** + * Ruleset: fetching-data-fetching-resources-array-level-self-link + */ + describe('fetching-data-fetching-resources-array-level-self-link:', function fetchingDataFetchingResourcesArrayLevelSelfLink() { + + const testingRuleName = 'fetching-data-fetching-resources-array-level-self-link'; + + beforeEach(setupSpectralBeforeEach(ruleset, [testingRuleName])); + + it('the json path expression should find the correct paths from the given document', function fetchingDataFetchingResourcesArrayLevelSelfLinkPath() { + + try { + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferenceValidApiDocument, null, 2)}`); + + const jsonPathExpression = ruleset.rules[testingRuleName].given; + debugDebug(`JSONPath Expression: ${jsonPathExpression}`); + const expectedExpressionPaths = [ + { expected: dereferenceValidApiDocument.paths['/users'].get.responses['200'].content['application/vnd.api+json'].schema.properties.data.items.properties.links.properties } + ]; + + expectedExpressionPaths.forEach((path, index) => { + + const result = JSONPath({ path: jsonPathExpression, + json: dereferenceValidApiDocument }); + + debugInfo(`Element ${index + 1} found from JSONPath Expression: \x1b[32m${JSON.stringify(result[index], null, 2)}`); + + // Check if the number of results matches the expected number + expect(result.length).to.equal(expectedExpressionPaths.length, `\x1b[31mExpected ${expectedExpressionPaths.length} elements to match in the OpenAPI Document.\x1b[0m\n`); + + // Check if each result matches the corresponding expected path + expect(result[index]).to.deep.equal(path.expected, `\x1b[31mThe wrong JSONPath Expression was provided in expected path: ${index + 1}\x1b[0m`); + + }); + + } catch (error) { + + processErrors(error); + + } + + }); + + it(`the rule should return "${testingRuleName}" errors, if ${ruleset.rules[testingRuleName].description} is false`, async function fetchingDataFetchingResourcesArrayLevelSelfLinkFailure() { + + try { + + const dereferencedOpenApiDocument = resolveRef(invalidApiDocumentArrayLevelSelfLink, invalidApiDocumentArrayLevelSelfLink); + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferencedOpenApiDocument,null,2)}`); + + const relevantResults = await handleSpectralResults(this.spectral, dereferencedOpenApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + relevantResults.forEach((result) => { + + debugError(`\x1b[32mResults for '\x1b[33m${testingRuleName}\x1b[32m':\x1b[36m ${JSON.stringify(result, ['message', 'path'], 2)} \x1b[0m\n`); + + }); + + const confirmedErrors = 1; + const errorMessage = `\x1b[31mError count should be ${confirmedErrors}.\x1b[0m`; + + expect(relevantResults.length).to.equal(confirmedErrors, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + it('the rule should pass with NO errors', async function fetchingDataFetchingResourcesArrayLevelSelfLinkPassing() { + + try { + + const relevantResults = await handleSpectralResults(this.spectral, dereferenceValidApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + const errorMessage = `\x1b[31mError count should be 0, ${ruleset.rules[testingRuleName].description}\x1b[0m`; + expect(relevantResults.length).to.equal(0, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + }); + + /** + * Ruleset: fetching-data-fetching-resources-array-relationship-level-related-link + */ + describe('fetching-data-fetching-resources-array-relationship-level-related-link:', function fetchingDataFetchingResourcesArrayRelationshipLevelRelatedLink() { + + const testingRuleName = 'fetching-data-fetching-resources-array-relationship-level-related-link'; + + beforeEach(setupSpectralBeforeEach(ruleset, [testingRuleName])); + + it('the json path expression should find the correct paths from the given document', function fetchingDataFetchingResourcesArrayRelationshipLevelRelatedLinkPath() { + + try { + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferenceValidApiDocument, null, 2)}`); + + const jsonPathExpression = ruleset.rules[testingRuleName].given; + debugDebug(`JSONPath Expression: ${jsonPathExpression}`); + const expectedExpressionPaths = [ + { expected: dereferenceValidApiDocument.paths['/users'].get.responses['200'].content['application/vnd.api+json'].schema.properties.data.items.properties.relationships.properties.posts.properties } + ]; + + expectedExpressionPaths.forEach((path, index) => { + + const result = JSONPath({ path: jsonPathExpression, + json: dereferenceValidApiDocument }); + + debugInfo(`Element ${index + 1} found from JSONPath Expression: \x1b[32m${JSON.stringify(result[index], null, 2)}`); + + // Check if the number of results matches the expected number + expect(result.length).to.equal(expectedExpressionPaths.length, `\x1b[31mExpected ${expectedExpressionPaths.length} elements to match in the OpenAPI Document.\x1b[0m\n`); + + // Check if each result matches the corresponding expected path + expect(result[index]).to.deep.equal(path.expected, `\x1b[31mThe wrong JSONPath Expression was provided in expected path: ${index + 1}\x1b[0m`); + + }); + + } catch (error) { + + processErrors(error); + + } + + }); + + it(`the rule should return "${testingRuleName}" errors, if ${ruleset.rules[testingRuleName].description} is false`, async function fetchingDataFetchingResourcesArrayRelationshipLevelRelatedLinkFailure() { + + try { + + const dereferencedOpenApiDocument = resolveRef(invalidApiDocumentArrayRelationshipLevelRelatedLink, invalidApiDocumentArrayRelationshipLevelRelatedLink); + + // debugDebug(`Dereferenced Document: ${JSON.stringify(dereferencedOpenApiDocument,null,2)}`); + + const relevantResults = await handleSpectralResults(this.spectral, dereferencedOpenApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + relevantResults.forEach((result) => { + + debugError(`\x1b[32mResults for '\x1b[33m${testingRuleName}\x1b[32m':\x1b[36m ${JSON.stringify(result, ['message', 'path'], 2)} \x1b[0m\n`); + + }); + + const confirmedErrors = 1; + const errorMessage = `\x1b[31mError count should be ${confirmedErrors}.\x1b[0m`; + + expect(relevantResults.length).to.equal(confirmedErrors, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + it('the rule should pass with NO errors', async function fetchingDataFetchingResourcesArrayRelationshipLevelRelatedLinkPassing() { + + try { + + const relevantResults = await handleSpectralResults(this.spectral, dereferenceValidApiDocument, testingRuleName); + + debugLog(` Confirmed Errors:`); + debugLog(`\x1b[33m - ${relevantResults.length}\x1b[0m\n`); + + const errorMessage = `\x1b[31mError count should be 0, ${ruleset.rules[testingRuleName].description}\x1b[0m`; + expect(relevantResults.length).to.equal(0, errorMessage); + + } catch (error) { + + processErrors(error); + + } + + }); + + }); + +});