@stuntman/client 0.1.1 → 0.1.3

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.
package/README.md CHANGED
@@ -29,3 +29,5 @@ const rule = ruleBuilder()
29
29
 
30
30
  client.addRule(rule).then((x) => console.log(x));
31
31
  ```
32
+
33
+ Check [example app](https://github.com/andrzej-woof/stuntman/tree/master/apps/example#readme) for more samples
@@ -4,6 +4,7 @@ type ClientOptions = {
4
4
  host?: string;
5
5
  port?: number;
6
6
  timeout?: number;
7
+ apiKey?: string;
7
8
  };
8
9
  export declare class Client {
9
10
  private options;
package/dist/apiClient.js CHANGED
@@ -107,7 +107,14 @@ class Client {
107
107
  controller.abort();
108
108
  }, this.options.timeout);
109
109
  try {
110
- const response = await fetch(url, { ...init, signal: (_a = init === null || init === void 0 ? void 0 : init.signal) !== null && _a !== void 0 ? _a : controller.signal });
110
+ const response = await fetch(url, {
111
+ ...init,
112
+ headers: {
113
+ ...(this.options.apiKey && { 'x-api-key': this.options.apiKey }),
114
+ ...init === null || init === void 0 ? void 0 : init.headers,
115
+ },
116
+ signal: (_a = init === null || init === void 0 ? void 0 : init.signal) !== null && _a !== void 0 ? _a : controller.signal,
117
+ });
111
118
  if (!response.ok) {
112
119
  const text = await response.text();
113
120
  let json;
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { Client as StuntmanClient } from './apiClient';
2
+ export { ruleBuilder } from './ruleBuilder';
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StuntmanClient = void 0;
3
+ exports.ruleBuilder = exports.StuntmanClient = void 0;
4
4
  var apiClient_1 = require("./apiClient");
5
5
  Object.defineProperty(exports, "StuntmanClient", { enumerable: true, get: function () { return apiClient_1.Client; } });
6
+ var ruleBuilder_1 = require("./ruleBuilder");
7
+ Object.defineProperty(exports, "ruleBuilder", { enumerable: true, get: function () { return ruleBuilder_1.ruleBuilder; } });
@@ -45,6 +45,7 @@ declare class RuleBuilder extends RuleBuilderBase {
45
45
  onRequestTo(filter: string | RegExp): RuleBuilderInitialized;
46
46
  onRequestToHostname(hostname: string | RegExp): RuleBuilderInitialized;
47
47
  onRequestToPathname(pathname: string | RegExp): RuleBuilderInitialized;
48
+ onRequestToPort(port: string | number | RegExp): RuleBuilderInitialized;
48
49
  onAnyRequest(): RuleBuilderInitialized;
49
50
  }
50
51
  declare class RuleBuilderInitialized extends RuleBuilderBase {
@@ -56,7 +57,7 @@ declare class RuleBuilderInitialized extends RuleBuilderBase {
56
57
  withSearchParams(params: KeyValueMatcher[]): RuleBuilderInitialized;
57
58
  withHeader(key: string | RegExp): RuleBuilderInitialized;
58
59
  withHeader(key: string, value?: string | RegExp): RuleBuilderInitialized;
59
- withHeaders(headers: KeyValueMatcher[]): RuleBuilderInitialized;
60
+ withHeaders(...headers: KeyValueMatcher[]): RuleBuilderInitialized;
60
61
  withBodyText(includes: string): RuleBuilderInitialized;
61
62
  withBodyText(matches: RegExp): RuleBuilderInitialized;
62
63
  withoutBody(): RuleBuilderInitialized;
@@ -67,11 +68,12 @@ declare class RuleBuilderInitialized extends RuleBuilderBase {
67
68
  proxyPass(): Stuntman.SerializableRule;
68
69
  mockResponse(staticResponse: Stuntman.Response): Stuntman.SerializableRule;
69
70
  mockResponse(generationFunction: Stuntman.RemotableFunction<Stuntman.ResponseGenerationFn>): Stuntman.SerializableRule;
70
- modifyRequest(modifyFunction: Stuntman.RemotableFunction<Stuntman.RequestManipulationFn>): RuleBuilderRequestInitialized;
71
- modifyResponse(modifyFunction: Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>): Stuntman.SerializableRule;
71
+ mockResponse(localFn: Stuntman.ResponseGenerationFn, localVariables?: Stuntman.LocalVariables): Stuntman.SerializableRule;
72
+ modifyRequest(modifyFunction: Stuntman.RequestManipulationFn | Stuntman.RemotableFunction<Stuntman.RequestManipulationFn>, localVariables?: Stuntman.LocalVariables): RuleBuilderRequestInitialized;
73
+ modifyResponse(modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>, localVariables?: Stuntman.LocalVariables): Stuntman.SerializableRule;
72
74
  }
73
75
  declare class RuleBuilderRequestInitialized extends RuleBuilderBase {
74
- modifyResponse(modifyFunction: Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>): Stuntman.SerializableRule;
76
+ modifyResponse(modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>, localVariables?: Stuntman.LocalVariables): Stuntman.SerializableRule;
75
77
  }
76
78
  export declare const ruleBuilder: () => RuleBuilder;
77
79
  export {};
@@ -13,43 +13,54 @@ class RuleBuilderBaseBase {
13
13
  priority: shared_1.DEFAULT_RULE_PRIORITY,
14
14
  matches: {
15
15
  localFn: (req) => {
16
+ var _a, _b, _c, _d;
16
17
  const ___url = new URL(req.url);
17
18
  const ___headers = req.rawHeaders;
18
19
  const arrayIndexerRegex = /\[(?<arrayIndex>[0-9]*)\]/i;
19
- const matchObject = (obj, path, value) => {
20
- var _a, _b;
20
+ const matchObject = (obj, path, value, parentPath) => {
21
+ var _a, _b, _c, _d;
21
22
  if (!obj) {
22
- return false;
23
+ return { result: false, description: `${parentPath} is falsey` };
23
24
  }
24
25
  const [rawKey, ...rest] = path.split('.');
25
26
  const key = rawKey.replace(arrayIndexerRegex, '');
26
27
  const shouldBeArray = arrayIndexerRegex.test(rawKey);
27
- const arrayIndex = Number((_b = (_a = arrayIndexerRegex.exec(rawKey)) === null || _a === void 0 ? void 0 : _a.groups) === null || _b === void 0 ? void 0 : _b.arrayIndex);
28
+ const arrayIndex = (((_b = (_a = arrayIndexerRegex.exec(rawKey)) === null || _a === void 0 ? void 0 : _a.groups) === null || _b === void 0 ? void 0 : _b.arrayIndex) || '').length > 0
29
+ ? Number((_d = (_c = arrayIndexerRegex.exec(rawKey)) === null || _c === void 0 ? void 0 : _c.groups) === null || _d === void 0 ? void 0 : _d.arrayIndex)
30
+ : Number.NaN;
28
31
  const actualValue = key ? obj[key] : obj;
32
+ const currentPath = `${parentPath ? `${parentPath}.` : ''}${rawKey}`;
29
33
  if (value === undefined && actualValue === undefined) {
30
- return false;
34
+ return { result: false, description: `${currentPath}=undefined` };
31
35
  }
32
36
  if (rest.length === 0) {
33
37
  if (shouldBeArray &&
34
38
  (!Array.isArray(actualValue) ||
35
- !(!Number.isInteger(arrayIndex) && actualValue.length > Number(arrayIndex)))) {
36
- return false;
39
+ (Number.isInteger(arrayIndex) && actualValue.length <= Number(arrayIndex)))) {
40
+ return { result: false, description: `${currentPath} empty array` };
37
41
  }
38
42
  if (value === undefined) {
39
- return shouldBeArray
43
+ const result = shouldBeArray
40
44
  ? !Number.isInteger(arrayIndex) || actualValue.length >= Number(arrayIndex)
41
45
  : actualValue !== undefined;
46
+ return { result, description: `${currentPath}` };
42
47
  }
43
48
  if (!shouldBeArray) {
44
- return value instanceof RegExp ? value.test(actualValue) : value === actualValue;
49
+ const result = value instanceof RegExp ? value.test(actualValue) : value === actualValue;
50
+ return { result, description: `${currentPath}` };
45
51
  }
46
52
  }
47
53
  if (shouldBeArray) {
48
- return Number.isInteger(arrayIndex)
49
- ? matchObject(actualValue[Number(arrayIndex)], rest.join('.'), value)
50
- : actualValue.some((arrayValue) => matchObject(arrayValue, rest.join('.'), value));
54
+ if (Number.isInteger(arrayIndex)) {
55
+ return matchObject(actualValue[Number(arrayIndex)], rest.join('.'), value, currentPath);
56
+ }
57
+ const hasArrayMatch = actualValue.some((arrayValue) => matchObject(arrayValue, rest.join('.'), value, currentPath).result);
58
+ return { result: hasArrayMatch, description: `array match ${currentPath}` };
51
59
  }
52
- return typeof actualValue !== 'object' ? false : matchObject(actualValue, rest.join('.'), value);
60
+ if (typeof actualValue !== 'object') {
61
+ return { result: false, description: `${currentPath} not an object` };
62
+ }
63
+ return matchObject(actualValue, rest.join('.'), value, currentPath);
53
64
  };
54
65
  const ___matchesValue = (matcher, value) => {
55
66
  if (matcher === undefined) {
@@ -70,32 +81,60 @@ class RuleBuilderBaseBase {
70
81
  return true;
71
82
  };
72
83
  if (!___matchesValue(matchBuilderVariables.filter, req.url)) {
73
- return false;
84
+ return {
85
+ result: false,
86
+ description: `url ${req.url} doesn't match ${(_a = matchBuilderVariables.filter) === null || _a === void 0 ? void 0 : _a.toString()}`,
87
+ };
74
88
  }
