diff options
Diffstat (limited to 'test/redirect-request.js')
-rw-r--r-- | test/redirect-request.js | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/test/redirect-request.js b/test/redirect-request.js new file mode 100644 index 0000000..5a1ae6d --- /dev/null +++ b/test/redirect-request.js @@ -0,0 +1,420 @@ +'use strict' + +const t = require('tap') +const undici = require('..') +const { nodeMajor } = require('../lib/core/util') +const { + startRedirectingServer, + startRedirectingWithBodyServer, + startRedirectingChainServers, + startRedirectingWithoutLocationServer, + startRedirectingWithAuthorization, + startRedirectingWithCookie, + startRedirectingWithQueryParams +} = require('./utils/redirecting-servers') +const { createReadable, createReadableStream } = require('./utils/stream') + +for (const factory of [ + (server, opts) => new undici.Agent(opts), + (server, opts) => new undici.Pool(`http://${server}`, opts), + (server, opts) => new undici.Client(`http://${server}`, opts) +]) { + const request = (t, server, opts, ...args) => { + const dispatcher = factory(server, opts) + t.teardown(() => dispatcher.close()) + return undici.request(args[0], { ...args[1], dispatcher }, args[2]) + } + + t.test('should always have a history with the final URL even if no redirections were followed', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, undefined, `http://${server}/200?key=value`, { + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.same(history.map(x => x.toString()), [`http://${server}/200?key=value`]) + t.equal(body, `GET /5 key=value :: host@${server} connection@keep-alive`) + }) + + t.test('should not follow redirection by default if not using RedirectAgent', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}`) + const body = await bodyStream.text() + + t.equal(statusCode, 302) + t.equal(headers.location, `http://${server}/302/1`) + t.equal(body.length, 0) + }) + + t.test('should follow redirection after a HTTP 300', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, undefined, `http://${server}/300?key=value`, { + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.same(history.map(x => x.toString()), [ + `http://${server}/300?key=value`, + `http://${server}/300/1?key=value`, + `http://${server}/300/2?key=value`, + `http://${server}/300/3?key=value`, + `http://${server}/300/4?key=value`, + `http://${server}/300/5?key=value` + ]) + t.equal(body, `GET /5 key=value :: host@${server} connection@keep-alive`) + }) + + t.test('should follow redirection after a HTTP 300 default', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, { maxRedirections: 10 }, `http://${server}/300?key=value`) + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.same(history.map(x => x.toString()), [ + `http://${server}/300?key=value`, + `http://${server}/300/1?key=value`, + `http://${server}/300/2?key=value`, + `http://${server}/300/3?key=value`, + `http://${server}/300/4?key=value`, + `http://${server}/300/5?key=value` + ]) + t.equal(body, `GET /5 key=value :: host@${server} connection@keep-alive`) + }) + + t.test('should follow redirection after a HTTP 301', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/301`, { + method: 'POST', + body: 'REQUEST', + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `POST /5 :: host@${server} connection@keep-alive content-length@7 :: REQUEST`) + }) + + t.test('should follow redirection after a HTTP 302', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/302`, { + method: 'PUT', + body: Buffer.from('REQUEST'), + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `PUT /5 :: host@${server} connection@keep-alive content-length@7 :: REQUEST`) + }) + + t.test('should follow redirection after a HTTP 303 changing method to GET', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/303`, { + method: 'PATCH', + body: 'REQUEST', + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `GET /5 :: host@${server} connection@keep-alive`) + }) + + t.test('should remove Host and request body related headers when following HTTP 303 (array)', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/303`, { + method: 'PATCH', + headers: [ + 'Content-Encoding', + 'gzip', + 'X-Foo1', + '1', + 'X-Foo2', + '2', + 'Content-Type', + 'application/json', + 'X-Foo3', + '3', + 'Host', + 'localhost', + 'X-Bar', + '4' + ], + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `GET /5 :: host@${server} connection@keep-alive x-foo1@1 x-foo2@2 x-foo3@3 x-bar@4`) + }) + + t.test('should remove Host and request body related headers when following HTTP 303 (object)', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/303`, { + method: 'PATCH', + headers: { + 'Content-Encoding': 'gzip', + 'X-Foo1': '1', + 'X-Foo2': '2', + 'Content-Type': 'application/json', + 'X-Foo3': '3', + Host: 'localhost', + 'X-Bar': '4' + }, + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `GET /5 :: host@${server} connection@keep-alive x-foo1@1 x-foo2@2 x-foo3@3 x-bar@4`) + }) + + t.test('should follow redirection after a HTTP 307', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/307`, { + method: 'DELETE', + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `DELETE /5 :: host@${server} connection@keep-alive`) + }) + + t.test('should follow redirection after a HTTP 308', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/308`, { + method: 'OPTIONS', + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.equal(body, `OPTIONS /5 :: host@${server} connection@keep-alive`) + }) + + t.test('should ignore HTTP 3xx response bodies', async t => { + const server = await startRedirectingWithBodyServer(t) + + const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, undefined, `http://${server}/`, { + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.same(history.map(x => x.toString()), [`http://${server}/`, `http://${server}/end`]) + t.equal(body, 'FINAL') + }) + + t.test('should ignore query after redirection', async t => { + const server = await startRedirectingWithQueryParams(t) + + const { statusCode, headers, context: { history } } = await request(t, server, undefined, `http://${server}/`, { + maxRedirections: 10, + query: { param1: 'first' } + }) + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.same(history.map(x => x.toString()), [`http://${server}/`, `http://${server}/?param2=second`]) + }) + + t.test('should follow a redirect chain up to the allowed number of times', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream, context: { history } } = await request(t, server, undefined, `http://${server}/300`, { + maxRedirections: 2 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 300) + t.equal(headers.location, `http://${server}/300/3`) + t.same(history.map(x => x.toString()), [`http://${server}/300`, `http://${server}/300/1`, `http://${server}/300/2`]) + t.equal(body.length, 0) + }) + + t.test('when a Location response header is NOT present', async t => { + const redirectCodes = [300, 301, 302, 303, 307, 308] + const server = await startRedirectingWithoutLocationServer(t) + + for (const code of redirectCodes) { + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/${code}`, { + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, code) + t.notOk(headers.location) + t.equal(body.length, 0) + } + }) + + t.test('should not allow invalid maxRedirections arguments', async t => { + try { + await request(t, 'localhost', undefined, 'http://localhost', { + method: 'GET', + maxRedirections: 'INVALID' + }) + + t.fail('Did not throw') + } catch (err) { + t.equal(err.message, 'maxRedirections must be a positive number') + } + }) + + t.test('should not allow invalid maxRedirections arguments default', async t => { + try { + await request(t, 'localhost', { + maxRedirections: 'INVALID' + }, 'http://localhost', { + method: 'GET' + }) + + t.fail('Did not throw') + } catch (err) { + t.equal(err.message, 'maxRedirections must be a positive number') + } + }) + + t.test('should not follow redirects when using ReadableStream request bodies', { skip: nodeMajor < 16 }, async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/301`, { + method: 'POST', + body: createReadableStream('REQUEST'), + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 301) + t.equal(headers.location, `http://${server}/301/2`) + t.equal(body.length, 0) + }) + + t.test('should not follow redirects when using Readable request bodies', async t => { + const server = await startRedirectingServer(t) + + const { statusCode, headers, body: bodyStream } = await request(t, server, undefined, `http://${server}/301`, { + method: 'POST', + body: createReadable('REQUEST'), + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 301) + t.equal(headers.location, `http://${server}/301/1`) + t.equal(body.length, 0) + }) +} + +t.test('should follow redirections when going cross origin', async t => { + const [server1, server2, server3] = await startRedirectingChainServers(t) + + const { statusCode, headers, body: bodyStream, context: { history } } = await undici.request(`http://${server1}`, { + method: 'POST', + maxRedirections: 10 + }) + + const body = await bodyStream.text() + + t.equal(statusCode, 200) + t.notOk(headers.location) + t.same(history.map(x => x.toString()), [ + `http://${server1}/`, + `http://${server2}/`, + `http://${server3}/`, + `http://${server2}/end`, + `http://${server3}/end`, + `http://${server1}/end` + ]) + t.equal(body, 'POST') +}) + +t.test('should handle errors (callback)', t => { + t.plan(1) + + undici.request( + 'http://localhost:0', + { + maxRedirections: 10 + }, + error => { + t.match(error.code, /EADDRNOTAVAIL|ECONNREFUSED/) + } + ) +}) + +t.test('should handle errors (promise)', async t => { + try { + await undici.request('http://localhost:0', { maxRedirections: 10 }) + t.fail('Did not throw') + } catch (error) { + t.match(error.code, /EADDRNOTAVAIL|ECONNREFUSED/) + } +}) + +t.test('removes authorization header on third party origin', async t => { + const [server1] = await startRedirectingWithAuthorization(t, 'secret') + const { body: bodyStream } = await undici.request(`http://${server1}`, { + maxRedirections: 10, + headers: { + authorization: 'secret' + } + }) + + const body = await bodyStream.text() + + t.equal(body, '') +}) + +t.test('removes cookie header on third party origin', async t => { + const [server1] = await startRedirectingWithCookie(t, 'a=b') + const { body: bodyStream } = await undici.request(`http://${server1}`, { + maxRedirections: 10, + headers: { + cookie: 'a=b' + } + }) + + const body = await bodyStream.text() + + t.equal(body, '') +}) |