diff options
Diffstat (limited to 'test/jest')
-rw-r--r-- | test/jest/instanceof-error.test.js | 44 | ||||
-rw-r--r-- | test/jest/interceptor.test.js | 197 | ||||
-rw-r--r-- | test/jest/issue-1757.test.js | 61 | ||||
-rw-r--r-- | test/jest/mock-agent.test.js | 46 | ||||
-rw-r--r-- | test/jest/mock-scope.test.js | 32 | ||||
-rw-r--r-- | test/jest/test.js | 36 |
6 files changed, 416 insertions, 0 deletions
diff --git a/test/jest/instanceof-error.test.js b/test/jest/instanceof-error.test.js new file mode 100644 index 0000000..8bb36d2 --- /dev/null +++ b/test/jest/instanceof-error.test.js @@ -0,0 +1,44 @@ +'use strict' + +const { createServer } = require('http') +const { once } = require('events') + +/* global expect, it, jest, AbortController */ + +// https://github.com/facebook/jest/issues/11607#issuecomment-899068995 +jest.useRealTimers() + +const runIf = (condition) => condition ? it : it.skip +const nodeMajor = Number(process.versions.node.split('.', 1)[0]) + +runIf(nodeMajor >= 16)('isErrorLike sanity check', () => { + const { isErrorLike } = require('../../lib/fetch/util') + const { DOMException } = require('../../lib/fetch/constants') + const error = new DOMException('') + + // https://github.com/facebook/jest/issues/2549 + expect(error instanceof Error).toBeFalsy() + expect(isErrorLike(error)).toBeTruthy() +}) + +runIf(nodeMajor >= 16)('Real use-case', async () => { + const { fetch } = require('../..') + + const ac = new AbortController() + ac.abort() + + const server = createServer((req, res) => { + res.end() + }).listen(0) + + await once(server, 'listening') + + const promise = fetch(`https://localhost:${server.address().port}`, { + signal: ac.signal + }) + + await expect(promise).rejects.toThrowError(/^Th(e|is) operation was aborted\.?$/) + + server.close() + await once(server, 'close') +}) diff --git a/test/jest/interceptor.test.js b/test/jest/interceptor.test.js new file mode 100644 index 0000000..73d70b7 --- /dev/null +++ b/test/jest/interceptor.test.js @@ -0,0 +1,197 @@ +'use strict' + +const { createServer } = require('http') +const { Agent, request } = require('../../index') +const DecoratorHandler = require('../../lib/handler/DecoratorHandler') +/* global expect */ + +const defaultOpts = { keepAliveTimeout: 10, keepAliveMaxTimeout: 10 } + +describe('interceptors', () => { + let server + beforeEach(async () => { + server = createServer((req, res) => { + res.setHeader('Content-Type', 'text/plain') + res.end('hello') + }) + await new Promise((resolve) => { server.listen(0, resolve) }) + }) + afterEach(async () => { + await new Promise((resolve) => server.close(resolve)) + }) + + test('interceptors are applied on client from an agent', async () => { + const interceptors = [] + const buildInterceptor = dispatch => { + const interceptorContext = { requestCount: 0 } + interceptors.push(interceptorContext) + return (opts, handler) => { + interceptorContext.requestCount++ + return dispatch(opts, handler) + } + } + + const opts = { interceptors: { Client: [buildInterceptor] }, ...defaultOpts } + const agent = new Agent(opts) + const origin = new URL(`http://localhost:${server.address().port}`) + await Promise.all([ + request(origin, { dispatcher: agent }), + request(origin, { dispatcher: agent }) + ]) + + // Assert that the requests are run on different interceptors (different Clients) + const requestCounts = interceptors.map(x => x.requestCount) + expect(requestCounts).toEqual([1, 1]) + }) + + test('interceptors are applied in the correct order', async () => { + const setHeaderInterceptor = (dispatch) => { + return (opts, handler) => { + opts.headers.push('foo', 'bar') + return dispatch(opts, handler) + } + } + + const assertHeaderInterceptor = (dispatch) => { + return (opts, handler) => { + expect(opts.headers).toEqual(['foo', 'bar']) + return dispatch(opts, handler) + } + } + + const opts = { interceptors: { Pool: [setHeaderInterceptor, assertHeaderInterceptor] }, ...defaultOpts } + const agent = new Agent(opts) + const origin = new URL(`http://localhost:${server.address().port}`) + await request(origin, { dispatcher: agent, headers: [] }) + }) + + test('interceptors handlers are called in reverse order', async () => { + const clearResponseHeadersInterceptor = (dispatch) => { + return (opts, handler) => { + class ResultInterceptor extends DecoratorHandler { + onHeaders (statusCode, headers, resume) { + return super.onHeaders(statusCode, [], resume) + } + } + + return dispatch(opts, new ResultInterceptor(handler)) + } + } + + const assertHeaderInterceptor = (dispatch) => { + return (opts, handler) => { + class ResultInterceptor extends DecoratorHandler { + onHeaders (statusCode, headers, resume) { + expect(headers).toEqual([]) + return super.onHeaders(statusCode, headers, resume) + } + } + + return dispatch(opts, new ResultInterceptor(handler)) + } + } + + const opts = { interceptors: { Agent: [assertHeaderInterceptor, clearResponseHeadersInterceptor] }, ...defaultOpts } + const agent = new Agent(opts) + const origin = new URL(`http://localhost:${server.address().port}`) + await request(origin, { dispatcher: agent, headers: [] }) + }) +}) + +describe('interceptors with NtlmRequestHandler', () => { + class FakeNtlmRequestHandler { + constructor (dispatch, opts, handler) { + this.dispatch = dispatch + this.opts = opts + this.handler = handler + this.requestCount = 0 + } + + onConnect (...args) { + return this.handler.onConnect(...args) + } + + onError (...args) { + return this.handler.onError(...args) + } + + onUpgrade (...args) { + return this.handler.onUpgrade(...args) + } + + onHeaders (statusCode, headers, resume, statusText) { + this.requestCount++ + if (this.requestCount < 2) { + // Do nothing + } else { + return this.handler.onHeaders(statusCode, headers, resume, statusText) + } + } + + onData (...args) { + if (this.requestCount < 2) { + // Do nothing + } else { + return this.handler.onData(...args) + } + } + + onComplete (...args) { + if (this.requestCount < 2) { + this.dispatch(this.opts, this) + } else { + return this.handler.onComplete(...args) + } + } + + onBodySent (...args) { + if (this.requestCount < 2) { + // Do nothing + } else { + return this.handler.onBodySent(...args) + } + } + } + let server + + beforeEach(async () => { + // This Test is important because NTLM and Negotiate require several + // http requests in sequence to run on the same keepAlive socket + + const socketRequestCountSymbol = Symbol('Socket Request Count') + server = createServer((req, res) => { + if (req.socket[socketRequestCountSymbol] === undefined) { + req.socket[socketRequestCountSymbol] = 0 + } + req.socket[socketRequestCountSymbol]++ + res.setHeader('Content-Type', 'text/plain') + + // Simulate NTLM/Negotiate logic, by returning 200 + // on the second request of each socket + if (req.socket[socketRequestCountSymbol] >= 2) { + res.statusCode = 200 + res.end() + } else { + res.statusCode = 401 + res.end() + } + }) + await new Promise((resolve) => { server.listen(0, resolve) }) + }) + afterEach(async () => { + await new Promise((resolve) => server.close(resolve)) + }) + + test('Retry interceptor on Client will use the same socket', async () => { + const interceptor = dispatch => { + return (opts, handler) => { + return dispatch(opts, new FakeNtlmRequestHandler(dispatch, opts, handler)) + } + } + const opts = { interceptors: { Client: [interceptor] }, ...defaultOpts } + const agent = new Agent(opts) + const origin = new URL(`http://localhost:${server.address().port}`) + const { statusCode } = await request(origin, { dispatcher: agent, headers: [] }) + expect(statusCode).toEqual(200) + }) +}) diff --git a/test/jest/issue-1757.test.js b/test/jest/issue-1757.test.js new file mode 100644 index 0000000..b6519d9 --- /dev/null +++ b/test/jest/issue-1757.test.js @@ -0,0 +1,61 @@ +'use strict' + +const { Dispatcher, setGlobalDispatcher, MockAgent } = require('../..') + +/* global expect, it */ + +class MiniflareDispatcher extends Dispatcher { + constructor (inner, options) { + super(options) + this.inner = inner + } + + dispatch (options, handler) { + return this.inner.dispatch(options, handler) + } + + close (...args) { + return this.inner.close(...args) + } + + destroy (...args) { + return this.inner.destroy(...args) + } +} + +const runIf = (condition) => condition ? it : it.skip +const nodeMajor = Number(process.versions.node.split('.', 1)[0]) + +runIf(nodeMajor >= 16)('https://github.com/nodejs/undici/issues/1757', async () => { + // fetch isn't exported in <16.8 + const { fetch } = require('../..') + + const mockAgent = new MockAgent() + const mockClient = mockAgent.get('http://localhost:3000') + mockAgent.disableNetConnect() + setGlobalDispatcher(new MiniflareDispatcher(mockAgent)) + + mockClient.intercept({ + path: () => true, + method: () => true + }).reply(200, async (opts) => { + if (opts.body?.[Symbol.asyncIterator]) { + const chunks = [] + for await (const chunk of opts.body) { + chunks.push(chunk) + } + + return Buffer.concat(chunks) + } + + return opts.body + }) + + const response = await fetch('http://localhost:3000', { + method: 'POST', + body: JSON.stringify({ foo: 'bar' }) + }) + + expect(response.json()).resolves.toMatchObject({ foo: 'bar' }) + expect(response.status).toBe(200) +}) diff --git a/test/jest/mock-agent.test.js b/test/jest/mock-agent.test.js new file mode 100644 index 0000000..6f6bac2 --- /dev/null +++ b/test/jest/mock-agent.test.js @@ -0,0 +1,46 @@ +'use strict' + +const { request, setGlobalDispatcher, MockAgent } = require('../..') +const { getResponse } = require('../../lib/mock/mock-utils') + +/* global describe, it, expect */ + +describe('MockAgent', () => { + let mockAgent + + afterEach(() => { + mockAgent.close() + }) + + it('should work in jest', async () => { + expect.assertions(4) + + const baseUrl = 'http://localhost:9999' + + mockAgent = new MockAgent() + setGlobalDispatcher(mockAgent) + const mockClient = mockAgent.get(baseUrl) + + mockClient.intercept({ + path: '/foo?hello=there&see=ya', + method: 'POST', + body: 'form1=data1&form2=data2' + }).reply(200, { foo: 'bar' }, { + headers: { + 'content-type': 'application/json' + }, + trailers: { 'Content-MD5': 'test' } + }) + + const { statusCode, headers, trailers, body } = await request(`${baseUrl}/foo?hello=there&see=ya`, { + method: 'POST', + body: 'form1=data1&form2=data2' + }) + expect(statusCode).toBe(200) + expect(headers).toEqual({ 'content-type': 'application/json' }) + expect(trailers).toEqual({ 'content-md5': 'test' }) + + const jsonResponse = JSON.parse(await getResponse(body)) + expect(jsonResponse).toEqual({ foo: 'bar' }) + }) +}) diff --git a/test/jest/mock-scope.test.js b/test/jest/mock-scope.test.js new file mode 100644 index 0000000..cab77f6 --- /dev/null +++ b/test/jest/mock-scope.test.js @@ -0,0 +1,32 @@ +const { MockAgent, setGlobalDispatcher, request } = require('../../index') + +/* global afterAll, expect, it, AbortController */ + +const runIf = (condition) => condition ? it : it.skip + +const nodeMajor = Number(process.versions.node.split('.', 1)[0]) +const mockAgent = new MockAgent() + +afterAll(async () => { + await mockAgent.close() +}) + +runIf(nodeMajor >= 16)('Jest works with MockScope.delay - issue #1327', async () => { + mockAgent.disableNetConnect() + setGlobalDispatcher(mockAgent) + + const mockPool = mockAgent.get('http://localhost:3333') + + mockPool.intercept({ + path: '/jest-bugs', + method: 'GET' + }).reply(200, 'Hello').delay(100) + + const ac = new AbortController() + setTimeout(() => ac.abort(), 5) + const promise = request('http://localhost:3333/jest-bugs', { + signal: ac.signal + }) + + await expect(promise).rejects.toThrowError('Request aborted') +}, 1000) diff --git a/test/jest/test.js b/test/jest/test.js new file mode 100644 index 0000000..079a41f --- /dev/null +++ b/test/jest/test.js @@ -0,0 +1,36 @@ +'use strict' + +const { Client } = require('../..') +const { createServer } = require('http') +/* global test, expect */ + +test('should work in jest', async () => { + const server = createServer((req, res) => { + expect(req.url).toBe('/') + expect(req.method).toBe('POST') + expect(req.headers.host).toBe(`localhost:${server.address().port}`) + res.setHeader('Content-Type', 'text/plain') + res.end('hello') + }) + await expect(new Promise((resolve, reject) => { + server.listen(0, () => { + const client = new Client(`http://localhost:${server.address().port}`) + client.request({ + path: '/', + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: '{}' + }, (err, result) => { + server.close() + client.close() + if (err) { + reject(err) + } else { + resolve(result.body.text()) + } + }) + }) + })).resolves.toBe('hello') +}) |