feat(RequestBody): validation support for required fields (#6223)
fixes #5181 * application/json * application/xml * application/x-www-form-urlencoded * Set requestBodyValue values to be an immutable Map, as "value". Previously stored as a normal String. * This enables adding "errors" to the Map, for validation use * note: getOAS3RequiredRequestBodyContentType requires state.spec, * which is not available to state.oas3
This commit is contained in:
@@ -9,26 +9,79 @@ export default class Execute extends Component {
|
||||
operation: PropTypes.object.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
method: PropTypes.string.isRequired,
|
||||
oas3Selectors: PropTypes.object.isRequired,
|
||||
oas3Actions: PropTypes.object.isRequired,
|
||||
onExecute: PropTypes.func
|
||||
}
|
||||
|
||||
onClick=()=>{
|
||||
let { specSelectors, specActions, operation, path, method } = this.props
|
||||
handleValidateParameters = () => {
|
||||
let { specSelectors, specActions, path, method } = this.props
|
||||
specActions.validateParams([path, method])
|
||||
return specSelectors.validateBeforeExecute([path, method])
|
||||
}
|
||||
|
||||
specActions.validateParams( [path, method] )
|
||||
handleValidateRequestBody = () => {
|
||||
let { path, method, specSelectors, oas3Selectors, oas3Actions } = this.props
|
||||
let validationErrors = {
|
||||
missingBodyValue: false,
|
||||
missingRequiredKeys: []
|
||||
}
|
||||
// context: reset errors, then (re)validate
|
||||
oas3Actions.clearRequestBodyValidateError({ path, method })
|
||||
let oas3RequiredRequestBodyContentType = specSelectors.getOAS3RequiredRequestBodyContentType([path, method])
|
||||
let oas3RequestBodyValue = oas3Selectors.requestBodyValue(path, method)
|
||||
let oas3ValidateBeforeExecuteSuccess = oas3Selectors.validateBeforeExecute([path, method])
|
||||
|
||||
if ( specSelectors.validateBeforeExecute([path, method]) ) {
|
||||
if(this.props.onExecute) {
|
||||
if (!oas3ValidateBeforeExecuteSuccess) {
|
||||
validationErrors.missingBodyValue = true
|
||||
oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })
|
||||
return false
|
||||
}
|
||||
if (!oas3RequiredRequestBodyContentType) {
|
||||
return true
|
||||
}
|
||||
let missingRequiredKeys = oas3Selectors.validateShallowRequired({ oas3RequiredRequestBodyContentType, oas3RequestBodyValue })
|
||||
if (!missingRequiredKeys || missingRequiredKeys.length < 1) {
|
||||
return true
|
||||
}
|
||||
missingRequiredKeys.forEach((missingKey) => {
|
||||
validationErrors.missingRequiredKeys.push(missingKey)
|
||||
})
|
||||
oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })
|
||||
return false
|
||||
}
|
||||
|
||||
handleValidationResultPass = () => {
|
||||
let { specActions, operation, path, method } = this.props
|
||||
if (this.props.onExecute) {
|
||||
// loading spinner
|
||||
this.props.onExecute()
|
||||
}
|
||||
specActions.execute( { operation, path, method } )
|
||||
} else {
|
||||
specActions.execute({ operation, path, method })
|
||||
}
|
||||
|
||||
handleValidationResultFail = () => {
|
||||
let { specActions, path, method } = this.props
|
||||
// deferred by 40ms, to give element class change time to settle.
|
||||
specActions.clearValidateParams( [path, method] )
|
||||
specActions.clearValidateParams([path, method])
|
||||
setTimeout(() => {
|
||||
specActions.validateParams([path, method])
|
||||
}, 40)
|
||||
}
|
||||
|
||||
handleValidationResult = (isPass) => {
|
||||
if (isPass) {
|
||||
this.handleValidationResultPass()
|
||||
} else {
|
||||
this.handleValidationResultFail()
|
||||
}
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
let paramsResult = this.handleValidateParameters()
|
||||
let requestBodyResult = this.handleValidateRequestBody()
|
||||
let isPass = paramsResult && requestBodyResult
|
||||
this.handleValidationResult(isPass)
|
||||
}
|
||||
|
||||
onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)
|
||||
|
||||
@@ -192,6 +192,8 @@ export default class Operation extends PureComponent {
|
||||
operation={ operation }
|
||||
specActions={ specActions }
|
||||
specSelectors={ specSelectors }
|
||||
oas3Selectors={ oas3Selectors }
|
||||
oas3Actions={ oas3Actions }
|
||||
path={ path }
|
||||
method={ method }
|
||||
onExecute={ onExecute } />
|
||||
|
||||
@@ -187,6 +187,7 @@ export default class Parameters extends Component {
|
||||
contentTypes={ requestBody.get("content", List()).keySeq() }
|
||||
onChange={(value) => {
|
||||
oas3Actions.setRequestContentType({ value, pathMethod })
|
||||
oas3Actions.initRequestBodyValidateError({ pathMethod })
|
||||
}}
|
||||
className="body-param-content-type" />
|
||||
</label>
|
||||
@@ -197,6 +198,7 @@ export default class Parameters extends Component {
|
||||
requestBody={requestBody}
|
||||
requestBodyValue={oas3Selectors.requestBodyValue(...pathMethod)}
|
||||
requestBodyInclusionSetting={oas3Selectors.requestBodyInclusionSetting(...pathMethod)}
|
||||
requestBodyErrors={oas3Selectors.requestBodyErrors(...pathMethod)}
|
||||
isExecute={isExecute}
|
||||
activeExamplesKey={oas3Selectors.activeExamplesMember(
|
||||
...pathMethod,
|
||||
|
||||
@@ -8,6 +8,8 @@ export const UPDATE_ACTIVE_EXAMPLES_MEMBER = "oas3_set_active_examples_member"
|
||||
export const UPDATE_REQUEST_CONTENT_TYPE = "oas3_set_request_content_type"
|
||||
export const UPDATE_RESPONSE_CONTENT_TYPE = "oas3_set_response_content_type"
|
||||
export const UPDATE_SERVER_VARIABLE_VALUE = "oas3_set_server_variable_value"
|
||||
export const SET_REQUEST_BODY_VALIDATE_ERROR = "oas3_set_request_body_validate_error"
|
||||
export const CLEAR_REQUEST_BODY_VALIDATE_ERROR = "oas3_clear_request_body_validate_error"
|
||||
|
||||
export function setSelectedServer (selectedServerUrl, namespace) {
|
||||
return {
|
||||
@@ -57,3 +59,24 @@ export function setServerVariableValue ({ server, namespace, key, val }) {
|
||||
payload: { server, namespace, key, val }
|
||||
}
|
||||
}
|
||||
|
||||
export const setRequestBodyValidateError = ({ path, method, validationErrors }) => {
|
||||
return {
|
||||
type: SET_REQUEST_BODY_VALIDATE_ERROR,
|
||||
payload: { path, method, validationErrors }
|
||||
}
|
||||
}
|
||||
|
||||
export const clearRequestBodyValidateError = ({ path, method }) => {
|
||||
return {
|
||||
type: CLEAR_REQUEST_BODY_VALIDATE_ERROR,
|
||||
payload: { path, method }
|
||||
}
|
||||
}
|
||||
|
||||
export const initRequestBodyValidateError = ({ pathMethod } ) => {
|
||||
return {
|
||||
type: CLEAR_REQUEST_BODY_VALIDATE_ERROR,
|
||||
payload: { path: pathMethod[0], method: pathMethod[1] }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { PureComponent } from "react"
|
||||
import PropTypes from "prop-types"
|
||||
import cx from "classnames"
|
||||
import { stringify } from "core/utils"
|
||||
|
||||
const NOOP = Function.prototype
|
||||
@@ -11,6 +12,7 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
value: PropTypes.string,
|
||||
defaultValue: PropTypes.string,
|
||||
errors: PropTypes.array,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -74,19 +76,22 @@ export default class RequestBodyEditor extends PureComponent {
|
||||
|
||||
render() {
|
||||
let {
|
||||
getComponent
|
||||
getComponent,
|
||||
errors
|
||||
} = this.props
|
||||
|
||||
let {
|
||||
value
|
||||
} = this.state
|
||||
|
||||
let isInvalid = errors.size > 0 ? true : false
|
||||
const TextArea = getComponent("TextArea")
|
||||
|
||||
return (
|
||||
<div className="body-param">
|
||||
<TextArea
|
||||
className={"body-param__text"}
|
||||
className={cx("body-param__text", { invalid: isInvalid } )}
|
||||
title={errors.size ? errors.join(", ") : ""}
|
||||
value={value}
|
||||
onChange={ this.onDomChange }
|
||||
/>
|
||||
|
||||
@@ -38,6 +38,7 @@ const RequestBody = ({
|
||||
requestBody,
|
||||
requestBodyValue,
|
||||
requestBodyInclusionSetting,
|
||||
requestBodyErrors,
|
||||
getComponent,
|
||||
getConfigs,
|
||||
specSelectors,
|
||||
@@ -88,6 +89,7 @@ const RequestBody = ({
|
||||
const handleExamplesSelect = (key /*, { isSyntheticChange } */) => {
|
||||
updateActiveExamplesKey(key)
|
||||
}
|
||||
requestBodyErrors = List.isList(requestBodyErrors) ? requestBodyErrors : List()
|
||||
|
||||
if(!mediaTypeValue.size) {
|
||||
return null
|
||||
@@ -138,7 +140,8 @@ const RequestBody = ({
|
||||
const type = prop.get("type")
|
||||
const format = prop.get("format")
|
||||
const description = prop.get("description")
|
||||
const currentValue = requestBodyValue.get(key)
|
||||
const currentValue = requestBodyValue.getIn([key, "value"])
|
||||
const currentErrors = requestBodyValue.getIn([key, "errors"]) || requestBodyErrors
|
||||
|
||||
let initialValue = prop.get("default") || prop.get("example") || ""
|
||||
|
||||
@@ -179,6 +182,8 @@ const RequestBody = ({
|
||||
description={key}
|
||||
getComponent={getComponent}
|
||||
value={currentValue === undefined ? initialValue : currentValue}
|
||||
required = { required }
|
||||
errors = { currentErrors }
|
||||
onChange={(value) => {
|
||||
onChange(value, [key])
|
||||
}}
|
||||
@@ -223,6 +228,7 @@ const RequestBody = ({
|
||||
<div>
|
||||
<RequestBodyEditor
|
||||
value={requestBodyValue}
|
||||
errors={requestBodyErrors}
|
||||
defaultValue={getDefaultRequestBodyValue(
|
||||
requestBody,
|
||||
contentType,
|
||||
@@ -270,6 +276,7 @@ RequestBody.propTypes = {
|
||||
requestBody: ImPropTypes.orderedMap.isRequired,
|
||||
requestBodyValue: ImPropTypes.orderedMap.isRequired,
|
||||
requestBodyInclusionSetting: ImPropTypes.Map.isRequired,
|
||||
requestBodyErrors: ImPropTypes.list.isRequired,
|
||||
getComponent: PropTypes.func.isRequired,
|
||||
getConfigs: PropTypes.func.isRequired,
|
||||
fn: PropTypes.object.isRequired,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { fromJS, Map } from "immutable"
|
||||
|
||||
import {
|
||||
UPDATE_SELECTED_SERVER,
|
||||
UPDATE_REQUEST_BODY_VALUE,
|
||||
@@ -5,7 +7,9 @@ import {
|
||||
UPDATE_ACTIVE_EXAMPLES_MEMBER,
|
||||
UPDATE_REQUEST_CONTENT_TYPE,
|
||||
UPDATE_SERVER_VARIABLE_VALUE,
|
||||
UPDATE_RESPONSE_CONTENT_TYPE
|
||||
UPDATE_RESPONSE_CONTENT_TYPE,
|
||||
SET_REQUEST_BODY_VALIDATE_ERROR,
|
||||
CLEAR_REQUEST_BODY_VALIDATE_ERROR,
|
||||
} from "./actions"
|
||||
|
||||
export default {
|
||||
@@ -15,7 +19,27 @@ export default {
|
||||
},
|
||||
[UPDATE_REQUEST_BODY_VALUE]: (state, { payload: { value, pathMethod } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
if (!Map.isMap(value)) {
|
||||
// context: application/json is always a String (instead of Map)
|
||||
return state.setIn( [ "requestData", path, method, "bodyValue" ], value)
|
||||
}
|
||||
let currentVal = state.getIn(["requestData", path, method, "bodyValue"]) || Map()
|
||||
if (!Map.isMap(currentVal)) {
|
||||
// context: user switch from application/json to application/x-www-form-urlencoded
|
||||
currentVal = Map()
|
||||
}
|
||||
let newVal
|
||||
const [...valueKeys] = value.keys()
|
||||
valueKeys.forEach((valueKey) => {
|
||||
let valueKeyVal = value.getIn([valueKey])
|
||||
if (!currentVal.has(valueKey)) {
|
||||
newVal = currentVal.setIn([valueKey, "value"], valueKeyVal)
|
||||
} else if (!Map.isMap(valueKeyVal)) {
|
||||
// context: user input will be received as String
|
||||
newVal = currentVal.setIn([valueKey, "value"], valueKeyVal)
|
||||
}
|
||||
})
|
||||
return state.setIn(["requestData", path, method, "bodyValue"], newVal)
|
||||
},
|
||||
[UPDATE_REQUEST_BODY_INCLUSION]: (state, { payload: { value, pathMethod, name } } ) =>{
|
||||
let [path, method] = pathMethod
|
||||
@@ -36,4 +60,38 @@ export default {
|
||||
const path = namespace ? [ namespace, "serverVariableValues", server, key ] : [ "serverVariableValues", server, key ]
|
||||
return state.setIn(path, val)
|
||||
},
|
||||
[SET_REQUEST_BODY_VALIDATE_ERROR]: (state, { payload: { path, method, validationErrors } } ) => {
|
||||
let errors = []
|
||||
errors.push("Required field is not provided")
|
||||
if (validationErrors.missingBodyValue) {
|
||||
// context: is application/json or application/xml, where typeof (missing) bodyValue = String
|
||||
return state.setIn(["requestData", path, method, "errors"], fromJS(errors))
|
||||
}
|
||||
if (validationErrors.missingRequiredKeys && validationErrors.missingRequiredKeys.length > 0) {
|
||||
// context: is application/x-www-form-urlencoded, with list of missing keys
|
||||
const { missingRequiredKeys } = validationErrors
|
||||
return state.updateIn(["requestData", path, method, "bodyValue"], fromJS({}), missingKeyValues => {
|
||||
return missingRequiredKeys.reduce((bodyValue, currentMissingKey) => {
|
||||
return bodyValue.setIn([currentMissingKey, "errors"], fromJS(errors))
|
||||
}, missingKeyValues)
|
||||
})
|
||||
}
|
||||
console.warn("unexpected result: SET_REQUEST_BODY_VALIDATE_ERROR")
|
||||
return state
|
||||
},
|
||||
[CLEAR_REQUEST_BODY_VALIDATE_ERROR]: (state, { payload: { path, method } }) => {
|
||||
const requestBodyValue = state.getIn(["requestData", path, method, "bodyValue"])
|
||||
if (!Map.isMap(requestBodyValue)) {
|
||||
return state.setIn(["requestData", path, method, "errors"], fromJS([]))
|
||||
}
|
||||
const [...valueKeys] = requestBodyValue.keys()
|
||||
if (!valueKeys) {
|
||||
return state
|
||||
}
|
||||
return state.updateIn(["requestData", path, method, "bodyValue"], fromJS({}), bodyValues => {
|
||||
return valueKeys.reduce((bodyValue, curr) => {
|
||||
return bodyValue.setIn([curr, "errors"], fromJS([]))
|
||||
}, bodyValues)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { OrderedMap, Map } from "immutable"
|
||||
import { isOAS3 as isOAS3Helper } from "./helpers"
|
||||
|
||||
|
||||
// Helpers
|
||||
|
||||
function onlyOAS3(selector) {
|
||||
@@ -15,6 +14,35 @@ function onlyOAS3(selector) {
|
||||
}
|
||||
}
|
||||
|
||||
function validateRequestBodyIsRequired(selector) {
|
||||
return (...args) => (system) => {
|
||||
const specJson = system.getSystem().specSelectors.specJson()
|
||||
const argsList = [...args]
|
||||
// expect argsList[0] = state
|
||||
let pathMethod = argsList[1] || []
|
||||
let isOas3RequestBodyRequired = specJson.getIn(["paths", ...pathMethod, "requestBody", "required"])
|
||||
|
||||
if (isOas3RequestBodyRequired) {
|
||||
return selector(...args)
|
||||
} else {
|
||||
// validation pass b/c not required
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validateRequestBodyValueExists = (state, pathMethod) => {
|
||||
pathMethod = pathMethod || []
|
||||
let oas3RequestBodyValue = state.getIn(["requestData", ...pathMethod, "bodyValue"])
|
||||
// context: bodyValue can be a String, or a Map
|
||||
if (!oas3RequestBodyValue) {
|
||||
return false
|
||||
}
|
||||
// validation pass if String is not empty, or if Map exists
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
export const selectedServer = onlyOAS3((state, namespace) => {
|
||||
const path = namespace ? [namespace, "selectedServer"] : ["selectedServer"]
|
||||
return state.getIn(path) || ""
|
||||
@@ -31,6 +59,11 @@ export const requestBodyInclusionSetting = onlyOAS3((state, path, method) => {
|
||||
}
|
||||
)
|
||||
|
||||
export const requestBodyErrors = onlyOAS3((state, path, method) => {
|
||||
return state.getIn(["requestData", path, method, "errors"]) || null
|
||||
}
|
||||
)
|
||||
|
||||
export const activeExamplesMember = onlyOAS3((state, path, method, type, name) => {
|
||||
return state.getIn(["examples", path, method, type, name, "activeExample"]) || null
|
||||
}
|
||||
@@ -116,3 +149,34 @@ export const serverEffectiveValue = onlyOAS3((state, locationData) => {
|
||||
return str
|
||||
}
|
||||
)
|
||||
|
||||
export const validateBeforeExecute = validateRequestBodyIsRequired(
|
||||
(state, pathMethod) => validateRequestBodyValueExists(state, pathMethod)
|
||||
)
|
||||
|
||||
export const validateShallowRequired = ( state, {oas3RequiredRequestBodyContentType, oas3RequestBodyValue} ) => {
|
||||
let missingRequiredKeys = []
|
||||
// context: json => String; urlencoded => Map
|
||||
if (!Map.isMap(oas3RequestBodyValue)) {
|
||||
return missingRequiredKeys
|
||||
}
|
||||
let requiredKeys = []
|
||||
// We intentionally cycle through list of contentTypes for defined requiredKeys
|
||||
// instead of assuming first contentType will accurately list all expected requiredKeys
|
||||
// Alternatively, we could try retrieving the contentType first, and match exactly. This would be a more accurate representation of definition
|
||||
Object.keys(oas3RequiredRequestBodyContentType.requestContentType).forEach((contentType) => {
|
||||
let contentTypeVal = oas3RequiredRequestBodyContentType.requestContentType[contentType]
|
||||
contentTypeVal.forEach((requiredKey) => {
|
||||
if (requiredKeys.indexOf(requiredKey) < 0 ) {
|
||||
requiredKeys.push(requiredKey)
|
||||
}
|
||||
})
|
||||
})
|
||||
requiredKeys.forEach((key) => {
|
||||
let requiredKeyValue = oas3RequestBodyValue.getIn([key, "value"])
|
||||
if (!requiredKeyValue) {
|
||||
missingRequiredKeys.push(key)
|
||||
}
|
||||
})
|
||||
return missingRequiredKeys
|
||||
}
|
||||
|
||||
@@ -406,7 +406,19 @@ export const executeRequest = (req) =>
|
||||
if(isJSONObject(requestBody)) {
|
||||
req.requestBody = JSON.parse(requestBody)
|
||||
} else if(requestBody && requestBody.toJS) {
|
||||
req.requestBody = requestBody.filter((value, key) => !isEmptyValue(value) || requestBodyInclusionSetting.get(key)).toJS()
|
||||
req.requestBody = requestBody
|
||||
.map(
|
||||
(val) => {
|
||||
if (Map.isMap(val)) {
|
||||
return val.get("value")
|
||||
}
|
||||
return val
|
||||
}
|
||||
)
|
||||
.filter(
|
||||
(value, key) => !isEmptyValue(value) || requestBodyInclusionSetting.get(key)
|
||||
)
|
||||
.toJS()
|
||||
} else{
|
||||
req.requestBody = requestBody
|
||||
}
|
||||
|
||||
@@ -314,7 +314,6 @@ export const parameterWithMetaByIdentity = (state, pathMethod, param) => {
|
||||
hashKeyedMeta
|
||||
)
|
||||
})
|
||||
|
||||
return mergedParams.find(curr => curr.get("in") === param.get("in") && curr.get("name") === param.get("name"), OrderedMap())
|
||||
}
|
||||
|
||||
@@ -327,7 +326,6 @@ export const parameterInclusionSettingFor = (state, pathMethod, paramName, param
|
||||
export const parameterWithMeta = (state, pathMethod, paramName, paramIn) => {
|
||||
const opParams = specJsonWithResolvedSubtrees(state).getIn(["paths", ...pathMethod, "parameters"], OrderedMap())
|
||||
const currentParam = opParams.find(param => param.get("in") === paramIn && param.get("name") === paramName, OrderedMap())
|
||||
|
||||
return parameterWithMetaByIdentity(state, pathMethod, currentParam)
|
||||
}
|
||||
|
||||
@@ -364,7 +362,6 @@ export const hasHost = createSelector(
|
||||
// Get the parameter values, that the user filled out
|
||||
export function parameterValues(state, pathMethod, isXml) {
|
||||
pathMethod = pathMethod || []
|
||||
// let paramValues = state.getIn(["meta", "paths", ...pathMethod, "parameters"], fromJS([]))
|
||||
let paramValues = operationWithMeta(state, ...pathMethod).get("parameters", List())
|
||||
return paramValues.reduce( (hash, p) => {
|
||||
let value = isXml && p.get("in") === "body" ? p.get("value_xml") : p.get("value")
|
||||
@@ -495,6 +492,28 @@ export const validateBeforeExecute = ( state, pathMethod ) => {
|
||||
return isValid
|
||||
}
|
||||
|
||||
export const getOAS3RequiredRequestBodyContentType = (state, pathMethod) => {
|
||||
let requiredObj = {
|
||||
requestBody: false,
|
||||
requestContentType: {}
|
||||
}
|
||||
let requestBody = state.getIn(["resolvedSubtrees", "paths", ...pathMethod, "requestBody"], fromJS([]))
|
||||
if (requestBody.size < 1) {
|
||||
return requiredObj
|
||||
}
|
||||
if (requestBody.getIn(["required"])) {
|
||||
requiredObj.requestBody = requestBody.getIn(["required"])
|
||||
}
|
||||
requestBody.getIn(["content"]).entrySeq().forEach((contentType) => { // e.g application/json
|
||||
const key = contentType[0]
|
||||
if (contentType[1].getIn(["schema", "required"])) {
|
||||
const val = contentType[1].getIn(["schema", "required"]).toJS()
|
||||
requiredObj.requestContentType[key] = val
|
||||
}
|
||||
})
|
||||
return requiredObj
|
||||
}
|
||||
|
||||
function returnSelfOrNewMap(obj) {
|
||||
// returns obj if obj is an Immutable map, else returns a new Map
|
||||
return Map.isMap(obj) ? obj : new Map()
|
||||
|
||||
27
test/e2e-cypress/static/documents/bugs/5181.yaml
Normal file
27
test/e2e-cypress/static/documents/bugs/5181.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
info:
|
||||
title: Required parameter missing, doesn't block request from executing.
|
||||
version: '1'
|
||||
openapi: 3.0.0
|
||||
servers:
|
||||
- url: http://httpbin.org/anything
|
||||
paths:
|
||||
/foos:
|
||||
post:
|
||||
requestBody:
|
||||
content:
|
||||
application/x-www-form-urlencoded:
|
||||
# application/json:
|
||||
# application/xml:
|
||||
schema:
|
||||
properties:
|
||||
foo:
|
||||
type: string
|
||||
bar:
|
||||
type: string
|
||||
required:
|
||||
- foo
|
||||
type: object
|
||||
required: true # Note this doesn't have an impact
|
||||
responses:
|
||||
default:
|
||||
description: ok
|
||||
@@ -67,6 +67,9 @@ describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => {
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// add item to pass required validation
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button")
|
||||
.click()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
@@ -91,6 +94,9 @@ describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => {
|
||||
// Request Body
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(5) > .parameters-col_description .parameter__empty_value_toggle input")
|
||||
.uncheck()
|
||||
// add item to pass required validation
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button")
|
||||
.click()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
@@ -118,6 +124,9 @@ describe("OpenAPI 3.0 Allow Empty Values in Request Body", () => {
|
||||
.uncheck()
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(6) > .parameters-col_description .parameter__empty_value_toggle input")
|
||||
.uncheck()
|
||||
// add item to pass required validation
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button")
|
||||
.click()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
|
||||
214
test/e2e-cypress/tests/features/oas3-request-body-required.js
Normal file
214
test/e2e-cypress/tests/features/oas3-request-body-required.js
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* @prettier
|
||||
*/
|
||||
|
||||
describe("OpenAPI 3.0 Validation for Required Request Body and Request Body Fields", () => {
|
||||
describe("Request Body required bug/5181", () => {
|
||||
it("on execute, if empty value, SHOULD render class 'invalid' and should NOT render cURL component", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/bugs/5181.yaml"
|
||||
)
|
||||
.get("#operations-default-post_foos")
|
||||
.click()
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get input
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input")
|
||||
.should("not.have.class", "invalid")
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
// class "invalid" should now exist (and render red, which we won't check)
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input")
|
||||
.should("have.class", "invalid")
|
||||
// cURL component should not exist
|
||||
.get(".responses-wrapper .copy-paste")
|
||||
.should("not.exist")
|
||||
})
|
||||
it("on execute, if value exists, should NOT render class 'invalid' and SHOULD render cURL component", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/bugs/5181.yaml"
|
||||
)
|
||||
.get("#operations-default-post_foos")
|
||||
.click()
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get input
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(1) > .parameters-col_description input")
|
||||
.type("abc")
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
.should("not.have.class", "invalid")
|
||||
// cURL component should exist
|
||||
.get(".responses-wrapper .copy-paste")
|
||||
.should("exist")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Request Body required fields - application/json", () => {
|
||||
it("on execute, if empty value, SHOULD render class 'invalid' and should NOT render cURL component", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/features/petstore-only-pet.openapi.yaml"
|
||||
)
|
||||
.get("#operations-pet-addPet")
|
||||
.click()
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get and clear textarea
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.should("not.have.class", "invalid")
|
||||
.clear()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
// class "invalid" should now exist (and render red, which we won't check)
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.should("have.class", "invalid")
|
||||
// cURL component should not exist
|
||||
.get(".responses-wrapper .copy-paste")
|
||||
.should("not.exist")
|
||||
})
|
||||
it("on execute, if value exists, even if just single space, should NOT render class 'invalid' and SHOULD render cURL component that contains the single space", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/features/petstore-only-pet.openapi.yaml"
|
||||
)
|
||||
.get("#operations-pet-addPet")
|
||||
.click()
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get, clear, then modify textarea
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.clear()
|
||||
.type(" ")
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.should("not.have.class", "invalid")
|
||||
// cURL component should exist
|
||||
.get(".responses-wrapper .copy-paste")
|
||||
.should("exist")
|
||||
.get(".responses-wrapper .copy-paste textarea")
|
||||
.should("contains.text", "-d \" \"")
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
petstore ux notes:
|
||||
- required field, but if example value exists, will populate the field. So this test will clear the example value.
|
||||
- "add item" will insert an empty array, and display an input text box. This establishes a value for the field.
|
||||
*/
|
||||
describe("Request Body required fields - application/x-www-form-urlencoded", () => {
|
||||
it("on execute, if empty value, SHOULD render class 'invalid' and should NOT render cURL component", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/features/petstore-only-pet.openapi.yaml"
|
||||
)
|
||||
.get("#operations-pet-addPet")
|
||||
.click()
|
||||
.get(".opblock-section .opblock-section-request-body .body-param-content-type > select")
|
||||
.select("application/x-www-form-urlencoded")
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get and clear input populated from example value
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
|
||||
.clear()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
// class "invalid" should now exist (and render red, which we won't check)
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
|
||||
.should("have.class", "invalid")
|
||||
// cURL component should not exist
|
||||
.get(".responses-wrapper .copy-paste")
|
||||
.should("not.exist")
|
||||
})
|
||||
it("on execute, if all values exist, even if array exists but is empty, should NOT render class 'invalid' and SHOULD render cURL component", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/features/petstore-only-pet.openapi.yaml"
|
||||
)
|
||||
.get("#operations-pet-addPet")
|
||||
.click()
|
||||
.get(".opblock-section .opblock-section-request-body .body-param-content-type > select")
|
||||
.select("application/x-www-form-urlencoded")
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// add item to get input
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description button")
|
||||
.click()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
|
||||
.should("have.value", "doggie")
|
||||
.should("not.have.class", "invalid")
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description input")
|
||||
.should("have.value", "")
|
||||
.should("not.have.class", "invalid")
|
||||
// cURL component should exist
|
||||
.get(".responses-wrapper .copy-paste")
|
||||
.should("exist")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Request Body: switching between Content Types", () => {
|
||||
it("after application/json 'invalid' error, on switch content type to application/x-www-form-urlencoded, SHOULD be free of errors", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/features/petstore-only-pet.openapi.yaml"
|
||||
)
|
||||
.get("#operations-pet-addPet")
|
||||
.click()
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get and clear textarea
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.should("not.have.class", "invalid")
|
||||
.clear()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.should("have.class", "invalid")
|
||||
// switch content type
|
||||
.get(".opblock-section .opblock-section-request-body .body-param-content-type > select")
|
||||
.select("application/x-www-form-urlencoded")
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
|
||||
.should("not.have.class", "invalid")
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(4) > .parameters-col_description input")
|
||||
.should("not.have.class", "invalid")
|
||||
})
|
||||
it("after application/x-www-form-urlencoded 'invalid' error, on switch content type to application/json, SHOULD be free of errors", () => {
|
||||
cy.visit(
|
||||
"/?url=/documents/features/petstore-only-pet.openapi.yaml"
|
||||
)
|
||||
.get("#operations-pet-addPet")
|
||||
.click()
|
||||
.get(".opblock-section .opblock-section-request-body .body-param-content-type > select")
|
||||
.select("application/x-www-form-urlencoded")
|
||||
// Expand Try It Out
|
||||
.get(".try-out__btn")
|
||||
.click()
|
||||
// get and clear input
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
|
||||
.clear()
|
||||
// Execute
|
||||
.get(".execute.opblock-control__btn")
|
||||
.click()
|
||||
// class "invalid" should now exist (and render red, which we won't check)
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .parameters:nth-child(2) > .parameters-col_description input")
|
||||
.should("have.class", "invalid")
|
||||
// switch content type
|
||||
.get(".opblock-section .opblock-section-request-body .body-param-content-type > select")
|
||||
.select("application/json")
|
||||
.get(".opblock-body .opblock-section .opblock-section-request-body .body-param textarea")
|
||||
.should("not.have.class", "invalid")
|
||||
})
|
||||
})
|
||||
})
|
||||
508
test/mocha/core/plugins/oas3/reducers.js
Normal file
508
test/mocha/core/plugins/oas3/reducers.js
Normal file
@@ -0,0 +1,508 @@
|
||||
/* eslint-env mocha */
|
||||
import expect from "expect"
|
||||
import { fromJS } from "immutable"
|
||||
import reducer from "corePlugins/oas3/reducers"
|
||||
|
||||
describe("oas3 plugin - reducer", function () {
|
||||
describe("SET_REQUEST_BODY_VALIDATE_ERROR", () => {
|
||||
const setRequestBodyValidateError = reducer["oas3_set_request_body_validate_error"]
|
||||
|
||||
describe("missingBodyValue exists, e.g. application/json", () => {
|
||||
it("should set errors", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: "",
|
||||
requestContentType: "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: true,
|
||||
missingRequiredKeys: []
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: "",
|
||||
requestContentType: "application/json",
|
||||
errors: ["Required field is not provided"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
describe("missingRequiredKeys exists with length, e.g. application/x-www-form-urleconded", () => {
|
||||
it("should set nested errors", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: null,
|
||||
missingRequiredKeys: ["name"]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
errors: ["Required field is not provided"]
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
|
||||
it("should overwrite nested errors, for keys listed in missingRequiredKeys", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
errors: ["some fake error"]
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: null,
|
||||
missingRequiredKeys: ["name"]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
errors: ["Required field is not provided"]
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
|
||||
it("should not overwrite nested errors, for keys not listed in missingRequiredKeys", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
errors: ["random error should not be overwritten"]
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
errors: ["some fake error"]
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: null,
|
||||
missingRequiredKeys: ["name"]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
errors: ["random error should not be overwritten"]
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
errors: ["Required field is not provided"]
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
|
||||
it("should set multiple nested errors", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: null,
|
||||
missingRequiredKeys: ["id", "name"]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "",
|
||||
errors: ["Required field is not provided"]
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
errors: ["Required field is not provided"]
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
describe("missingRequiredKeys is empty list", () => {
|
||||
it("should not set any errors, and return state unchanged", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: null,
|
||||
missingRequiredKeys: []
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
describe("other unexpected payload, e.g. no missingBodyValue or missingRequiredKeys", () => {
|
||||
it("should not throw error if receiving unexpected validationError format. return state unchanged", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = setRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
validationErrors: {
|
||||
missingBodyValue: null,
|
||||
// missingRequiredKeys: ["none provided"]
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
},
|
||||
name: {
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("CLEAR_REQUEST_BODY_VALIDATE_ERROR", function() {
|
||||
const clearRequestBodyValidateError = reducer["oas3_clear_request_body_validate_error"]
|
||||
|
||||
describe("bodyValue is String, e.g. application/json", () => {
|
||||
it("should clear errors", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: "{}",
|
||||
requestContentType: "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = clearRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: "{}",
|
||||
requestContentType: "application/json",
|
||||
errors: []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
describe("bodyValue is Map with entries, e.g. application/x-www-form-urleconded", () => {
|
||||
it("should clear nested errors, and apply empty error list to all entries", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
errors: ["some random error"]
|
||||
},
|
||||
name: {
|
||||
value: "doggie",
|
||||
errors: ["Required field is not provided"]
|
||||
},
|
||||
status: {
|
||||
value: "available"
|
||||
}
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = clearRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
id: {
|
||||
value: "10",
|
||||
errors: [],
|
||||
},
|
||||
name: {
|
||||
value: "doggie",
|
||||
errors: [],
|
||||
},
|
||||
status: {
|
||||
value: "available",
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
|
||||
describe("bodyValue is empty Map", () => {
|
||||
it("should return state unchanged", () => {
|
||||
const state = fromJS({
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {},
|
||||
requestContentType: "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const result = clearRequestBodyValidateError(state, {
|
||||
payload: {
|
||||
path: "/pet",
|
||||
method: "post",
|
||||
}
|
||||
})
|
||||
|
||||
const expectedResult = {
|
||||
requestData: {
|
||||
"/pet": {
|
||||
post: {
|
||||
bodyValue: {
|
||||
},
|
||||
requestContentType: "application/x-www-form-urlencoded",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(result.toJS()).toEqual(expectedResult)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
Reference in New Issue
Block a user