diff --git a/src/execute.js b/src/execute/index.js similarity index 50% rename from src/execute.js rename to src/execute/index.js index 760051e08..1fb893d18 100755 --- a/src/execute.js +++ b/src/execute/index.js @@ -4,9 +4,19 @@ import isPlainObject from 'lodash/isPlainObject' import isArray from 'lodash/isArray' import btoa from 'btoa' import url from 'url' -import http, {mergeInQueryOrForm} from './http' -import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from './helpers' -import createError from './specmap/lib/create-error' +import http, {mergeInQueryOrForm} from '../http' +import createError from '../specmap/lib/create-error' + +import SWAGGER2_PARAMETER_BUILDERS from './swagger2/parameter-builders' +import OAS3_PARAMETER_BUILDERS from './oas3/parameter-builders' +import oas3BuildRequest from './oas3/build-request' +import swagger2BuildRequest from './swagger2/build-request' +import { + getOperationRaw, + idFromPathMethod, + legacyIdFromPathMethod, + isOAS3 +} from '../helpers' const arrayOrEmpty = (ar) => { return Array.isArray(ar) ? ar : [] @@ -26,18 +36,6 @@ export const self = { buildRequest } -// These functions will update the request. -// They'll be given {req, value, paramter, spec, operation}. - - -export const PARAMETER_BUILDERS = { - body: bodyBuilder, - header: headerBuilder, - query: queryBuilder, - path: pathBuilder, - formData: formDataBuilder -} - // Execute request, with the given operationId and parameters // pathName/method or operationId is optional export function execute({ @@ -69,15 +67,39 @@ export function execute({ } // Build a request, which can be handled by the `http.js` implementation. -export function buildRequest({ - spec, operationId, parameters, securities, requestContentType, - responseContentType, parameterBuilders, scheme, - requestInterceptor, responseInterceptor, contextUrl, userFetch, - requestBody, server, serverVariables -}) { +export function buildRequest(options) { + const { + spec, + operationId, + securities, + requestContentType, + responseContentType, + scheme, + requestInterceptor, + responseInterceptor, + contextUrl, + userFetch, + requestBody, + server, + serverVariables + } = options + + let { + parameters, + parameterBuilders + } = options + const specIsOAS3 = isOAS3(spec) - parameterBuilders = parameterBuilders || PARAMETER_BUILDERS + if (!parameterBuilders) { + // user did not provide custom parameter builders + if (specIsOAS3) { + parameterBuilders = OAS3_PARAMETER_BUILDERS + } + else { + parameterBuilders = SWAGGER2_PARAMETER_BUILDERS + } + } // Base Template let req = { @@ -165,78 +187,15 @@ export function buildRequest({ } }) - const requestBodyDef = operation.requestBody || {} - const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {}) - - // for OAS3: set the Content-Type - if (specIsOAS3 && 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 - } - else if (!requestContentType) { - const firstMediaType = requestBodyMediaTypes[0] - if (firstMediaType) { - req.headers['Content-Type'] = firstMediaType - requestContentType = firstMediaType - } - } - } + // Do version-specific tasks, then return those results. + const versionSpecificOptions = {...options, operation} - // for OAS3: add requestBody to request - if (specIsOAS3 && requestBody) { - if (requestContentType) { - if (requestBodyMediaTypes.indexOf(requestContentType) > -1) { - // only attach body if the requestBody has a definition for the - // contentType that has been explicitly set - if (requestContentType === 'application/x-www-form-urlencoded') { - if (typeof requestBody === 'object') { - req.form = {} - Object.keys(requestBody).forEach((k) => { - const val = requestBody[k] - req.form[k] = { - value: val - } - }) - } - else { - req.form = requestBody - } - } - else { - req.body = requestBody - } - } - } - else { - req.body = requestBody - } + if (specIsOAS3) { + req = oas3BuildRequest(versionSpecificOptions, req) } - - // Add securities, which are applicable - // REVIEW: OAS3: what changed in securities? - req = applySecurities({request: req, securities, operation, spec}) - - if (!specIsOAS3 && (req.body || req.form)) { - // all following conditionals are Swagger2 only - if (requestContentType) { - req.headers['Content-Type'] = requestContentType - } - else if (Array.isArray(operation.consumes)) { - req.headers['Content-Type'] = operation.consumes[0] - } - else if (Array.isArray(spec.consumes)) { - req.headers['Content-Type'] = spec.consumes[0] - } - else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) { - req.headers['Content-Type'] = 'multipart/form-data' - } - else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) { - req.headers['Content-Type'] = 'application/x-www-form-urlencoded' - } + else { + // If not OAS3, then treat as Swagger2. + req = swagger2BuildRequest(versionSpecificOptions, req) } // Will add the query object into the URL, if it exists @@ -246,68 +205,6 @@ export function buildRequest({ return req } -// Add the body to the request -export function bodyBuilder({req, value, specIsOAS3}) { - if (specIsOAS3) { - return - } - req.body = value -} - -// Add a form data object. -export function formDataBuilder({req, value, parameter}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.form = req.form || {} - if (value || parameter.allowEmptyValue) { - req.form[parameter.name] = { - value, - allowEmptyValue: parameter.allowEmptyValue, - collectionFormat: parameter.collectionFormat, - } - } -} - -// Add a header to the request -export function headerBuilder({req, parameter, value}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.headers = req.headers || {} - if (typeof value !== 'undefined') { - req.headers[parameter.name] = value - } -} - -// Replace path paramters, with values ( ie: the URL ) -export function pathBuilder({req, value, parameter}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.url = req.url.replace(`{${parameter.name}}`, encodeURIComponent(value)) -} - -// Add a query to the `query` object, which will later be stringified into the URL's search -export function queryBuilder({req, value, parameter}) { - // REVIEW: OAS3: check for any parameter changes that affect the builder - req.query = req.query || {} - - if (value === false && parameter.type === 'boolean') { - value = 'false' - } - - if (value === 0 && ['number', 'integer'].indexOf(parameter.type) > -1) { - value = '0' - } - - if (value) { - req.query[parameter.name] = { - collectionFormat: parameter.collectionFormat, - value - } - } - else if (parameter.allowEmptyValue) { - const paramName = parameter.name - req.query[paramName] = req.query[paramName] || {} - req.query[paramName].allowEmptyValue = true - } -} - const stripNonAlpha = str => (str ? str.replace(/\W/g, '') : null) export function baseUrl(obj) { @@ -389,59 +286,3 @@ function swagger2BaseUrl({spec, scheme, contextUrl = ''}) { return '' } - - -// Add security values, to operations - that declare their need on them -export function applySecurities({request, securities = {}, operation = {}, spec}) { - const result = assign({}, request) - const {authorized = {}, specSecurity = []} = securities - const security = operation.security || specSecurity - const isAuthorized = authorized && !!Object.keys(authorized).length - const securityDef = spec.securityDefinitions - - result.headers = result.headers || {} - result.query = result.query || {} - - if (!Object.keys(securities).length || !isAuthorized || !security || - (Array.isArray(operation.security) && !operation.security.length)) { - return request - } - - security.forEach((securityObj, index) => { - for (const key in securityObj) { - const auth = authorized[key] - if (!auth) { - continue - } - - const token = auth.token - const value = auth.value || auth - const schema = securityDef[key] - const {type} = schema - const accessToken = token && token.access_token - const tokenType = token && token.token_type - - if (auth) { - if (type === 'apiKey') { - const inType = schema.in === 'query' ? 'query' : 'headers' - result[inType] = result[inType] || {} - result[inType][schema.name] = value - } - else if (type === 'basic') { - if (value.header) { - result.headers.authorization = value.header - } - else { - value.base64 = btoa(`${value.username}:${value.password}`) - result.headers.authorization = `Basic ${value.base64}` - } - } - else if (type === 'oauth2' && accessToken) { - result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}` - } - } - } - }) - - return result -} diff --git a/src/execute/oas3/build-request.js b/src/execute/oas3/build-request.js new file mode 100644 index 000000000..6f392079c --- /dev/null +++ b/src/execute/oas3/build-request.js @@ -0,0 +1,66 @@ +// This function runs after the common function, +// `src/execute/index.js#buildRequest` + +export default function (options, req) { + const { + operation, + requestBody + } = options + + let { + requestContentType + } = options + + const requestBodyDef = operation.requestBody || {} + const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {}) + + // 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 + } + else if (!requestContentType) { + const firstMediaType = requestBodyMediaTypes[0] + if (firstMediaType) { + req.headers['Content-Type'] = firstMediaType + requestContentType = firstMediaType + } + } + } + + // for OAS3: add requestBody to request + if (requestBody) { + if (requestContentType) { + if (requestBodyMediaTypes.indexOf(requestContentType) > -1) { + // only attach body if the requestBody has a definition for the + // contentType that has been explicitly set + if (requestContentType === 'application/x-www-form-urlencoded') { + if (typeof requestBody === 'object') { + req.form = {} + Object.keys(requestBody).forEach((k) => { + const val = requestBody[k] + req.form[k] = { + value: val + } + }) + } + else { + req.form = requestBody + } + } + else { + req.body = requestBody + } + } + } + else { + req.body = requestBody + } + } + + return req +} diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js new file mode 100644 index 000000000..10909ab9a --- /dev/null +++ b/src/execute/oas3/parameter-builders.js @@ -0,0 +1,126 @@ +import stylize from './style-serializer' + +export default { + path, + query, + header, + cookie +} + +function path({req, value, parameter}) { + const {name, style, explode} = parameter + const styledValue = stylize({ + key: parameter.name, + value, + style: style || 'simple', + explode: explode || false, + escape: !parameter.allowReserved, + }) + + req.url = req.url.replace(`{${name}}`, styledValue) +} + +function query({req, value, parameter}) { + req.query = req.query || {} + + if (value === false) { + value = 'false' + } + + if (value === 0) { + value = '0' + } + + if (value) { + const type = typeof value + + if (parameter.style === 'deepObject') { + const valueKeys = Object.keys(value) + valueKeys.forEach((k) => { + const v = value[k] + req.query[`${parameter.name}[${k}]`] = { + value: stylize({ + key: k, + value: v, + style: 'deepObject', + escape: !parameter.allowReserved, + }), + skipEncoding: true + } + }) + } + else if ( + type === 'object' && + !Array.isArray(value) && + (parameter.style === 'form' || !parameter.style) && + (parameter.explode || parameter.explode === undefined) + ) { + // form explode needs to be handled here, + // since we aren't assigning to `req.query[parameter.name]` + // like we usually do. + const valueKeys = Object.keys(value) + valueKeys.forEach((k) => { + const v = value[k] + req.query[k] = { + value: stylize({ + key: k, + value: v, + style: parameter.style || 'form', + escape: !parameter.allowReserved, + }) + } + }) + } + else { + req.query[parameter.name] = { + value: stylize({ + key: parameter.name, + value, + style: parameter.style || 'form', + explode: typeof parameter.explode === 'undefined' ? true : parameter.explode, + escape: !parameter.allowReserved, + }), + skipEncoding: true + } + } + } + else if (parameter.allowEmptyValue) { + const paramName = parameter.name + req.query[paramName] = req.query[paramName] || {} + req.query[paramName].allowEmptyValue = true + } +} + +function header({req, parameter, value}) { + req.headers = req.headers || {} + if (typeof value !== 'undefined') { + req.headers[parameter.name] = stylize({ + key: parameter.name, + value, + style: parameter.style || 'simple', + explode: typeof parameter.explode === 'undefined' ? false : parameter.explode, + escape: !parameter.allowReserved, + }) + } +} + +function cookie({req, parameter, value}) { + req.headers = req.headers || {} + const type = typeof value + + if (type !== 'undefined') { + const prefix = ( + type === 'object' && + !Array.isArray(value) && + parameter.explode + ) ? '' : `${parameter.name}=` + + req.headers.Cookie = prefix + stylize({ + key: parameter.name, + value, + escape: false, + style: parameter.style || 'form', + explode: typeof parameter.explode === 'undefined' ? false : parameter.explode + }) + } +} diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js new file mode 100644 index 000000000..d7595dcb1 --- /dev/null +++ b/src/execute/oas3/style-serializer.js @@ -0,0 +1,125 @@ +export default function (config) { + const {value} = config + + if (Array.isArray(value)) { + return encodeArray(config) + } + else if (typeof value === 'object') { + return encodeObject(config) + } + + return encodePrimitive(config) +} + +const escapeFn = str => encodeURIComponent(str) + +function encodeArray({key, value, style, explode, escape}) { + if (style === 'simple') { + return value.join(',') + } + + if (style === 'label') { + return `.${value.join('.')}` + } + + if (style === 'matrix') { + return value.reduce((prev, curr) => { + if (!prev || explode) { + return `${(prev || '')};${key}=${curr}` + } + return `${prev},${curr}` + }, '') + } + + if (style === 'form') { + const commaValue = escape ? escapeFn(',') : ',' + const after = explode ? `&${key}=` : commaValue + return value.join(after) + } + + if (style === 'spaceDelimited') { + const after = explode ? `${key}=` : '' + return value.join(`${escapeFn(' ')}${after}`) + } + + if (style === 'pipeDelimited') { + const after = explode ? `${key}=` : '' + const separator = escape ? escapeFn('|') : '|' + return value.join(`${separator}${after}`) + } +} + +function encodeObject({key, value, style, explode}) { + const valueKeys = Object.keys(value) + + if (style === 'simple') { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const middleChar = explode ? '=' : ',' + const prefix = prev ? `${prev},` : '' + + return `${prefix}${curr}${middleChar}${val}` + }, '') + } + + if (style === 'label') { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const middleChar = explode ? '=' : '.' + const prefix = prev ? `${prev}.` : '.' + + return `${prefix}${curr}${middleChar}${val}` + }, '') + } + + if (style === 'matrix' && explode) { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const prefix = prev ? `${prev};` : ';' + + return `${prefix}${curr}=${val}` + }, '') + } + + if (style === 'matrix') { + // no explode + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const prefix = prev ? `${prev},` : `;${key}=` + + return `${prefix}${curr},${val}` + }, '') + } + + if (style === 'form') { + return valueKeys.reduce((prev, curr) => { + const val = value[curr] + const prefix = prev ? `${prev}${explode ? '&' : ','}` : '' + const separator = explode ? '=' : ',' + + return `${prefix}${curr}${separator}${val}` + }, '') + } +} + +function encodePrimitive({key, value, style, explode}) { + if (style === 'simple') { + return value + } + + if (style === 'label') { + return `.${value}` + } + + if (style === 'matrix') { + return `;${key}=${value}` + } + + if (style === 'form') { + return value + } + + if (style === 'deepObject') { + return value + } +} diff --git a/src/execute/swagger2/build-request.js b/src/execute/swagger2/build-request.js new file mode 100644 index 000000000..16002bcdf --- /dev/null +++ b/src/execute/swagger2/build-request.js @@ -0,0 +1,94 @@ +// This function runs after the common function, +// `src/execute/index.js#buildRequest` + +import btoa from 'btoa' +import assign from 'lodash/assign' +import http, {mergeInQueryOrForm} from '../../http' + + +export default function (options, req) { + const { + spec, + operation, + securities, + requestContentType + } = options + // Add securities, which are applicable + req = applySecurities({request: req, securities, operation, spec}) + + if (req.body || req.form) { + // all following conditionals are Swagger2 only + if (requestContentType) { + req.headers['Content-Type'] = requestContentType + } + else if (Array.isArray(operation.consumes)) { + req.headers['Content-Type'] = operation.consumes[0] + } + else if (Array.isArray(spec.consumes)) { + req.headers['Content-Type'] = spec.consumes[0] + } + else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) { + req.headers['Content-Type'] = 'multipart/form-data' + } + else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) { + req.headers['Content-Type'] = 'application/x-www-form-urlencoded' + } + } + + return req +} + +// Add security values, to operations - that declare their need on them +export function applySecurities({request, securities = {}, operation = {}, spec}) { + const result = assign({}, request) + const {authorized = {}, specSecurity = []} = securities + const security = operation.security || specSecurity + const isAuthorized = authorized && !!Object.keys(authorized).length + const securityDef = spec.securityDefinitions + + result.headers = result.headers || {} + result.query = result.query || {} + + if (!Object.keys(securities).length || !isAuthorized || !security || + (Array.isArray(operation.security) && !operation.security.length)) { + return request + } + + security.forEach((securityObj, index) => { + for (const key in securityObj) { + const auth = authorized[key] + if (!auth) { + continue + } + + const token = auth.token + const value = auth.value || auth + const schema = securityDef[key] + const {type} = schema + const accessToken = token && token.access_token + const tokenType = token && token.token_type + + if (auth) { + if (type === 'apiKey') { + const inType = schema.in === 'query' ? 'query' : 'headers' + result[inType] = result[inType] || {} + result[inType][schema.name] = value + } + else if (type === 'basic') { + if (value.header) { + result.headers.authorization = value.header + } + else { + value.base64 = btoa(`${value.username}:${value.password}`) + result.headers.authorization = `Basic ${value.base64}` + } + } + else if (type === 'oauth2' && accessToken) { + result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}` + } + } + } + }) + + return result +} diff --git a/src/execute/swagger2/parameter-builders.js b/src/execute/swagger2/parameter-builders.js new file mode 100644 index 000000000..97315c933 --- /dev/null +++ b/src/execute/swagger2/parameter-builders.js @@ -0,0 +1,67 @@ +// These functions will update the request. +// They'll be given {req, value, paramter, spec, operation}. + + +export default { + body: bodyBuilder, + header: headerBuilder, + query: queryBuilder, + path: pathBuilder, + formData: formDataBuilder +} + + +// Add the body to the request +function bodyBuilder({req, value}) { + req.body = value +} + +// Add a form data object. +function formDataBuilder({req, value, parameter}) { + req.form = req.form || {} + if (value || parameter.allowEmptyValue) { + req.form[parameter.name] = { + value, + allowEmptyValue: parameter.allowEmptyValue, + collectionFormat: parameter.collectionFormat, + } + } +} + +// Add a header to the request +function headerBuilder({req, parameter, value}) { + req.headers = req.headers || {} + if (typeof value !== 'undefined') { + req.headers[parameter.name] = value + } +} + +// Replace path paramters, with values ( ie: the URL ) +function pathBuilder({req, value, parameter}) { + req.url = req.url.replace(`{${parameter.name}}`, encodeURIComponent(value)) +} + +// Add a query to the `query` object, which will later be stringified into the URL's search +function queryBuilder({req, value, parameter}) { + req.query = req.query || {} + + if (value === false && parameter.type === 'boolean') { + value = 'false' + } + + if (value === 0 && ['number', 'integer'].indexOf(parameter.type) > -1) { + value = '0' + } + + if (value) { + req.query[parameter.name] = { + collectionFormat: parameter.collectionFormat, + value + } + } + else if (parameter.allowEmptyValue) { + const paramName = parameter.name + req.query[paramName] = req.query[paramName] || {} + req.query[paramName].allowEmptyValue = true + } +} diff --git a/src/http.js b/src/http.js index 96d99b4e8..3c4d34d22 100644 --- a/src/http.js +++ b/src/http.js @@ -180,9 +180,12 @@ export function encodeFormOrQuery(data) { const encodedQuery = Object.keys(data).reduce((result, parameterName) => { const isObject = a => a && typeof a === 'object' const paramValue = data[parameterName] - const encodedParameterName = encodeURIComponent(parameterName) + const skipEncoding = !!paramValue.skipEncoding + const encodedParameterName = skipEncoding ? parameterName : encodeURIComponent(parameterName) const notArray = isObject(paramValue) && !Array.isArray(paramValue) - result[encodedParameterName] = formatValue(notArray ? paramValue : {value: paramValue}) + result[encodedParameterName] = formatValue( + notArray ? paramValue : {value: paramValue}, skipEncoding + ) return result }, {}) return qs.stringify(encodedQuery, {encode: false, indices: false}) || '' diff --git a/test/execute-baseurl.js b/test/execute/baseurl.js similarity index 99% rename from test/execute-baseurl.js rename to test/execute/baseurl.js index b775a77f9..b0443f96f 100644 --- a/test/execute-baseurl.js +++ b/test/execute/baseurl.js @@ -1,6 +1,6 @@ import expect, {createSpy, spyOn} from 'expect' import xmock from 'xmock' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../src/execute' +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method diff --git a/test/execute.js b/test/execute/main.js similarity index 91% rename from test/execute.js rename to test/execute/main.js index 14dd2ac72..04a4fcd36 100644 --- a/test/execute.js +++ b/test/execute/main.js @@ -1,6 +1,6 @@ import expect, {createSpy, spyOn} from 'expect' import xmock from 'xmock' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../src/execute' +import {execute, buildRequest, baseUrl, self as stubs} from '../../src/execute' // Supported shape... { spec, operationId, parameters, securities, fetch } // One can use operationId or pathItem + method @@ -926,172 +926,6 @@ describe('execute', () => { expect(fetchSpy.calls[0].arguments[0].body).toBeA(FormData) }) - describe('applySecurities', function () { - it('should NOT add any securities, if the operation does not require it', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{apiKey: []}], - paths: { - '/one': { - get: { - operationId: 'getMe' - } - } - }, - securityDefinitions: { - apiKey: { - in: 'header', - name: 'api_key', - type: 'apiKey' - } - } - } - - const securities = {} - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET' - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity).toEqual({ - url: 'http://swagger.io/v1/one', - method: 'GET' - }) - }) - - it('should add a basic auth if operation requires it and has header passed', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{authMe: []}], - paths: { - '/one': { - get: { - operationId: 'getMe', - security: [{authMe: []}] - } - } - }, - securityDefinitions: { - authMe: { - type: 'basic' - } - } - } - - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET', - query: {} - } - const securities = { - authorized: { - authMe: { - header: 'Basic Zm9vOmJhcg==' - } - } - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity.headers).toEqual({ - authorization: 'Basic Zm9vOmJhcg==' - }) - }) - - it('should add a basic auth if operation requires it', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{authMe: []}], - paths: { - '/one': { - get: { - operationId: 'getMe', - security: [{authMe: []}] - } - } - }, - securityDefinitions: { - authMe: { - type: 'basic' - } - } - } - - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET', - query: {} - } - const securities = { - authorized: { - authMe: { - username: 'foo', - password: 'bar' - } - } - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity.headers).toEqual({ - authorization: 'Basic Zm9vOmJhcg==' - }) - }) - - it('should be able to apply multiple auths', function () { - const spec = { - host: 'swagger.io', - basePath: '/v1', - security: [{authMe: []}, {apiKey: []}], - paths: { - '/one': { - get: { - operationId: 'getMe', - security: [{authMe: []}, {apiKey: []}] - } - } - }, - securityDefinitions: { - authMe: { - type: 'basic' - }, - apiKey: { - in: 'header', - name: 'api_key', - type: 'apiKey' - } - } - } - - const request = { - url: 'http://swagger.io/v1/one', - method: 'GET', - query: {} - } - const securities = { - authorized: { - authMe: { - username: 'foo', - password: 'bar' - }, - apiKey: 'hello' - } - } - - const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) - - expect(applySecurity.headers).toEqual({ - authorization: 'Basic Zm9vOmJhcg==', - api_key: 'hello' - }) - }) - }) - describe('parameterBuilders', function () { describe('query', function () { it('should include the values for array of query parameters', function () { diff --git a/test/oas3/execute.js b/test/oas3/execute/main.js similarity index 99% rename from test/oas3/execute.js rename to test/oas3/execute/main.js index b3c47b01a..c56f7526d 100644 --- a/test/oas3/execute.js +++ b/test/oas3/execute/main.js @@ -4,7 +4,7 @@ import path from 'path' import fs from 'fs' import jsYaml from 'js-yaml' -import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../src/execute' +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../../src/execute' const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', 'data', 'petstore-oas3.yaml'), 'utf8')) diff --git a/test/oas3/execute/style-explode.js b/test/oas3/execute/style-explode.js new file mode 100644 index 000000000..f73cec7fb --- /dev/null +++ b/test/oas3/execute/style-explode.js @@ -0,0 +1,2266 @@ +import expect, {createSpy, spyOn} from 'expect' +import xmock from 'xmock' +import path from 'path' +import fs from 'fs' +import qs from 'querystring' +import jsYaml from 'js-yaml' + +import {execute, buildRequest, baseUrl, applySecurities, self as stubs} from '../../../src/execute' + +const petstoreSpec = jsYaml.safeLoad(fs.readFileSync(path.join('test', 'oas3', 'data', 'petstore-oas3.yaml'), 'utf8')) + +// Supported shape... { spec, operationId, parameters, securities, fetch } +// One can use operationId or pathItem + method + +describe('buildRequest w/ `style` & `explode` - OpenAPI Specification 3.0', function () { + describe('path parameters', function () { + describe('primitive values', function () { + it('default: should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: 5 + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('array values', function () { + it('default: should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.3.4.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.3.4.5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: [3, 4, 5] + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=3;id=4;id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('object values', function () { + it('default: should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/role=admin,firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.role.admin.firstName.Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a label/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'label', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/.role=admin.firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;id=role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a path parameter in a matrix/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/path/{id}': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'path', + style: 'matrix', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: { + role: 'admin', + firstName: 'Alex' + } + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/path/;role=admin;firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + }) + describe('query parameters', function () { + describe('primitive values', function () { + const VALUE = 5 + + it('default: should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('array values', function () { + const VALUE = [3, 4, 5] + + it('default: should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3&id=4&id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3&id=4&id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%2C4%2C5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/no-explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false, + allowReserved: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3,4,5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in space-delimited/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'spaceDelimited', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%20id=4%20id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in space-delimited/explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'spaceDelimited', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + // whitespace is _not_ an RFC3986 reserved character, + // so it should still be escaped! + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%20id=4%20id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in space-delimited/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'spaceDelimited', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%204%205', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in pipe-delimited/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%7Cid=4%7Cid=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in pipe-delimited/explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: true, + allowReserved: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3|id=4|id=5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in pipe-delimited/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3%7C4%7C5', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in pipe-delimited/no-explode format with allowReserved', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'pipeDelimited', + explode: false, + allowReserved: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=3|4|5', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + describe('object values', function () { + const VALUE = { + role: 'admin', + firstName: 'Alex' + } + + it('default: should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?role=admin&firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?role=admin&firstName=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id=role,admin,firstName,Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + + it('should build a query parameter in deepObject/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'query', + style: 'deepObject', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users?id[role]=admin&id[firstName]=Alex', + credentials: 'same-origin', + headers: {}, + }) + }) + }) + }) + describe('header parameters', function () { + describe('primitive values', function () { + const VALUE = 5 + + it('default: should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 5 + }, + }) + }) + + it('should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 5 + }, + }) + }) + + it('should build a header parameter in simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 5 + }, + }) + }) + }) + describe('array values', function () { + const VALUE = [3, 4, 5] + + it('default: should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': '3,4,5' + }, + }) + }) + + it('should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': '3,4,5' + }, + }) + }) + + it('should build a header parameter in simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': '3,4,5' + }, + }) + }) + }) + describe('object values', function () { + const VALUE = { + role: 'admin', + firstName: 'Alex' + } + + it('default: should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 'role,admin,firstName,Alex' + }, + }) + }) + + it('should build a header parameter in simple/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 'role,admin,firstName,Alex' + }, + }) + }) + + it('should build a header parameter in simple/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'X-MyHeader', + in: 'header', + style: 'simple', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + 'X-MyHeader': VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + 'X-MyHeader': 'role=admin,firstName=Alex' + }, + }) + }) + }) + }) + describe('cookie parameters', function () { + describe('primitive values', function () { + const VALUE = 5 + + it('default: should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=5' + }, + }) + }) + + it('should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=5' + }, + }) + }) + + it('should build a cookie parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=5' + }, + }) + }) + }) + describe('array values', function () { + const VALUE = [3, 4, 5] + + it('default: should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=3,4,5' + }, + }) + }) + + it('should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=3,4,5' + }, + }) + }) + + it('should build a cookie parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=3&id=4&id=5' + }, + }) + }) + }) + describe('object values', function () { + const VALUE = { + role: 'admin', + firstName: 'Alex' + } + + it('default: should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie' + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=role,admin,firstName,Alex' + }, + }) + }) + + it('should build a cookie parameter in form/no-explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: false + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'id=role,admin,firstName,Alex' + }, + }) + }) + + it('should build a cookie parameter in form/explode format', function () { + // Given + const spec = { + openapi: '3.0.0', + paths: { + '/users': { + get: { + operationId: 'myOperation', + parameters: [ + { + name: 'id', + in: 'cookie', + style: 'form', + explode: true + } + ] + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'myOperation', + parameters: { + id: VALUE + } + }) + + expect(req).toEqual({ + method: 'GET', + url: '/users', + credentials: 'same-origin', + headers: { + Cookie: 'role=admin&firstName=Alex' + }, + }) + }) + }) + }) +}) diff --git a/test/swagger2/execute/apply-securities.js b/test/swagger2/execute/apply-securities.js new file mode 100644 index 000000000..1e912d4d0 --- /dev/null +++ b/test/swagger2/execute/apply-securities.js @@ -0,0 +1,168 @@ +import expect from 'expect' +import {applySecurities} from '../../../src/execute/swagger2/build-request' + +describe('swagger2 - execute - applySecurities', function () { + it('should NOT add any securities, if the operation does not require it', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{apiKey: []}], + paths: { + '/one': { + get: { + operationId: 'getMe' + } + } + }, + securityDefinitions: { + apiKey: { + in: 'header', + name: 'api_key', + type: 'apiKey' + } + } + } + + const securities = {} + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET' + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity).toEqual({ + url: 'http://swagger.io/v1/one', + method: 'GET' + }) + }) + + it('should add a basic auth if operation requires it and has header passed', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{authMe: []}], + paths: { + '/one': { + get: { + operationId: 'getMe', + security: [{authMe: []}] + } + } + }, + securityDefinitions: { + authMe: { + type: 'basic' + } + } + } + + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET', + query: {} + } + const securities = { + authorized: { + authMe: { + header: 'Basic Zm9vOmJhcg==' + } + } + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity.headers).toEqual({ + authorization: 'Basic Zm9vOmJhcg==' + }) + }) + + it('should add a basic auth if operation requires it', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{authMe: []}], + paths: { + '/one': { + get: { + operationId: 'getMe', + security: [{authMe: []}] + } + } + }, + securityDefinitions: { + authMe: { + type: 'basic' + } + } + } + + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET', + query: {} + } + const securities = { + authorized: { + authMe: { + username: 'foo', + password: 'bar' + } + } + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity.headers).toEqual({ + authorization: 'Basic Zm9vOmJhcg==' + }) + }) + + it('should be able to apply multiple auths', function () { + const spec = { + host: 'swagger.io', + basePath: '/v1', + security: [{authMe: []}, {apiKey: []}], + paths: { + '/one': { + get: { + operationId: 'getMe', + security: [{authMe: []}, {apiKey: []}] + } + } + }, + securityDefinitions: { + authMe: { + type: 'basic' + }, + apiKey: { + in: 'header', + name: 'api_key', + type: 'apiKey' + } + } + } + + const request = { + url: 'http://swagger.io/v1/one', + method: 'GET', + query: {} + } + const securities = { + authorized: { + authMe: { + username: 'foo', + password: 'bar' + }, + apiKey: 'hello' + } + } + + const applySecurity = applySecurities({request, securities, operation: spec.paths['/one'].get, spec}) + + expect(applySecurity.headers).toEqual({ + authorization: 'Basic Zm9vOmJhcg==', + api_key: 'hello' + }) + }) +})