mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-05 22:19:46 -06:00
fix: Boolean and null literals should be considered valid request bodies (#28835)
* fix(types): RequestBody type should be able to accept booleans and null values, which are all valid JSON literals * refactor: boolean literals are valid JSON objects. Null values should also be considered valid when explicitly passed to the request function. * refactor: body is explicitly defined when passed as positional argument or when supplied through the options object * test: JSON literals should be parsed as valid JSON and set json=true * docs: issue reference * fix: boolean and null literal should be send to request promise as strings * docs: fixes #28789 -- added issue reference * test: tests proper conversion of JSON literals to strings. * docs: added isssue reference * docs: fixes #28789 -- changelog entry * refactor: change isValidJsonObj to isValidBody Co-authored-by: Bill Glesias <bglesias@gmail.com> * refactor: change isValidJsonObj to isValidBody Co-authored-by: Bill Glesias <bglesias@gmail.com> * refactor: use lodash utils Co-authored-by: Bill Glesias <bglesias@gmail.com> * Update cli/CHANGELOG.md Co-authored-by: Bill Glesias <bglesias@gmail.com> * docs: moved entry to 13.6.5 * docs: fixed changelog entry * Update CHANGELOG.md --------- Co-authored-by: Bill Glesias <bglesias@gmail.com> Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
<!-- See the ../guides/writing-the-cypress-changelog.md for details on writing the changelog. -->
|
||||
## 13.6.7
|
||||
|
||||
_Released 2/27/2024 (PENDING)_
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
- Changed RequestBody type to allow for boolean and null literals to be passed as body values. [#28789](https://github.com/cypress-io/cypress/issues/28789)
|
||||
|
||||
## 13.6.6
|
||||
|
||||
_Released 2/22/2024_
|
||||
|
||||
2
cli/types/cypress.d.ts
vendored
2
cli/types/cypress.d.ts
vendored
@@ -7,7 +7,7 @@ declare namespace Cypress {
|
||||
type FileContents = string | any[] | object
|
||||
type HistoryDirection = 'back' | 'forward'
|
||||
type HttpMethod = string
|
||||
type RequestBody = string | object
|
||||
type RequestBody = string | object | boolean | null
|
||||
type ViewportOrientation = 'portrait' | 'landscape'
|
||||
type PrevSubject = keyof PrevSubjectMap
|
||||
type TestingType = 'e2e' | 'component'
|
||||
|
||||
@@ -129,6 +129,60 @@ describe('src/cy/commands/request', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/28789
|
||||
context('accepts trivial RFC 8259 compliant body objects', () => {
|
||||
it('accepts body equal to true', () => {
|
||||
cy.request({ method: 'POST', url: 'http://www.github.com/projects/foo', body: true }).then(function () {
|
||||
this.expectOptionsToBe({
|
||||
method: 'POST',
|
||||
url: 'http://www.github.com/projects/foo',
|
||||
body: true,
|
||||
json: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('accepts body equal to false', () => {
|
||||
cy.request({ method: 'POST', url: 'http://www.github.com/projects/foo', body: false }).then(function () {
|
||||
this.expectOptionsToBe({
|
||||
method: 'POST',
|
||||
url: 'http://www.github.com/projects/foo',
|
||||
body: false,
|
||||
json: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('accepts (explicitly defined) null body', () => {
|
||||
cy.request({ method: 'POST', url: 'http://www.github.com/projects/foo', body: null }).then(function () {
|
||||
this.expectOptionsToBe({
|
||||
method: 'POST',
|
||||
url: 'http://www.github.com/projects/foo',
|
||||
//body: null,
|
||||
json: true,
|
||||
})
|
||||
})
|
||||
|
||||
cy.request('POST', 'http://www.github.com/projects/foo', null).then(function () {
|
||||
this.expectOptionsToBe({
|
||||
method: 'POST',
|
||||
url: 'http://www.github.com/projects/foo',
|
||||
//body: null,
|
||||
json: true,
|
||||
})
|
||||
})
|
||||
|
||||
cy.request('http://www.github.com/projects/foo', null).then(function () {
|
||||
this.expectOptionsToBe({
|
||||
method: 'POST',
|
||||
url: 'http://www.github.com/projects/foo',
|
||||
//body: null,
|
||||
json: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('method normalization', () => {
|
||||
it('uppercases method', () => {
|
||||
cy.request('post', 'https://www.foo.com').then(function () {
|
||||
|
||||
@@ -43,8 +43,9 @@ const hasFormUrlEncodedContentTypeHeader = (headers) => {
|
||||
return header && (_.toLower(header) === 'content-type')
|
||||
}
|
||||
|
||||
const isValidJsonObj = (body) => {
|
||||
return _.isObject(body) && !_.isFunction(body)
|
||||
const isValidBody = (body, isExplicitlyDefined: boolean = false) => {
|
||||
return (_.isObject(body) || _.isBoolean(body) || (isExplicitlyDefined && _.isNull(body)))
|
||||
&& !_.isFunction(body)
|
||||
}
|
||||
|
||||
const whichAreOptional = (val, key) => {
|
||||
@@ -81,9 +82,11 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
request (...args) {
|
||||
const o: any = {}
|
||||
const userOptions = o
|
||||
let bodyIsExplicitlyDefined = false
|
||||
|
||||
if (_.isObject(args[0])) {
|
||||
_.extend(userOptions, args[0])
|
||||
bodyIsExplicitlyDefined = _.has(args[0], 'body')
|
||||
} else if (args.length === 1) {
|
||||
o.url = args[0]
|
||||
} else if (args.length === 2) {
|
||||
@@ -96,11 +99,13 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
// set url + body
|
||||
o.url = args[0]
|
||||
o.body = args[1]
|
||||
bodyIsExplicitlyDefined = true
|
||||
}
|
||||
} else if (args.length === 3) {
|
||||
o.method = args[0]
|
||||
o.url = args[1]
|
||||
o.body = args[2]
|
||||
bodyIsExplicitlyDefined = true
|
||||
}
|
||||
|
||||
let options = _.defaults({}, userOptions, REQUEST_DEFAULTS, {
|
||||
@@ -222,7 +227,7 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
|
||||
// only set json to true if form isnt true
|
||||
// and we have a valid object for body
|
||||
if ((options.form !== true) && isValidJsonObj(options.body)) {
|
||||
if ((options.form !== true) && isValidBody(options.body, bodyIsExplicitlyDefined)) {
|
||||
options.json = true
|
||||
}
|
||||
|
||||
|
||||
@@ -698,6 +698,11 @@ module.exports = function (options = {}) {
|
||||
// either turn these both on or off
|
||||
options.followAllRedirects = options.followRedirect
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/28789
|
||||
if (options.json === true) {
|
||||
if (_.isBoolean(options.body) || _.isNull(options.body)) options.body = String(options.body)
|
||||
}
|
||||
|
||||
if (options.form === true) {
|
||||
// reset form to whatever body is
|
||||
// and nuke body
|
||||
|
||||
@@ -937,6 +937,64 @@ describe('lib/request', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/28789
|
||||
context('json=true', () => {
|
||||
beforeEach(() => {
|
||||
nock('http://localhost:8080')
|
||||
.matchHeader('Content-Type', 'application/json')
|
||||
.post('/login')
|
||||
.reply(200, '<html></html>')
|
||||
})
|
||||
|
||||
it('does not modify regular JSON objects', function () {
|
||||
const init = sinon.spy(request.rp.Request.prototype, 'init')
|
||||
const body = {
|
||||
foo: 'bar',
|
||||
}
|
||||
|
||||
return request.sendPromise({}, this.fn, {
|
||||
url: 'http://localhost:8080/login',
|
||||
method: 'POST',
|
||||
cookies: false,
|
||||
json: true,
|
||||
body,
|
||||
})
|
||||
.then(() => {
|
||||
expect(init).to.be.calledWithMatch({ body })
|
||||
})
|
||||
})
|
||||
|
||||
it('converts boolean JSON literals to strings', function () {
|
||||
const init = sinon.spy(request.rp.Request.prototype, 'init')
|
||||
|
||||
return request.sendPromise({}, this.fn, {
|
||||
url: 'http://localhost:8080/login',
|
||||
method: 'POST',
|
||||
cookies: false,
|
||||
json: true,
|
||||
body: true,
|
||||
})
|
||||
.then(() => {
|
||||
expect(init).to.be.calledWithMatch({ body: 'true' })
|
||||
})
|
||||
})
|
||||
|
||||
it('converts null JSON literals to \'null\'', function () {
|
||||
const init = sinon.spy(request.rp.Request.prototype, 'init')
|
||||
|
||||
return request.sendPromise({}, this.fn, {
|
||||
url: 'http://localhost:8080/login',
|
||||
method: 'POST',
|
||||
cookies: false,
|
||||
json: true,
|
||||
body: null,
|
||||
})
|
||||
.then(() => {
|
||||
expect(init).to.be.calledWithMatch({ body: 'null' })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('bad headers', () => {
|
||||
beforeEach(function (done) {
|
||||
this.srv = http.createServer((req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user