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); + + } + + }); + + }); + +});