1'use strict' 2 3const { kClients } = require('../core/symbols') 4const Agent = require('../agent') 5const { 6 kAgent, 7 kMockAgentSet, 8 kMockAgentGet, 9 kDispatches, 10 kIsMockActive, 11 kNetConnect, 12 kGetNetConnect, 13 kOptions, 14 kFactory 15} = require('./mock-symbols') 16const MockClient = require('./mock-client') 17const MockPool = require('./mock-pool') 18const { matchValue, buildMockOptions } = require('./mock-utils') 19const { InvalidArgumentError, UndiciError } = require('../core/errors') 20const Dispatcher = require('../dispatcher') 21const Pluralizer = require('./pluralizer') 22const PendingInterceptorsFormatter = require('./pending-interceptors-formatter') 23 24class FakeWeakRef { 25 constructor (value) { 26 this.value = value 27 } 28 29 deref () { 30 return this.value 31 } 32} 33 34class MockAgent extends Dispatcher { 35 constructor (opts) { 36 super(opts) 37 38 this[kNetConnect] = true 39 this[kIsMockActive] = true 40 41 // Instantiate Agent and encapsulate 42 if ((opts && opts.agent && typeof opts.agent.dispatch !== 'function')) { 43 throw new InvalidArgumentError('Argument opts.agent must implement Agent') 44 } 45 const agent = opts && opts.agent ? opts.agent : new Agent(opts) 46 this[kAgent] = agent 47 48 this[kClients] = agent[kClients] 49 this[kOptions] = buildMockOptions(opts) 50 } 51 52 get (origin) { 53 let dispatcher = this[kMockAgentGet](origin) 54 55 if (!dispatcher) { 56 dispatcher = this[kFactory](origin) 57 this[kMockAgentSet](origin, dispatcher) 58 } 59 return dispatcher 60 } 61 62 dispatch (opts, handler) { 63 // Call MockAgent.get to perform additional setup before dispatching as normal 64 this.get(opts.origin) 65 return this[kAgent].dispatch(opts, handler) 66 } 67 68 async close () { 69 await this[kAgent].close() 70 this[kClients].clear() 71 } 72 73 deactivate () { 74 this[kIsMockActive] = false 75 } 76 77 activate () { 78 this[kIsMockActive] = true 79 } 80 81 enableNetConnect (matcher) { 82 if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) { 83 if (Array.isArray(this[kNetConnect])) { 84 this[kNetConnect].push(matcher) 85 } else { 86 this[kNetConnect] = [matcher] 87 } 88 } else if (typeof matcher === 'undefined') { 89 this[kNetConnect] = true 90 } else { 91 throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.') 92 } 93 } 94 95 disableNetConnect () { 96 this[kNetConnect] = false 97 } 98 99 // This is required to bypass issues caused by using global symbols - see: 100 // https://github.com/nodejs/undici/issues/1447 101 get isMockActive () { 102 return this[kIsMockActive] 103 } 104 105 [kMockAgentSet] (origin, dispatcher) { 106 this[kClients].set(origin, new FakeWeakRef(dispatcher)) 107 } 108 109 [kFactory] (origin) { 110 const mockOptions = Object.assign({ agent: this }, this[kOptions]) 111 return this[kOptions] && this[kOptions].connections === 1 112 ? new MockClient(origin, mockOptions) 113 : new MockPool(origin, mockOptions) 114 } 115 116 [kMockAgentGet] (origin) { 117 // First check if we can immediately find it 118 const ref = this[kClients].get(origin) 119 if (ref) { 120 return ref.deref() 121 } 122 123 // If the origin is not a string create a dummy parent pool and return to user 124 if (typeof origin !== 'string') { 125 const dispatcher = this[kFactory]('http://localhost:9999') 126 this[kMockAgentSet](origin, dispatcher) 127 return dispatcher 128 } 129 130 // If we match, create a pool and assign the same dispatches 131 for (const [keyMatcher, nonExplicitRef] of Array.from(this[kClients])) { 132 const nonExplicitDispatcher = nonExplicitRef.deref() 133 if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { 134 const dispatcher = this[kFactory](origin) 135 this[kMockAgentSet](origin, dispatcher) 136 dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches] 137 return dispatcher 138 } 139 } 140 } 141 142 [kGetNetConnect] () { 143 return this[kNetConnect] 144 } 145 146 pendingInterceptors () { 147 const mockAgentClients = this[kClients] 148 149 return Array.from(mockAgentClients.entries()) 150 .flatMap(([origin, scope]) => scope.deref()[kDispatches].map(dispatch => ({ ...dispatch, origin }))) 151 .filter(({ pending }) => pending) 152 } 153 154 assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { 155 const pending = this.pendingInterceptors() 156 157 if (pending.length === 0) { 158 return 159 } 160 161 const pluralizer = new Pluralizer('interceptor', 'interceptors').pluralize(pending.length) 162 163 throw new UndiciError(` 164${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: 165 166${pendingInterceptorsFormatter.format(pending)} 167`.trim()) 168 } 169} 170 171module.exports = MockAgent 172