| 'use strict' |
| |
| const { kClients } = require('../core/symbols') |
| const Agent = require('../agent') |
| const { |
| kAgent, |
| kMockAgentSet, |
| kMockAgentGet, |
| kDispatches, |
| kIsMockActive, |
| kNetConnect, |
| kGetNetConnect, |
| kOptions, |
| kFactory |
| } = require('./mock-symbols') |
| const MockClient = require('./mock-client') |
| const MockPool = require('./mock-pool') |
| const { matchValue, buildMockOptions } = require('./mock-utils') |
| const { InvalidArgumentError, UndiciError } = require('../core/errors') |
| const Dispatcher = require('../dispatcher') |
| const Pluralizer = require('./pluralizer') |
| const PendingInterceptorsFormatter = require('./pending-interceptors-formatter') |
| |
| class FakeWeakRef { |
| constructor (value) { |
| this.value = value |
| } |
| |
| deref () { |
| return this.value |
| } |
| } |
| |
| class MockAgent extends Dispatcher { |
| constructor (opts) { |
| super(opts) |
| |
| this[kNetConnect] = true |
| this[kIsMockActive] = true |
| |
| // Instantiate Agent and encapsulate |
| if ((opts && opts.agent && typeof opts.agent.dispatch !== 'function')) { |
| throw new InvalidArgumentError('Argument opts.agent must implement Agent') |
| } |
| const agent = opts && opts.agent ? opts.agent : new Agent(opts) |
| this[kAgent] = agent |
| |
| this[kClients] = agent[kClients] |
| this[kOptions] = buildMockOptions(opts) |
| } |
| |
| get (origin) { |
| let dispatcher = this[kMockAgentGet](origin) |
| |
| if (!dispatcher) { |
| dispatcher = this[kFactory](origin) |
| this[kMockAgentSet](origin, dispatcher) |
| } |
| return dispatcher |
| } |
| |
| dispatch (opts, handler) { |
| // Call MockAgent.get to perform additional setup before dispatching as normal |
| this.get(opts.origin) |
| return this[kAgent].dispatch(opts, handler) |
| } |
| |
| async close () { |
| await this[kAgent].close() |
| this[kClients].clear() |
| } |
| |
| deactivate () { |
| this[kIsMockActive] = false |
| } |
| |
| activate () { |
| this[kIsMockActive] = true |
| } |
| |
| enableNetConnect (matcher) { |
| if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) { |
| if (Array.isArray(this[kNetConnect])) { |
| this[kNetConnect].push(matcher) |
| } else { |
| this[kNetConnect] = [matcher] |
| } |
| } else if (typeof matcher === 'undefined') { |
| this[kNetConnect] = true |
| } else { |
| throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.') |
| } |
| } |
| |
| disableNetConnect () { |
| this[kNetConnect] = false |
| } |
| |
| // This is required to bypass issues caused by using global symbols - see: |
| // https://github.com/nodejs/undici/issues/1447 |
| get isMockActive () { |
| return this[kIsMockActive] |
| } |
| |
| [kMockAgentSet] (origin, dispatcher) { |
| this[kClients].set(origin, new FakeWeakRef(dispatcher)) |
| } |
| |
| [kFactory] (origin) { |
| const mockOptions = Object.assign({ agent: this }, this[kOptions]) |
| return this[kOptions] && this[kOptions].connections === 1 |
| ? new MockClient(origin, mockOptions) |
| : new MockPool(origin, mockOptions) |
| } |
| |
| [kMockAgentGet] (origin) { |
| // First check if we can immediately find it |
| const ref = this[kClients].get(origin) |
| if (ref) { |
| return ref.deref() |
| } |
| |
| // If the origin is not a string create a dummy parent pool and return to user |
| if (typeof origin !== 'string') { |
| const dispatcher = this[kFactory]('http://localhost:9999') |
| this[kMockAgentSet](origin, dispatcher) |
| return dispatcher |
| } |
| |
| // If we match, create a pool and assign the same dispatches |
| for (const [keyMatcher, nonExplicitRef] of Array.from(this[kClients])) { |
| const nonExplicitDispatcher = nonExplicitRef.deref() |
| if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { |
| const dispatcher = this[kFactory](origin) |
| this[kMockAgentSet](origin, dispatcher) |
| dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches] |
| return dispatcher |
| } |
| } |
| } |
| |
| [kGetNetConnect] () { |
| return this[kNetConnect] |
| } |
| |
| pendingInterceptors () { |
| const mockAgentClients = this[kClients] |
| |
| return Array.from(mockAgentClients.entries()) |
| .flatMap(([origin, scope]) => scope.deref()[kDispatches].map(dispatch => ({ ...dispatch, origin }))) |
| .filter(({ pending }) => pending) |
| } |
| |
| assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { |
| const pending = this.pendingInterceptors() |
| |
| if (pending.length === 0) { |
| return |
| } |
| |
| const pluralizer = new Pluralizer('interceptor', 'interceptors').pluralize(pending.length) |
| |
| throw new UndiciError(` |
| ${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: |
| |
| ${pendingInterceptorsFormatter.format(pending)} |
| `.trim()) |
| } |
| } |
| |
| module.exports = MockAgent |