From 6bb7b8893ad733d306f78e9c5d8fc0a20fbdfd12 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Wed, 25 Apr 2018 23:08:51 -0700 Subject: [PATCH 1/9] add failing tests for `requestContentType` --- test/execute/main.js | 42 ++++++++++++++++++++++- test/oas3/execute/main.js | 71 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/test/execute/main.js b/test/execute/main.js index fb3eb0877..9394565c0 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -570,7 +570,47 @@ 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', + headers: { + 'Content-Type': 'application/josh' + }, + 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', diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index 479aeb49c..bc97d21e5 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 with a provided requestContentType even if the requestBody definition lacks that media type', function () { // Given const spec = { openapi: '3.0.0', @@ -369,7 +369,74 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { method: 'GET', url: 'http://petstore.swagger.io/v2/one', credentials: 'same-origin', - headers: {} + headers: { + 'Content-Type': 'application/not-json' + } + }) + }) + + it('should build a request body-bearing operation with a provided requestContentType 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: { + 'Content-Type': 'application/not-json' + } + }) + }) + + 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' }) }) }) From ed44911c12977c6c715a84dfbb65e09b66d25b13 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Wed, 25 Apr 2018 23:22:19 -0700 Subject: [PATCH 2/9] fix: swagger2 `requestContentType` priority --- src/execute/swagger2/build-request.js | 6 ++++++ test/execute/main.js | 1 + 2 files changed, 7 insertions(+) diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js index ef1333bcd..349bb0083 100644 --- a/src/execute/swagger2/build-request.js +++ b/src/execute/swagger2/build-request.js @@ -34,6 +34,12 @@ 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 + if (isBodyParamPresent) { + req.headers['Content-Type'] = requestContentType + } + } return req } diff --git a/test/execute/main.js b/test/execute/main.js index 9394565c0..32008a42c 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -602,6 +602,7 @@ describe('execute', () => { // Then expect(req).toEqual({ url: 'http://swagger.io/one', + body: undefined, headers: { 'Content-Type': 'application/josh' }, From 7ff8085c60048efa824df9d508c91b441248c210 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Wed, 25 Apr 2018 23:26:14 -0700 Subject: [PATCH 3/9] don't expect a body or Content-Type when we can't resolve... to a requestBody definition --- test/oas3/execute/main.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index bc97d21e5..326ddbcbd 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 an operation with a provided requestContentType even if the requestBody definition lacks that media type', 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', @@ -369,9 +369,7 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { method: 'GET', url: 'http://petstore.swagger.io/v2/one', credentials: 'same-origin', - headers: { - 'Content-Type': 'application/not-json' - } + headers: {} }) }) From e29e5684978c66a72766cf57650e71711149089a Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Wed, 25 Apr 2018 23:32:03 -0700 Subject: [PATCH 4/9] only allow valid media types to be used as empty-payload Content-Type --- src/execute/oas3/build-request.js | 7 +++-- test/oas3/execute/main.js | 49 +++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/execute/oas3/build-request.js b/src/execute/oas3/build-request.js index e621e177f..53f3276b5 100644 --- a/src/execute/oas3/build-request.js +++ b/src/execute/oas3/build-request.js @@ -20,12 +20,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) { // 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 +38,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/test/oas3/execute/main.js b/test/oas3/execute/main.js index 326ddbcbd..b861e6e1d 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -373,7 +373,7 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { }) }) - it('should build a request body-bearing operation with a provided requestContentType even if no payload is present', function () { + 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', @@ -405,7 +405,7 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { const req = buildRequest({ spec, operationId: 'getOne', - requestContentType: 'application/not-json' + requestContentType: 'application/json' }) expect(req).toEqual({ @@ -413,11 +413,54 @@ describe('buildRequest - OpenAPI Specification 3.0', function () { url: 'http://petstore.swagger.io/v2/one', credentials: 'same-origin', headers: { - 'Content-Type': 'application/not-json' + '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 = { From f27f8ebe0e85ce2327cba028ad86d484a72b3a16 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 26 Apr 2018 16:04:25 -0700 Subject: [PATCH 5/9] attachContentTypeForEmptyPayload for Swagger 2 --- src/execute/swagger2/build-request.js | 5 +- src/execute/swagger2/parameter-builders.js | 2 +- test/execute/main.js | 165 +++++++++++++++++++++ 3 files changed, 169 insertions(+), 3 deletions(-) diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js index 349bb0083..69176e58d 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 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 32008a42c..63011e940 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -857,6 +857,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' + }) + }) + }) }) From 6b1d4ee0dddcc565bd4948ce8c8f0d05600551f9 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 26 Apr 2018 16:06:01 -0700 Subject: [PATCH 6/9] attachContentTypeForEmptyPayload for OpenAPI 3 --- src/execute/oas3/build-request.js | 5 +- test/oas3/execute/main.js | 112 ++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/execute/oas3/build-request.js b/src/execute/oas3/build-request.js index 53f3276b5..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 { @@ -24,7 +25,7 @@ export default function (options, req) { && requestBodyMediaTypes.indexOf(requestContentType) > -1 // for OAS3: set the Content-Type - if (requestBody) { + if (requestBody || attachContentTypeForEmptyPayload) { // does the passed requestContentType appear in the requestBody definition? if (requestContentType && isExplicitContentTypeValid) { diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index b861e6e1d..6c8893184 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -785,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' + }) + }) + }) }) From ae7008a5f018e25aef1c0223397417252e4374df Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 26 Apr 2018 16:07:50 -0700 Subject: [PATCH 7/9] fix swagger 2 FormData test parameter name logical error --- test/execute/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/execute/main.js b/test/execute/main.js index 63011e940..d56226717 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -649,7 +649,7 @@ describe('execute', () => { const req = buildRequest({ spec, operationId: 'postMe', - parameters: {file: 'test'}}) + parameters: {foo: 'test'}}) // Then expect(req.headers).toEqual({ From 2e139a813ab92b51a5918d9d787402c84350c100 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 26 Apr 2018 16:11:05 -0700 Subject: [PATCH 8/9] test for requestContentType with payload-free formData --- src/execute/swagger2/build-request.js | 3 +- test/execute/main.js | 40 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js index 69176e58d..39e76703e 100644 --- a/src/execute/swagger2/build-request.js +++ b/src/execute/swagger2/build-request.js @@ -37,7 +37,8 @@ export default function (options, req) { } else if (requestContentType) { const isBodyParamPresent = operation.parameters && operation.parameters.filter(p => p.in === 'body').length > 0 - if (isBodyParamPresent) { + const isFormDataParamPresent = operation.parameters && operation.parameters.filter(p => p.in === 'formData').length > 0 + if (isBodyParamPresent || isFormDataParamPresent) { req.headers['Content-Type'] = requestContentType } } diff --git a/test/execute/main.js b/test/execute/main.js index d56226717..20f9d4f2e 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -611,6 +611,46 @@ describe('execute', () => { }) }) + 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 = { From 5a06ab67f8a4975f49dc7363ebfeaae8154249ce Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 26 Apr 2018 16:15:53 -0700 Subject: [PATCH 9/9] add test for proxying attachContentTypeForEmptyPayload through execute --- test/execute/main.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/execute/main.js b/test/execute/main.js index 20f9d4f2e..f0f328391 100644 --- a/test/execute/main.js +++ b/test/execute/main.js @@ -1080,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 }) })