summaryrefslogtreecommitdiffstats
path: root/test/redirect-request.js
diff options
context:
space:
mode:
Diffstat (limited to 'test/redirect-request.js')
-rw-r--r--test/redirect-request.js420
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, '')
+})