[d24f17c] | 1 | 'use strict'
|
---|
| 2 |
|
---|
| 3 | const { kClients } = require('../core/symbols')
|
---|
| 4 | const Agent = require('../agent')
|
---|
| 5 | const {
|
---|
| 6 | kAgent,
|
---|
| 7 | kMockAgentSet,
|
---|
| 8 | kMockAgentGet,
|
---|
| 9 | kDispatches,
|
---|
| 10 | kIsMockActive,
|
---|
| 11 | kNetConnect,
|
---|
| 12 | kGetNetConnect,
|
---|
| 13 | kOptions,
|
---|
| 14 | kFactory
|
---|
| 15 | } = require('./mock-symbols')
|
---|
| 16 | const MockClient = require('./mock-client')
|
---|
| 17 | const MockPool = require('./mock-pool')
|
---|
| 18 | const { matchValue, buildMockOptions } = require('./mock-utils')
|
---|
| 19 | const { InvalidArgumentError, UndiciError } = require('../core/errors')
|
---|
| 20 | const Dispatcher = require('../dispatcher')
|
---|
| 21 | const Pluralizer = require('./pluralizer')
|
---|
| 22 | const PendingInterceptorsFormatter = require('./pending-interceptors-formatter')
|
---|
| 23 |
|
---|
| 24 | class FakeWeakRef {
|
---|
| 25 | constructor (value) {
|
---|
| 26 | this.value = value
|
---|
| 27 | }
|
---|
| 28 |
|
---|
| 29 | deref () {
|
---|
| 30 | return this.value
|
---|
| 31 | }
|
---|
| 32 | }
|
---|
| 33 |
|
---|
| 34 | class 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 |
|
---|
| 171 | module.exports = MockAgent
|
---|