75
89
  if (!___matchesValue(matchBuilderVariables.hostname, ___url.hostname)) {
76
- return false;
90
+ return {
91
+ result: false,
92
+ description: `hostname ${___url.hostname} doesn't match ${(_b = matchBuilderVariables.hostname) === null || _b === void 0 ? void 0 : _b.toString()}`,
93
+ };
77
94
  }
78
95
  if (!___matchesValue(matchBuilderVariables.pathname, ___url.pathname)) {
79
- return false;
96
+ return {
97
+ result: false,
98
+ description: `pathname ${___url.pathname} doesn't match ${(_c = matchBuilderVariables.pathname) === null || _c === void 0 ? void 0 : _c.toString()}`,
99
+ };
100
+ }
101
+ if (matchBuilderVariables.port) {
102
+ const port = ___url.port && ___url.port !== '' ? ___url.port : ___url.protocol === 'https:' ? '443' : '80';
103
+ if (!___matchesValue(matchBuilderVariables.port instanceof RegExp
104
+ ? matchBuilderVariables.port
105
+ : `${matchBuilderVariables.port}`, port)) {
106
+ return {
107
+ result: false,
108
+ description: `port ${port} doesn't match ${(_d = matchBuilderVariables.port) === null || _d === void 0 ? void 0 : _d.toString()}`,
109
+ };
110
+ }
80
111
  }
