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