@stuntman/client 0.1.4 → 0.1.6

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.
@@ -0,0 +1,627 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import type * as Stuntman from '@stuntman/shared';
3
+ import { DEFAULT_RULE_PRIORITY, DEFAULT_RULE_TTL_SECONDS, MAX_RULE_TTL_SECONDS, MIN_RULE_TTL_SECONDS } from '@stuntman/shared';
4
+
5
+ type KeyValueMatcher = string | RegExp | { key: string; value?: string | RegExp };
6
+ type ObjectValueMatcher = string | RegExp | number | boolean | null;
7
+ type ObjectKeyValueMatcher = { key: string; value?: ObjectValueMatcher };
8
+ type GQLRequestMatcher = {
9
+ operationName?: string | RegExp;
10
+ variables?: ObjectKeyValueMatcher[];
11
+ query?: string | RegExp;
12
+ type?: 'query' | 'mutation';
13
+ methodName?: string | RegExp;
14
+ };
15
+
16
+ type MatchBuilderVariables = {
17
+ filter?: string | RegExp;
18
+ hostname?: string | RegExp;
19
+ pathname?: string | RegExp;
20
+ port?: number | string | RegExp;
21
+ searchParams?: KeyValueMatcher[];
22
+ headers?: KeyValueMatcher[];
23
+ bodyText?: string | RegExp | null;
24
+ bodyJson?: ObjectKeyValueMatcher[];
25
+ bodyGql?: GQLRequestMatcher;
26
+ };
27
+
28
+ // eslint-disable-next-line no-var
29
+ declare var matchBuilderVariables: MatchBuilderVariables;
30
+
31
+ // TODO add fluent match on multipart from data
32
+
33
+ class RuleBuilderBaseBase {
34
+ protected rule: Stuntman.SerializableRule;
35
+ protected _matchBuilderVariables: MatchBuilderVariables;
36
+
37
+ constructor(rule?: Stuntman.SerializableRule, _matchBuilderVariables?: MatchBuilderVariables) {
38
+ this._matchBuilderVariables = _matchBuilderVariables || {};
39
+ this.rule = rule || {
40
+ id: uuidv4(),
41
+ ttlSeconds: DEFAULT_RULE_TTL_SECONDS,
42
+ priority: DEFAULT_RULE_PRIORITY,
43
+ actions: {
44
+ mockResponse: { status: 200 },
45
+ },
46
+ 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 = arrayIndexerRegex.test(rawKey);
64
+ const arrayIndex =
65
+ (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}` };
86
+ }
87
+ if (!shouldBeArray) {
88
+ const result = value instanceof RegExp ? value.test(actualValue) : value === actualValue;
89
+ return { result, description: `${currentPath}` };
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
+ return { result: true, description: `no variables to match` };
297
+ }
298
+ for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyGql.variables)
299
+ ? matchBuilderVariables.bodyGql.variables
300
+ : [matchBuilderVariables.bodyGql.variables]) {
301
+ const matchObjectResult = matchObject(req.gqlBody.variables, jsonMatcher.key, jsonMatcher.value);
302
+ if (!matchObjectResult.result) {
303
+ return {
304
+ result: false,
305
+ description: `GQL variable ${jsonMatcher.key} != "${jsonMatcher.value}". Detail: ${matchObjectResult.description}`,
306
+ };
307
+ }
308
+ }
309
+ }
310
+ return { result: true, description: 'match' };
311
+ },
312
+ localVariables: { matchBuilderVariables: this._matchBuilderVariables },
313
+ },
314
+ };
315
+ }
316
+ }
317
+
318
+ class RuleBuilderBase extends RuleBuilderBaseBase {
319
+ limitedUse(hitCount: number) {
320
+ if (this.rule.removeAfterUse) {
321
+ throw new Error(`limit already set at ${this.rule.removeAfterUse}`);
322
+ }
323
+ if (Number.isNaN(hitCount) || !Number.isFinite(hitCount) || !Number.isInteger(hitCount) || hitCount <= 0) {
324
+ throw new Error('Invalid hitCount');
325
+ }
326
+ this.rule.removeAfterUse = hitCount;
327
+ return this;
328
+ }
329
+
330
+ singleUse() {
331
+ return this.limitedUse(1);
332
+ }
333
+
334
+ storeTraffic() {
335
+ this.rule.storeTraffic = true;
336
+ return this;
337
+ }
338
+
339
+ disabled() {
340
+ this.rule.isEnabled = false;
341
+ }
342
+ }
343
+
344
+ class RuleBuilder extends RuleBuilderBase {
345
+ raisePriority(by?: number) {
346
+ if (this.rule.priority !== DEFAULT_RULE_PRIORITY) {
347
+ throw new Error('you should not alter rule priority more than once');
348
+ }
349
+ const subtract = by ?? 1;
350
+ if (subtract >= DEFAULT_RULE_PRIORITY) {
351
+ throw new Error(`Unable to raise priority over the default ${DEFAULT_RULE_PRIORITY}`);
352
+ }
353
+ this.rule.priority = DEFAULT_RULE_PRIORITY - subtract;
354
+ return this;
355
+ }
356
+
357
+ decreasePriority(by?: number) {
358
+ if (this.rule.priority !== DEFAULT_RULE_PRIORITY) {
359
+ throw new Error('you should not alter rule priority more than once');
360
+ }
361
+ const add = by ?? 1;
362
+ this.rule.priority = DEFAULT_RULE_PRIORITY + add;
363
+ return this;
364
+ }
365
+
366
+ customTtl(ttlSeconds: number) {
367
+ if (Number.isNaN(ttlSeconds) || !Number.isInteger(ttlSeconds) || !Number.isFinite(ttlSeconds) || ttlSeconds < 0) {
368
+ throw new Error('Invalid ttl');
369
+ }
370
+ if (ttlSeconds < MIN_RULE_TTL_SECONDS || ttlSeconds > MAX_RULE_TTL_SECONDS) {
371
+ throw new Error(
372
+ `ttl of ${ttlSeconds} seconds is outside range min: ${MIN_RULE_TTL_SECONDS}, max:${MAX_RULE_TTL_SECONDS}`
373
+ );
374
+ }
375
+ this.rule.ttlSeconds = ttlSeconds;
376
+ return this;
377
+ }
378
+
379
+ customId(id: string) {
380
+ this.rule.id = id;
381
+ return this;
382
+ }
383
+
384
+ onRequestTo(filter: string | RegExp): RuleBuilderInitialized {
385
+ this._matchBuilderVariables.filter = filter;
386
+ return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
387
+ }
388
+
389
+ onRequestToHostname(hostname: string | RegExp): RuleBuilderInitialized {
390
+ this._matchBuilderVariables.hostname = hostname;
391
+ return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
392
+ }
393
+
394
+ onRequestToPathname(pathname: string | RegExp): RuleBuilderInitialized {
395
+ this._matchBuilderVariables.pathname = pathname;
396
+ return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
397
+ }
398
+
399
+ onRequestToPort(port: string | number | RegExp): RuleBuilderInitialized {
400
+ this._matchBuilderVariables.port = port;
401
+ return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
402
+ }
403
+
404
+ onAnyRequest(): RuleBuilderInitialized {
405
+ return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
406
+ }
407
+ }
408
+
409
+ class RuleBuilderInitialized extends RuleBuilderBase {
410
+ withHostname(hostname: string | RegExp) {
411
+ if (this._matchBuilderVariables.hostname) {
412
+ throw new Error('hostname already set');
413
+ }
414
+ this._matchBuilderVariables.hostname = hostname;
415
+ return this;
416
+ }
417
+
418
+ withPathname(pathname: string | RegExp) {
419
+ if (this._matchBuilderVariables.pathname) {
420
+ throw new Error('pathname already set');
421
+ }
422
+ this._matchBuilderVariables.pathname = pathname;
423
+ return this;
424
+ }
425
+
426
+ withPort(port: number | string | RegExp) {
427
+ if (this._matchBuilderVariables.port) {
428
+ throw new Error('port already set');
429
+ }
430
+ this._matchBuilderVariables.port = port;
431
+ return this;
432
+ }
433
+
434
+ withSearchParam(key: string | RegExp): RuleBuilderInitialized;
435
+ withSearchParam(key: string, value?: string | RegExp): RuleBuilderInitialized;
436
+ withSearchParam(key: string | RegExp, value?: string | RegExp): RuleBuilderInitialized {
437
+ if (!this._matchBuilderVariables.searchParams) {
438
+ this._matchBuilderVariables.searchParams = [];
439
+ }
440
+ if (!key) {
441
+ throw new Error('key cannot be empty');
442
+ }
443
+ if (!value) {
444
+ this._matchBuilderVariables.searchParams.push(key);
445
+ return this;
446
+ }
447
+ if (key instanceof RegExp) {
448
+ throw new Error('Unsupported regex param key with value');
449
+ }
450
+ this._matchBuilderVariables.searchParams.push({ key, value });
451
+ return this;
452
+ }
453
+
454
+ withSearchParams(params: KeyValueMatcher[]): RuleBuilderInitialized {
455
+ if (!this._matchBuilderVariables.searchParams) {
456
+ this._matchBuilderVariables.searchParams = [];
457
+ }
458
+ for (const param of params) {
459
+ if (typeof param === 'string' || param instanceof RegExp) {
460
+ this.withSearchParam(param);
461
+ } else {
462
+ this.withSearchParam(param.key, param.value);
463
+ }
464
+ }
465
+ return this;
466
+ }
467
+
468
+ withHeader(key: string | RegExp): RuleBuilderInitialized;
469
+ withHeader(key: string, value?: string | RegExp): RuleBuilderInitialized;
470
+ withHeader(key: string | RegExp, value?: string | RegExp): RuleBuilderInitialized {
471
+ if (!this._matchBuilderVariables.headers) {
472
+ this._matchBuilderVariables.headers = [];
473
+ }
474
+ if (!key) {
475
+ throw new Error('key cannot be empty');
476
+ }
477
+ if (!value) {
478
+ this._matchBuilderVariables.headers.push(key);
479
+ return this;
480
+ }
481
+ if (key instanceof RegExp) {
482
+ throw new Error('Unsupported regex param key with value');
483
+ }
484
+ this._matchBuilderVariables.headers.push({ key, value });
485
+ return this;
486
+ }
487
+
488
+ withHeaders(...headers: KeyValueMatcher[]): RuleBuilderInitialized {
489
+ if (!this._matchBuilderVariables.headers) {
490
+ this._matchBuilderVariables.headers = [];
491
+ }
492
+ for (const header of headers) {
493
+ if (typeof header === 'string' || header instanceof RegExp) {
494
+ this.withHeader(header);
495
+ } else {
496
+ this.withHeader(header.key, header.value);
497
+ }
498
+ }
499
+ return this;
500
+ }
501
+
502
+ withBodyText(includes: string): RuleBuilderInitialized;
503
+ withBodyText(matches: RegExp): RuleBuilderInitialized;
504
+ withBodyText(includesOrMatches: string | RegExp): RuleBuilderInitialized {
505
+ if (this._matchBuilderVariables.bodyText) {
506
+ throw new Error('bodyText already set');
507
+ }
508
+ if (this._matchBuilderVariables.bodyText === null) {
509
+ throw new Error('cannot use both withBodyText and withoutBody');
510
+ }
511
+ this._matchBuilderVariables.bodyText = includesOrMatches;
512
+ return this;
513
+ }
514
+
515
+ withoutBody(): RuleBuilderInitialized {
516
+ if (this._matchBuilderVariables.bodyText) {
517
+ throw new Error('cannot use both withBodyText and withoutBody');
518
+ }
519
+ this._matchBuilderVariables.bodyText = null;
520
+ return this;
521
+ }
522
+
523
+ withBodyJson(hasKey: string): RuleBuilderInitialized;
524
+ withBodyJson(hasKey: string, withValue: ObjectValueMatcher): RuleBuilderInitialized;
525
+ withBodyJson(matches: ObjectKeyValueMatcher): RuleBuilderInitialized;
526
+ withBodyJson(keyOrMatcher: string | ObjectKeyValueMatcher, withValue?: ObjectValueMatcher): RuleBuilderInitialized {
527
+ const keyRegex = /^(?:(?:[a-z0-9_-]+)|(?:\[[0-9]*\]))(?:\.(?:(?:[a-z0-9_-]+)|(?:\[[0-9]*\])))*$/i;
528
+ if (!this._matchBuilderVariables.bodyJson) {
529
+ this._matchBuilderVariables.bodyJson = [];
530
+ }
531
+ if (typeof keyOrMatcher === 'string') {
532
+ if (!keyRegex.test(keyOrMatcher)) {
533
+ throw new Error(`invalid key "${keyOrMatcher}"`);
534
+ }
535
+ this._matchBuilderVariables.bodyJson.push({ key: keyOrMatcher, value: withValue });
536
+ return this;
537
+ }
538
+ if (withValue !== undefined) {
539
+ throw new Error('invalid usage');
540
+ }
541
+ if (!keyRegex.test(keyOrMatcher.key)) {
542
+ throw new Error(`invalid key "${keyOrMatcher}"`);
543
+ }
544
+ this._matchBuilderVariables.bodyJson.push(keyOrMatcher);
545
+ return this;
546
+ }
547
+
548
+ withBodyGql(gqlMatcher: GQLRequestMatcher): RuleBuilderInitialized {
549
+ this._matchBuilderVariables.bodyGql = gqlMatcher;
550
+ return this;
551
+ }
552
+
553
+ proxyPass(): Stuntman.SerializableRule {
554
+ this.rule.actions = { proxyPass: true };
555
+ return this.rule;
556
+ }
557
+
558
+ mockResponse(staticResponse: Stuntman.Response): Stuntman.SerializableRule;
559
+ mockResponse(generationFunction: Stuntman.RemotableFunction<Stuntman.ResponseGenerationFn>): Stuntman.SerializableRule;
560
+ mockResponse(localFn: Stuntman.ResponseGenerationFn, localVariables?: Stuntman.LocalVariables): Stuntman.SerializableRule;
561
+ mockResponse(
562
+ response: Stuntman.Response | Stuntman.RemotableFunction<Stuntman.ResponseGenerationFn> | Stuntman.ResponseGenerationFn,
563
+ localVariables?: Stuntman.LocalVariables
564
+ ): Stuntman.SerializableRule {
565
+ if (typeof response === 'function') {
566
+ this.rule.actions = { mockResponse: { localFn: response, localVariables: localVariables ?? {} } };
567
+ return this.rule;
568
+ }
569
+ if (localVariables) {
570
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
571
+ }
572
+ this.rule.actions = { mockResponse: response };
573
+ return this.rule;
574
+ }
575
+
576
+ modifyRequest(
577
+ modifyFunction: Stuntman.RequestManipulationFn | Stuntman.RemotableFunction<Stuntman.RequestManipulationFn>,
578
+ localVariables?: Stuntman.LocalVariables
579
+ ): RuleBuilderRequestInitialized {
580
+ if (typeof modifyFunction === 'function') {
581
+ this.rule.actions = { modifyRequest: { localFn: modifyFunction, localVariables: localVariables ?? {} } };
582
+ return new RuleBuilderRequestInitialized(this.rule, this._matchBuilderVariables);
583
+ }
584
+ if (localVariables) {
585
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
586
+ }
587
+ this.rule.actions = { modifyRequest: modifyFunction };
588
+ return new RuleBuilderRequestInitialized(this.rule, this._matchBuilderVariables);
589
+ }
590
+
591
+ modifyResponse(
592
+ modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>,
593
+ localVariables?: Stuntman.LocalVariables
594
+ ): Stuntman.SerializableRule {
595
+ if (typeof modifyFunction === 'function') {
596
+ this.rule.actions = { modifyResponse: { localFn: modifyFunction, localVariables: localVariables ?? {} } };
597
+ return this.rule;
598
+ }
599
+ if (localVariables) {
600
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
601
+ }
602
+ this.rule.actions = { modifyResponse: modifyFunction };
603
+ return this.rule;
604
+ }
605
+ }
606
+
607
+ class RuleBuilderRequestInitialized extends RuleBuilderBase {
608
+ modifyResponse(
609
+ modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>,
610
+ localVariables?: Stuntman.LocalVariables
611
+ ): Stuntman.SerializableRule {
612
+ if (!this.rule.actions) {
613
+ throw new Error('rule.actions not defined - builder implementation error');
614
+ }
615
+ if (typeof modifyFunction === 'function') {
616
+ this.rule.actions = { modifyResponse: { localFn: modifyFunction, localVariables: localVariables ?? {} } };
617
+ return this.rule;
618
+ }
619
+ if (localVariables) {
620
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
621
+ }
622
+ this.rule.actions.modifyResponse = modifyFunction;
623
+ return this.rule;
624
+ }
625
+ }
626
+
627
+ export const ruleBuilder = () => new RuleBuilder();