mock-agent.js 4.4 KB

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