@stuntman/client 0.1.5 → 0.1.7
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/package.json +3 -3
- package/src/apiClient.ts +13 -23
- package/src/clientError.ts +3 -1
- package/src/ruleBuilder.ts +28 -18
- package/dist/apiClient.d.ts +0 -23
- package/dist/apiClient.js +0 -170
- package/dist/clientError.d.ts +0 -18
- package/dist/clientError.js +0 -22
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -7
- package/dist/ruleBuilder.d.ts +0 -79
- package/dist/ruleBuilder.js +0 -523
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stuntman/client",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Stuntman - HTTP proxy / mock API client",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"test": "SUPPRESS_NO_CONFIG_WARNING=1 jest",
|
|
56
56
|
"clean": "rm -fr dist",
|
|
57
57
|
"build": "tsc",
|
|
58
|
-
"lint": "prettier --check
|
|
59
|
-
"lint:fix": "prettier --write ./{src,test} && eslint ./{src,test} --
|
|
58
|
+
"lint": "prettier --check \"./{src,test}/**/*\" && eslint \"./{src,test}/**/*\"",
|
|
59
|
+
"lint:fix": "prettier --write \"./{src,test}/**/*\" && eslint \"./{src,test}/**/*\" --fix"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/src/apiClient.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import serializeJavascript from 'serialize-javascript';
|
|
2
2
|
import { ClientError } from './clientError';
|
|
3
|
-
import {
|
|
3
|
+
import { stuntmanConfig } from '@stuntman/shared';
|
|
4
4
|
import type * as Stuntman from '@stuntman/shared';
|
|
5
5
|
|
|
6
|
-
type ClientOptions = {
|
|
7
|
-
protocol?: 'http' | 'https';
|
|
8
|
-
host?: string;
|
|
9
|
-
port?: number;
|
|
10
|
-
timeout?: number;
|
|
11
|
-
apiKey?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
6
|
const SERIALIZE_JAVASCRIPT_OPTIONS: serializeJavascript.SerializeJSOptions = {
|
|
15
7
|
unsafe: true,
|
|
16
8
|
ignoreFunction: true,
|
|
@@ -97,19 +89,17 @@ const serializeRemotableFunctions = <T>(obj: any): Stuntman.WithSerializedFuncti
|
|
|
97
89
|
export class Client {
|
|
98
90
|
// TODO websockets connection to API and hooks `onIntereceptedRequest`, `onInterceptedResponse`
|
|
99
91
|
|
|
100
|
-
private options:
|
|
92
|
+
private options: Stuntman.ClientConfig;
|
|
101
93
|
|
|
102
94
|
private get baseUrl() {
|
|
103
95
|
return `${this.options.protocol}://${this.options.host}${this.options.port ? `:${this.options.port}` : ''}`;
|
|
104
96
|
}
|
|
105
97
|
|
|
106
|
-
constructor(options?:
|
|
98
|
+
constructor(options?: Partial<Stuntman.ClientConfig>) {
|
|
107
99
|
this.options = {
|
|
100
|
+
...stuntmanConfig.client,
|
|
108
101
|
...options,
|
|
109
|
-
|
|
110
|
-
host: options?.host || 'localhost',
|
|
111
|
-
protocol: options?.protocol || 'http',
|
|
112
|
-
port: options?.port || options?.protocol ? (options.protocol === 'https' ? 443 : 80) : DEFAULT_API_PORT,
|
|
102
|
+
port: options?.port || options?.protocol ? (options.protocol === 'https' ? 443 : 80) : stuntmanConfig.client.port,
|
|
113
103
|
};
|
|
114
104
|
}
|
|
115
105
|
|
|
@@ -135,7 +125,7 @@ export class Client {
|
|
|
135
125
|
} catch (kiss) {
|
|
136
126
|
// and swallow
|
|
137
127
|
}
|
|
138
|
-
if ('error' in json) {
|
|
128
|
+
if (json && 'error' in json) {
|
|
139
129
|
throw new ClientError(json.error);
|
|
140
130
|
}
|
|
141
131
|
throw new Error(`Unexpected errror: ${text}`);
|
|
@@ -148,12 +138,12 @@ export class Client {
|
|
|
148
138
|
|
|
149
139
|
async getRules(): Promise<Stuntman.LiveRule[]> {
|
|
150
140
|
const response = await this.fetch(`${this.baseUrl}/rules`);
|
|
151
|
-
return response.json() as
|
|
141
|
+
return (await response.json()) as Promise<Stuntman.LiveRule[]>;
|
|
152
142
|
}
|
|
153
143
|
|
|
154
144
|
async getRule(id: string): Promise<Stuntman.LiveRule> {
|
|
155
145
|
const response = await this.fetch(`${this.baseUrl}/rule/${encodeURIComponent(id)}`);
|
|
156
|
-
return response.json() as
|
|
146
|
+
return (await response.json()) as Stuntman.LiveRule;
|
|
157
147
|
}
|
|
158
148
|
|
|
159
149
|
async disableRule(id: string): Promise<void> {
|
|
@@ -175,15 +165,15 @@ export class Client {
|
|
|
175
165
|
body: JSON.stringify(serializedRule),
|
|
176
166
|
headers: { 'content-type': 'application/json' },
|
|
177
167
|
});
|
|
178
|
-
return response.json() as
|
|
168
|
+
return (await response.json()) as Stuntman.Rule;
|
|
179
169
|
}
|
|
180
170
|
|
|
181
171
|
// TODO improve filtering by timestamp from - to, multiple labels, etc.
|
|
182
|
-
async getTraffic(rule: Stuntman.Rule): Promise<
|
|
183
|
-
async getTraffic(ruleIdOrLabel: string): Promise<
|
|
184
|
-
async getTraffic(ruleOrIdOrLabel: string | Stuntman.Rule): Promise<
|
|
172
|
+
async getTraffic(rule: Stuntman.Rule): Promise<Stuntman.LogEntry[]>;
|
|
173
|
+
async getTraffic(ruleIdOrLabel: string): Promise<Stuntman.LogEntry[]>;
|
|
174
|
+
async getTraffic(ruleOrIdOrLabel: string | Stuntman.Rule): Promise<Stuntman.LogEntry[]> {
|
|
185
175
|
const ruleId = typeof ruleOrIdOrLabel === 'object' ? ruleOrIdOrLabel.id : ruleOrIdOrLabel;
|
|
186
176
|
const response = await this.fetch(`${this.baseUrl}/traffic${ruleId ? `/${encodeURIComponent(ruleId)}` : ''}`);
|
|
187
|
-
return response.json() as
|
|
177
|
+
return (await response.json()) as Stuntman.LogEntry[];
|
|
188
178
|
}
|
|
189
179
|
}
|
package/src/clientError.ts
CHANGED
package/src/ruleBuilder.ts
CHANGED
|
@@ -40,6 +40,9 @@ class RuleBuilderBaseBase {
|
|
|
40
40
|
id: uuidv4(),
|
|
41
41
|
ttlSeconds: DEFAULT_RULE_TTL_SECONDS,
|
|
42
42
|
priority: DEFAULT_RULE_PRIORITY,
|
|
43
|
+
actions: {
|
|
44
|
+
mockResponse: { status: 200 },
|
|
45
|
+
},
|
|
43
46
|
matches: {
|
|
44
47
|
localFn: (req: Stuntman.Request): Stuntman.RuleMatchResult => {
|
|
45
48
|
const ___url = new URL(req.url);
|
|
@@ -56,10 +59,10 @@ class RuleBuilderBaseBase {
|
|
|
56
59
|
return { result: false, description: `${parentPath} is falsey` };
|
|
57
60
|
}
|
|
58
61
|
const [rawKey, ...rest] = path.split('.');
|
|
59
|
-
const key = rawKey.replace(arrayIndexerRegex, '');
|
|
60
|
-
const shouldBeArray = arrayIndexerRegex.test(rawKey);
|
|
62
|
+
const key = (rawKey ?? '').replace(arrayIndexerRegex, '');
|
|
63
|
+
const shouldBeArray = rawKey ? arrayIndexerRegex.test(rawKey) : false;
|
|
61
64
|
const arrayIndex =
|
|
62
|
-
(arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex || '').length > 0
|
|
65
|
+
rawKey && (arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex || '').length > 0
|
|
63
66
|
? Number(arrayIndexerRegex.exec(rawKey)?.groups?.arrayIndex)
|
|
64
67
|
: Number.NaN;
|
|
65
68
|
const actualValue = key ? obj[key] : obj;
|
|
@@ -79,11 +82,11 @@ class RuleBuilderBaseBase {
|
|
|
79
82
|
const result = shouldBeArray
|
|
80
83
|
? !Number.isInteger(arrayIndex) || actualValue.length >= Number(arrayIndex)
|
|
81
84
|
: actualValue !== undefined;
|
|
82
|
-
return { result, description: `${currentPath}` };
|
|
85
|
+
return { result, description: `${currentPath} === undefined` };
|
|
83
86
|
}
|
|
84
87
|
if (!shouldBeArray) {
|
|
85
88
|
const result = value instanceof RegExp ? value.test(actualValue) : value === actualValue;
|
|
86
|
-
return { result, description: `${currentPath}` };
|
|
89
|
+
return { result, description: `${currentPath} === "${actualValue}"` };
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
if (shouldBeArray) {
|
|
@@ -289,18 +292,17 @@ class RuleBuilderBaseBase {
|
|
|
289
292
|
description: `type "${matchBuilderVariables.bodyGql.type}" !== "${req.gqlBody.type}"`,
|
|
290
293
|
};
|
|
291
294
|
}
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
};
|
|
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
|
+
}
|
|
304
306
|
}
|
|
305
307
|
}
|
|
306
308
|
}
|
|
@@ -529,7 +531,11 @@ class RuleBuilderInitialized extends RuleBuilderBase {
|
|
|
529
531
|
if (!keyRegex.test(keyOrMatcher)) {
|
|
530
532
|
throw new Error(`invalid key "${keyOrMatcher}"`);
|
|
531
533
|
}
|
|
532
|
-
|
|
534
|
+
if (withValue === undefined) {
|
|
535
|
+
this._matchBuilderVariables.bodyJson.push({ key: keyOrMatcher });
|
|
536
|
+
} else {
|
|
537
|
+
this._matchBuilderVariables.bodyJson.push({ key: keyOrMatcher, value: withValue });
|
|
538
|
+
}
|
|
533
539
|
return this;
|
|
534
540
|
}
|
|
535
541
|
if (withValue !== undefined) {
|
|
@@ -543,11 +549,15 @@ class RuleBuilderInitialized extends RuleBuilderBase {
|
|
|
543
549
|
}
|
|
544
550
|
|
|
545
551
|
withBodyGql(gqlMatcher: GQLRequestMatcher): RuleBuilderInitialized {
|
|
552
|
+
if (this._matchBuilderVariables.bodyGql) {
|
|
553
|
+
throw new Error('gqlMatcher already set');
|
|
554
|
+
}
|
|
546
555
|
this._matchBuilderVariables.bodyGql = gqlMatcher;
|
|
547
556
|
return this;
|
|
548
557
|
}
|
|
549
558
|
|
|
550
559
|
proxyPass(): Stuntman.SerializableRule {
|
|
560
|
+
this.rule.actions = { proxyPass: true };
|
|
551
561
|
return this.rule;
|
|
552
562
|
}
|
|
553
563
|
|
package/dist/apiClient.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type * as Stuntman from '@stuntman/shared';
|
|
2
|
-
type ClientOptions = {
|
|
3
|
-
protocol?: 'http' | 'https';
|
|
4
|
-
host?: string;
|
|
5
|
-
port?: number;
|
|
6
|
-
timeout?: number;
|
|
7
|
-
apiKey?: string;
|
|
8
|
-
};
|
|
9
|
-
export declare class Client {
|
|
10
|
-
private options;
|
|
11
|
-
private get baseUrl();
|
|
12
|
-
constructor(options?: ClientOptions);
|
|
13
|
-
private fetch;
|
|
14
|
-
getRules(): Promise<Stuntman.LiveRule[]>;
|
|
15
|
-
getRule(id: string): Promise<Stuntman.LiveRule>;
|
|
16
|
-
disableRule(id: string): Promise<void>;
|
|
17
|
-
enableRule(id: string): Promise<void>;
|
|
18
|
-
removeRule(id: string): Promise<void>;
|
|
19
|
-
addRule(rule: Stuntman.SerializableRule): Promise<Stuntman.Rule>;
|
|
20
|
-
getTraffic(rule: Stuntman.Rule): Promise<Record<string, Stuntman.LogEntry>>;
|
|
21
|
-
getTraffic(ruleIdOrLabel: string): Promise<Record<string, Stuntman.LogEntry>>;
|
|
22
|
-
}
|
|
23
|
-
export {};
|
package/dist/apiClient.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Client = void 0;
|
|
7
|
-
const serialize_javascript_1 = __importDefault(require("serialize-javascript"));
|
|
8
|
-
const clientError_1 = require("./clientError");
|
|
9
|
-
const shared_1 = require("@stuntman/shared");
|
|
10
|
-
const SERIALIZE_JAVASCRIPT_OPTIONS = {
|
|
11
|
-
unsafe: true,
|
|
12
|
-
ignoreFunction: true,
|
|
13
|
-
};
|
|
14
|
-
const getFunctionParams = (func) => {
|
|
15
|
-
const funstr = func.toString();
|
|
16
|
-
const params = funstr.slice(funstr.indexOf('(') + 1, funstr.indexOf(')')).match(/([^\s,]+)/g) || new Array();
|
|
17
|
-
if (params.includes('=')) {
|
|
18
|
-
throw new Error('default argument values are not supported');
|
|
19
|
-
}
|
|
20
|
-
return params;
|
|
21
|
-
};
|
|
22
|
-
const serializeApiFunction = (fn, variables) => {
|
|
23
|
-
const variableInitializer = [];
|
|
24
|
-
const functionParams = getFunctionParams(fn);
|
|
25
|
-
if (variables) {
|
|
26
|
-
for (const varName of Object.keys(variables)) {
|
|
27
|
-
let varValue = variables[varName];
|
|
28
|
-
if (varValue === undefined || varValue === null || typeof varValue === 'number' || typeof varValue === 'boolean') {
|
|
29
|
-
varValue = `${varValue}`;
|
|
30
|
-
}
|
|
31
|
-
else if (typeof varValue === 'string') {
|
|
32
|
-
varValue = `${(0, serialize_javascript_1.default)(variables[varName], SERIALIZE_JAVASCRIPT_OPTIONS)}`;
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
varValue = `eval('(${(0, serialize_javascript_1.default)(variables[varName], SERIALIZE_JAVASCRIPT_OPTIONS).replace(/'/g, "\\'")})')`;
|
|
36
|
-
}
|
|
37
|
-
variableInitializer.push(`const ${varName} = ${varValue};`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const functionString = fn.toString();
|
|
41
|
-
const serializedHeader = `return ((${functionParams.map((_param, index) => `____arg${index}`).join(',')}) => {`;
|
|
42
|
-
const serializedParams = `${functionParams
|
|
43
|
-
.map((_param, index) => `const ${functionParams[index]} = ____arg${index};`)
|
|
44
|
-
.join('\n')}`;
|
|
45
|
-
const serializedVariables = `${variableInitializer.join('\n')}`;
|
|
46
|
-
// prettier-ignore
|
|
47
|
-
const serializedFunction = `return (${functionString.substring(0, functionString.indexOf('('))}()${functionString.substring(functionString.indexOf(')') + 1)})(); })(${functionParams.map((_param, index) => `____arg${index}`).join(',')})`;
|
|
48
|
-
if (!serializedParams && !serializedVariables) {
|
|
49
|
-
return `${serializedHeader}${serializedFunction}`;
|
|
50
|
-
}
|
|
51
|
-
return [serializedHeader, serializedParams, serializedVariables, serializedFunction].filter((x) => !!x).join('\n');
|
|
52
|
-
};
|
|
53
|
-
const keysOf = (obj) => {
|
|
54
|
-
return Array.from(Object.keys(obj));
|
|
55
|
-
};
|
|
56
|
-
const serializeRemotableFunctions = (obj) => {
|
|
57
|
-
const objectKeys = keysOf(obj);
|
|
58
|
-
if (!objectKeys || objectKeys.length === 0) {
|
|
59
|
-
return obj;
|
|
60
|
-
}
|
|
61
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
62
|
-
// @ts-ignore
|
|
63
|
-
const output = {};
|
|
64
|
-
for (const key of objectKeys) {
|
|
65
|
-
if (typeof obj[key] === 'object') {
|
|
66
|
-
if ('localFn' in obj[key]) {
|
|
67
|
-
const remotableFunction = obj[key];
|
|
68
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
output[key] = {
|
|
71
|
-
remoteFn: serializeApiFunction(remotableFunction.localFn, remotableFunction.localVariables),
|
|
72
|
-
localFn: remotableFunction.localFn.toString(),
|
|
73
|
-
localVariables: (0, serialize_javascript_1.default)(remotableFunction.localVariables, SERIALIZE_JAVASCRIPT_OPTIONS),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
78
|
-
// @ts-ignore
|
|
79
|
-
output[key] = serializeRemotableFunctions(obj[key]);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
84
|
-
// @ts-ignore
|
|
85
|
-
output[key] = obj[key];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return output;
|
|
89
|
-
};
|
|
90
|
-
class Client {
|
|
91
|
-
get baseUrl() {
|
|
92
|
-
return `${this.options.protocol}://${this.options.host}${this.options.port ? `:${this.options.port}` : ''}`;
|
|
93
|
-
}
|
|
94
|
-
constructor(options) {
|
|
95
|
-
this.options = {
|
|
96
|
-
...options,
|
|
97
|
-
timeout: (options === null || options === void 0 ? void 0 : options.timeout) || 60000,
|
|
98
|
-
host: (options === null || options === void 0 ? void 0 : options.host) || 'localhost',
|
|
99
|
-
protocol: (options === null || options === void 0 ? void 0 : options.protocol) || 'http',
|
|
100
|
-
port: (options === null || options === void 0 ? void 0 : options.port) || (options === null || options === void 0 ? void 0 : options.protocol) ? (options.protocol === 'https' ? 443 : 80) : shared_1.DEFAULT_API_PORT,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
async fetch(url, init) {
|
|
104
|
-
var _a;
|
|
105
|
-
const controller = new AbortController();
|
|
106
|
-
const timeout = setTimeout(() => {
|
|
107
|
-
controller.abort();
|
|
108
|
-
}, this.options.timeout);
|
|
109
|
-
try {
|
|
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
|
-
});
|
|
118
|
-
if (!response.ok) {
|
|
119
|
-
const text = await response.text();
|
|
120
|
-
let json;
|
|
121
|
-
try {
|
|
122
|
-
json = JSON.parse(text);
|
|
123
|
-
}
|
|
124
|
-
catch (kiss) {
|
|
125
|
-
// and swallow
|
|
126
|
-
}
|
|
127
|
-
if ('error' in json) {
|
|
128
|
-
throw new clientError_1.ClientError(json.error);
|
|
129
|
-
}
|
|
130
|
-
throw new Error(`Unexpected errror: ${text}`);
|
|
131
|
-
}
|
|
132
|
-
return response;
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
clearTimeout(timeout);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async getRules() {
|
|
139
|
-
const response = await this.fetch(`${this.baseUrl}/rules`);
|
|
140
|
-
return response.json();
|
|
141
|
-
}
|
|
142
|
-
async getRule(id) {
|
|
143
|
-
const response = await this.fetch(`${this.baseUrl}/rule/${encodeURIComponent(id)}`);
|
|
144
|
-
return response.json();
|
|
145
|
-
}
|
|
146
|
-
async disableRule(id) {
|
|
147
|
-
await this.fetch(`${this.baseUrl}/rule/${encodeURIComponent(id)}/disable`);
|
|
148
|
-
}
|
|
149
|
-
async enableRule(id) {
|
|
150
|
-
await this.fetch(`${this.baseUrl}/rule/${encodeURIComponent(id)}/enable`);
|
|
151
|
-
}
|
|
152
|
-
async removeRule(id) {
|
|
153
|
-
await this.fetch(`${this.baseUrl}/rule/${encodeURIComponent(id)}/remove`);
|
|
154
|
-
}
|
|
155
|
-
async addRule(rule) {
|
|
156
|
-
const serializedRule = serializeRemotableFunctions(rule);
|
|
157
|
-
const response = await this.fetch(`${this.baseUrl}/rule`, {
|
|
158
|
-
method: 'POST',
|
|
159
|
-
body: JSON.stringify(serializedRule),
|
|
160
|
-
headers: { 'content-type': 'application/json' },
|
|
161
|
-
});
|
|
162
|
-
return response.json();
|
|
163
|
-
}
|
|
164
|
-
async getTraffic(ruleOrIdOrLabel) {
|
|
165
|
-
const ruleId = typeof ruleOrIdOrLabel === 'object' ? ruleOrIdOrLabel.id : ruleOrIdOrLabel;
|
|
166
|
-
const response = await this.fetch(`${this.baseUrl}/traffic${ruleId ? `/${encodeURIComponent(ruleId)}` : ''}`);
|
|
167
|
-
return response.json();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
exports.Client = Client;
|
package/dist/clientError.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { AppError } from '@stuntman/shared';
|
|
2
|
-
import type * as Stuntman from '@stuntman/shared';
|
|
3
|
-
export declare enum HttpCode {
|
|
4
|
-
OK = 200,
|
|
5
|
-
NO_CONTENT = 204,
|
|
6
|
-
BAD_REQUEST = 400,
|
|
7
|
-
UNAUTHORIZED = 401,
|
|
8
|
-
NOT_FOUND = 404,
|
|
9
|
-
CONFLICT = 409,
|
|
10
|
-
UNPROCESSABLE_ENTITY = 422,
|
|
11
|
-
INTERNAL_SERVER_ERROR = 500
|
|
12
|
-
}
|
|
13
|
-
export declare class ClientError extends AppError {
|
|
14
|
-
readonly originalStack?: string;
|
|
15
|
-
constructor(args: Stuntman.AppError & {
|
|
16
|
-
stack?: string;
|
|
17
|
-
});
|
|
18
|
-
}
|
package/dist/clientError.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ClientError = exports.HttpCode = void 0;
|
|
4
|
-
const shared_1 = require("@stuntman/shared");
|
|
5
|
-
var HttpCode;
|
|
6
|
-
(function (HttpCode) {
|
|
7
|
-
HttpCode[HttpCode["OK"] = 200] = "OK";
|
|
8
|
-
HttpCode[HttpCode["NO_CONTENT"] = 204] = "NO_CONTENT";
|
|
9
|
-
HttpCode[HttpCode["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
10
|
-
HttpCode[HttpCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
11
|
-
HttpCode[HttpCode["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
12
|
-
HttpCode[HttpCode["CONFLICT"] = 409] = "CONFLICT";
|
|
13
|
-
HttpCode[HttpCode["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
|
|
14
|
-
HttpCode[HttpCode["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
|
|
15
|
-
})(HttpCode = exports.HttpCode || (exports.HttpCode = {}));
|
|
16
|
-
class ClientError extends shared_1.AppError {
|
|
17
|
-
constructor(args) {
|
|
18
|
-
super(args);
|
|
19
|
-
this.originalStack = args.stack;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
exports.ClientError = ClientError;
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ruleBuilder = exports.StuntmanClient = void 0;
|
|
4
|
-
var apiClient_1 = require("./apiClient");
|
|
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; } });
|
package/dist/ruleBuilder.d.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type * as Stuntman from '@stuntman/shared';
|
|
2
|
-
type KeyValueMatcher = string | RegExp | {
|
|
3
|
-
key: string;
|
|
4
|
-
value?: string | RegExp;
|
|
5
|
-
};
|
|
6
|
-
type ObjectValueMatcher = string | RegExp | number | boolean | null;
|
|
7
|
-
type ObjectKeyValueMatcher = {
|
|
8
|
-
key: string;
|
|
9
|
-
value?: ObjectValueMatcher;
|
|
10
|
-
};
|
|
11
|
-
type GQLRequestMatcher = {
|
|
12
|
-
operationName?: string | RegExp;
|
|
13
|
-
variables?: ObjectKeyValueMatcher[];
|
|
14
|
-
query?: string | RegExp;
|
|
15
|
-
type?: 'query' | 'mutation';
|
|
16
|
-
methodName?: string | RegExp;
|
|
17
|
-
};
|
|
18
|
-
type MatchBuilderVariables = {
|
|
19
|
-
filter?: string | RegExp;
|
|
20
|
-
hostname?: string | RegExp;
|
|
21
|
-
pathname?: string | RegExp;
|
|
22
|
-
port?: number | string | RegExp;
|
|
23
|
-
searchParams?: KeyValueMatcher[];
|
|
24
|
-
headers?: KeyValueMatcher[];
|
|
25
|
-
bodyText?: string | RegExp | null;
|
|
26
|
-
bodyJson?: ObjectKeyValueMatcher[];
|
|
27
|
-
bodyGql?: GQLRequestMatcher;
|
|
28
|
-
};
|
|
29
|
-
declare class RuleBuilderBaseBase {
|
|
30
|
-
protected rule: Stuntman.SerializableRule;
|
|
31
|
-
protected _matchBuilderVariables: MatchBuilderVariables;
|
|
32
|
-
constructor(rule?: Stuntman.SerializableRule, _matchBuilderVariables?: MatchBuilderVariables);
|
|
33
|
-
}
|
|
34
|
-
declare class RuleBuilderBase extends RuleBuilderBaseBase {
|
|
35
|
-
limitedUse(hitCount: number): this;
|
|
36
|
-
singleUse(): this;
|
|
37
|
-
storeTraffic(): this;
|
|
38
|
-
disabled(): void;
|
|
39
|
-
}
|
|
40
|
-
declare class RuleBuilder extends RuleBuilderBase {
|
|
41
|
-
raisePriority(by?: number): this;
|
|
42
|
-
decreasePriority(by?: number): this;
|
|
43
|
-
customTtl(ttlSeconds: number): this;
|
|
44
|
-
customId(id: string): this;
|
|
45
|
-
onRequestTo(filter: string | RegExp): RuleBuilderInitialized;
|
|
46
|
-
onRequestToHostname(hostname: string | RegExp): RuleBuilderInitialized;
|
|
47
|
-
onRequestToPathname(pathname: string | RegExp): RuleBuilderInitialized;
|
|
48
|
-
onRequestToPort(port: string | number | RegExp): RuleBuilderInitialized;
|
|
49
|
-
onAnyRequest(): RuleBuilderInitialized;
|
|
50
|
-
}
|
|
51
|
-
declare class RuleBuilderInitialized extends RuleBuilderBase {
|
|
52
|
-
withHostname(hostname: string | RegExp): this;
|
|
53
|
-
withPathname(pathname: string | RegExp): this;
|
|
54
|
-
withPort(port: number | string | RegExp): this;
|
|
55
|
-
withSearchParam(key: string | RegExp): RuleBuilderInitialized;
|
|
56
|
-
withSearchParam(key: string, value?: string | RegExp): RuleBuilderInitialized;
|
|
57
|
-
withSearchParams(params: KeyValueMatcher[]): RuleBuilderInitialized;
|
|
58
|
-
withHeader(key: string | RegExp): RuleBuilderInitialized;
|
|
59
|
-
withHeader(key: string, value?: string | RegExp): RuleBuilderInitialized;
|
|
60
|
-
withHeaders(...headers: KeyValueMatcher[]): RuleBuilderInitialized;
|
|
61
|
-
withBodyText(includes: string): RuleBuilderInitialized;
|
|
62
|
-
withBodyText(matches: RegExp): RuleBuilderInitialized;
|
|
63
|
-
withoutBody(): RuleBuilderInitialized;
|
|
64
|
-
withBodyJson(hasKey: string): RuleBuilderInitialized;
|
|
65
|
-
withBodyJson(hasKey: string, withValue: ObjectValueMatcher): RuleBuilderInitialized;
|
|
66
|
-
withBodyJson(matches: ObjectKeyValueMatcher): RuleBuilderInitialized;
|
|
67
|
-
withBodyGql(gqlMatcher: GQLRequestMatcher): RuleBuilderInitialized;
|
|
68
|
-
proxyPass(): Stuntman.SerializableRule;
|
|
69
|
-
mockResponse(staticResponse: Stuntman.Response): Stuntman.SerializableRule;
|
|
70
|
-
mockResponse(generationFunction: Stuntman.RemotableFunction<Stuntman.ResponseGenerationFn>): 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;
|
|
74
|
-
}
|
|
75
|
-
declare class RuleBuilderRequestInitialized extends RuleBuilderBase {
|
|
76
|
-
modifyResponse(modifyFunction: Stuntman.ResponseManipulationFn | Stuntman.RemotableFunction<Stuntman.ResponseManipulationFn>, localVariables?: Stuntman.LocalVariables): Stuntman.SerializableRule;
|
|
77
|
-
}
|
|
78
|
-
export declare const ruleBuilder: () => RuleBuilder;
|
|
79
|
-
export {};
|
package/dist/ruleBuilder.js
DELETED
|
@@ -1,523 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ruleBuilder = void 0;
|
|
4
|
-
const uuid_1 = require("uuid");
|
|
5
|
-
const shared_1 = require("@stuntman/shared");
|
|
6
|
-
// TODO add fluent match on multipart from data
|
|
7
|
-
class RuleBuilderBaseBase {
|
|
8
|
-
constructor(rule, _matchBuilderVariables) {
|
|
9
|
-
this._matchBuilderVariables = _matchBuilderVariables || {};
|
|
10
|
-
this.rule = rule || {
|
|
11
|
-
id: (0, uuid_1.v4)(),
|
|
12
|
-
ttlSeconds: shared_1.DEFAULT_RULE_TTL_SECONDS,
|
|
13
|
-
priority: shared_1.DEFAULT_RULE_PRIORITY,
|
|
14
|
-
matches: {
|
|
15
|
-
localFn: (req) => {
|
|
16
|
-
var _a, _b, _c, _d;
|
|
17
|
-
const ___url = new URL(req.url);
|
|
18
|
-
const ___headers = req.rawHeaders;
|
|
19
|
-
const arrayIndexerRegex = /\[(?<arrayIndex>[0-9]*)\]/i;
|
|
20
|
-
const matchObject = (obj, path, value, parentPath) => {
|
|
21
|
-
var _a, _b, _c, _d;
|
|
22
|
-
if (!obj) {
|
|
23
|
-
return { result: false, description: `${parentPath} is falsey` };
|
|
24
|
-
}
|
|
25
|
-
const [rawKey, ...rest] = path.split('.');
|
|
26
|
-
const key = rawKey.replace(arrayIndexerRegex, '');
|
|
27
|
-
const shouldBeArray = arrayIndexerRegex.test(rawKey);
|
|
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;
|
|
31
|
-
const actualValue = key ? obj[key] : obj;
|
|
32
|
-
const currentPath = `${parentPath ? `${parentPath}.` : ''}${rawKey}`;
|
|
33
|
-
if (value === undefined && actualValue === undefined) {
|
|
34
|
-
return { result: false, description: `${currentPath}=undefined` };
|
|
35
|
-
}
|
|
36
|
-
if (rest.length === 0) {
|
|
37
|
-
if (shouldBeArray &&
|
|
38
|
-
(!Array.isArray(actualValue) ||
|
|
39
|
-
(Number.isInteger(arrayIndex) && actualValue.length <= Number(arrayIndex)))) {
|
|
40
|
-
return { result: false, description: `${currentPath} empty array` };
|
|
41
|
-
}
|
|
42
|
-
if (value === undefined) {
|
|
43
|
-
const result = shouldBeArray
|
|
44
|
-
? !Number.isInteger(arrayIndex) || actualValue.length >= Number(arrayIndex)
|
|
45
|
-
: actualValue !== undefined;
|
|
46
|
-
return { result, description: `${currentPath}` };
|
|
47
|
-
}
|
|
48
|
-
if (!shouldBeArray) {
|
|
49
|
-
const result = value instanceof RegExp ? value.test(actualValue) : value === actualValue;
|
|
50
|
-
return { result, description: `${currentPath}` };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (shouldBeArray) {
|
|
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}` };
|
|
59
|
-
}
|
|
60
|
-
if (typeof actualValue !== 'object') {
|
|
61
|
-
return { result: false, description: `${currentPath} not an object` };
|
|
62
|
-
}
|
|
63
|
-
return matchObject(actualValue, rest.join('.'), value, currentPath);
|
|
64
|
-
};
|
|
65
|
-
const ___matchesValue = (matcher, value) => {
|
|
66
|
-
if (matcher === undefined) {
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
if (typeof matcher !== 'string' && !(matcher instanceof RegExp) && typeof matcher !== 'number') {
|
|
70
|
-
throw new Error('invalid matcher');
|
|
71
|
-
}
|
|
72
|
-
if (typeof matcher === 'string' && matcher !== value) {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
if (matcher instanceof RegExp && (typeof value !== 'string' || !matcher.test(value))) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
if (typeof matcher === 'number' && (typeof value !== 'number' || matcher !== value)) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
return true;
|
|
82
|
-
};
|
|
83
|
-
if (!___matchesValue(matchBuilderVariables.filter, req.url)) {
|
|
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
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (!___matchesValue(matchBuilderVariables.hostname, ___url.hostname)) {
|
|
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
|
-
};
|
|
94
|
-
}
|
|
95
|
-
if (!___matchesValue(matchBuilderVariables.pathname, ___url.pathname)) {
|
|
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
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (matchBuilderVariables.searchParams) {
|
|
113
|
-
for (const searchParamMatcher of matchBuilderVariables.searchParams) {
|
|
114
|
-
if (typeof searchParamMatcher === 'string') {
|
|
115
|
-
const result = ___url.searchParams.has(searchParamMatcher);
|
|
116
|
-
return { result, description: `searchParams.has("${searchParamMatcher}")` };
|
|
117
|
-
}
|
|
118
|
-
if (searchParamMatcher instanceof RegExp) {
|
|
119
|
-
const result = Array.from(___url.searchParams.keys()).some((key) => searchParamMatcher.test(key));
|
|
120
|
-
return { result, description: `searchParams.keys() matches ${searchParamMatcher.toString()}` };
|
|
121
|
-
}
|
|
122
|
-
if (!___url.searchParams.has(searchParamMatcher.key)) {
|
|
123
|
-
return { result: false, description: `searchParams.has("${searchParamMatcher.key}")` };
|
|
124
|
-
}
|
|
125
|
-
if (searchParamMatcher.value) {
|
|
126
|
-
const value = ___url.searchParams.get(searchParamMatcher.key);
|
|
127
|
-
if (value === null) {
|
|
128
|
-
return {
|
|
129
|
-
result: false,
|
|
130
|
-
description: `searchParams.get("${searchParamMatcher.key}") === null`,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
if (!___matchesValue(searchParamMatcher.value, value)) {
|
|
134
|
-
return {
|
|
135
|
-
result: false,
|
|
136
|
-
description: `searchParams.get("${searchParamMatcher.key}") = "${searchParamMatcher.value}"`,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (matchBuilderVariables.headers) {
|
|
143
|
-
for (const headerMatcher of matchBuilderVariables.headers) {
|
|
144
|
-
if (typeof headerMatcher === 'string') {
|
|
145
|
-
const result = ___headers.has(headerMatcher);
|
|
146
|
-
if (result) {
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
return { result: false, description: `headers.has("${headerMatcher}")` };
|
|
150
|
-
}
|
|
151
|
-
if (headerMatcher instanceof RegExp) {
|
|
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()}` };
|
|
157
|
-
}
|
|
158
|
-
if (!___headers.has(headerMatcher.key)) {
|
|
159
|
-
return { result: false, description: `headers.has("${headerMatcher.key}")` };
|
|
160
|
-
}
|
|
161
|
-
if (headerMatcher.value) {
|
|
162
|
-
const value = ___headers.get(headerMatcher.key);
|
|
163
|
-
if (value === null) {
|
|
164
|
-
return { result: false, description: `headers.get("${headerMatcher.key}") === null` };
|
|
165
|
-
}
|
|
166
|
-
if (!___matchesValue(headerMatcher.value, value)) {
|
|
167
|
-
return {
|
|
168
|
-
result: false,
|
|
169
|
-
description: `headerMatcher.get("${headerMatcher.key}") = "${headerMatcher.value}"`,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
if (matchBuilderVariables.bodyText === null && !!req.body) {
|
|
176
|
-
return { result: false, description: `empty body` };
|
|
177
|
-
}
|
|
178
|
-
if (matchBuilderVariables.bodyText) {
|
|
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
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (matchBuilderVariables.bodyJson) {
|
|
198
|
-
let json;
|
|
199
|
-
try {
|
|
200
|
-
json = JSON.parse(req.body);
|
|
201
|
-
}
|
|
202
|
-
catch (kiss) {
|
|
203
|
-
return { result: false, description: `unparseable json` };
|
|
204
|
-
}
|
|
205
|
-
if (!json) {
|
|
206
|
-
return { result: false, description: `empty json object` };
|
|
207
|
-
}
|
|
208
|
-
for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyJson)
|
|
209
|
-
? matchBuilderVariables.bodyJson
|
|
210
|
-
: [matchBuilderVariables.bodyJson]) {
|
|
211
|
-
const matchObjectResult = matchObject(json, jsonMatcher.key, jsonMatcher.value);
|
|
212
|
-
if (!matchObjectResult.result) {
|
|
213
|
-
return { result: false, description: `$.${jsonMatcher.key} != "${jsonMatcher.value}"` };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
if (matchBuilderVariables.bodyGql) {
|
|
218
|
-
if (!req.gqlBody) {
|
|
219
|
-
return { result: false, description: `not a gql body` };
|
|
220
|
-
}
|
|
221
|
-
if (!___matchesValue(matchBuilderVariables.bodyGql.methodName, req.gqlBody.methodName)) {
|
|
222
|
-
return {
|
|
223
|
-
result: false,
|
|
224
|
-
description: `methodName "${matchBuilderVariables.bodyGql.methodName}" !== "${req.gqlBody.methodName}"`,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
if (!___matchesValue(matchBuilderVariables.bodyGql.operationName, req.gqlBody.operationName)) {
|
|
228
|
-
return {
|
|
229
|
-
result: false,
|
|
230
|
-
description: `operationName "${matchBuilderVariables.bodyGql.operationName}" !== "${req.gqlBody.operationName}"`,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
if (!___matchesValue(matchBuilderVariables.bodyGql.query, req.gqlBody.query)) {
|
|
234
|
-
return {
|
|
235
|
-
result: false,
|
|
236
|
-
description: `query "${matchBuilderVariables.bodyGql.query}" !== "${req.gqlBody.query}"`,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
if (!___matchesValue(matchBuilderVariables.bodyGql.type, req.gqlBody.type)) {
|
|
240
|
-
return {
|
|
241
|
-
result: false,
|
|
242
|
-
description: `type "${matchBuilderVariables.bodyGql.type}" !== "${req.gqlBody.type}"`,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (!matchBuilderVariables.bodyGql.variables) {
|
|
246
|
-
return { result: true, description: `no variables to match` };
|
|
247
|
-
}
|
|
248
|
-
for (const jsonMatcher of Array.isArray(matchBuilderVariables.bodyGql.variables)
|
|
249
|
-
? matchBuilderVariables.bodyGql.variables
|
|
250
|
-
: [matchBuilderVariables.bodyGql.variables]) {
|
|
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
|
-
};
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return { result: true, description: 'match' };
|
|
261
|
-
},
|
|
262
|
-
localVariables: { matchBuilderVariables: this._matchBuilderVariables },
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
class RuleBuilderBase extends RuleBuilderBaseBase {
|
|
268
|
-
limitedUse(hitCount) {
|
|
269
|
-
if (this.rule.removeAfterUse) {
|
|
270
|
-
throw new Error(`limit already set at ${this.rule.removeAfterUse}`);
|
|
271
|
-
}
|
|
272
|
-
if (Number.isNaN(hitCount) || !Number.isFinite(hitCount) || !Number.isInteger(hitCount) || hitCount <= 0) {
|
|
273
|
-
throw new Error('Invalid hitCount');
|
|
274
|
-
}
|
|
275
|
-
this.rule.removeAfterUse = hitCount;
|
|
276
|
-
return this;
|
|
277
|
-
}
|
|
278
|
-
singleUse() {
|
|
279
|
-
return this.limitedUse(1);
|
|
280
|
-
}
|
|
281
|
-
storeTraffic() {
|
|
282
|
-
this.rule.storeTraffic = true;
|
|
283
|
-
return this;
|
|
284
|
-
}
|
|
285
|
-
disabled() {
|
|
286
|
-
this.rule.isEnabled = false;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
class RuleBuilder extends RuleBuilderBase {
|
|
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
|
-
}
|
|
294
|
-
const subtract = by !== null && by !== void 0 ? by : 1;
|
|
295
|
-
if (subtract >= shared_1.DEFAULT_RULE_PRIORITY) {
|
|
296
|
-
throw new Error(`Unable to raise priority over the default ${shared_1.DEFAULT_RULE_PRIORITY}`);
|
|
297
|
-
}
|
|
298
|
-
this.rule.priority = shared_1.DEFAULT_RULE_PRIORITY - subtract;
|
|
299
|
-
return this;
|
|
300
|
-
}
|
|
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
|
-
}
|
|
305
|
-
const add = by !== null && by !== void 0 ? by : 1;
|
|
306
|
-
this.rule.priority = shared_1.DEFAULT_RULE_PRIORITY + add;
|
|
307
|
-
return this;
|
|
308
|
-
}
|
|
309
|
-
customTtl(ttlSeconds) {
|
|
310
|
-
if (Number.isNaN(ttlSeconds) || !Number.isInteger(ttlSeconds) || !Number.isFinite(ttlSeconds) || ttlSeconds < 0) {
|
|
311
|
-
throw new Error('Invalid ttl');
|
|
312
|
-
}
|
|
313
|
-
if (ttlSeconds < shared_1.MIN_RULE_TTL_SECONDS || ttlSeconds > shared_1.MAX_RULE_TTL_SECONDS) {
|
|
314
|
-
throw new Error(`ttl of ${ttlSeconds} seconds is outside range min: ${shared_1.MIN_RULE_TTL_SECONDS}, max:${shared_1.MAX_RULE_TTL_SECONDS}`);
|
|
315
|
-
}
|
|
316
|
-
this.rule.ttlSeconds = ttlSeconds;
|
|
317
|
-
return this;
|
|
318
|
-
}
|
|
319
|
-
customId(id) {
|
|
320
|
-
this.rule.id = id;
|
|
321
|
-
return this;
|
|
322
|
-
}
|
|
323
|
-
onRequestTo(filter) {
|
|
324
|
-
this._matchBuilderVariables.filter = filter;
|
|
325
|
-
return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
|
|
326
|
-
}
|
|
327
|
-
onRequestToHostname(hostname) {
|
|
328
|
-
this._matchBuilderVariables.hostname = hostname;
|
|
329
|
-
return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
|
|
330
|
-
}
|
|
331
|
-
onRequestToPathname(pathname) {
|
|
332
|
-
this._matchBuilderVariables.pathname = pathname;
|
|
333
|
-
return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
|
|
334
|
-
}
|
|
335
|
-
onRequestToPort(port) {
|
|
336
|
-
this._matchBuilderVariables.port = port;
|
|
337
|
-
return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
|
|
338
|
-
}
|
|
339
|
-
onAnyRequest() {
|
|
340
|
-
return new RuleBuilderInitialized(this.rule, this._matchBuilderVariables);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
class RuleBuilderInitialized extends RuleBuilderBase {
|
|
344
|
-
withHostname(hostname) {
|
|
345
|
-
if (this._matchBuilderVariables.hostname) {
|
|
346
|
-
throw new Error('hostname already set');
|
|
347
|
-
}
|
|
348
|
-
this._matchBuilderVariables.hostname = hostname;
|
|
349
|
-
return this;
|
|
350
|
-
}
|
|
351
|
-
withPathname(pathname) {
|
|
352
|
-
if (this._matchBuilderVariables.pathname) {
|
|
353
|
-
throw new Error('pathname already set');
|
|
354
|
-
}
|
|
355
|
-
this._matchBuilderVariables.pathname = pathname;
|
|
356
|
-
return this;
|
|
357
|
-
}
|
|
358
|
-
withPort(port) {
|
|
359
|
-
if (this._matchBuilderVariables.port) {
|
|
360
|
-
throw new Error('port already set');
|
|
361
|
-
}
|
|
362
|
-
this._matchBuilderVariables.port = port;
|
|
363
|
-
return this;
|
|
364
|
-
}
|
|
365
|
-
withSearchParam(key, value) {
|
|
366
|
-
if (!this._matchBuilderVariables.searchParams) {
|
|
367
|
-
this._matchBuilderVariables.searchParams = [];
|
|
368
|
-
}
|
|
369
|
-
if (!key) {
|
|
370
|
-
throw new Error('key cannot be empty');
|
|
371
|
-
}
|
|
372
|
-
if (!value) {
|
|
373
|
-
this._matchBuilderVariables.searchParams.push(key);
|
|
374
|
-
return this;
|
|
375
|
-
}
|
|
376
|
-
if (key instanceof RegExp) {
|
|
377
|
-
throw new Error('Unsupported regex param key with value');
|
|
378
|
-
}
|
|
379
|
-
this._matchBuilderVariables.searchParams.push({ key, value });
|
|
380
|
-
return this;
|
|
381
|
-
}
|
|
382
|
-
withSearchParams(params) {
|
|
383
|
-
if (!this._matchBuilderVariables.searchParams) {
|
|
384
|
-
this._matchBuilderVariables.searchParams = [];
|
|
385
|
-
}
|
|
386
|
-
for (const param of params) {
|
|
387
|
-
if (typeof param === 'string' || param instanceof RegExp) {
|
|
388
|
-
this.withSearchParam(param);
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
this.withSearchParam(param.key, param.value);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return this;
|
|
395
|
-
}
|
|
396
|
-
withHeader(key, value) {
|
|
397
|
-
if (!this._matchBuilderVariables.headers) {
|
|
398
|
-
this._matchBuilderVariables.headers = [];
|
|
399
|
-
}
|
|
400
|
-
if (!key) {
|
|
401
|
-
throw new Error('key cannot be empty');
|
|
402
|
-
}
|
|
403
|
-
if (!value) {
|
|
404
|
-
this._matchBuilderVariables.headers.push(key);
|
|
405
|
-
return this;
|
|
406
|
-
}
|
|
407
|
-
if (key instanceof RegExp) {
|
|
408
|
-
throw new Error('Unsupported regex param key with value');
|
|
409
|
-
}
|
|
410
|
-
this._matchBuilderVariables.headers.push({ key, value });
|
|
411
|
-
return this;
|
|
412
|
-
}
|
|
413
|
-
withHeaders(...headers) {
|
|
414
|
-
if (!this._matchBuilderVariables.headers) {
|
|
415
|
-
this._matchBuilderVariables.headers = [];
|
|
416
|
-
}
|
|
417
|
-
for (const header of headers) {
|
|
418
|
-
if (typeof header === 'string' || header instanceof RegExp) {
|
|
419
|
-
this.withHeader(header);
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
this.withHeader(header.key, header.value);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
return this;
|
|
426
|
-
}
|
|
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
|
-
}
|
|
434
|
-
this._matchBuilderVariables.bodyText = includesOrMatches;
|
|
435
|
-
return this;
|
|
436
|
-
}
|
|
437
|
-
withoutBody() {
|
|
438
|
-
if (this._matchBuilderVariables.bodyText) {
|
|
439
|
-
throw new Error('cannot use both withBodyText and withoutBody');
|
|
440
|
-
}
|
|
441
|
-
this._matchBuilderVariables.bodyText = null;
|
|
442
|
-
return this;
|
|
443
|
-
}
|
|
444
|
-
withBodyJson(keyOrMatcher, withValue) {
|
|
445
|
-
const keyRegex = /^(?:(?:[a-z0-9_-]+)|(?:\[[0-9]*\]))(?:\.(?:(?:[a-z0-9_-]+)|(?:\[[0-9]*\])))*$/i;
|
|
446
|
-
if (!this._matchBuilderVariables.bodyJson) {
|
|
447
|
-
this._matchBuilderVariables.bodyJson = [];
|
|
448
|
-
}
|
|
449
|
-
if (typeof keyOrMatcher === 'string') {
|
|
450
|
-
if (!keyRegex.test(keyOrMatcher)) {
|
|
451
|
-
throw new Error(`invalid key "${keyOrMatcher}"`);
|
|
452
|
-
}
|
|
453
|
-
this._matchBuilderVariables.bodyJson.push({ key: keyOrMatcher, value: withValue });
|
|
454
|
-
return this;
|
|
455
|
-
}
|
|
456
|
-
if (withValue !== undefined) {
|
|
457
|
-
throw new Error('invalid usage');
|
|
458
|
-
}
|
|
459
|
-
if (!keyRegex.test(keyOrMatcher.key)) {
|
|
460
|
-
throw new Error(`invalid key "${keyOrMatcher}"`);
|
|
461
|
-
}
|
|
462
|
-
this._matchBuilderVariables.bodyJson.push(keyOrMatcher);
|
|
463
|
-
return this;
|
|
464
|
-
}
|
|
465
|
-
withBodyGql(gqlMatcher) {
|
|
466
|
-
this._matchBuilderVariables.bodyGql = gqlMatcher;
|
|
467
|
-
return this;
|
|
468
|
-
}
|
|
469
|
-
proxyPass() {
|
|
470
|
-
return this.rule;
|
|
471
|
-
}
|
|
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
|
-
}
|
|
480
|
-
this.rule.actions = { mockResponse: response };
|
|
481
|
-
return this.rule;
|
|
482
|
-
}
|
|
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
|
-
}
|
|
491
|
-
this.rule.actions = { modifyRequest: modifyFunction };
|
|
492
|
-
return new RuleBuilderRequestInitialized(this.rule, this._matchBuilderVariables);
|
|
493
|
-
}
|
|
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
|
-
}
|
|
502
|
-
this.rule.actions = { modifyResponse: modifyFunction };
|
|
503
|
-
return this.rule;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
class RuleBuilderRequestInitialized extends RuleBuilderBase {
|
|
507
|
-
modifyResponse(modifyFunction, localVariables) {
|
|
508
|
-
if (!this.rule.actions) {
|
|
509
|
-
throw new Error('rule.actions not defined - builder implementation error');
|
|
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
|
-
}
|
|
518
|
-
this.rule.actions.modifyResponse = modifyFunction;
|
|
519
|
-
return this.rule;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
const ruleBuilder = () => new RuleBuilder();
|
|
523
|
-
exports.ruleBuilder = ruleBuilder;
|