From a17f80d0f678acd5e76de0475dd989b6b06d2338 Mon Sep 17 00:00:00 2001 From: abcang Date: Sun, 22 Mar 2020 19:09:09 +0900 Subject: [PATCH 1/5] Encode form data according to Encoding Object --- src/execute/oas3/build-request.js | 45 +----- src/execute/oas3/parameter-builders.js | 56 +------ src/http.js | 191 +++++++++++++++++------ test/bugs/ui-4071.js | 5 +- test/data/sample-multipart-oas3.js | 66 ++++---- test/http-multipart.js | 92 +++++++++++ test/http.js | 23 +++ test/oas3/execute/style-explode/query.js | 12 +- 8 files changed, 312 insertions(+), 178 deletions(-) diff --git a/src/execute/oas3/build-request.js b/src/execute/oas3/build-request.js index 009017db8..d524382be 100644 --- a/src/execute/oas3/build-request.js +++ b/src/execute/oas3/build-request.js @@ -3,7 +3,6 @@ import assign from 'lodash/assign' import get from 'lodash/get' import btoa from 'btoa' -import {Buffer} from 'buffer/' export default function (options, req) { const { @@ -50,49 +49,15 @@ export default function (options, req) { 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' || requestContentType.indexOf('multipart/') === 0) { + if (requestContentType === 'application/x-www-form-urlencoded' || requestContentType === 'multipart/form-data') { if (typeof requestBody === 'object') { + const encoding = (requestBodyDef.content[requestContentType] || {}).encoding || {} + req.form = {} Object.keys(requestBody).forEach((k) => { - const val = requestBody[k] - let newVal - let isFile - let isOAS3formatArray = false // oas3 query (default false) vs oas3 multipart - - if (typeof File !== 'undefined') { - isFile = val instanceof File // eslint-disable-line no-undef - } - - if (typeof Blob !== 'undefined') { - isFile = isFile || val instanceof Blob // eslint-disable-line no-undef - } - - if (typeof Buffer !== 'undefined') { - isFile = isFile || Buffer.isBuffer(val) - } - - if (typeof val === 'object' && !isFile) { - if (Array.isArray(val)) { - if (requestContentType === 'application/x-www-form-urlencoded') { - newVal = val.toString() - } - else { - // multipart case - newVal = val // keep as array - isOAS3formatArray = true - } - } - else { - newVal = JSON.stringify(val) - } - } - else { - newVal = val - } - req.form[k] = { - value: newVal, - isOAS3formatArray + value: requestBody[k], + encoding: encoding[k] || {}, } }) } diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index 09ad41411..4445eefa7 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -1,3 +1,4 @@ +import pick from 'lodash/pick' import stylize, {encodeDisallowedCharacters} from './style-serializer' import serialize from './content-serializer' @@ -46,58 +47,9 @@ export function query({req, value, parameter}) { } 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 ? 'unsafe' : 'reserved', - }), - 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 ? 'unsafe' : 'reserved', - }), - skipEncoding: true - } - }) - } - else { - const encodedParamName = encodeURIComponent(parameter.name) - req.query[encodedParamName] = { - value: stylize({ - key: encodedParamName, - value, - style: parameter.style || 'form', - explode: typeof parameter.explode === 'undefined' ? true : parameter.explode, - escape: parameter.allowReserved ? 'unsafe' : 'reserved', - }), - skipEncoding: true - } + req.query[parameter.name] = { + value, + serializationOption: pick(parameter, ['style', 'explode', 'allowReserved']) } } else if (parameter.allowEmptyValue && value !== undefined) { diff --git a/src/http.js b/src/http.js index 2d6b849ea..1ffc052a5 100644 --- a/src/http.js +++ b/src/http.js @@ -1,9 +1,10 @@ import 'cross-fetch/polyfill' /* global fetch */ import qs from 'qs' import jsYaml from 'js-yaml' -import isString from 'lodash/isString' +import pick from 'lodash/pick' import isFunction from 'lodash/isFunction' import FormData from '@tim-lai/isomorphic-form-data' +import {encodeDisallowedCharacters} from './execute/oas3/style-serializer' // For testing export const self = { @@ -155,71 +156,169 @@ export function isFile(obj, navigatorObj) { } return false } - if (typeof File !== 'undefined') { - // eslint-disable-next-line no-undef - return obj instanceof File + + if (typeof File !== 'undefined' && obj instanceof File) { // eslint-disable-line no-undef + return true + } + if (typeof Blob !== 'undefined' && obj instanceof Blob) { // eslint-disable-line no-undef + return true } + if (typeof Buffer !== 'undefined' && obj instanceof Buffer) { + return true + } + return obj !== null && typeof obj === 'object' && typeof obj.pipe === 'function' } -function formatValue(input, skipEncoding) { - const {collectionFormat, allowEmptyValue} = input - // `input` can be string in OAS3 contexts - const value = typeof input === 'object' ? input.value : input - const SEPARATORS = { - csv: ',', - ssv: '%20', - tsv: '%09', - pipes: '|' - } +function isArrayOfFile(obj, navigatorObj) { + return (Array.isArray(obj) && obj.some(v => isFile(v, navigatorObj))) +} + +const STYLE_SEPARATORS = { + form: ',', + spaceDelimited: '%20', + pipeDelimited: '|' +} + +const SEPARATORS = { + csv: ',', + ssv: '%20', + tsv: '%09', + pipes: '|' +} + +// Formats a key-value and returns an array of key-value pairs. +// +// Return value example 1: [['color', 'blue']] +// Return value example 2: [['color', 'blue,black,brown']] +// Return value example 3: [['color', ['blue', 'black', 'brown']]] +// Return value example 4: [['color', 'R,100,G,200,B,150']] +// Return value example 5: [['R', '100'], ['G', '200'], ['B', '150']] +// Return value example 6: [['color[R]', '100'], ['color[G]', '200'], ['color[B]', '150']] +function formatKeyValue(key, input, skipEncoding = false) { + const {collectionFormat, allowEmptyValue, serializationOption, encoding} = input + // `input` can be string + const value = (typeof input === 'object' && !Array.isArray(input)) ? input.value : input + const encodeFn = skipEncoding ? (k => k.toString()) : (k => encodeURIComponent(k)) + const encodedKey = encodeFn(key) if (typeof value === 'undefined' && allowEmptyValue) { - return '' + return [[encodedKey, '']] + } + + // file + if (isFile(value) || isArrayOfFile(value)) { + return [[encodedKey, value]] + } + + // for OAS 3 Parameter Object for serialization + if (serializationOption) { + return formatKeyValueBySerializationOption(key, value, skipEncoding, serializationOption) + } + + // for OAS 3 Encoding Object + if (encoding) { + if ([typeof encoding.style, typeof encoding.explode, typeof encoding.allowReserved].some(type => type !== 'undefined')) { + return formatKeyValueBySerializationOption(key, value, skipEncoding, pick(encoding, ['style', 'explode', 'allowReserved'])) + } + + if (encoding.contentType) { + if (encoding.contentType === 'application/json') { + // If value is a string, assume value is already a JSON string + const json = typeof value === 'string' ? value : JSON.stringify(value) + return [[encodedKey, encodeFn(json)]] + } + return [[encodedKey, encodeFn(value.toString())]] + } + + // Primitive + if (typeof value !== 'object') { + return [[encodedKey, encodeFn(value)]] + } + + // Array of primitives + if (Array.isArray(value) && value.every(v => typeof v !== 'object')) { + return [[encodedKey, value.map(encodeFn).join(',')]] + } + + // Array or object + return [[encodedKey, encodeFn(JSON.stringify(value))]] } - if (isFile(value) || typeof value === 'boolean') { - return value + // for OAS 2 Parameter Object + // Primitive + if (typeof value !== 'object') { + return [[encodedKey, encodeFn(value)]] } - let encodeFn = encodeURIComponent - if (skipEncoding) { - if (isString(value)) encodeFn = str => str - else encodeFn = obj => JSON.stringify(obj) + // Array + if (Array.isArray(value)) { + if (collectionFormat === 'multi') { + // In case of multipart/formdata, it is used as array. + // Otherwise, the caller will convert it to a query by qs.stringify. + return [[encodedKey, value.map(encodeFn)]] + } + + return [[encodedKey, value.map(encodeFn).join(SEPARATORS[collectionFormat || 'csv'])]] } - if (typeof value === 'object' && !Array.isArray(value)) { - return '' + // Object + return [[encodedKey, '']] +} + +function formatKeyValueBySerializationOption(key, value, skipEncoding, serializationOption) { + const style = serializationOption.style || 'form' + const explode = typeof serializationOption.explode === 'undefined' ? style === 'form' : serializationOption.explode + // eslint-disable-next-line no-nested-ternary + const escape = skipEncoding ? false : (serializationOption && serializationOption.allowReserved ? 'unsafe' : 'reserved') + const encodeFn = v => encodeDisallowedCharacters(v, {escape}) + const encodeKeyFn = skipEncoding ? (k => k) : (k => encodeDisallowedCharacters(k, {escape})) + + // Primitive + if (typeof value !== 'object') { + return [[encodeKeyFn(key), encodeFn(value)]] } - if (!Array.isArray(value)) { - return encodeFn(value) + // Array + if (Array.isArray(value)) { + if (explode) { + // In case of multipart/formdata, it is used as array. + // Otherwise, the caller will convert it to a query by qs.stringify. + return [[encodeKeyFn(key), value.map(encodeFn)]] + } + return [[encodeKeyFn(key), value.map(encodeFn).join(STYLE_SEPARATORS[style])]] } - if (Array.isArray(value) && !collectionFormat) { - return value.map(encodeFn).join(',') + // Object + if (style === 'deepObject') { + return Object.keys(value).map(valueKey => [encodeKeyFn(`${key}[${valueKey}]`), encodeFn(value[valueKey])]) } - if (collectionFormat === 'multi') { - // query case (not multipart/formdata) - return value.map(encodeFn) + + if (explode) { + return Object.keys(value).map(valueKey => [encodeKeyFn(valueKey), encodeFn(value[valueKey])]) } - return value.map(encodeFn).join(SEPARATORS[collectionFormat]) + + return [[encodeKeyFn(key), Object.keys(value).map(valueKey => [`${encodeKeyFn(valueKey)},${encodeFn(value[valueKey])}`]).join(',')]] } function buildFormData(reqForm) { /** * Build a new FormData instance, support array as field value - * OAS2.0 - via collectionFormat in spec definition - * OAS3.0 - via oas3BuildRequest, isOAS3formatArray flag + * OAS2.0 - when collectionFormat is multi + * OAS3.0 - when explode of Encoding Object is true * @param {Object} reqForm - ori req.form * @return {FormData} - new FormData instance */ return Object.entries(reqForm).reduce((formData, [name, input]) => { - if (!input.collectionFormat && !input.isOAS3formatArray) { - formData.append(name, formatValue(input, true)) - } - else { - input.value.forEach(item => - formData.append(name, formatValue({...input, value: item}, true))) + for (const [key, value] of formatKeyValue(name, input, true)) { + if (Array.isArray(value)) { + for (const v of value) { + formData.append(key, v) + } + } + else { + formData.append(key, value) + } } return formData }, new FormData()) @@ -234,14 +333,9 @@ export function encodeFormOrQuery(data) { * @return {object} encoded parameter names and values */ const encodedQuery = Object.keys(data).reduce((result, parameterName) => { - const isObject = a => a && typeof a === 'object' - const paramValue = data[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}, skipEncoding - ) + for (const [key, value] of formatKeyValue(parameterName, data[parameterName])) { + result[key] = value + } return result }, {}) return qs.stringify(encodedQuery, {encode: false, indices: false}) || '' @@ -258,7 +352,8 @@ export function mergeInQueryOrForm(req = {}) { if (form) { const hasFile = Object.keys(form).some((key) => { - return isFile(form[key].value) + const value = form[key].value + return isFile(value) || isArrayOfFile(value) }) const contentType = req.headers['content-type'] || req.headers['Content-Type'] diff --git a/test/bugs/ui-4071.js b/test/bugs/ui-4071.js index dbe8ed954..bd82c833b 100644 --- a/test/bugs/ui-4071.js +++ b/test/bugs/ui-4071.js @@ -35,7 +35,8 @@ const spec = { }, encoding: { industries: { - style: 'form' + style: 'form', + explode: false } } } @@ -69,7 +70,7 @@ test( headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, - body: 'industries=1%2C16' + body: 'industries=1,16' }) } ) diff --git a/test/data/sample-multipart-oas3.js b/test/data/sample-multipart-oas3.js index 6cef4bc24..0c23fad2a 100644 --- a/test/data/sample-multipart-oas3.js +++ b/test/data/sample-multipart-oas3.js @@ -46,6 +46,12 @@ export default { } } } + }, + encoding: { + 'email[]': { + style: 'form', + explode: true, + } } } } @@ -63,41 +69,41 @@ export default { } } }, - '/land/content/uploadImage': { - post: { - summary: 'upload image(s)', - requestBody: { - content: { - 'multipart/form-data': { - schema: { - type: 'object', - properties: { - imageId: { - description: '', - default: 'id', - type: 'string' - }, - 'images[]': { - description: 'The list of files', - type: 'array', - items: { - type: 'file', - format: 'binary' - } + }, + '/land/content/uploadImage': { + post: { + summary: 'upload image(s)', + requestBody: { + content: { + 'multipart/form-data': { + schema: { + type: 'object', + properties: { + imageId: { + description: '', + default: 'id', + type: 'string' + }, + 'images[]': { + description: 'The list of files', + type: 'array', + items: { + type: 'file', + format: 'binary' } } } } } - }, - responses: { - 200: { - description: '', - content: { - 'application/json': { - schema: { - type: 'object' - } + } + }, + responses: { + 200: { + description: '', + content: { + 'application/json': { + schema: { + type: 'object' } } } diff --git a/test/http-multipart.js b/test/http-multipart.js index 07efc6677..04a1b6603 100644 --- a/test/http-multipart.js +++ b/test/http-multipart.js @@ -188,4 +188,96 @@ describe('buildRequest - openapi 3.0', () => { }) }) }) + + describe('formData with file', () => { + const file1 = Buffer.from('test file data1') + const file2 = Buffer.from('test file data2') + + const req = buildRequest({ + spec: sampleMultipartOpenApi3, + operationId: 'post_land_content_uploadImage', + requestBody: { + imageId: 'id', + 'images[]': [file1, file2] + } + }) + + test('should return FormData entry list and item entries (in order)', () => { + expect(req).toMatchObject({ + method: 'POST', + url: '/api/v1/land/content/uploadImage', + credentials: 'same-origin', + headers: { + 'Content-Type': 'multipart/form-data' + }, + }) + const validateFormDataInstance = req.body instanceof FormData + expect(validateFormDataInstance).toEqual(true) + const itemEntries = req.body.getAll('images[]') + expect(itemEntries.length).toEqual(2) + expect(itemEntries[0].__content).toEqual(file1) + expect(itemEntries[1].__content).toEqual(file2) + }) + }) + + describe('respect Encoding Object', () => { + test('Should be set to object in the style of deepObject', () => { + const spec = { + openapi: '3.0.0', + servers: [ + { + url: 'http://petstore.swagger.io/v2', + name: 'Petstore' + } + ], + paths: { + '/one': { + post: { + operationId: 'getOne', + requestBody: { + content: { + 'multipart/form-data': { + schema: { + type: 'object', + properties: { + color: { + type: 'object', + }, + } + }, + encoding: { + color: { + style: 'deepObject', + explode: true + } + } + } + } + } + } + } + } + } + + // when + const req = buildRequest({ + spec, + operationId: 'getOne', + requestBody: { + color: {R: 100, G: 200, B: 150} + } + }) + + expect(req).toMatchObject({ + method: 'POST', + url: 'http://petstore.swagger.io/v2/one', + credentials: 'same-origin', + headers: { + 'Content-Type': 'multipart/form-data' + }, + }) + + expect(Array.from(req.body.entries())).toEqual([['color[R]', '100'], ['color[G]', '200'], ['color[B]', '150']]) + }) + }) }) diff --git a/test/http.js b/test/http.js index 94947852b..d3f9818d1 100644 --- a/test/http.js +++ b/test/http.js @@ -457,6 +457,8 @@ describe('http', () => { describe('isFile', () => { // mock browser File class global.File = class MockBrowserFile {} + // mock browser Blob class + global.Blob = class MockBrowserBlob {} const mockBrowserNavigator = { product: 'Gecko' @@ -466,6 +468,7 @@ describe('http', () => { } const browserFile = new global.File() + const browserBlob = new global.File() const reactNativeFileObject = { uri: '/mock/path' } @@ -480,6 +483,26 @@ describe('http', () => { expect(isFile(browserFile, mockReactNativeNavigator)).toEqual(false) }) + test('should return true for browser Blob type', () => { + expect(isFile(browserBlob)).toEqual(true) + }) + test('should return true for browser Blob type and browser user agent', () => { + expect(isFile(browserBlob, mockBrowserNavigator)).toEqual(true) + }) + test('should return false for browser Blob type and React Native user agent', () => { + expect(isFile(browserBlob, mockReactNativeNavigator)).toEqual(false) + }) + + test('should return true for browser Buffer type', () => { + expect(isFile(new Buffer([]))).toEqual(true) + }) + test('should return true for browser Buffer type and browser user agent', () => { + expect(isFile(new Buffer([]), mockBrowserNavigator)).toEqual(true) + }) + test('should return false for browser Buffer type and React Native user agent', () => { + expect(isFile(new Buffer([]), mockReactNativeNavigator)).toEqual(false) + }) + test('should return false for React Native-like file object', () => { expect(isFile(reactNativeFileObject)).toEqual(false) }) diff --git a/test/oas3/execute/style-explode/query.js b/test/oas3/execute/style-explode/query.js index 704a749da..a32bbba64 100644 --- a/test/oas3/execute/style-explode/query.js +++ b/test/oas3/execute/style-explode/query.js @@ -699,7 +699,7 @@ describe('OAS 3.0 - buildRequest w/ `style` & `explode` - query parameters', () expect(req).toEqual({ method: 'GET', - url: `/users?id=3 id=4 id=5 id=${SAFE_INPUT_RESULT}`, + url: `/users?id=3&id=4&id=5&id=${SAFE_INPUT_RESULT}`, credentials: 'same-origin', headers: {}, }) @@ -740,7 +740,7 @@ describe('OAS 3.0 - buildRequest w/ `style` & `explode` - query parameters', () expect(req).toEqual({ method: 'GET', - url: `/users?id=3 id=4 id=5 id=${SAFE_INPUT_RESULT}`, + url: `/users?id=3&id=4&id=5&id=${SAFE_INPUT_RESULT}`, credentials: 'same-origin', headers: {}, }) @@ -781,7 +781,7 @@ describe('OAS 3.0 - buildRequest w/ `style` & `explode` - query parameters', () expect(req).toEqual({ method: 'GET', - url: `/users?id=3 4 5 ${SAFE_INPUT_RESULT}`, + url: `/users?id=3%204%205%20${SAFE_INPUT_RESULT}`, credentials: 'same-origin', headers: {}, }) @@ -822,7 +822,7 @@ describe('OAS 3.0 - buildRequest w/ `style` & `explode` - query parameters', () expect(req).toEqual({ method: 'GET', - url: `/users?id=3|id=4|id=5|id=${SAFE_INPUT_RESULT}`, + url: `/users?id=3&id=4&id=5&id=${SAFE_INPUT_RESULT}`, credentials: 'same-origin', headers: {}, }) @@ -864,7 +864,7 @@ describe('OAS 3.0 - buildRequest w/ `style` & `explode` - query parameters', () expect(req).toEqual({ method: 'GET', - url: `/users?id=3|id=4|id=5|id=${SAFE_INPUT_RESULT}|id=${RESERVED_INPUT_UNENCODED_RESULT}`, + url: `/users?id=3&id=4&id=5&id=${SAFE_INPUT_RESULT}&id=${RESERVED_INPUT_UNENCODED_RESULT}`, credentials: 'same-origin', headers: {}, }) @@ -1327,7 +1327,7 @@ describe('OAS 3.0 - buildRequest w/ `style` & `explode` - query parameters', () expect(req).toEqual({ method: 'GET', - url: `/users?id[role]=admin&id[firstName]=Alex&id[greeting]=${SAFE_INPUT_RESULT}`, + url: `/users?id%5Brole%5D=admin&id%5BfirstName%5D=Alex&id%5Bgreeting%5D=${SAFE_INPUT_RESULT}`, credentials: 'same-origin', headers: {}, }) From f5e9cfdb466652d7f145e08667a822afcf8a5ea9 Mon Sep 17 00:00:00 2001 From: Tim Lai Date: Fri, 29 May 2020 14:34:27 -0700 Subject: [PATCH 2/5] Update test/http-multipart.js --- test/http-multipart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http-multipart.js b/test/http-multipart.js index 40760683c..ff9e60198 100644 --- a/test/http-multipart.js +++ b/test/http-multipart.js @@ -259,7 +259,7 @@ describe('buildRequest - openapi 3.0', () => { expect(validateFormDataInstance).toEqual(true) const itemEntries = req.body.getAll('images[]') expect(itemEntries.length).toEqual(2) - expect(itemEntries[0].__content).toEqual(file1) + expect(itemEntries[0]).toEqual(file1) expect(itemEntries[1].__content).toEqual(file2) }) }) From 63271e83d66d39ea5908f952f02db8f679799e4b Mon Sep 17 00:00:00 2001 From: Tim Lai Date: Fri, 29 May 2020 14:36:46 -0700 Subject: [PATCH 3/5] Update test/http-multipart.js --- test/http-multipart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http-multipart.js b/test/http-multipart.js index ff9e60198..a7128496c 100644 --- a/test/http-multipart.js +++ b/test/http-multipart.js @@ -260,7 +260,7 @@ describe('buildRequest - openapi 3.0', () => { const itemEntries = req.body.getAll('images[]') expect(itemEntries.length).toEqual(2) expect(itemEntries[0]).toEqual(file1) - expect(itemEntries[1].__content).toEqual(file2) + expect(itemEntries[1]).toEqual(file2) }) }) From 2af62dd6391102f9e957276d80852657e7b4b9d4 Mon Sep 17 00:00:00 2001 From: Tim Lai Date: Fri, 29 May 2020 14:37:03 -0700 Subject: [PATCH 4/5] Update test/http-multipart.js --- test/http-multipart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http-multipart.js b/test/http-multipart.js index a7128496c..8fa2ede44 100644 --- a/test/http-multipart.js +++ b/test/http-multipart.js @@ -321,7 +321,7 @@ describe('buildRequest - openapi 3.0', () => { }, }) - expect(Array.from(req.body.entries())).toEqual([['color[R]', '100'], ['color[G]', '200'], ['color[B]', '150']]) + expect(Array.from(req.body.entries())).toEqual([{name: 'color[R]', value: '100'}, {name: 'color[G]', value: '200'}, {name: 'color[B]', value: '150'}]) }) }) }) From bb107ae1b2e1b482d19699cfc344f86bf8b966dc Mon Sep 17 00:00:00 2001 From: Tim Lai Date: Fri, 29 May 2020 14:39:12 -0700 Subject: [PATCH 5/5] Update test/http-multipart.js --- test/http-multipart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http-multipart.js b/test/http-multipart.js index 8fa2ede44..334af4f56 100644 --- a/test/http-multipart.js +++ b/test/http-multipart.js @@ -321,7 +321,7 @@ describe('buildRequest - openapi 3.0', () => { }, }) - expect(Array.from(req.body.entries())).toEqual([{name: 'color[R]', value: '100'}, {name: 'color[G]', value: '200'}, {name: 'color[B]', value: '150'}]) + expect(Array.from(req.body._entryList)).toEqual([{name: 'color[R]', value: '100'}, {name: 'color[G]', value: '200'}, {name: 'color[B]', value: '150'}]) }) }) })