From f84ded23bdcc93c6056c86516e37ba6c349aabf4 Mon Sep 17 00:00:00 2001 From: Artem-Babich <51639399+Artem-Babich@users.noreply.github.com> Date: Mon, 17 Apr 2023 12:58:53 +0400 Subject: [PATCH] fix: request action returns bad request if request method is specified in lowercase in native automation(closes #7609) (#7633) ## Purpose Request action returns bad request if request method is specified in lowercase. The issue is only in native automation mode, because the request is not fully handled by hammerhead. ## Approach Use uppercase ## References #7609 ## Pre-Merge TODO - [ ] Write tests for your proposed changes - [ ] Make sure that existing tests do not fail --- .../request/create-request-options.ts | 27 +++++++-------- .../fixtures/api/es-next/request/test.js | 8 +++++ .../request/testcafe-fixtures/request-test.js | 33 +++++++++++++++++++ test/functional/site/api.js | 6 ++++ 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/test-run/request/create-request-options.ts b/src/test-run/request/create-request-options.ts index 81837047ea6..c3d57713adf 100644 --- a/src/test-run/request/create-request-options.ts +++ b/src/test-run/request/create-request-options.ts @@ -28,7 +28,7 @@ import { GetProxyUrlCommand } from '../commands/actions'; import { CallsiteRecord } from 'callsite-record'; const DEFAULT_ACCEPT = { [HTTP_HEADERS.accept]: `${CONTENT_TYPES.json}, ${CONTENT_TYPES.textPlain}, ${CONTENT_TYPES.all}` }; -const METHODS_WITH_CONTENT_TYPE = ['post', 'put', 'patch']; +const METHODS_WITH_CONTENT_TYPE = ['POST', 'PUT', 'PATCH']; const DEFAULT_REQUEST_METHOD = 'GET'; const DEFAULT_PROTOCOL = 'http:'; @@ -92,23 +92,23 @@ function changeHeaderNamesToLowercase (headers: OutgoingHttpHeaders): OutgoingHt return lowerCaseHeaders; } -async function prepareHeaders (headers: OutgoingHttpHeaders, currentPageUrl: URL, url: URL, body: Buffer, testRun: TestRun, withCredentials: boolean, options: ExternalRequestOptions): Promise { - const { host, origin } = url; - - const preparedHeaders: OutgoingHttpHeaders = Object.assign({}, DEFAULT_ACCEPT, changeHeaderNamesToLowercase(headers)); +async function prepareHeaders (options: ExternalRequestOptions, currentPageUrl: URL, url: URL, body: Buffer, testRun: TestRun, withCredentials: boolean): Promise { + const { host, origin } = url; + const { method, proxy, auth, headers = {} } = options; + const preparedHeaders: OutgoingHttpHeaders = Object.assign({}, DEFAULT_ACCEPT, changeHeaderNamesToLowercase(headers)); preparedHeaders[HTTP_HEADERS.host] = host; preparedHeaders[HTTP_HEADERS.origin] = origin; preparedHeaders[HTTP_HEADERS.contentLength] = body.length; - if (headers.method && METHODS_WITH_CONTENT_TYPE.includes(String(headers.method))) - preparedHeaders[HTTP_HEADERS.contentType] = CONTENT_TYPES.urlencoded; + if (method && METHODS_WITH_CONTENT_TYPE.includes(String(method))) + preparedHeaders[HTTP_HEADERS.contentType] = preparedHeaders[HTTP_HEADERS.contentType] || CONTENT_TYPES.urlencoded; - if (options.auth && withCredentials) - preparedHeaders[HTTP_HEADERS.authorization] = getAuthString(options.auth); + if (auth && withCredentials) + preparedHeaders[HTTP_HEADERS.authorization] = getAuthString(auth); - if (options.proxy?.auth) - preparedHeaders[HTTP_HEADERS.proxyAuthorization] = getAuthString(options.proxy.auth); + if (proxy?.auth) + preparedHeaders[HTTP_HEADERS.proxyAuthorization] = getAuthString(proxy.auth); if (withCredentials) { const currentPageCookies = await testRun.cookieProvider.getCookieHeader(currentPageUrl.href, currentPageUrl.hostname); @@ -199,11 +199,12 @@ function resolveUrlParts (testRun: TestRun, url: URL, withCredentials: boolean): export async function createRequestOptions (currentPageUrl: URL, testRun: TestRun, options: ExternalRequestOptions, callsite: CallsiteRecord | null): Promise { options.headers = options.headers || {}; + options.method = options.method?.toUpperCase() || DEFAULT_REQUEST_METHOD; const url = await prepareUrl(testRun, currentPageUrl, options.url, callsite); const withCredentials = !currentPageUrl.host || sameOriginCheck(currentPageUrl.href, url.href) || options.withCredentials || false; const body = transformBody(options.headers, options.body); - const headers = await prepareHeaders(options.headers, currentPageUrl, url, body, testRun, withCredentials, options); + const headers = await prepareHeaders(options, currentPageUrl, url, body, testRun, withCredentials); let auth = options.auth; const { @@ -222,7 +223,7 @@ export async function createRequestOptions (currentPageUrl: URL, testRun: TestRu } const requestParams: RequestOptionsParams = { - method: options.method || DEFAULT_REQUEST_METHOD, + method: options.method, url: href, protocol: protocol, hostname: hostname, diff --git a/test/functional/fixtures/api/es-next/request/test.js b/test/functional/fixtures/api/es-next/request/test.js index ab2d2ce8cc0..1c4fa697250 100644 --- a/test/functional/fixtures/api/es-next/request/test.js +++ b/test/functional/fixtures/api/es-next/request/test.js @@ -141,6 +141,14 @@ describe('Request', () => { return runTests('testcafe-fixtures/request-test.js', 'Should execute a request with relative url'); }); + it('Should execute a GET HTTPS request with method in lowercase', function () { + return runTests('testcafe-fixtures/request-test.js', 'Should execute a GET HTTPS request with method in lowercase'); + }); + + it('Should set a content type for POST request', function () { + return runTests('testcafe-fixtures/request-test.js', 'Should set a content type for POST request'); + }); + if (config.useLocalBrowsers) { it('Should rise request runtime error', function () { return runTests('testcafe-fixtures/request-test.js', 'Should rise request runtime error', { shouldFail: true }) diff --git a/test/functional/fixtures/api/es-next/request/testcafe-fixtures/request-test.js b/test/functional/fixtures/api/es-next/request/testcafe-fixtures/request-test.js index fda315ab399..e3cf57707bc 100644 --- a/test/functional/fixtures/api/es-next/request/testcafe-fixtures/request-test.js +++ b/test/functional/fixtures/api/es-next/request/testcafe-fixtures/request-test.js @@ -387,3 +387,36 @@ test('Should execute a GET HTTPS request', async (t) => { position: 'CTO', }); }); + + +test('Should execute a GET HTTPS request with method in lowercase', async (t) => { + const { + status, + statusText, + headers, + body, + } = await t.request(HTTPS_API_URL, { + method: 'get', + }); + + await t + .expect(status).eql(200) + .expect(statusText).eql('OK') + .expect(headers).contains({ 'content-type': 'application/json; charset=utf-8' }) + .expect(body).eql({ + name: 'John Hearts', + position: 'CTO', + }); +}); + +test('Should set a content type for POST request', async (t) => { + const options = { + method: 'POST', + }; + + const data = await t.request(`${API_URL}/request-info`, options); + + await t.expect(data.body.headers['content-type']).eql('application/x-www-form-urlencoded'); +}); + + diff --git a/test/functional/site/api.js b/test/functional/site/api.js index 955c6781883..5b103574d99 100644 --- a/test/functional/site/api.js +++ b/test/functional/site/api.js @@ -90,6 +90,12 @@ router.post('/data', (req, res) => { res.send(responses.handlePostResult(req.body)); }); +router.post('/request-info', (req, res) => { + res.send({ + headers: req.headers, + }); +}); + router.delete('/data/:dataId', (req, res) => { res.send(responses.handleDeleteResult(req.params)); });