@stuntman/client 0.1.11 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,10 +25,264 @@ type MatchBuilderVariables = {
25
25
  bodyGql?: GQLRequestMatcher;
26
26
  };
27
27
 
28
- // eslint-disable-next-line no-var
29
- declare var matchBuilderVariables: MatchBuilderVariables;
30
-
31
28
  // TODO add fluent match on multipart from data
29
+ function matchFunction(req: Stuntman.Request): Stuntman.RuleMatchResult {
30
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
31
+ // @ts-ignore
32
+ const localMatchBuilderVariables: MatchBuilderVariables = this?.matchBuilderVariables ?? matchBuilderVariables;
33
+ const ___url = new URL(req.url);
34
+ const ___headers = req.rawHeaders;
35
+ const arrayIndexerRegex = /\[(?<arrayIndex>[0-9]*)\]/i;
36
+ const matchObject = (
37
+ obj: any,
38
+ path: string,
39
+ value?: string | RegExp | number | boolean | null,
40
+ parentPath?: string
41
+ ): Exclude<Stuntman.RuleMatchResult, boolean> => {
42
+ if (!obj) {
43
+ return { result: false, description: `${parentPath} is falsey` };
44
+ }
45
+ const [rawKey, ...rest] = path.split('.');
46
+ const key = (rawKey ?? '').replace(arrayIndexerRegex, '');
47
+ const shouldBeArray = rawKey ? arrayIndexerRegex.test(rawKey) : false;
48
+ const arrayIndex =
49
+ rawKey && (arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex || '').length > 0
50
+ ? Number(arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex)
51
+ : Number.NaN;
52
+ const actualValue = key ? obj[key] : obj;
53
+ const currentPath = `${parentPath ? `${parentPath}.` : ''}${rawKey}`;
54
+ if (value === undefined && actualValue === undefined) {
55
+ return { result: false, description: `${currentPath}=undefined` };
56
+ }
57
+ if (rest.length === 0) {
58
+ if (
59
+ shouldBeArray &&
60
+ (!Array.isArray(actualValue) || (Number.isInteger(arrayIndex) && actualValue.length <= Number(arrayIndex)))
61
+ ) {
62
+ return { result: false, description: `${currentPath} empty array` };
63
+ }
64
+ if (value === undefined) {
65
+ const result = shouldBeArray
66
+ ? !Number.isInteger(arrayIndex) || actualValue.length >= Number(arrayIndex)
67
+ : actualValue !== undefined;
68
+ return { result, description: `${currentPath} === undefined` };
69
+ }
70
+ if (!shouldBeArray) {
71
+ const result = value instanceof RegExp ? value.test(actualValue) : value === actualValue;
72
+ return { result, description: `${currentPath} === "${actualValue}"` };
73
+ }
74
+ }
75
+ if (shouldBeArray) {
76
+ if (Number.isInteger(arrayIndex)) {
77
+ return matchObject(actualValue[Number(arrayIndex)], rest.join('.'), value, currentPath);
78
+ }
79
+ const hasArrayMatch = (actualValue as Array<any>).some(
80
+ (arrayValue) => matchObject(arrayValue, rest.join('.'), value, currentPath).result
81
+ );
82
+ return { result: hasArrayMatch, description: `array match ${currentPath}` };
83
+ }
84
+ if (typeof actualValue !== 'object') {
85
+ return { result: false, description: `${currentPath} not an object` };
86
+ }
87
+ return matchObject(actualValue, rest.join('.'), value, currentPath);
88
+ };
89
+
90
+ const ___matchesValue = (matcher: number | string | RegExp | undefined, value?: string | number): boolean => {
91
+ if (matcher === undefined) {
92
+ return true;
93
+ }
94
+ if (typeof matcher !== 'string' && !(matcher instanceof RegExp) && typeof matcher !== 'number') {
95
+ throw new Error('invalid matcher');
96
+ }
97
+ if (typeof matcher === 'string' && matcher !== value) {
98
+ return false;
99
+ }
100
+ if (matcher instanceof RegExp && (typeof value !== 'string' || !matcher.test(value))) {
101
+ return false;
102
+ }
103
+ if (typeof matcher === 'number' && (typeof value !== 'number' || matcher !== value)) {
104
+ return false;
105
+ }
106
+ return true;
107
+ };
108
+ if (!___matchesValue(localMatchBuilderVariables.filter, req.url)) {
109
+ return {
110
+ result: false,
111
+ description: `url ${req.url} doesn't match ${localMatchBuilderVariables.filter?.toString()}`,
112
+ };
113
+ }
114
+ if (!___matchesValue(localMatchBuilderVariables.hostname, ___url.hostname)) {
115
+ return {
116
+ result: false,
117
+ description: `hostname ${___url.hostname} doesn't match ${localMatchBuilderVariables.hostname?.toString()}`,
118
+ };
119
+ }
120
+ if (!___matchesValue(localMatchBuilderVariables.pathname, ___url.pathname)) {
121
+ return {
122
+ result: false,
123
+ description: `pathname ${___url.pathname} doesn't match ${localMatchBuilderVariables.pathname?.toString()}`,
124
+ };
125
+ }
126
+ if (localMatchBuilderVariables.port) {
127
+ const port = ___url.port && ___url.port !== '' ? ___url.port : ___url.protocol === 'https:' ? '443' : '80';
128
+ if (
129
+ !___matchesValue(
130
+ localMatchBuilderVariables.port instanceof RegExp
131
+ ? localMatchBuilderVariables.port
132
+ : `${localMatchBuilderVariables.port}`,
133
+ port
134
+ )
135
+ ) {
136
+ return {
137
+ result: false,
138
+ description: `port ${port} doesn't match ${localMatchBuilderVariables.port?.toString()}`,
139
+ };
140
+ }
141
+ }
142
+ if (localMatchBuilderVariables.searchParams) {
143
+ for (const searchParamMatcher of localMatchBuilderVariables.searchParams) {
144
+ if (typeof searchParamMatcher === 'string') {
145
+ const result = ___url.searchParams.has(searchParamMatcher);
146
+ if (!result) {
147
+ return { result, description: `searchParams.has("${searchParamMatcher}")` };
148
+ }
149
+ continue;
150
+ }
151
+ if (searchParamMatcher instanceof RegExp) {
152
+ const result = Array.from(___url.searchParams.keys()).some((key) => searchParamMatcher.test(key));
153
+ if (!result) {
154
+ return { result, description: `searchParams.keys() matches ${searchParamMatcher.toString()}` };
155
+ }
156
+ continue;
157
+ }
158
+ if (!___url.searchParams.has(searchParamMatcher.key)) {
159
+ return { result: false, description: `searchParams.has("${searchParamMatcher.key}")` };
160
+ }
161
+ if (searchParamMatcher.value) {
162
+ const value = ___url.searchParams.get(searchParamMatcher.key);
163
+ if (!___matchesValue(searchParamMatcher.value, value as string)) {
164
+ return {
165
+ result: false,
166
+ description: `searchParams.get("${searchParamMatcher.key}") = "${searchParamMatcher.value}"`,
167
+ };
168
+ }
169
+ }
170
+ }
171
+ }
172
+ if (localMatchBuilderVariables.headers) {
173
+ for (const headerMatcher of localMatchBuilderVariables.headers) {
174
+ if (typeof headerMatcher === 'string') {
175
+ const result = ___headers.has(headerMatcher);
176
+ if (result) {
177
+ continue;
178
+ }
179
+ return { result: false, description: `headers.has("${headerMatcher}")` };
180
+ }
181
+ if (headerMatcher instanceof RegExp) {
182
+ const result = ___headers.toHeaderPairs().some(([key]) => headerMatcher.test(key));
183
+ if (result) {
184
+ continue;
185
+ }
186
+ return { result: false, description: `headers.keys matches ${headerMatcher.toString()}` };
187
+ }
188
+ if (!___headers.has(headerMatcher.key)) {
189
+ return { result: false, description: `headers.has("${headerMatcher.key}")` };
190
+ }
191
+ if (headerMatcher.value) {
192
+ const value = ___headers.get(headerMatcher.key);
193
+ if (!___matchesValue(headerMatcher.value, value)) {
194
+ return {
195
+ result: false,
196
+ description: `headerMatcher.get("${headerMatcher.key}") = "${headerMatcher.value}"`,
197
+ };
198
+ }
199
+ }
200
+ }
201
+ }
202
+ if (localMatchBuilderVariables.bodyText === null && !!req.body) {
203
+ return { result: false, description: `empty body` };
204
+ }
205
+ if (localMatchBuilderVariables.bodyText) {
206
+ if (!req.body) {
207
+ return { result: false, description: `empty body` };
208
+ }
209
+ if (localMatchBuilderVariables.bodyText instanceof RegExp) {
210
+ if (!___matchesValue(localMatchBuilderVariables.bodyText, req.body)) {
211
+ return {
212
+ result: false,
213
+ description: `body text doesn't match ${localMatchBuilderVariables.bodyText.toString()}`,
214
+ };
215
+ }
216
+ } else if (!req.body.includes(localMatchBuilderVariables.bodyText)) {
217
+ return {
218
+ result: false,
219
+ description: `body text doesn't include "${localMatchBuilderVariables.bodyText}"`,
220
+ };
221
+ }
222
+ }
223
+ if (localMatchBuilderVariables.bodyJson) {
224
+ let json: any;
225
+ try {
226
+ json = JSON.parse(req.body);
227
+ } catch (kiss) {
228
+ return { result: false, description: `unparseable json` };
229
+ }
230
+ if (!json) {
231
+ return { result: false, description: `empty json object` };
232
+ }
233
+ for (const jsonMatcher of Array.isArray(localMatchBuilderVariables.bodyJson)
234
+ ? localMatchBuilderVariables.bodyJson
235
+ : [localMatchBuilderVariables.bodyJson]) {
236
+ const matchObjectResult = matchObject(json, jsonMatcher.key, jsonMatcher.value);
237
+ if (!matchObjectResult.result) {
238
+ return { result: false, description: `$.${jsonMatcher.key} != "${jsonMatcher.value}"` };
239
+ }
240
+ }
241
+ }
242
+ if (localMatchBuilderVariables.bodyGql) {
243
+ if (!req.gqlBody) {
244
+ return { result: false, description: `not a gql body` };
245
+ }
246
+ if (!___matchesValue(localMatchBuilderVariables.bodyGql.methodName, req.gqlBody.methodName)) {
247
+ return {
248
+ result: false,
249
+ description: `methodName "${localMatchBuilderVariables.bodyGql.methodName}" !== "${req.gqlBody.methodName}"`,
250
+ };
251
+ }
252
+ if (!___matchesValue(localMatchBuilderVariables.bodyGql.operationName, req.gqlBody.operationName)) {
253
+ return {
254
+ result: false,
255
+ description: `operationName "${localMatchBuilderVariables.bodyGql.operationName}" !== "${req.gqlBody.operationName}"`,
256
+ };
257
+ }
258
+ if (!___matchesValue(localMatchBuilderVariables.bodyGql.query, req.gqlBody.query)) {
259
+ return {
260
+ result: false,
261
+ description: `query "${localMatchBuilderVariables.bodyGql.query}" !== "${req.gqlBody.query}"`,
262
+ };
263
+ }
264
+ if (!___matchesValue(localMatchBuilderVariables.bodyGql.type, req.gqlBody.type)) {
265
+ return {
266
+ result: false,
267
+ description: `type "${localMatchBuilderVariables.bodyGql.type}" !== "${req.gqlBody.type}"`,
268
+ };
269
+ }
270
+ if (localMatchBuilderVariables.bodyGql.variables) {
271
+ for (const jsonMatcher of Array.isArray(localMatchBuilderVariables.bodyGql.variables)
272
+ ? localMatchBuilderVariables.bodyGql.variables
273
+ : [localMatchBuilderVariables.bodyGql.variables]) {
274
+ const matchObjectResult = matchObject(req.gqlBody.variables, jsonMatcher.key, jsonMatcher.value);
275
+ if (!matchObjectResult.result) {
276
+ return {
277
+ result: false,
278
+ description: `GQL variable ${jsonMatcher.key} != "${jsonMatcher.value}". Detail: ${matchObjectResult.description}`,
279
+ };
280
+ }
281
+ }
282
+ }
283
+ }
284
+ return { result: true, description: 'match' };
285
+ }
32
286
 
