@stuntman/server 0.1.5 → 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.
package/dist/api/api.js CHANGED
@@ -76,17 +76,17 @@ class API {
76
76
  res.send();
77
77
  });
78
78
  this.apiApp.get('/traffic', this.authReadOnly, (req, res) => {
79
- const serializedTraffic = {};
80
- for (const [key, value] of this.trafficStore.entries()) {
81
- serializedTraffic[key] = value;
79
+ const serializedTraffic = [];
80
+ for (const value of this.trafficStore.values()) {
81
+ serializedTraffic.push(value);
82
82
  }
83
83
  res.json(serializedTraffic);
84
84
  });
85
85
  this.apiApp.get('/traffic/:ruleIdOrLabel', this.authReadOnly, (req, res) => {
86
- const serializedTraffic = {};
87
- for (const [key, value] of this.trafficStore.entries()) {
86
+ const serializedTraffic = [];
87
+ for (const value of this.trafficStore.values()) {
88
88
  if (value.mockRuleId === req.params.ruleIdOrLabel || (value.labels || []).includes(req.params.ruleIdOrLabel)) {
89
- serializedTraffic[key] = value;
89
+ serializedTraffic.push(value);
90
90
  }
91
91
  }
92
92
  res.json(serializedTraffic);
@@ -150,13 +150,7 @@ class API {
150
150
  id: rule.id,
151
151
  matches: rule.matches,
152
152
  ttlSeconds: rule.ttlSeconds,
153
- ...(rule.actions && {
154
- actions: {
155
- ...(rule.actions.mockResponse
156
- ? { mockResponse: rule.actions.mockResponse }
157
- : { modifyRequest: rule.actions.modifyRequest, modifyResponse: rule.actions.modifyResponse }),
158
- },
159
- }),
153
+ actions: rule.actions,
160
154
  ...(rule.disableAfterUse !== undefined && { disableAfterUse: rule.disableAfterUse }),
161
155
  ...(rule.isEnabled !== undefined && { isEnabled: rule.isEnabled }),
162
156
  ...(rule.labels !== undefined && { labels: rule.labels }),
package/dist/api/utils.js CHANGED
@@ -15,6 +15,7 @@ const deserializeRule = (serializedRule) => {
15
15
  id: serializedRule.id,
16
16
  matches: (req) => new Function('____arg0', serializedRule.matches.remoteFn)(req),
17
17
  ttlSeconds: serializedRule.ttlSeconds,
18
+ actions: { mockResponse: { status: 200 } },
18
19
  ...(serializedRule.disableAfterUse !== undefined && { disableAfterUse: serializedRule.disableAfterUse }),
19
20
  ...(serializedRule.removeAfterUse !== undefined && { removeAfterUse: serializedRule.removeAfterUse }),
20
21
  ...(serializedRule.labels !== undefined && { labels: serializedRule.labels }),
@@ -22,33 +23,23 @@ const deserializeRule = (serializedRule) => {
22
23
  ...(serializedRule.isEnabled !== undefined && { isEnabled: serializedRule.isEnabled }),
23
24
  ...(serializedRule.storeTraffic !== undefined && { storeTraffic: serializedRule.storeTraffic }),
24
25
  };
25
- if (serializedRule.actions) {
26
- // TODO store the original localFn and variables sent from client for web UI editing maybe
27
- if (serializedRule.actions.mockResponse) {
28
- rule.actions = {
29
- mockResponse: 'remoteFn' in serializedRule.actions.mockResponse
30
- ? (req) => {
31
- var _a;
32
- return new Function('____arg0', ((_a = serializedRule.actions) === null || _a === void 0 ? void 0 : _a.mockResponse).remoteFn)(req);
33
- }
34
- : serializedRule.actions.mockResponse,
35
- };
36
- }
37
- else {
38
- rule.actions = {};
39
- if (serializedRule.actions.modifyRequest) {
40
- rule.actions.modifyRequest = (req) => {
41
- var _a;
42
- return new Function('____arg0', ((_a = serializedRule.actions) === null || _a === void 0 ? void 0 : _a.modifyRequest).remoteFn)(req);
43
- };
44
- }
45
- if (serializedRule.actions.modifyResponse) {
46
- rule.actions.modifyResponse = (req, res) => {
47
- var _a;
48
- return new Function('____arg0', '____arg1', ((_a = serializedRule.actions) === null || _a === void 0 ? void 0 : _a.modifyResponse).remoteFn)(req, res);
49
- };
50
- }
51
- }
26
+ // TODO store the original localFn and variables sent from client for web UI editing maybe
27
+ if (serializedRule.actions.mockResponse) {
28
+ rule.actions = {
29
+ mockResponse: 'remoteFn' in serializedRule.actions.mockResponse
30
+ ? (req) => new Function('____arg0', serializedRule.actions.mockResponse.remoteFn)(req)
31
+ : serializedRule.actions.mockResponse,
32
+ };
33
+ }
34
+ else {
35
+ rule.actions = {
36
+ ...(serializedRule.actions.modifyRequest && {
37
+ modifyRequest: ((req) => new Function('____arg0', serializedRule.actions.modifyRequest.remoteFn)(req)),
38
+ }),
39
+ ...(serializedRule.actions.modifyResponse && {
40
+ modifyResponse: ((req, res) => new Function('____arg0', '____arg1', serializedRule.actions.modifyResponse.remoteFn)(req, res)),
41
+ }),
42
+ };
52
43
  }
53
44
  shared_1.logger.debug(rule, 'deserialized rule');
54
45
  return rule;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateDeserializedRule = exports.validateSerializedRuleProperties = void 0;
4
4
  const shared_1 = require("@stuntman/shared");
5
5
  const validateSerializedRuleProperties = (rule) => {
6
- var _a;
7
6
  if (!rule) {
8
7
  throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid serialized rule' });
9
8
  }
@@ -16,43 +15,50 @@ const validateSerializedRuleProperties = (rule) => {
16
15
  if (rule.priority && typeof rule.priority !== 'number') {
17
16
  throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.priority' });
18
17
  }
19
- if (typeof rule.actions !== 'undefined') {
20
- if (typeof rule.actions !== 'object') {
21
- throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions' });
18
+ if (typeof rule.actions !== 'object') {
19
+ throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions - not an object' });
20
+ }
21
+ if (rule.actions.proxyPass !== true &&
22
+ typeof rule.actions.mockResponse !== 'object' &&
23
+ typeof rule.actions.modifyRequest !== 'object' &&
24
+ typeof rule.actions.modifyResponse !== 'object') {
25
+ throw new shared_1.AppError({
26
+ httpCode: shared_1.HttpCode.BAD_REQUEST,
27
+ message: 'invalid rule.actions - missing one of: proxyPass, mockResponse, modifyRequest, modifyResponse',
28
+ });
29
+ }
30
+ if (typeof rule.actions.mockResponse !== 'undefined') {
31
+ if (typeof rule.actions.mockResponse !== 'object') {
32
+ throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
22
33
  }
23
- if (typeof rule.actions.mockResponse !== 'undefined') {
24
- if (typeof rule.actions.mockResponse !== 'object') {
25
- throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
34
+ if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
35
+ throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
36
+ }
37
+ else if ('status' in rule.actions.mockResponse) {
38
+ if (typeof rule.actions.mockResponse.status !== 'number') {
39
+ throw new shared_1.AppError({
40
+ httpCode: shared_1.HttpCode.BAD_REQUEST,
41
+ message: 'invalid rule.actions.mockResponse.status',
42
+ });
26
43
  }
27
- if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
28
- throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
44
+ if (typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
45
+ (!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
46
+ rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))) {
47
+ throw new shared_1.AppError({
48
+ httpCode: shared_1.HttpCode.BAD_REQUEST,
49
+ message: 'invalid rule.actions.mockResponse.rawHeaders',
50
+ });
29
51
  }
30
- else if ('status' in rule.actions.mockResponse) {
31
- if (typeof rule.actions.mockResponse.status !== 'number') {
32
- throw new shared_1.AppError({
33
- httpCode: shared_1.HttpCode.BAD_REQUEST,
34
- message: 'invalid rule.actions.mockResponse.status',
35
- });
36
- }
37
- if (typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
38
- (!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
39
- rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))) {
40
- throw new shared_1.AppError({
41
- httpCode: shared_1.HttpCode.BAD_REQUEST,
42
- message: 'invalid rule.actions.mockResponse.rawHeaders',
43
- });
44
- }
45
- if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
46
- throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
47
- }
52
+ if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
53
+ throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
48
54
  }
49
55
  }
50
- if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
51
- throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
52
- }
53
- if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
54
- throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
55
- }
56
+ }
57
+ if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
58
+ throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
59
+ }
60
+ if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
61
+ throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
56
62
  }
57
63
  if (typeof rule.disableAfterUse !== 'undefined' &&
58
64
  typeof rule.disableAfterUse !== 'boolean' &&
@@ -68,7 +74,7 @@ const validateSerializedRuleProperties = (rule) => {
68
74
  (!Array.isArray(rule.labels) || rule.labels.some((label) => typeof label !== 'string'))) {
69
75
  throw new shared_1.AppError({ httpCode: shared_1.HttpCode.BAD_REQUEST, message: 'invalid rule.labels' });
70
76
  }
71
- if (((_a = rule.actions) === null || _a === void 0 ? void 0 : _a.mockResponse) && rule.actions.modifyResponse) {
77
+ if (rule.actions.mockResponse && rule.actions.modifyResponse) {
72
78
  throw new shared_1.AppError({
73
79
  httpCode: shared_1.HttpCode.BAD_REQUEST,
74
80
  message: 'rule.actions.mockResponse and rule.actions.modifyResponse are mutually exclusive',
@@ -120,6 +120,8 @@ html
120
120
  body: ruleFunctionText + '\n return STUNTMAN_RULE;',
121
121
  }).then((response) => {
122
122
  if (response.ok) {
123
+ const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?ruleId=${encodeURIComponent(newId)}`;
124
+ window.history.pushState({ path: newUrl }, '', newUrl);
123
125
  window.location.reload();
124
126
  return;
125
127
  }
@@ -133,7 +135,7 @@ html
133
135
 
134
136
  window.newRule = () => {
135
137
  const ruleId = uuidv4();
136
- const emptyRule = `import type * as Stuntman from \'stuntman\';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600 };`;
138
+ const emptyRule = `import type * as Stuntman from \'stuntman\';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600, actions: { mockResponse: { status: '200', body: '${ruleId}' }} };`;
137
139
  models[ruleId] = monaco.editor.createModel(emptyRule, 'typescript', `file:///${ruleId}.ts`);
138
140
  const ruleKeyNode = document.getElementById('ruleKeys').firstChild;
139
141
  const ruleKeyNodeClone = ruleKeyNode.cloneNode(true);
package/dist/mock.js CHANGED
@@ -79,7 +79,6 @@ class Mock {
79
79
  ? null
80
80
  : new ipUtils_1.IPUtils({ mockUuid: this.mockUuid, externalDns: this.options.mock.externalDns });
81
81
  this.requestHandler = async (req, res) => {
82
- var _a, _b, _c, _d, _e;
83
82
  const ctx = requestContext_1.default.get(req);
84
83
  const requestUuid = (ctx === null || ctx === void 0 ? void 0 : ctx.uuid) || (0, uuid_1.v4)();
85
84
  const timestamp = Date.now();
@@ -115,7 +114,7 @@ class Mock {
115
114
  if (matchingRule) {
116
115
  mockEntry.mockRuleId = matchingRule.id;
117
116
  mockEntry.labels = matchingRule.labels;
118
- if ((_a = matchingRule.actions) === null || _a === void 0 ? void 0 : _a.mockResponse) {
117
+ if (matchingRule.actions.mockResponse) {
119
118
  const staticResponse = typeof matchingRule.actions.mockResponse === 'function'
120
119
  ? matchingRule.actions.mockResponse(mockEntry.modifiedRequest)
121
120
  : matchingRule.actions.mockResponse;
@@ -134,8 +133,8 @@ class Mock {
134
133
  // static response blocks any further processing
135
134
  return;
136
135
  }
137
- if ((_b = matchingRule.actions) === null || _b === void 0 ? void 0 : _b.modifyRequest) {
138
- mockEntry.modifiedRequest = (_c = matchingRule.actions) === null || _c === void 0 ? void 0 : _c.modifyRequest(mockEntry.modifiedRequest);
136
+ if (matchingRule.actions.modifyRequest) {
137
+ mockEntry.modifiedRequest = matchingRule.actions.modifyRequest(mockEntry.modifiedRequest);
139
138
  shared_1.logger.debug({ ...logContext, modifiedRequest: mockEntry.modifiedRequest }, 'modified original request');
140
139
  }
141
140
  }
@@ -154,7 +153,14 @@ class Mock {
154
153
  shared_1.logger.warn({ ...logContext, error }, `error trying to resolve IP for "${hostname}"`);
155
154
  }
156
155
  }
157
- const originalResponse = await this.proxyRequest(req, mockEntry, logContext);
156
+ const originalResponse = this.options.mock.disableProxy
157
+ ? {
158
+ timestamp: Date.now(),
159
+ body: undefined,
160
+ rawHeaders: new shared_1.RawHeaders(),
161
+ status: 404,
162
+ }
163
+ : await this.proxyRequest(req, mockEntry, logContext);
158
164
  shared_1.logger.debug({ ...logContext, originalResponse }, 'received response');
159
165
  mockEntry.originalResponse = originalResponse;
160
166
  let modifedResponse = {
@@ -169,8 +175,8 @@ class Mock {
169
175
  ];
170
176
  })),
171
177
  };
172
- if ((_d = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions) === null || _d === void 0 ? void 0 : _d.modifyResponse) {
173
- modifedResponse = (_e = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions) === null || _e === void 0 ? void 0 : _e.modifyResponse(mockEntry.modifiedRequest, originalResponse);
178
+ if (matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions.modifyResponse) {
179
+ modifedResponse = matchingRule === null || matchingRule === void 0 ? void 0 : matchingRule.actions.modifyResponse(mockEntry.modifiedRequest, originalResponse);
174
180
  shared_1.logger.debug({ ...logContext, modifedResponse }, 'modified response');
175
181
  }
176
182
  mockEntry.modifiedResponse = modifedResponse;
@@ -200,7 +206,7 @@ class Mock {
200
206
  next();
201
207
  });
202
208
  this.mockApp.all(/.*/, this.requestHandler);
203
- this.mockApp.use((error, req, res, _next) => {
209
+ this.mockApp.use((error, req, res) => {
204
210
  const ctx = requestContext_1.default.get(req);
205
211
  const uuid = (ctx === null || ctx === void 0 ? void 0 : ctx.uuid) || (0, uuid_1.v4)();
206
212
  shared_1.logger.error({ message: error.message, stack: error.stack, name: error.name, uuid }, 'unexpected error');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stuntman/server",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Stuntman - HTTP proxy / mock server with API",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -41,17 +41,19 @@
41
41
  "pug": "3.0.2",
42
42
  "serialize-javascript": "6.0.1",
43
43
  "ts-import": "4.0.0-beta.10",
44
- "typescript": "4.9.5",
45
44
  "undici": "5.20.0",
46
45
  "uuid": "9.0.0"
47
46
  },
48
47
  "devDependencies": {
48
+ "@jest/globals": "29.4.3",
49
49
  "@prettier/plugin-pug": "2.4.1",
50
50
  "@types/express": "4.17.17",
51
51
  "@types/glob": "8.1.0",
52
52
  "@types/serialize-javascript": "5.0.2",
53
53
  "@types/uuid": "9.0.0",
54
- "prettier": "2.8.4"
54
+ "jest": "29.4.3",
55
+ "prettier": "2.8.4",
56
+ "typescript": "4.9.5"
55
57
  },
56
58
  "bin": {
57
59
  "stuntman": "./dist/bin/stuntman.js"
@@ -64,7 +66,7 @@
64
66
  "CHANGELOG.md"
65
67
  ],
66
68
  "scripts": {
67
- "test": "echo \"Error: no test specified\" && exit 1",
69
+ "test": "SUPPRESS_NO_CONFIG_WARNING=1 jest",
68
70
  "clean": "rm -fr dist",
69
71
  "build": "tsc && cp -rv src/api/webgui dist/api",
70
72
  "lint": "prettier --check . && eslint . --ext ts",
package/src/api/api.ts CHANGED
@@ -103,18 +103,18 @@ export class API {
103
103
  });
104
104
 
105
105
  this.apiApp.get('/traffic', this.authReadOnly, (req, res) => {
106
- const serializedTraffic: Record<string, Stuntman.LogEntry> = {};
107
- for (const [key, value] of this.trafficStore.entries()) {
108
- serializedTraffic[key] = value;
106
+ const serializedTraffic: Stuntman.LogEntry[] = [];
107
+ for (const value of this.trafficStore.values()) {
108
+ serializedTraffic.push(value);
109
109
  }
110
110
  res.json(serializedTraffic);
111
111
  });
112
112
 
113
113
  this.apiApp.get('/traffic/:ruleIdOrLabel', this.authReadOnly, (req, res) => {
114
- const serializedTraffic: Record<string, Stuntman.LogEntry> = {};
115
- for (const [key, value] of this.trafficStore.entries()) {
114
+ const serializedTraffic: Stuntman.LogEntry[] = [];
115
+ for (const value of this.trafficStore.values()) {
116
116
  if (value.mockRuleId === req.params.ruleIdOrLabel || (value.labels || []).includes(req.params.ruleIdOrLabel)) {
117
- serializedTraffic[key] = value;
117
+ serializedTraffic.push(value);
118
118
  }
119
119
  }
120
120
  res.json(serializedTraffic);
@@ -190,13 +190,7 @@ export class API {
190
190
  id: rule.id,
191
191
  matches: rule.matches,
192
192
  ttlSeconds: rule.ttlSeconds,
193
- ...(rule.actions && {
194
- actions: {
195
- ...(rule.actions.mockResponse
196
- ? { mockResponse: rule.actions.mockResponse }
197
- : { modifyRequest: rule.actions.modifyRequest, modifyResponse: rule.actions.modifyResponse }),
198
- },
199
- }),
193
+ actions: rule.actions,
200
194
  ...(rule.disableAfterUse !== undefined && { disableAfterUse: rule.disableAfterUse }),
201
195
  ...(rule.isEnabled !== undefined && { isEnabled: rule.isEnabled }),
202
196
  ...(rule.labels !== undefined && { labels: rule.labels }),
package/src/api/utils.ts CHANGED
@@ -11,6 +11,7 @@ export const deserializeRule = (serializedRule: Stuntman.SerializedRule): Stuntm
11
11
  id: serializedRule.id,
12
12
  matches: (req: Stuntman.Request) => new Function('____arg0', serializedRule.matches.remoteFn)(req),
13
13
  ttlSeconds: serializedRule.ttlSeconds,
14
+ actions: { mockResponse: { status: 200 } },
14
15
  ...(serializedRule.disableAfterUse !== undefined && { disableAfterUse: serializedRule.disableAfterUse }),
15
16
  ...(serializedRule.removeAfterUse !== undefined && { removeAfterUse: serializedRule.removeAfterUse }),
16
17
  ...(serializedRule.labels !== undefined && { labels: serializedRule.labels }),
@@ -18,37 +19,36 @@ export const deserializeRule = (serializedRule: Stuntman.SerializedRule): Stuntm
18
19
  ...(serializedRule.isEnabled !== undefined && { isEnabled: serializedRule.isEnabled }),
19
20
  ...(serializedRule.storeTraffic !== undefined && { storeTraffic: serializedRule.storeTraffic }),
20
21
  };
21
- if (serializedRule.actions) {
22
- // TODO store the original localFn and variables sent from client for web UI editing maybe
23
- if (serializedRule.actions.mockResponse) {
24
- rule.actions = {
25
- mockResponse:
26
- 'remoteFn' in serializedRule.actions.mockResponse
27
- ? (req: Stuntman.Request) =>
28
- new Function(
29
- '____arg0',
30
- (serializedRule.actions?.mockResponse as Stuntman.SerializedRemotableFunction).remoteFn
31
- )(req)
32
- : serializedRule.actions.mockResponse,
33
- };
34
- } else {
35
- rule.actions = {};
36
- if (serializedRule.actions.modifyRequest) {
37
- rule.actions.modifyRequest = (req: Stuntman.Request) =>
22
+ // TODO store the original localFn and variables sent from client for web UI editing maybe
23
+ if (serializedRule.actions.mockResponse) {
24
+ rule.actions = {
25
+ mockResponse:
26
+ 'remoteFn' in serializedRule.actions.mockResponse
27
+ ? (req: Stuntman.Request) =>
28
+ new Function(
29
+ '____arg0',
30
+ (serializedRule.actions.mockResponse as Stuntman.SerializedRemotableFunction).remoteFn
31
+ )(req)
32
+ : serializedRule.actions.mockResponse,
33
+ };
34
+ } else {
35
+ rule.actions = {
36
+ ...(serializedRule.actions.modifyRequest && {
37
+ modifyRequest: ((req: Stuntman.Request) =>
38
38
  new Function(
39
39
  '____arg0',
40
- (serializedRule.actions?.modifyRequest as Stuntman.SerializedRemotableFunction).remoteFn
41
- )(req);
42
- }
43
- if (serializedRule.actions.modifyResponse) {
44
- rule.actions.modifyResponse = (req: Stuntman.Request, res: Stuntman.Response) =>
40
+ (serializedRule.actions.modifyRequest as Stuntman.SerializedRemotableFunction).remoteFn
41
+ )(req)) as Stuntman.RequestManipulationFn,
42
+ }),
43
+ ...(serializedRule.actions.modifyResponse && {
44
+ modifyResponse: ((req: Stuntman.Request, res: Stuntman.Response) =>
45
45
  new Function(
46
46
  '____arg0',
47
47
  '____arg1',
48
- (serializedRule.actions?.modifyResponse as Stuntman.SerializedRemotableFunction).remoteFn
49
- )(req, res);
50
- }
51
- }
48
+ (serializedRule.actions.modifyResponse as Stuntman.SerializedRemotableFunction).remoteFn
49
+ )(req, res)) as Stuntman.ResponseManipulationFn,
50
+ }),
51
+ } as Stuntman.Actions;
52
52
  }
53
53
  logger.debug(rule, 'deserialized rule');
54
54
  return rule;
@@ -14,44 +14,53 @@ export const validateSerializedRuleProperties = (rule: Stuntman.SerializedRule):
14
14
  if (rule.priority && typeof rule.priority !== 'number') {
15
15
  throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.priority' });
16
16
  }
17
- if (typeof rule.actions !== 'undefined') {
18
- if (typeof rule.actions !== 'object') {
19
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions' });
17
+ if (typeof rule.actions !== 'object') {
18
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions - not an object' });
19
+ }
20
+ if (
21
+ rule.actions.proxyPass !== true &&
22
+ typeof rule.actions.mockResponse !== 'object' &&
23
+ typeof rule.actions.modifyRequest !== 'object' &&
24
+ typeof rule.actions.modifyResponse !== 'object'
25
+ ) {
26
+ throw new AppError({
27
+ httpCode: HttpCode.BAD_REQUEST,
28
+ message: 'invalid rule.actions - missing one of: proxyPass, mockResponse, modifyRequest, modifyResponse',
29
+ });
30
+ }
31
+ if (typeof rule.actions.mockResponse !== 'undefined') {
32
+ if (typeof rule.actions.mockResponse !== 'object') {
33
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
20
34
  }
21
- if (typeof rule.actions.mockResponse !== 'undefined') {
22
- if (typeof rule.actions.mockResponse !== 'object') {
23
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
35
+ if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
36
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
37
+ } else if ('status' in rule.actions.mockResponse) {
38
+ if (typeof rule.actions.mockResponse.status !== 'number') {
39
+ throw new AppError({
40
+ httpCode: HttpCode.BAD_REQUEST,
41
+ message: 'invalid rule.actions.mockResponse.status',
42
+ });
24
43
  }
25
- if ('remoteFn' in rule.actions.mockResponse && typeof rule.actions.mockResponse.remoteFn !== 'string') {
26
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse' });
27
- } else if ('status' in rule.actions.mockResponse) {
28
- if (typeof rule.actions.mockResponse.status !== 'number') {
29
- throw new AppError({
30
- httpCode: HttpCode.BAD_REQUEST,
31
- message: 'invalid rule.actions.mockResponse.status',
32
- });
33
- }
34
- if (
35
- typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
36
- (!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
37
- rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))
38
- ) {
39
- throw new AppError({
40
- httpCode: HttpCode.BAD_REQUEST,
41
- message: 'invalid rule.actions.mockResponse.rawHeaders',
42
- });
43
- }
44
- if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
45
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
46
- }
44
+ if (
45
+ typeof rule.actions.mockResponse.rawHeaders !== 'undefined' &&
46
+ (!Array.isArray(rule.actions.mockResponse.rawHeaders) ||
47
+ rule.actions.mockResponse.rawHeaders.some((header) => typeof header !== 'string'))
48
+ ) {
49
+ throw new AppError({
50
+ httpCode: HttpCode.BAD_REQUEST,
51
+ message: 'invalid rule.actions.mockResponse.rawHeaders',
52
+ });
53
+ }
54
+ if (typeof rule.actions.mockResponse.body !== 'undefined' && typeof rule.actions.mockResponse.body !== 'string') {
55
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.mockResponse.body' });
47
56
  }
48
57
  }
49
- if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
50
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
51
- }
52
- if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
53
- throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
54
- }
58
+ }
59
+ if (typeof rule.actions.modifyRequest !== 'undefined' && typeof rule.actions.modifyRequest.remoteFn !== 'string') {
60
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyRequest' });
61
+ }
62
+ if (typeof rule.actions.modifyResponse !== 'undefined' && typeof rule.actions.modifyResponse.remoteFn !== 'string') {
63
+ throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.actions.modifyResponse' });
55
64
  }
56
65
  if (
57
66
  typeof rule.disableAfterUse !== 'undefined' &&
@@ -73,7 +82,7 @@ export const validateSerializedRuleProperties = (rule: Stuntman.SerializedRule):
73
82
  ) {
74
83
  throw new AppError({ httpCode: HttpCode.BAD_REQUEST, message: 'invalid rule.labels' });
75
84
  }
76
- if (rule.actions?.mockResponse && rule.actions.modifyResponse) {
85
+ if (rule.actions.mockResponse && rule.actions.modifyResponse) {
77
86
  throw new AppError({
78
87
  httpCode: HttpCode.BAD_REQUEST,
79
88
  message: 'rule.actions.mockResponse and rule.actions.modifyResponse are mutually exclusive',
@@ -120,6 +120,8 @@ html
120
120
  body: ruleFunctionText + '\n return STUNTMAN_RULE;',
121
121
  }).then((response) => {
122
122
  if (response.ok) {
123
+ const newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?ruleId=${encodeURIComponent(newId)}`;
124
+ window.history.pushState({ path: newUrl }, '', newUrl);
123
125
  window.location.reload();
124
126
  return;
125
127
  }
@@ -133,7 +135,7 @@ html
133
135
 
134
136
  window.newRule = () => {
135
137
  const ruleId = uuidv4();
136
- const emptyRule = `import type * as Stuntman from \'stuntman\';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600 };`;
138
+ const emptyRule = `import type * as Stuntman from \'stuntman\';\n\nvar STUNTMAN_RULE: Stuntman.Rule = { id: '${ruleId}', matches: (req: Stuntman.Request) => true, ttlSeconds: 600, actions: { mockResponse: { status: '200', body: '${ruleId}' }} };`;
137
139
  models[ruleId] = monaco.editor.createModel(emptyRule, 'typescript', `file:///${ruleId}.ts`);
138
140
  const ruleKeyNode = document.getElementById('ruleKeys').firstChild;
139
141
  const ruleKeyNodeClone = ruleKeyNode.cloneNode(true);
package/src/mock.ts CHANGED
@@ -137,7 +137,7 @@ export class Mock {
137
137
  if (matchingRule) {
138
138
  mockEntry.mockRuleId = matchingRule.id;
139
139
  mockEntry.labels = matchingRule.labels;
140
- if (matchingRule.actions?.mockResponse) {
140
+ if (matchingRule.actions.mockResponse) {
141
141
  const staticResponse =
142
142
  typeof matchingRule.actions.mockResponse === 'function'
143
143
  ? matchingRule.actions.mockResponse(mockEntry.modifiedRequest)
@@ -157,8 +157,8 @@ export class Mock {
157
157
  // static response blocks any further processing
158
158
  return;
159
159
  }
160
- if (matchingRule.actions?.modifyRequest) {
161
- mockEntry.modifiedRequest = matchingRule.actions?.modifyRequest(mockEntry.modifiedRequest);
160
+ if (matchingRule.actions.modifyRequest) {
161
+ mockEntry.modifiedRequest = matchingRule.actions.modifyRequest(mockEntry.modifiedRequest);
162
162
  logger.debug({ ...logContext, modifiedRequest: mockEntry.modifiedRequest }, 'modified original request');
163
163
  }
164
164
  }
@@ -180,7 +180,14 @@ export class Mock {
180
180
  }
181
181
  }
182
182
 
183
- const originalResponse = await this.proxyRequest(req, mockEntry, logContext);
183
+ const originalResponse: Required<Stuntman.Response> = this.options.mock.disableProxy
184
+ ? {
185
+ timestamp: Date.now(),
186
+ body: undefined,
187
+ rawHeaders: new RawHeaders(),
188
+ status: 404,
189
+ }
190
+ : await this.proxyRequest(req, mockEntry, logContext);
184
191
 
185
192
  logger.debug({ ...logContext, originalResponse }, 'received response');
186
193
  mockEntry.originalResponse = originalResponse;
@@ -201,8 +208,8 @@ export class Mock {
201
208
  })
202
209
  ),
203
210
  };
204
- if (matchingRule?.actions?.modifyResponse) {
205
- modifedResponse = matchingRule?.actions?.modifyResponse(mockEntry.modifiedRequest, originalResponse);
211
+ if (matchingRule?.actions.modifyResponse) {
212
+ modifedResponse = matchingRule?.actions.modifyResponse(mockEntry.modifiedRequest, originalResponse);
206
213
  logger.debug({ ...logContext, modifedResponse }, 'modified response');
207
214
  }
208
215