source: node_modules/undici/lib/mock/mock-interceptor.js@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 6.5 KB
Line 
1'use strict'
2
3const { getResponseData, buildKey, addMockDispatch } = require('./mock-utils')
4const {
5 kDispatches,
6 kDispatchKey,
7 kDefaultHeaders,
8 kDefaultTrailers,
9 kContentLength,
10 kMockDispatch
11} = require('./mock-symbols')
12const { InvalidArgumentError } = require('../core/errors')
13const { buildURL } = require('../core/util')
14
15/**
16 * Defines the scope API for an interceptor reply
17 */
18class MockScope {
19 constructor (mockDispatch) {
20 this[kMockDispatch] = mockDispatch
21 }
22
23 /**
24 * Delay a reply by a set amount in ms.
25 */
26 delay (waitInMs) {
27 if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) {
28 throw new InvalidArgumentError('waitInMs must be a valid integer > 0')
29 }
30
31 this[kMockDispatch].delay = waitInMs
32 return this
33 }
34
35 /**
36 * For a defined reply, never mark as consumed.
37 */
38 persist () {
39 this[kMockDispatch].persist = true
40 return this
41 }
42
43 /**
44 * Allow one to define a reply for a set amount of matching requests.
45 */
46 times (repeatTimes) {
47 if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) {
48 throw new InvalidArgumentError('repeatTimes must be a valid integer > 0')
49 }
50
51 this[kMockDispatch].times = repeatTimes
52 return this
53 }
54}
55
56/**
57 * Defines an interceptor for a Mock
58 */
59class MockInterceptor {
60 constructor (opts, mockDispatches) {
61 if (typeof opts !== 'object') {
62 throw new InvalidArgumentError('opts must be an object')
63 }
64 if (typeof opts.path === 'undefined') {
65 throw new InvalidArgumentError('opts.path must be defined')
66 }
67 if (typeof opts.method === 'undefined') {
68 opts.method = 'GET'
69 }
70 // See https://github.com/nodejs/undici/issues/1245
71 // As per RFC 3986, clients are not supposed to send URI
72 // fragments to servers when they retrieve a document,
73 if (typeof opts.path === 'string') {
74 if (opts.query) {
75 opts.path = buildURL(opts.path, opts.query)
76 } else {
77 // Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811
78 const parsedURL = new URL(opts.path, 'data://')
79 opts.path = parsedURL.pathname + parsedURL.search
80 }
81 }
82 if (typeof opts.method === 'string') {
83 opts.method = opts.method.toUpperCase()
84 }
85
86 this[kDispatchKey] = buildKey(opts)
87 this[kDispatches] = mockDispatches
88 this[kDefaultHeaders] = {}
89 this[kDefaultTrailers] = {}
90 this[kContentLength] = false
91 }
92
93 createMockScopeDispatchData (statusCode, data, responseOptions = {}) {
94 const responseData = getResponseData(data)
95 const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {}
96 const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers }
97 const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers }
98
99 return { statusCode, data, headers, trailers }
100 }
101
102 validateReplyParameters (statusCode, data, responseOptions) {
103 if (typeof statusCode === 'undefined') {
104 throw new InvalidArgumentError('statusCode must be defined')
105 }
106 if (typeof data === 'undefined') {
107 throw new InvalidArgumentError('data must be defined')
108 }
109 if (typeof responseOptions !== 'object') {
110 throw new InvalidArgumentError('responseOptions must be an object')
111 }
112 }
113
114 /**
115 * Mock an undici request with a defined reply.
116 */
117 reply (replyData) {
118 // Values of reply aren't available right now as they
119 // can only be available when the reply callback is invoked.
120 if (typeof replyData === 'function') {
121 // We'll first wrap the provided callback in another function,
122 // this function will properly resolve the data from the callback
123 // when invoked.
124 const wrappedDefaultsCallback = (opts) => {
125 // Our reply options callback contains the parameter for statusCode, data and options.
126 const resolvedData = replyData(opts)
127
128 // Check if it is in the right format
129 if (typeof resolvedData !== 'object') {
130 throw new InvalidArgumentError('reply options callback must return an object')
131 }
132
133 const { statusCode, data = '', responseOptions = {} } = resolvedData
134 this.validateReplyParameters(statusCode, data, responseOptions)
135 // Since the values can be obtained immediately we return them
136 // from this higher order function that will be resolved later.
137 return {
138 ...this.createMockScopeDispatchData(statusCode, data, responseOptions)
139 }
140 }
141
142 // Add usual dispatch data, but this time set the data parameter to function that will eventually provide data.
143 const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback)
144 return new MockScope(newMockDispatch)
145 }
146
147 // We can have either one or three parameters, if we get here,
148 // we should have 1-3 parameters. So we spread the arguments of
149 // this function to obtain the parameters, since replyData will always
150 // just be the statusCode.
151 const [statusCode, data = '', responseOptions = {}] = [...arguments]
152 this.validateReplyParameters(statusCode, data, responseOptions)
153
154 // Send in-already provided data like usual
155 const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions)
156 const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData)
157 return new MockScope(newMockDispatch)
158 }
159
160 /**
161 * Mock an undici request with a defined error.
162 */
163 replyWithError (error) {
164 if (typeof error === 'undefined') {
165 throw new InvalidArgumentError('error must be defined')
166 }
167
168 const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error })
169 return new MockScope(newMockDispatch)
170 }
171
172 /**
173 * Set default reply headers on the interceptor for subsequent replies
174 */
175 defaultReplyHeaders (headers) {
176 if (typeof headers === 'undefined') {
177 throw new InvalidArgumentError('headers must be defined')
178 }
179
180 this[kDefaultHeaders] = headers
181 return this
182 }
183
184 /**
185 * Set default reply trailers on the interceptor for subsequent replies
186 */
187 defaultReplyTrailers (trailers) {
188 if (typeof trailers === 'undefined') {
189 throw new InvalidArgumentError('trailers must be defined')
190 }
191
192 this[kDefaultTrailers] = trailers
193 return this
194 }
195
196 /**
197 * Set reply content length header for replies on the interceptor
198 */
199 replyContentLength () {
200 this[kContentLength] = true
201 return this
202 }
203}
204
205module.exports.MockInterceptor = MockInterceptor
206module.exports.MockScope = MockScope
Note: See TracBrowser for help on using the repository browser.