33
287
  class RuleBuilderBaseBase {
34
288
  protected rule: Stuntman.SerializableRule;
@@ -44,270 +298,7 @@ class RuleBuilderBaseBase {
44
298
  mockResponse: { status: 200 },
45
299
  },
46
300
  matches: {
47
- localFn: (req: Stuntman.Request): Stuntman.RuleMatchResult => {
48
- const ___url = new URL(req.url);
49
- const ___headers = req.rawHeaders;
50
-
51
- const arrayIndexerRegex = /\[(?<arrayIndex>[0-9]*)\]/i;
52
- const matchObject = (
53
- obj: any,
54
- path: string,
55
- value?: string | RegExp | number | boolean | null,
56
- parentPath?: string
57
- ): Exclude<Stuntman.RuleMatchResult, boolean> => {
58
- if (!obj) {
59
- return { result: false, description: `${parentPath} is falsey` };
60
- }
61
- const [rawKey, ...rest] = path.split('.');
62
- const key = (rawKey ?? '').replace(arrayIndexerRegex, '');
63
- const shouldBeArray = rawKey ? arrayIndexerRegex.test(rawKey) : false;
64
- const arrayIndex =
65
- rawKey && (arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex || '').length > 0
66
- ? Number(arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex)
67
- : Number.NaN;
68
- const actualValue = key ? obj[key] : obj;
69
- const currentPath = `${parentPath ? `${parentPath}.` : ''}${rawKey}`;
70
- if (value === undefined && actualValue === undefined) {
71
- return { result: false, description: `${currentPath}=undefined` };
72
- }
73
- if (rest.length === 0) {
74
- if (
75
- shouldBeArray &&
76
- (!Array.isArray(actualValue) ||
77
- (Number.isInteger(arrayIndex) && actualValue.length <= Number(arrayIndex)))
78
- ) {
79
- return { result: false, description: `${currentPath} empty array` };
80
- }
81
- if (value === undefined) {
82
- const result = shouldBeArray
83
- ? !Number.isInteger(arrayIndex) || actualValue.length >= Number(arrayIndex)
84
- : actualValue !== undefined;
85
- return { result, description: `${currentPath} === undefined` };
86
- }
87
- if (!shouldBeArray) {
88
- const result = value instanceof RegExp ? value.test(actualValue) : value === actualValue;
89
- return { result, description: `${currentPath} === "${actualValue}"` };
90
- }
91
- }
92
- if (shouldBeArray) {
93
- if (Number.isInteger(arrayIndex)) {
94
- return matchObject(actualValue[Number(arrayIndex)], rest.join('.'), value, currentPath);
95
- }
96
- const hasArrayMatch = (actualValue as Array<any>).some(
97
- (arrayValue) => matchObject(arrayValue, rest.join('.'), value, currentPath).result
98
- );
99
- return { result: hasArrayMatch, description: `array match ${currentPath}` };
100
- }
101
- if (typeof actualValue !== 'object') {
102
- return { result: false, description: `${currentPath} not an object` };
103
- }
104
- return matchObject(actualValue, rest.join('.'), value, currentPath);
105
- };
106
-
107
- const ___matchesValue = (matcher: number | string | RegExp | undefined, value?: string | number): boolean => {
108
- if (matcher === undefined) {
109
- return true;
110
- }
111
- if (typeof matcher !== 'string' && !(matcher instanceof RegExp) && typeof matcher !== 'number') {
112
- throw new Error('invalid matcher');
113
- }
114
- if (typeof matcher === 'string' && matcher !== value) {
115
- return false;
116
- }
117
- if (matcher instanceof RegExp && (typeof value !== 'string' || !matcher.test(value))) {
118
- return false;
119
- }
120
- if (typeof matcher === 'number' && (typeof value !== 'number' || matcher !== value)) {
121
- return false;
122
- }
123
- return true;
124
- };
125
- if (!___matchesValue(matchBuilderVariables.filter, req.url)) {
126
- return {
127
- result: false,
128
- description: `url ${req.url} doesn't match ${matchBuilderVariables.filter?.toString()}`,
129
- };
130
- }
131
- if (!___matchesValue(matchBuilderVariables.hostname, ___url.hostname)) {
132
- return {
133
- result: false,
134
- description: `hostname ${
135
- ___url.hostname
136
- } doesn't match ${matchBuilderVariables.hostname?.toString()}`,
137
- };
138
- }
139
- if (!___matchesValue(matchBuilderVariables.pathname, ___url.pathname)) {
140
- return {
141
- result: false,
142
- description: `pathname ${
143
- ___url.pathname
144
- } doesn't match ${matchBuilderVariables.pathname?.toString()}`,
145
- };
146
- }
147
- if (matchBuilderVariables.port) {
148
- const port =
149
- ___url.port && ___url.port !== '' ? ___url.port : ___url.protocol === 'https:' ? '443' : '80';
150
- if (
151
- !___matchesValue(
152
- matchBuilderVariables.port instanceof RegExp
153
- ? matchBuilderVariables.port
154
- : `${matchBuilderVariables.port}`,
155
- port
156
- )
157
- ) {
158
- return {
159
- result: false,
160
- description: `port ${port} doesn't match ${matchBuilderVariables.port?.toString()}`,
161
- };
162
- }
163
- }
164
- if (matchBuilderVariables.searchParams) {
165
- for (const searchParamMatcher of matchBuilderVariables.searchParams) {
166
- if (typeof searchParamMatcher === 'string') {
167
- const result = ___url.searchParams.has(searchParamMatcher);
168
- return { result, description: `searchParams.has("${searchParamMatcher}")` };
169
- }
170
- if (searchParamMatcher instanceof RegExp) {
171
- const result = Array.from(___url.searchParams.keys()).some((key) => searchParamMatcher.test(key));
172
- return { result, description: `searchParams.keys() matches ${searchParamMatcher.toString()}` };
173
- }
174
- if (!___url.searchParams.has(searchParamMatcher.key)) {
175
- return { result: false, description: `searchParams.has("${searchParamMatcher.key}")` };
176
- }
177
- if (searchParamMatcher.value) {
178
- const value = ___url.searchParams.get(searchParamMatcher.key);
179
- if (value === null) {
180
- return {
181
- result: false,
182
- description: `searchParams.get("${searchParamMatcher.key}") === null`,
183
- };
184
- }
185
- if (!___matchesValue(searchParamMatcher.value, value)) {
186
- return {
187
- result: false,
188
- description: `searchParams.get("${searchParamMatcher.key}") = "${searchParamMatcher.value}"`,
189
- };
190
- }
191
- }
192
- }
193
- }
194
- if (matchBuilderVariables.headers) {
195
- for (const headerMatcher of matchBuilderVariables.headers) {
196
- if (typeof headerMatcher === 'string') {
197
- const result = ___headers.has(headerMatcher);
198
- if (result) {
199
- continue;
200
- }
201
- return { result: false, description: `headers.has("${headerMatcher}")` };
202
- }
203
- if (headerMatcher instanceof RegExp) {
204
- const result = ___headers.toHeaderPairs().some(([key]) => headerMatcher.test(key));
205
- if (result) {
206
- continue;
207
- }
208
- return { result: false, description: `headers.keys matches ${headerMatcher.toString()}` };
209
- }
210
- if (!___headers.has(headerMatcher.key)) {
211
- return { result: false, description: `headers.has("${headerMatcher.key}")` };
212
- }
213
- if (headerMatcher.value) {
214
- const value = ___headers.get(headerMatcher.key);
215
- if (value === null) {
216
- return { result: false, description: `headers.get("${headerMatcher.key}") === null` };
217
- }
218
- if (!___matchesValue(headerMatcher.value, value)) {
219
- return {
220
- result: false,
221
- description: `headerMatcher.get("${headerMatcher.key}") = "${headerMatcher.value}"`,
222
- };
223
- }
224
- }
225
- }
226
- }
227
- if (matchBuilderVariables.bodyText === null && !!req.body) {
228
- return { result: false, description: `empty body` };
229
- }
230
- if (matchBuilderVariables.bodyText) {
231
- if (!req.body) {
232
- return { result: false, description: `empty body` };
233
- }
234
- if (matchBuilderVariables.bodyText instanceof RegExp) {
235
- if (!___matchesValue(matchBuilderVariables.bodyText, req.body)) {
236
- return {
237
- result: false,
238
- description: `body text doesn't match ${matchBuilderVariables.bodyText.toString()}`,
239
- };
240
- }
241
- } else if (!req.body.includes(matchBuilderVariables.bodyText)) {
242
- return {
243
- result: false,
244
- description: `body text doesn't include "${matchBuilderVariables.bodyText}"`,
245
- };
246
- }
247
- }
248
- if (matchBuilderVariables.bodyJson) {
249
- let json: any;
250
- try {
251
- json = JSON.parse(req.body);
252
- } catch (kiss) {
253
- return { result: false, description: `unparseable json` };
254
- }
255
- if (!json) {
256
- return { result: false, description: `empty json object` };
257
- }
258
- for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyJson)
259
- ? matchBuilderVariables.bodyJson
260
- : [matchBuilderVariables.bodyJson]) {
261
- const matchObjectResult = matchObject(json, jsonMatcher.key, jsonMatcher.value);
262
- if (!matchObjectResult.result) {
263
- return { result: false, description: `$.${jsonMatcher.key} != "${jsonMatcher.value}"` };
264
- }
265
- }
266
- }
267
- if (matchBuilderVariables.bodyGql) {
268
- if (!req.gqlBody) {
269
- return { result: false, description: `not a gql body` };
270
- }
271
- if (!___matchesValue(matchBuilderVariables.bodyGql.methodName, req.gqlBody.methodName)) {
272
- return {
273
- result: false,
274
- description: `methodName "${matchBuilderVariables.bodyGql.methodName}" !== "${req.gqlBody.methodName}"`,
275
- };
276
- }
277
- if (!___matchesValue(matchBuilderVariables.bodyGql.operationName, req.gqlBody.operationName)) {
278
- return {
279
- result: false,
280
- description: `operationName "${matchBuilderVariables.bodyGql.operationName}" !== "${req.gqlBody.operationName}"`,
281
- };
282
- }
283
- if (!___matchesValue(matchBuilderVariables.bodyGql.query, req.gqlBody.query)) {
284
- return {
285
- result: false,
286
- description: `query "${matchBuilderVariables.bodyGql.query}" !== "${req.gqlBody.query}"`,
287
- };
288
- }
289
- if (!___matchesValue(matchBuilderVariables.bodyGql.type, req.gqlBody.type)) {
290
- return {
291
- result: false,
292
- description: `type "${matchBuilderVariables.bodyGql.type}" !== "${req.gqlBody.type}"`,
293
- };
294
- }
295
- if (matchBuilderVariables.bodyGql.variables) {
296
- for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyGql.variables)
297
- ? matchBuilderVariables.bodyGql.variables
298
- : [matchBuilderVariables.bodyGql.variables]) {
299
- const matchObjectResult = matchObject(req.gqlBody.variables, jsonMatcher.key, jsonMatcher.value);
300
- if (!matchObjectResult.result) {
301
- return {
302
- result: false,
303
- description: `GQL variable ${jsonMatcher.key} != "${jsonMatcher.value}". Detail: ${matchObjectResult.description}`,
304
- };
305
- }
306
- }
307
- }
308
- }
309
- return { result: true, description: 'match' };
310
- },
301
+ localFn: matchFunction,
311
302
  localVariables: { matchBuilderVariables: this._matchBuilderVariables },