81
112
  if (matchBuilderVariables.searchParams) {
82
113
  for (const searchParamMatcher of matchBuilderVariables.searchParams) {
83
114
  if (typeof searchParamMatcher === 'string') {
84
- return ___url.searchParams.has(searchParamMatcher);
115
+ const result = ___url.searchParams.has(searchParamMatcher);
116
+ return { result, description: `searchParams.has("${searchParamMatcher}")` };
85
117
  }
86
118
  if (searchParamMatcher instanceof RegExp) {
87
- return Array.from(___url.searchParams.keys()).some((key) => searchParamMatcher.test(key));
119
+ const result = Array.from(___url.searchParams.keys()).some((key) => searchParamMatcher.test(key));
120
+ return { result, description: `searchParams.keys() matches ${searchParamMatcher.toString()}` };
88
121
  }
89
122
  if (!___url.searchParams.has(searchParamMatcher.key)) {
90
- return false;
123
+ return { result: false, description: `searchParams.has("${searchParamMatcher.key}")` };
91
124
  }
92
125
  if (searchParamMatcher.value) {
93
126
  const value = ___url.searchParams.get(searchParamMatcher.key);
94
127
  if (value === null) {
95
- return false;
128
+ return {
129
+ result: false,
130
+ description: `searchParams.get("${searchParamMatcher.key}") === null`,
131
+ };
96
132
  }
97
133
  if (!___matchesValue(searchParamMatcher.value, value)) {
98
- return false;
134
+ return {
135
+ result: false,
136
+ description: `searchParams.get("${searchParamMatcher.key}") = "${searchParamMatcher.value}"`,
137
+ };
99
138
  }
100
139
  }
101
140
  }
@@ -103,31 +142,56 @@ class RuleBuilderBaseBase {
103
142
  if (matchBuilderVariables.headers) {
104
143
  for (const headerMatcher of matchBuilderVariables.headers) {
105
144
  if (typeof headerMatcher === 'string') {
106
- return ___headers.has(headerMatcher);
145
+ const result = ___headers.has(headerMatcher);
146
+ if (result) {
147
+ continue;
148
+ }
149
+ return { result: false, description: `headers.has("${headerMatcher}")` };
107
150
  }
108
151
  if (headerMatcher instanceof RegExp) {
109
- return ___headers.toHeaderPairs().some(([key]) => headerMatcher.test(key));
152
+ const result = ___headers.toHeaderPairs().some(([key]) => headerMatcher.test(key));
153
+ if (result) {
154
+ continue;
155
+ }
156
+ return { result: false, description: `headers.keys matches ${headerMatcher.toString()}` };
110
157
  }
111
158
  if (!___headers.has(headerMatcher.key)) {
112
- return false;
159
+ return { result: false, description: `headers.has("${headerMatcher.key}")` };
113
160
  }
114
161
  if (headerMatcher.value) {
115
162
  const value = ___headers.get(headerMatcher.key);
116
163
  if (value === null) {
117
- return false;
164
+ return { result: false, description: `headers.get("${headerMatcher.key}") === null` };
118
165
  }
119
166
  if (!___matchesValue(headerMatcher.value, value)) {
120
- return false;
167
+ return {
168
+ result: false,
169
+ description: `headerMatcher.get("${headerMatcher.key}") = "${headerMatcher.value}"`,
170
+ };
121
171
  }
122
172
  }
123
173
  }
124
174
  }
125
175
  if (matchBuilderVariables.bodyText === null && !!req.body) {
126
- return false;
176
+ return { result: false, description: `empty body` };
127
177
  }
128
178
  if (matchBuilderVariables.bodyText) {
129
- if (!___matchesValue(matchBuilderVariables.bodyText, req.body)) {
130
- return false;
179
+ if (!req.body) {
180
+ return { result: false, description: `empty body` };
181
+ }
182
+ if (matchBuilderVariables.bodyText instanceof RegExp) {
183
+ if (!___matchesValue(matchBuilderVariables.bodyText, req.body)) {
184
+ return {
185
+ result: false,
186
+ description: `body text doesn't match ${matchBuilderVariables.bodyText.toString()}`,
187
+ };
188
+ }
189
+ }
190
+ else if (!req.body.includes(matchBuilderVariables.bodyText)) {
191
+ return {
192
+ result: false,
193
+ description: `body text doesn't include "${matchBuilderVariables.bodyText}"`,
194
+ };
131
195
  }
132
196
  }
133
197
  if (matchBuilderVariables.bodyJson) {
@@ -136,47 +200,64 @@ class RuleBuilderBaseBase {
136
200
  json = JSON.parse(req.body);
137
201
  }
138
202
  catch (kiss) {
139
- return false;
203
+ return { result: false, description: `unparseable json` };
140
204
  }
141
205
  if (!json) {
142
- return false;
206
+ return { result: false, description: `empty json object` };
143
207
  }
144
208
  for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyJson)
145
209
  ? matchBuilderVariables.bodyJson
146
210
  : [matchBuilderVariables.bodyJson]) {
147
- if (!matchObject(json, jsonMatcher.key, jsonMatcher.value)) {
148
- return false;
211
+ const matchObjectResult = matchObject(json, jsonMatcher.key, jsonMatcher.value);
212
+ if (!matchObjectResult.result) {
213
+ return { result: false, description: `$.${jsonMatcher.key} != "${jsonMatcher.value}"` };
149
214
  }
150
215
  }
151
216
  }
152
217
  if (matchBuilderVariables.bodyGql) {
153
218
  if (!req.gqlBody) {
154
- return false;
219
+ return { result: false, description: `not a gql body` };
155
220
  }
156
221
  if (!___matchesValue(matchBuilderVariables.bodyGql.methodName, req.gqlBody.methodName)) {
157
- return false;
222
+ return {
223
+ result: false,
224
+ description: `methodName "${matchBuilderVariables.bodyGql.methodName}" !== "${req.gqlBody.methodName}"`,
225
+ };
158
226
  }
159
227
  if (!___matchesValue(matchBuilderVariables.bodyGql.operationName, req.gqlBody.operationName)) {
160
- return false;
228
+ return {
229
+ result: false,
230
+ description: `operationName "${matchBuilderVariables.bodyGql.operationName}" !== "${req.gqlBody.operationName}"`,
231
+ };
161
232
  }
162
233
  if (!___matchesValue(matchBuilderVariables.bodyGql.query, req.gqlBody.query)) {
163
- return false;
234
+ return {
235
+ result: false,
236
+ description: `query "${matchBuilderVariables.bodyGql.query}" !== "${req.gqlBody.query}"`,
237
+ };
164
238
  }
165
239
  if (!___matchesValue(matchBuilderVariables.bodyGql.type, req.gqlBody.type)) {
166
- return false;
240
+ return {
241
+ result: false,
242
+ description: `type "${matchBuilderVariables.bodyGql.type}" !== "${req.gqlBody.type}"`,
243
+ };
167
244
  }
168
245
  if (!matchBuilderVariables.bodyGql.variables) {
169
- return true;
246
+ return { result: true, description: `no variables to match` };
170
247
  }
171
248
  for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyGql.variables)
172
249
  ? matchBuilderVariables.bodyGql.variables
173
250
  : [matchBuilderVariables.bodyGql.variables]) {
174
- if (!matchObject(req.gqlBody.variables, jsonMatcher.key, jsonMatcher.value)) {
175
- return false;
251
+ const matchObjectResult = matchObject(req.gqlBody.variables, jsonMatcher.key, jsonMatcher.value);
252
+ if (!matchObjectResult.result) {
253
+ return {
254
+ result: false,
255
+ description: `GQL variable ${jsonMatcher.key} != "${jsonMatcher.value}". Detail: ${matchObjectResult.description}`,
256
+ };
176
257
  }
177
258
  }
178
259
  }
179
- return true;
260
+ return { result: true, description: 'match' };
180
261
  },
