diff --git a/src/__tests__/fixtures/response-content-negotiation/baseline.json b/src/__tests__/fixtures/response-content-negotiation/baseline.json index 3505b9f..46d1988 100644 --- a/src/__tests__/fixtures/response-content-negotiation/baseline.json +++ b/src/__tests__/fixtures/response-content-negotiation/baseline.json @@ -5,7 +5,7 @@ "mockDetails": { "interactionDescription": "should error on unknown request accept", "interactionState": "[none]", - "location": "[root].interactions[1].request.headers.accept", + "location": "[root].interactions[2].request.headers.accept", "value": "application/xxx" }, "specDetails": { @@ -29,7 +29,7 @@ "mockDetails": { "interactionDescription": "should error on incompatible request accept", "interactionState": "[none]", - "location": "[root].interactions[2].request.headers.accept", + "location": "[root].interactions[3].request.headers.accept", "value": "application/xxx" }, "specDetails": { diff --git a/src/__tests__/fixtures/response-content-negotiation/oas.yaml b/src/__tests__/fixtures/response-content-negotiation/oas.yaml index 556a79f..8393e78 100644 --- a/src/__tests__/fixtures/response-content-negotiation/oas.yaml +++ b/src/__tests__/fixtures/response-content-negotiation/oas.yaml @@ -17,3 +17,17 @@ paths: content: application/aaa: {} application/bbb: {} + /with-encoding: + get: + responses: + "200": + description: OK + content: + "application/aaa; utf-8": + schema: + type: object + required: + - name + properties: + name: + type: number diff --git a/src/__tests__/fixtures/response-content-negotiation/pact.json b/src/__tests__/fixtures/response-content-negotiation/pact.json index 657f474..f7be7d1 100644 --- a/src/__tests__/fixtures/response-content-negotiation/pact.json +++ b/src/__tests__/fixtures/response-content-negotiation/pact.json @@ -4,7 +4,6 @@ "interactions": [ { "description": "should pass on successful response content negotiation", - "providerState": "", "request": { "method": "GET", "path": "/with-content", @@ -19,9 +18,27 @@ } } }, + { + "description": "should pass on successful response content negotiation, with encoding", + "request": { + "method": "GET", + "path": "/with-encoding", + "headers": { + "accept": "application/aaa" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/aaa" + }, + "body": { + "name": "name" + } + } + }, { "description": "should error on unknown request accept", - "providerState": "", "request": { "method": "GET", "path": "/no-content", @@ -35,7 +52,6 @@ }, { "description": "should error on incompatible request accept", - "providerState": "", "request": { "method": "GET", "path": "/with-content", diff --git a/src/__tests__/fixtures/response-content-negotiation/results.json b/src/__tests__/fixtures/response-content-negotiation/results.json index 3505b9f..46d1988 100644 --- a/src/__tests__/fixtures/response-content-negotiation/results.json +++ b/src/__tests__/fixtures/response-content-negotiation/results.json @@ -5,7 +5,7 @@ "mockDetails": { "interactionDescription": "should error on unknown request accept", "interactionState": "[none]", - "location": "[root].interactions[1].request.headers.accept", + "location": "[root].interactions[2].request.headers.accept", "value": "application/xxx" }, "specDetails": { @@ -29,7 +29,7 @@ "mockDetails": { "interactionDescription": "should error on incompatible request accept", "interactionState": "[none]", - "location": "[root].interactions[2].request.headers.accept", + "location": "[root].interactions[3].request.headers.accept", "value": "application/xxx" }, "specDetails": { diff --git a/src/compare/requestBody.ts b/src/compare/requestBody.ts index be045b5..31c4284 100644 --- a/src/compare/requestBody.ts +++ b/src/compare/requestBody.ts @@ -17,7 +17,7 @@ import { minimumSchema, transformRequestSchema } from "../transform/index"; import { isValidRequest } from "../utils/interaction"; import { dereferenceOas, splitPath } from "../utils/schema"; import { getValidateFunction } from "../utils/validation"; -import { findMatchingType } from "./utils/content"; +import { findMatchingType, getByContentType } from "./utils/content"; const parseBody = (body: unknown, contentType: string) => { if ( @@ -56,14 +56,17 @@ export function* compareReqBody( Object.keys( dereferenceOas(operation.requestBody || {}, oas)?.content || {}, ); - const contentType = - findMatchingType( - requestHeaders.get("content-type") || DEFAULT_CONTENT_TYPE, - availableRequestContentTypes, - ) || DEFAULT_CONTENT_TYPE; + + const requestContentType = requestHeaders.get("content-type") || ""; + const contentType = requestContentType + ? findMatchingType(requestContentType, availableRequestContentTypes) || + requestContentType + : DEFAULT_CONTENT_TYPE; const schema: SchemaObject | undefined = - dereferenceOas(operation.requestBody || {}, oas)?.content?.[contentType] - ?.schema || + getByContentType( + dereferenceOas(operation.requestBody || {}, oas)?.content, + contentType, + )?.schema || dereferenceOas( (operation.parameters || []).find( (p: OpenAPIV2.ParameterObject) => p.in === "body", @@ -72,7 +75,7 @@ export function* compareReqBody( )?.schema; if (schema && canValidate(contentType) && isValidRequest(interaction)) { - const value = parseBody(body, contentType); + const value = parseBody(body, requestContentType); const schemaId = `[root].paths.${path}.${method}.requestBody.content.${contentType}`; const validate = getValidateFunction(ajv, schemaId, () => transformRequestSchema(minimumSchema(schema, oas)), diff --git a/src/compare/requestHeader.ts b/src/compare/requestHeader.ts index fd0d32d..ede8bb1 100644 --- a/src/compare/requestHeader.ts +++ b/src/compare/requestHeader.ts @@ -109,11 +109,7 @@ export function* compareReqHeader( const requestContentType: string = requestHeaders.get("content-type")?.split(";")[0] || ""; - if ( - requestContentType && - !availableRequestContentType.length && - isValidRequest(interaction) - ) { + if (requestContentType && !availableRequestContentType.length) { yield { code: "request.content-type.unknown", message: diff --git a/src/compare/responseBody.ts b/src/compare/responseBody.ts index b1d7428..6058a34 100644 --- a/src/compare/responseBody.ts +++ b/src/compare/responseBody.ts @@ -15,7 +15,7 @@ import { import { minimumSchema, transformResponseSchema } from "../transform/index"; import { dereferenceOas, splitPath } from "../utils/schema"; import { getValidateFunction } from "../utils/validation"; -import { findMatchingType } from "./utils/content"; +import { findMatchingType, getByContentType } from "./utils/content"; const canValidate = (contentType = ""): boolean => { return !!findMatchingType(contentType, ["application/json"]); @@ -74,7 +74,8 @@ export function* compareResBody( const dereferencedResponse = dereferenceOas(response, oas); const schema: SchemaObject | undefined = (dereferencedResponse as OpenAPIV2.ResponseObject)?.schema || - dereferencedResponse.content?.[contentType]?.schema; + getByContentType(dereferencedResponse.content || {}, contentType)?.schema; + const value = body; if (!statusResponse) { diff --git a/src/compare/utils/content.ts b/src/compare/utils/content.ts index 1b596a7..164dc6c 100644 --- a/src/compare/utils/content.ts +++ b/src/compare/utils/content.ts @@ -1,3 +1,5 @@ +import type { SchemaObject } from "ajv"; + const PARAMETER_SEPARATOR = ";"; const WILDCARD = "*"; const TYPE_SUBTYPE_SEPARATOR = "/"; @@ -91,6 +93,17 @@ export function findMatchingType( return undefined; } +export const getByContentType = ( + object = {}, + contentType: string, +): { schema: SchemaObject } => { + const key = findMatchingType( + contentType, + Object.keys(object), + ) as keyof object; + return object[key]; +}; + export const standardHttpRequestHeaders = [ "accept", "accept-charset",