diff --git a/src/execute/oas3/build-request.js b/src/execute/oas3/build-request.js index e621e177f..64926cde0 100644 --- a/src/execute/oas3/build-request.js +++ b/src/execute/oas3/build-request.js @@ -9,7 +9,8 @@ export default function (options, req) { operation, requestBody, securities, - spec + spec, + attachContentTypeForEmptyPayload } = options let { @@ -20,12 +21,12 @@ export default function (options, req) { const requestBodyDef = operation.requestBody || {} const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {}) + const isExplicitContentTypeValid = requestContentType + && requestBodyMediaTypes.indexOf(requestContentType) > -1 // for OAS3: set the Content-Type - if (requestBody) { + if (requestBody || attachContentTypeForEmptyPayload) { // does the passed requestContentType appear in the requestBody definition? - const isExplicitContentTypeValid = requestContentType - && requestBodyMediaTypes.indexOf(requestContentType) > -1 if (requestContentType && isExplicitContentTypeValid) { req.headers['Content-Type'] = requestContentType @@ -38,6 +39,9 @@ export default function (options, req) { } } } + else if (requestContentType && isExplicitContentTypeValid) { + req.headers['Content-Type'] = requestContentType + } // for OAS3: add requestBody to request if (requestBody) { diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js index ef1333bcd..39e76703e 100644 --- a/src/execute/swagger2/build-request.js +++ b/src/execute/swagger2/build-request.js @@ -11,12 +11,13 @@ export default function (options, req) { spec, operation, securities, - requestContentType + requestContentType, + attachContentTypeForEmptyPayload } = options // Add securities, which are applicable req = applySecurities({request: req, securities, operation, spec}) - if (req.body || req.form) { + if (req.body || req.form || attachContentTypeForEmptyPayload) { // all following conditionals are Swagger2 only if (requestContentType) { req.headers['Content-Type'] = requestContentType @@ -34,6 +35,13 @@ export default function (options, req) { req.headers['Content-Type'] = 'application/x-www-form-urlencoded' } } + else if (requestContentType) { + const isBodyParamPresent = operation.parameters && operation.parameters.filter(p => p.in === 'body').length > 0 + const isFormDataParamPresent = operation.parameters && operation.parameters.filter(p => p.in === 'formData').length > 0 + if (isBodyParamPresent || isFormDataParamPresent) { + req.headers['Content-Type'] = requestContentType + } + } return req } diff --git a/src/execute/swagger2/parameter-builders.js b/src/execute/swagger2/parameter-builders.js index 97315c933..13ad73231 100644 --- a/src/execute/swagger2/parameter-builders.js +++ b/src/execute/swagger2/parameter-builders.js @@ -18,8 +18,8 @@ function bodyBuilder({req, value}) { // Add a form data object. function formDataBuilder({req, value, parameter}) { - req.form = req.form || {} if (value || parameter.allowEmptyValue) { + req.form = req.form || {} req.form[parameter.name] = { value, allowEmptyValue: parameter.allowEmptyValue, diff --git a/test/execute/main.js b/test/execute/main.js index fb3eb0877..f0f328391 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -570,7 +570,88 @@ describe('execute', () => { }) }) - it('should not add Content-Type with no form-data or body param', function () { + it('should add Content-Type if a body param definition is present but there is no payload', function () { + // Given + const spec = { + host: 'swagger.io', + paths: { + '/one': { + get: { + operationId: 'getMe', + parameters: [ + { + name: 'body', + in: 'body', + schema: { + type: 'string' + } + } + ] + } + } + } + } + + // When + const req = buildRequest({ + spec, + operationId: 'getMe', + requestContentType: 'application/josh' + }) + + // Then + expect(req).toEqual({ + url: 'http://swagger.io/one', + body: undefined, + headers: { + 'Content-Type': 'application/josh' + }, + credentials: 'same-origin', + method: 'GET' + }) + }) + + it('should add Content-Type if a formData param definition is present but there is no payload', function () { + // Given + const spec = { + host: 'swagger.io', + paths: { + '/one': { + get: { + operationId: 'getMe', + parameters: [ + { + name: 'data', + in: 'formData', + schema: { + type: 'string' + } + } + ] + } + } + } + } + + // When + const req = buildRequest({ + spec, + operationId: 'getMe', + requestContentType: 'application/x-www-form-encoded' + }) + + // Then + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: { + 'Content-Type': 'application/x-www-form-encoded' + }, + credentials: 'same-origin', + method: 'GET' + }) + }) + + it('should not add Content-Type if no form-data or body param definition is present', function () { // Given const spec = { host: 'swagger.io', @@ -608,7 +689,7 @@ describe('execute', () => { const req = buildRequest({ spec, operationId: 'postMe', - parameters: {file: 'test'}}) + parameters: {foo: 'test'}}) // Then expect(req.headers).toEqual({ @@ -816,6 +897,171 @@ describe('execute', () => { one: 1 }) }) + + describe('attachContentTypeForEmptyPayload', () => { + it('should attach a Content-Type to a Swagger 2 operation with a body parameter defined but no body provided', () => { + const spec = { + swagger: '2.0', + host: 'swagger.io', + consumes: ['application/json'], + paths: { + '/one': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'body', + in: 'body' + } + ] + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + attachContentTypeForEmptyPayload: true + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'same-origin', + method: 'POST', + body: undefined + }) + }) + it('should attach a Content-Type to a Swagger 2 operation with a formData parameter defined but no body provided', () => { + const spec = { + swagger: '2.0', + host: 'swagger.io', + paths: { + '/one': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'data', + in: 'formData', + type: 'string' + } + ] + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + attachContentTypeForEmptyPayload: true + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + credentials: 'same-origin', + method: 'POST' + }) + }) + it('should not attach a Content-Type to a Swagger 2 operation with no body or formData parameter definition present', () => { + const spec = { + swagger: '2.0', + host: 'swagger.io', + paths: { + '/one': { + post: { + operationId: 'myOp' + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + attachContentTypeForEmptyPayload: true + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: {}, + credentials: 'same-origin', + method: 'POST' + }) + }) + it('should not attach a Content-Type to a Swagger 2 operation with a body parameter defined but no body provided if the option is not enabled', () => { + const spec = { + swagger: '2.0', + host: 'swagger.io', + consumes: ['application/json'], + paths: { + '/one': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'body', + in: 'body' + } + ] + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: {}, + credentials: 'same-origin', + method: 'POST', + body: undefined + }) + }) + it('should not attach a Content-Type to a Swagger 2 operation with a formData parameter defined but no body provided if the option is not enabled', () => { + const spec = { + swagger: '2.0', + host: 'swagger.io', + paths: { + '/one': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'data', + in: 'formData', + type: 'string' + } + ] + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: {}, + credentials: 'same-origin', + method: 'POST' + }) + }) + }) }) @@ -834,12 +1080,14 @@ describe('execute', () => { fetch: createSpy().andReturn({then() { }}), spec, operationId: 'getMe', - josh: 1 + josh: 1, + attachContentTypeForEmptyPayload: true }) expect(buildRequestSpy.calls.length).toEqual(1) expect(buildRequestSpy.calls[0].arguments[0]).toInclude({ - josh: 1 + josh: 1, + attachContentTypeForEmptyPayload: true }) }) diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index 479aeb49c..6c8893184 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -326,7 +326,7 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { }) }) - it('should build a request for the given operationId with a requestBody and a defined requestContentType that the requestBody lacks', function () { + it('should build an operation without a body or Content-Type if the requestBody definition lacks the requestContentType', function () { // Given const spec = { openapi: '3.0.0', @@ -372,6 +372,114 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { headers: {} }) }) + + it('should build a request body-bearing operation with a provided requestContentType that appears in the requestBody definition even if no payload is present', function () { + // Given + const spec = { + openapi: '3.0.0', + servers: [ + { + url: 'http://petstore.swagger.io/v2', + name: 'Petstore' + } + ], + paths: { + '/one': { + get: { + operationId: 'getOne', + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object' + } + } + } + } + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'getOne', + requestContentType: 'application/json' + }) + + expect(req).toEqual({ + method: 'GET', + url: 'http://petstore.swagger.io/v2/one', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' + } + }) + }) + + it('should build a request body-bearing operation without a provided requestContentType that does not appear in the requestBody definition even if no payload is present', function () { + // Given + const spec = { + openapi: '3.0.0', + servers: [ + { + url: 'http://petstore.swagger.io/v2', + name: 'Petstore' + } + ], + paths: { + '/one': { + get: { + operationId: 'getOne', + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object' + } + } + } + } + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'getOne', + requestContentType: 'application/not-json' + }) + + expect(req).toEqual({ + method: 'GET', + url: 'http://petstore.swagger.io/v2/one', + credentials: 'same-origin', + headers: {} + }) + }) + + it('should not add a Content-Type when the operation has no OAS3 request body definition', function () { + // Given + const spec = { + openapi: '3.0.0', + servers: [{url: 'http://swagger.io/'}], + paths: {'/one': {get: {operationId: 'getMe'}}} + } + + // When + const req = buildRequest({spec, operationId: 'getMe', requestContentType: 'application/josh'}) + + // Then + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: {}, + credentials: 'same-origin', + method: 'GET' + }) + }) }) describe('with petstore v3', function () { it('should build updatePetWithForm correctly', function () { @@ -677,4 +785,116 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { expect(res).toEqual('') }) }) + describe('attachContentTypeForEmptyPayload', () => { + it('should attach the first media type as Content-Type to an OAS3 operation with a request body defined but no body provided', function () { + const spec = { + openapi: '3.0.0', + servers: [ + { + url: 'http://swagger.io/' + } + ], + paths: { + '/one': { + post: { + operationId: 'myOp', + requestBody: { + content: { + 'application/json': { + schema: { + type: 'string' + } + } + } + } + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + attachContentTypeForEmptyPayload: true + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'same-origin', + method: 'POST' + }) + }) + it('should not attach a Content-Type to an OAS3 operation with no request body definition present', function () { + const spec = { + openapi: '3.0.0', + servers: [ + { + url: 'http://swagger.io/' + } + ], + paths: { + '/one': { + post: { + operationId: 'myOp' + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + attachContentTypeForEmptyPayload: true + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: {}, + credentials: 'same-origin', + method: 'POST' + }) + }) + it('should not attach the first media type as Content-Type without the option enabled', function () { + const spec = { + openapi: '3.0.0', + servers: [ + { + url: 'http://swagger.io/' + } + ], + paths: { + '/one': { + post: { + operationId: 'myOp', + requestBody: { + content: { + 'application/json': { + schema: { + type: 'string' + } + } + } + } + } + } + } + } + + const req = buildRequest({ + spec, + operationId: 'myOp', + // attachContentTypeForEmptyPayload is omitted + }) + + expect(req).toEqual({ + url: 'http://swagger.io/one', + headers: {}, + credentials: 'same-origin', + method: 'POST' + }) + }) + }) })