181
262
  localVariables: { matchBuilderVariables: this._matchBuilderVariables },
182
263
  },
@@ -185,6 +266,9 @@ class RuleBuilderBaseBase {
185
266
  }
186
267
  class RuleBuilderBase extends RuleBuilderBaseBase {
187
268
  limitedUse(hitCount) {
269
+ if (this.rule.removeAfterUse) {
270
+ throw new Error(`limit already set at ${this.rule.removeAfterUse}`);
271
+ }
188
272
  if (Number.isNaN(hitCount) || !Number.isFinite(hitCount) || !Number.isInteger(hitCount) || hitCount <= 0) {
189
273
  throw new Error('Invalid hitCount');
190
274
  }
@@ -204,6 +288,9 @@ class RuleBuilderBase extends RuleBuilderBaseBase {
204
288
  }
205
289
  class RuleBuilder extends RuleBuilderBase {
206
290
  raisePriority(by) {
291
+ if (this.rule.priority !== shared_1.DEFAULT_RULE_PRIORITY) {
292
+ throw new Error('you should not alter rule priority more than once');
293
+ }
207
294
  const subtract = by !== null && by !== void 0 ? by : 1;
208
295
  if (subtract >= shared_1.DEFAULT_RULE_PRIORITY) {
209
296
  throw new Error(`Unable to raise priority over the default ${shared_1.DEFAULT_RULE_PRIORITY}`);
@@ -212,6 +299,9 @@ class RuleBuilder extends RuleBuilderBase {
212
299
  return this;
213
300
  }
214
301
  decreasePriority(by) {
302
+ if (this.rule.priority !== shared_1.DEFAULT_RULE_PRIORITY) {
303
+ throw new Error('you should not alter rule priority more than once');
304
+ }
215
305
  const add = by !== null && by !== void 0 ? by : 1;
216
306
  this.rule.priority = shared_1.DEFAULT_RULE_PRIORITY + add;
217
307
  return this;
@@ -242,21 +332,33 @@ class RuleBuilder extends RuleBuilderBase {
242
332
  this._matchBuilderVariables.pathname = pathname;
243
333
  return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
244
334
  }
335
+ onRequestToPort(port) {
336
+ this._matchBuilderVariables.port = port;
337
+ return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
338
+ }
245
339
  onAnyRequest() {
246
- this.rule.matches = { localFn: () => true };
247
340
  return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
248
341
  }
249
342
  }
250
343
  class RuleBuilderInitialized extends RuleBuilderBase {
251
344
  withHostname(hostname) {
345
+ if (this._matchBuilderVariables.hostname) {
346
+ throw new Error('hostname already set');
347
+ }
252
348
  this._matchBuilderVariables.hostname = hostname;
253
349
  return this;
254
350
  }
255
351
  withPathname(pathname) {
352
+ if (this._matchBuilderVariables.pathname) {
353
+ throw new Error('pathname already set');
354
+ }
256
355
  this._matchBuilderVariables.pathname = pathname;
257
356
  return this;
258
357
  }
259
358
  withPort(port) {
359
+ if (this._matchBuilderVariables.port) {
360
+ throw new Error('port already set');
361
+ }
260
362
  this._matchBuilderVariables.port = port;
261
363
  return this;
262
364
  }
@@ -308,7 +410,7 @@ class RuleBuilderInitialized extends RuleBuilderBase {
308
410
  this._matchBuilderVariables.headers.push({ key, value });
309
411
  return this;
310
412
  }
311
- withHeaders(headers) {
413
+ withHeaders(...headers) {
312
414
  if (!this._matchBuilderVariables.headers) {
313
415
  this._matchBuilderVariables.headers = [];
314
416
  }
@@ -323,10 +425,19 @@ class RuleBuilderInitialized extends RuleBuilderBase {
323
425
  return this;
324
426
  }
325
427
  withBodyText(includesOrMatches) {
428
+ if (this._matchBuilderVariables.bodyText) {
429
+ throw new Error('bodyText already set');
430
+ }
431
+ if (this._matchBuilderVariables.bodyText === null) {
432
+ throw new Error('cannot use both withBodyText and withoutBody');
433
+ }
326
434
  this._matchBuilderVariables.bodyText = includesOrMatches;
327
435
  return this;
328
436
  }
329
437
  withoutBody() {
438
+ if (this._matchBuilderVariables.bodyText) {
439
+ throw new Error('cannot use both withBodyText and withoutBody');
440
+ }
330
441
  this._matchBuilderVariables.bodyText = null;
331
442
  return this;
332
443
  }
@@ -337,7 +448,7 @@ class RuleBuilderInitialized extends RuleBuilderBase {
337
448
  }
338
449
  if (typeof keyOrMatcher === 'string') {
339
450
  if (!keyRegex.test(keyOrMatcher)) {
340
- throw new Error('invalid key');
451
+ throw new Error(`invalid key "${keyOrMatcher}"`);
341
452
  }
342
453
  this._matchBuilderVariables.bodyJson.push({ key: keyOrMatcher, value: withValue });
343
454
  return this;
@@ -346,7 +457,7 @@ class RuleBuilderInitialized extends RuleBuilderBase {
346
457
  throw new Error('invalid usage');
347
458
  }
348
459
  if (!keyRegex.test(keyOrMatcher.key)) {
349
- throw new Error('invalid key');
460
+ throw new Error(`invalid key "${keyOrMatcher}"`);
350
461
  }
351
462
  this._matchBuilderVariables.bodyJson.push(keyOrMatcher);
352
463
  return this;
@@ -358,24 +469,52 @@ class RuleBuilderInitialized extends RuleBuilderBase {
358
469
  proxyPass() {
359
470
  return this.rule;
360
471
  }
361
- mockResponse(response) {
472
+ mockResponse(response, localVariables) {
473
+ if (typeof response === 'function') {
474
+ this.rule.actions = { mockResponse: { localFn: response, localVariables: localVariables !== null && localVariables !== void 0 ? localVariables : {} } };
475
+ return this.rule;
476
+ }
477
+ if (localVariables) {
478
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
479
+ }
362
480
  this.rule.actions = { mockResponse: response };
363
481
  return this.rule;
364
482
  }
365
- modifyRequest(modifyFunction) {
483
+ modifyRequest(modifyFunction, localVariables) {
484
+ if (typeof modifyFunction === 'function') {
485
+ this.rule.actions = { modifyRequest: { localFn: modifyFunction, localVariables: localVariables !== null && localVariables !== void 0 ? localVariables : {} } };
486
+ return new RuleBuilderRequestInitialized(this.rule, this._matchBuilderVariables);
487
+ }
488
+ if (localVariables) {
489
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
490
+ }
366
491
  this.rule.actions = { modifyRequest: modifyFunction };
367
492
  return new RuleBuilderRequestInitialized(this.rule, this._matchBuilderVariables);
368
493
  }
369
- modifyResponse(modifyFunction) {
494
+ modifyResponse(modifyFunction, localVariables) {
495
+ if (typeof modifyFunction === 'function') {
496
+ this.rule.actions = { modifyResponse: { localFn: modifyFunction, localVariables: localVariables !== null && localVariables !== void 0 ? localVariables : {} } };
497
+ return this.rule;
498
+ }
499
+ if (localVariables) {
500
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
501
+ }
370
502
  this.rule.actions = { modifyResponse: modifyFunction };
371
503
  return this.rule;
372
504
  }
373
505
  }
374
506
  class RuleBuilderRequestInitialized extends RuleBuilderBase {
375
- modifyResponse(modifyFunction) {
507
+ modifyResponse(modifyFunction, localVariables) {
376
508
  if (!this.rule.actions) {
377
509
  throw new Error('rule.actions not defined - builder implementation error');
378
510
  }
511
+ if (typeof modifyFunction === 'function') {
512
+ this.rule.actions = { modifyResponse: { localFn: modifyFunction, localVariables: localVariables !== null && localVariables !== void 0 ? localVariables : {} } };
513
+ return this.rule;
514
+ }
515
+ if (localVariables) {
516
+ throw new Error('invalid call - localVariables cannot be used together with Response or RemotableFunction');
517
+ }
379
518
  this.rule.actions.modifyResponse = modifyFunction;
380
519
  return this.rule;
381
520
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stuntman/client",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Stuntman - HTTP proxy / mock API client",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -32,13 +32,17 @@
32
32
  "author": "Andrzej Pasterczyk",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@stuntman/shared": "^0.1.1",
35
+ "@stuntman/shared": "^0.1.2",
36
36
  "serialize-javascript": "6.0.1",
37
37
  "uuid": "9.0.0"
38
38
  },
39
39
  "devDependencies": {
40
+ "@jest/globals": "29.4.3",
40
41
  "@types/serialize-javascript": "5.0.2",
41
- "@types/uuid": "9.0.0"
42
+ "@types/uuid": "9.0.0",
43
+ "jest": "29.4.3",
44
+ "ts-jest": "29.0.5",
45
+ "typescript": "4.9.5"
42
46
  },
43
47
  "files": [
44
48
  "dist/",
@@ -47,10 +51,10 @@
47
51
  "CHANGELOG.md"
48
52
  ],
49
53
  "scripts": {
50
- "test": "echo \"Error: no test specified\" && exit 1",
54
+ "test": "SUPPRESS_NO_CONFIG_WARNING=1 jest",
51
55
  "clean": "rm -fr dist",
52
56
  "build": "tsc",
53
57
  "lint": "prettier --check . && eslint . --ext ts",
54
- "lint:fix": "prettier --write ./src && eslint ./src --ext ts --fix"
58
+ "lint:fix": "prettier --write ./{src,test} && eslint ./{src,test} --ext ts --fix"
55
59
  }
56
60
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,22 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const apiClient_1 = require("./apiClient");
4
- const ruleBuilder_1 = require("./ruleBuilder");
5
- const client = new apiClient_1.Client();
6
- const uniqueQaUserEmail = 'unique_qa_email@example.com';
7
- const rule = (0, ruleBuilder_1.ruleBuilder)()
8
- .limitedUse(2)
9
- .onAnyRequest()
10
- .withBodyJson('test', 'value')
11
- .mockResponse({
12
- localFn: (req) => {
13
- if (JSON.parse(req.body).email !== uniqueQaUserEmail) {
14
- return {
15
- status: 500,
16
- };
17
- }
18
- return { status: 201 };
19
- },
20
- localVariables: { uniqueQaUserEmail },
21
- });
22
- client.addRule(rule).then((x) => console.log(x));