312
303
  },
313
304
  };
@@ -405,7 +396,27 @@ class RuleBuilder extends RuleBuilderBase {
405
396
  }
406
397
  }
407
398
 
408
- class RuleBuilderInitialized extends RuleBuilderBase {
399
+ class RuleBuilderRequestInitialized extends RuleBuilderBase {
400
+ modifyResponse(
401
+ modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>,
402
+ localVariables?: Stuntman.LocalVariables
403
+ ): Stuntman.SerializableRule {
404
+ if (!this.rule.actions) {
405
+ throw new Error('rule.actions not defined - builder implementation error');
406
+ }
407
+ if (typeof modifyFunction === 'function') {
408
+ this.rule.actions.modifyResponse = { localFn: modifyFunction, localVariables: localVariables ?? {} };
409
+ return this.rule;
410
+ }
411
+ if (localVariables) {
412
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
413
+ }
414
+ this.rule.actions.modifyResponse = modifyFunction;
415
+ return this.rule;
416
+ }
417
+ }
418
+
419
+ class RuleBuilderInitialized extends RuleBuilderRequestInitialized {
409
420
  withHostname(hostname: string | RegExp) {
410
421
  if (this._matchBuilderVariables.hostname) {
411
422
  throw new Error('hostname already set');
@@ -594,39 +605,14 @@ class RuleBuilderInitialized extends RuleBuilderBase {
594
605
  return new RuleBuilderRequestInitialized(this.rule, this._matchBuilderVariables);
595
606
  }
596
607
 
597
- modifyResponse(
598
- modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>,
599
- localVariables?: Stuntman.LocalVariables
600
- ): Stuntman.SerializableRule {
601
- if (typeof modifyFunction === 'function') {
602
- this.rule.actions = { modifyResponse: { localFn: modifyFunction, localVariables: localVariables ?? {} } };
603
- return this.rule;
604
- }
605
- if (localVariables) {
606
- throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
607
- }
608
- this.rule.actions = { modifyResponse: modifyFunction };
609
- return this.rule;
610
- }
611
- }
612
-
613
- class RuleBuilderRequestInitialized extends RuleBuilderBase {
614
- modifyResponse(
608
+ override modifyResponse(
615
609
  modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>,
616
610
  localVariables?: Stuntman.LocalVariables
617
611
  ): Stuntman.SerializableRule {
618
612
  if (!this.rule.actions) {
619
- throw new Error('rule.actions not defined - builder implementation error');
620
- }
621
- if (typeof modifyFunction === 'function') {
622
- this.rule.actions = { modifyResponse: { localFn: modifyFunction, localVariables: localVariables ?? {} } };
623
- return this.rule;
624
- }
625
- if (localVariables) {
626
- throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
613
+ this.rule.actions = { proxyPass: true };
627
614
  }
628
- this.rule.actions.modifyResponse = modifyFunction;
629
- return this.rule;
615
+ return super.modifyResponse(modifyFunction, localVariables);
630
616
  }
631
617
  }
632
618