@loadmill/core 0.3.50 → 0.3.53
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/conf/extrema.d.ts +1 -1
- package/dist/conf/extrema.js +2 -2
- package/dist/conf/extrema.js.map +1 -1
- package/dist/conf/types.d.ts +12 -0
- package/dist/conf/validate.d.ts +2 -1
- package/dist/conf/validate.js +6 -1
- package/dist/conf/validate.js.map +1 -1
- package/dist/multipart-form-data/form-data-utils.js +2 -2
- package/dist/multipart-form-data/form-data-utils.js.map +1 -1
- package/dist/multipart-form-data/is-binary-file.d.ts +2 -0
- package/dist/multipart-form-data/is-binary-file.js +215 -0
- package/dist/multipart-form-data/is-binary-file.js.map +1 -0
- package/dist/multipart-form-data/multipart-text-to-post-form-data.d.ts +5 -1
- package/dist/multipart-form-data/multipart-text-to-post-form-data.js +96 -35
- package/dist/multipart-form-data/multipart-text-to-post-form-data.js.map +1 -1
- package/dist/parameters/extractions.d.ts +2 -1
- package/dist/parameters/extractions.js.map +1 -1
- package/dist/parameters/index.d.ts +1 -1
- package/dist/parameters/index.js +4 -0
- package/dist/parameters/index.js.map +1 -1
- package/dist/request/index.d.ts +1 -0
- package/dist/request/index.js.map +1 -1
- package/package.json +7 -2
- package/src/conf/defaults.ts +0 -25
- package/src/conf/extrema.ts +0 -35
- package/src/conf/index.ts +0 -95
- package/src/conf/notifications.ts +0 -17
- package/src/conf/types.ts +0 -86
- package/src/conf/validate.ts +0 -548
- package/src/distributed-logger-reporter.ts +0 -19
- package/src/echo/firehose.ts +0 -64
- package/src/echo/index.ts +0 -4
- package/src/echo/stats.ts +0 -84
- package/src/har/index.ts +0 -81
- package/src/multipart-form-data/form-data-utils.ts +0 -81
- package/src/multipart-form-data/multipart-text-to-post-form-data.ts +0 -89
- package/src/parameters/extractions.ts +0 -51
- package/src/parameters/extractors/cheerio-extractor.ts +0 -57
- package/src/parameters/extractors/expression-extractor.ts +0 -13
- package/src/parameters/extractors/extractor.ts +0 -3
- package/src/parameters/extractors/header-extractor.ts +0 -24
- package/src/parameters/extractors/index.ts +0 -10
- package/src/parameters/extractors/json-path-extractor.ts +0 -63
- package/src/parameters/extractors/parametrized-extractor.ts +0 -27
- package/src/parameters/extractors/regex-extractor.ts +0 -18
- package/src/parameters/extractors/regex-matcher.ts +0 -17
- package/src/parameters/extractors/ws-extractor.ts +0 -91
- package/src/parameters/generate-random.ts +0 -114
- package/src/parameters/index.ts +0 -621
- package/src/parameters/json-path-utils.ts +0 -20
- package/src/parameters/operators/binary-operator.ts +0 -23
- package/src/parameters/operators/index.ts +0 -39
- package/src/parameters/parameter-functions/boolean-parameter-functions.ts +0 -24
- package/src/parameters/parameter-functions/crypto.ts +0 -55
- package/src/parameters/parameter-functions/json-schema.ts +0 -29
- package/src/parameters/parameter-functions/numeric-input-parameter-functions.ts +0 -22
- package/src/parameters/parameter-functions/numeric-parameter-functions.ts +0 -37
- package/src/parameters/parameter-functions/parameter-function-utils.ts +0 -55
- package/src/parameters/parameter-functions/parameter-function.ts +0 -7
- package/src/parameters/parameter-functions/parameter-functions.ts +0 -54
- package/src/parameters/parameter-functions/random-parameter-functions.ts +0 -22
- package/src/parameters/parameter-functions/textual-parameter-functions.ts +0 -464
- package/src/parameters/parameter-regex-providers.ts +0 -78
- package/src/parameters/resolvers/random-parameters-resolver.ts +0 -8
- package/src/parameters/type.ts +0 -7
- package/src/parameters/value-utils.ts +0 -47
- package/src/request/index.ts +0 -525
- package/src/schema/json-schema-generator.ts +0 -76
- package/test/conf/validate.spec.js +0 -141
- package/test/har/is-har.spec.js +0 -33
- package/test/multipart-form-data/form-data-utils.spec.ts +0 -121
- package/test/multipart-form-data/resources/multipart-form-data-file-text-content.json +0 -5
- package/test/parameters/builtin-functions.spec.js +0 -85
- package/test/parameters/json-path-utils.spec.ts +0 -50
- package/test/parameters/parameter-functions.spec.js +0 -48
- package/test/parameters/parameter-utils.spec.js +0 -185
- package/test/parameters/regex-functions.spec.ts +0 -57
- package/test/parameters/value-utils.spec.js +0 -73
- package/test/schema/json-schema-generator.spec.js +0 -227
- package/tsconfig.json +0 -9
package/src/har/index.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
export interface Har {
|
|
2
|
-
log: HarLog;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export interface HarLog {
|
|
6
|
-
version: string;
|
|
7
|
-
entries: HarEntry[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface HarEntry {
|
|
11
|
-
time: number;
|
|
12
|
-
request: HarRequest;
|
|
13
|
-
response: HarResponse;
|
|
14
|
-
startedDateTime: string;
|
|
15
|
-
_resourceType?: ResourceType;
|
|
16
|
-
_webSocketMessages?: WebSocketMessage[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type ResourceType = 'main_frame' | 'sub_frame' | 'stylesheet' | 'script' | 'image' | 'font' |
|
|
20
|
-
'object' | 'xmlhttprequest' | 'ping' | 'csp_report' | 'media' | 'websocket' | 'other';
|
|
21
|
-
|
|
22
|
-
export type WebSocketMessage = {
|
|
23
|
-
type: WebSocketMessageType,
|
|
24
|
-
time: number,
|
|
25
|
-
opcode: number,
|
|
26
|
-
data: string,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export type WebSocketMessageType = 'send' | 'receive';
|
|
30
|
-
|
|
31
|
-
export interface HarRequest {
|
|
32
|
-
url: string;
|
|
33
|
-
method: string;
|
|
34
|
-
headers: HarHeaders;
|
|
35
|
-
bodySize: number;
|
|
36
|
-
postData?: HarPostData;
|
|
37
|
-
cookies: Cookie[]
|
|
38
|
-
queryString: { name: string, value: string }[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type HarHeaders = HarHeader[];
|
|
42
|
-
|
|
43
|
-
export type HarHeader = {
|
|
44
|
-
name: string;
|
|
45
|
-
value: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export interface HarResponse {
|
|
49
|
-
status: number;
|
|
50
|
-
headers: HarHeaders;
|
|
51
|
-
statusText: string;
|
|
52
|
-
|
|
53
|
-
content: {
|
|
54
|
-
mimeType: string;
|
|
55
|
-
text?: string;
|
|
56
|
-
encoding?: string;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export type Cookie = {
|
|
61
|
-
name: string;
|
|
62
|
-
value: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export type HarPostData = {
|
|
66
|
-
mimeType: string;
|
|
67
|
-
text?: string;
|
|
68
|
-
params?: HarParam[];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export type HarParam = {
|
|
72
|
-
name: string;
|
|
73
|
-
value?: string;
|
|
74
|
-
fileName?: string;
|
|
75
|
-
contentType?: string;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const isHar = (obj: any) => Boolean(obj.log) &&
|
|
79
|
-
typeof obj.log.version === 'string' &&
|
|
80
|
-
obj.log.version.startsWith('1.') &&
|
|
81
|
-
Array.isArray(obj.log.entries);
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { HarParam } from '../har';
|
|
2
|
-
import { PostFormData, PostFormDataEntry } from '../request';
|
|
3
|
-
import { multipartFormDataTextToPostFormData } from './multipart-text-to-post-form-data';
|
|
4
|
-
|
|
5
|
-
export class Form {
|
|
6
|
-
entries: HarParam[];
|
|
7
|
-
boundary: string;
|
|
8
|
-
constructor(entries?: HarParam[], boundary?: string) {
|
|
9
|
-
this.entries = entries || [];
|
|
10
|
-
this.boundary = boundary || this.generateBoundary();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
generateBoundary = (): string => {
|
|
14
|
-
let boundary = '-'.repeat(26);
|
|
15
|
-
for (let i = 0; i < 24; i++) {
|
|
16
|
-
boundary += Math.floor(Math.random() * 10).toString(16);
|
|
17
|
-
}
|
|
18
|
-
return boundary;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
append = (name: string, value: string, fileName?: string) => {
|
|
22
|
-
const entry: HarParam = { name, value };
|
|
23
|
-
fileName && (entry.fileName = fileName);
|
|
24
|
-
this.entries.push(entry);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
toString = (): string => {
|
|
28
|
-
let result = '';
|
|
29
|
-
for (const { name, value, fileName, contentType } of this.entries) {
|
|
30
|
-
result += '--' + this.boundary + '\r\n';
|
|
31
|
-
result += `Content-Disposition: form-data; name="${name}"${this.addFileName(fileName)}${this.addContentType(contentType)}\r\n\r\n${value || ''}\r\n`;
|
|
32
|
-
}
|
|
33
|
-
result += '--' + this.boundary + '--\r\n';
|
|
34
|
-
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
private addFileName(fileName: string = '') {
|
|
39
|
-
let formattedFileName = '';
|
|
40
|
-
if (fileName) {
|
|
41
|
-
formattedFileName = `; filename="${fileName}"`;
|
|
42
|
-
}
|
|
43
|
-
return formattedFileName;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private addContentType = (contentType: string = ''): string => {
|
|
47
|
-
let formattedContentType = '';
|
|
48
|
-
if (contentType) {
|
|
49
|
-
formattedContentType = `\r\ncontent-type: ${contentType}`;
|
|
50
|
-
}
|
|
51
|
-
return formattedContentType;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export type FormEntry = { name: string, value: string, [other: string]: string };
|
|
56
|
-
|
|
57
|
-
export function mapToMultipartFormData(request) {
|
|
58
|
-
const { postData: { params, text } } = request;
|
|
59
|
-
if (text) {
|
|
60
|
-
request.postFormData = multipartFormDataTextToPostFormData(text);
|
|
61
|
-
} else if (params) {
|
|
62
|
-
request.postFormData = multipartParamsToPostFormData(params);
|
|
63
|
-
}
|
|
64
|
-
delete request.postData;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const multipartParamsToPostFormData = (params: FormEntry[] = []) => {
|
|
68
|
-
const postFormData: PostFormData = [];
|
|
69
|
-
for (const p of params) {
|
|
70
|
-
const { name, value, fileName } = p;
|
|
71
|
-
const entry: PostFormDataEntry = { name, value };
|
|
72
|
-
if (fileName) {
|
|
73
|
-
entry.fileName = fileName;
|
|
74
|
-
entry.value = '';
|
|
75
|
-
}
|
|
76
|
-
postFormData.push(entry);
|
|
77
|
-
}
|
|
78
|
-
return postFormData;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
export { multipartFormDataTextToPostFormData };
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { PostFormData, PostFormDataEntry } from '../request';
|
|
2
|
-
|
|
3
|
-
export const multipartFormDataTextToPostFormData = (multipartFormDataText: string): PostFormData => {
|
|
4
|
-
const result: PostFormData = [];
|
|
5
|
-
|
|
6
|
-
const lines = getLines(multipartFormDataText);
|
|
7
|
-
for (const line of lines) {
|
|
8
|
-
const postFormDataEntry: PostFormDataEntry = extractPostFormDataEntry(line);
|
|
9
|
-
result.push(postFormDataEntry);
|
|
10
|
-
}
|
|
11
|
-
return result;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const getLines = (multipartFormDataText: string) => {
|
|
15
|
-
const boundary = getBoundary(multipartFormDataText);
|
|
16
|
-
const boundaryRegex = splitByBoundaryRegex(boundary);
|
|
17
|
-
const linesSplittedByBoundary = multipartFormDataText.split(boundaryRegex);
|
|
18
|
-
return removeEmptyLines(linesSplittedByBoundary);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const getBoundary = (multipartFormDataText: string): string => {
|
|
22
|
-
return multipartFormDataText.substring(0, multipartFormDataText.indexOf('\r\n'));
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const splitByBoundaryRegex = (boundary: string) => {
|
|
26
|
-
const normalBoundary = boundary + '\r\n';
|
|
27
|
-
const lastBoundary = boundary + '--\r\n';
|
|
28
|
-
return new RegExp(`${normalBoundary}|${lastBoundary}`);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const removeEmptyLines = (lines: string[]): string[] => lines.filter(Boolean);
|
|
32
|
-
|
|
33
|
-
const extractPostFormDataEntry = (line: string): PostFormDataEntry => {
|
|
34
|
-
const indexOfFirst2Newlines = line.indexOf('\r\n\r\n');
|
|
35
|
-
|
|
36
|
-
const fields = extractFields(line, indexOfFirst2Newlines);
|
|
37
|
-
const value = calculateValue(fields, line, indexOfFirst2Newlines);
|
|
38
|
-
|
|
39
|
-
const postFormDataEntry: PostFormDataEntry = { name: fields.name, value };
|
|
40
|
-
fields.fileName && (postFormDataEntry.fileName = fields.fileName);
|
|
41
|
-
|
|
42
|
-
return postFormDataEntry;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
type Fields = Pick<PostFormDataEntry, 'name' | 'fileName'>;
|
|
46
|
-
|
|
47
|
-
const extractFields = (line: string, indexOfFirst2Newlines: number): Fields => {
|
|
48
|
-
const rawFields = extractRawFields(line, indexOfFirst2Newlines);
|
|
49
|
-
const res: Fields = { name: '' };
|
|
50
|
-
const onlyRawNameFields = rawFields.split('\r\n')[0];
|
|
51
|
-
const tokens = onlyRawNameFields.split(';').map(t => t.trim());
|
|
52
|
-
for (const token of tokens) {
|
|
53
|
-
const [k, v] = token.split('=');
|
|
54
|
-
if (k === 'name') {
|
|
55
|
-
res.name = removeDoubleQuotes(v);
|
|
56
|
-
} else if (k === 'filename') {
|
|
57
|
-
res.fileName = removeDoubleQuotes(v);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return res;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const extractRawFields = (line: string, indexOfFieldsValueSeperator: number): string => {
|
|
64
|
-
return line.substring(0, indexOfFieldsValueSeperator);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const removeDoubleQuotes = (v: string): string => {
|
|
68
|
-
return v.replace(/"/g, '');
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function calculateValue(fields: Fields, line: string, indexOfFirst2Newlines: number): string | '' {
|
|
72
|
-
return fields.fileName ? '' : extractValue(line, indexOfFirst2Newlines);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const extractValue = (line: string, indexOfFieldsValueSeperator: number): string => {
|
|
76
|
-
const value = line.substring(indexOfFieldsValueSeperator + 4);
|
|
77
|
-
return removeEndingNewline(value);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const removeEndingNewline = (s: string): string => {
|
|
81
|
-
if (s.endsWith('\r\n')) {
|
|
82
|
-
s = removeLast2Chars(s);
|
|
83
|
-
}
|
|
84
|
-
return s;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const removeLast2Chars = (s: string) => {
|
|
88
|
-
return s.substring(0, s.length - 2);
|
|
89
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { Extractions } from '../request';
|
|
2
|
-
import { parameterUtils } from '.';
|
|
3
|
-
const { getParameterName } = parameterUtils;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Gets an extractions instance which looks like this: { [paramName]: string }.
|
|
7
|
-
*
|
|
8
|
-
* If it's an assign extraction, i.e { [paramName]: string }
|
|
9
|
-
* OR { [paramName]: { jsonPath/jQuery/...: string } }
|
|
10
|
-
* OR { [paramName]: { jQuery: { query: string, attr?: string } } }}}
|
|
11
|
-
* And spreads this object into its parts.
|
|
12
|
-
*
|
|
13
|
-
* Example:
|
|
14
|
-
* input ext = { my_ext: { jsonPath: '$["users"][0]["id"]' } }
|
|
15
|
-
* output = { name: 'my_ext', value: '$["users"][0]["name"]', type: 'jsonPath' }
|
|
16
|
-
*
|
|
17
|
-
* @param ext The extraction object to take a part
|
|
18
|
-
* @returns The spreaded parts of the given extraction object
|
|
19
|
-
*/
|
|
20
|
-
export function getExtractionParts(ext: Extractions): {
|
|
21
|
-
name: string,
|
|
22
|
-
value: string | {
|
|
23
|
-
query: string;
|
|
24
|
-
attr?: string;
|
|
25
|
-
}
|
|
26
|
-
type?: string,
|
|
27
|
-
} {
|
|
28
|
-
const name = getParameterName(ext);
|
|
29
|
-
const extContent = ext[name];
|
|
30
|
-
if (typeof extContent === 'string') {
|
|
31
|
-
return { name, value: extContent };
|
|
32
|
-
}
|
|
33
|
-
const [type] = Object.keys(extContent);
|
|
34
|
-
const value = ext[name][type];
|
|
35
|
-
return { name, value, type };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function requestsExtractionsArr(requests, toIndex: number) {
|
|
39
|
-
const extractionParamNames = [] as string[];
|
|
40
|
-
for (let i = 0; i <= toIndex; i++) {
|
|
41
|
-
if (requests[i].extract) {
|
|
42
|
-
for (const param of requests[i].extract) {
|
|
43
|
-
const paramName = Object.keys(param)[0];
|
|
44
|
-
if (paramName && extractionParamNames.indexOf(paramName) < 0) {
|
|
45
|
-
extractionParamNames.push(paramName);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return extractionParamNames;
|
|
51
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import cheerio from 'cheerio';
|
|
2
|
-
import random = require('lodash/random');
|
|
3
|
-
import { parameterValueToNumber, RANDOM_SELECT_OPT } from '../value-utils';
|
|
4
|
-
import { Parameters } from '../type';
|
|
5
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
6
|
-
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
7
|
-
|
|
8
|
-
export class CheerioExtractor extends ParametrizedExtractor {
|
|
9
|
-
private $;
|
|
10
|
-
|
|
11
|
-
constructor(target, parameters: Parameters) {
|
|
12
|
-
super(parameters);
|
|
13
|
-
this.$ = cheerio.load(target);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
extractResolved = (
|
|
17
|
-
resolvedJQuery: string,
|
|
18
|
-
selection: string = '0',
|
|
19
|
-
resolvedAttrName?: string
|
|
20
|
-
) => {
|
|
21
|
-
let jQueryResult;
|
|
22
|
-
|
|
23
|
-
const matched = this.$(resolvedJQuery);
|
|
24
|
-
const numMatched = matched.length;
|
|
25
|
-
|
|
26
|
-
if (numMatched === 0) {
|
|
27
|
-
jQueryResult = null;
|
|
28
|
-
} else {
|
|
29
|
-
const index = getIndex(selection, numMatched);
|
|
30
|
-
const element = matched.eq(index);
|
|
31
|
-
|
|
32
|
-
if (resolvedAttrName) {
|
|
33
|
-
const attr = element.attr(resolvedAttrName);
|
|
34
|
-
jQueryResult = attr != null ? attr.toString() : null;
|
|
35
|
-
} else {
|
|
36
|
-
jQueryResult = element.html();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// log.debug('jQuery extraction:', { resolvedJQuery, jQueryResult });
|
|
41
|
-
|
|
42
|
-
return jQueryResult;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getIndex(selection: string, len) {
|
|
47
|
-
let index: number | null;
|
|
48
|
-
if (selection === RANDOM_SELECT_OPT) {
|
|
49
|
-
index = random(0, len - 1);
|
|
50
|
-
} else {
|
|
51
|
-
index = parameterValueToNumber(selection, { integer: true });
|
|
52
|
-
if (index == null) {
|
|
53
|
-
throw new PresentableError('Invalid index: ' + selection);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return index;
|
|
57
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Parameters } from '../type';
|
|
2
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
3
|
-
|
|
4
|
-
export class ExpressionExtractor extends ParametrizedExtractor {
|
|
5
|
-
constructor(parameters: Parameters) {
|
|
6
|
-
super(parameters);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
extractResolved = (resolvedQuery: string) => {
|
|
10
|
-
// log.debug('Expression extraction resolved:', resolvedQuery);
|
|
11
|
-
return resolvedQuery;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Parameters } from '../type';
|
|
2
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
3
|
-
|
|
4
|
-
export class HeaderExtractor extends ParametrizedExtractor {
|
|
5
|
-
private headers;
|
|
6
|
-
|
|
7
|
-
constructor(res, parameters: Parameters) {
|
|
8
|
-
super(parameters);
|
|
9
|
-
this.headers = res.header;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
extractResolved = (headerName: string) => {
|
|
13
|
-
const lowerCaseHeaderName = headerName.toLowerCase();
|
|
14
|
-
let headerValue = this.headers[lowerCaseHeaderName];
|
|
15
|
-
|
|
16
|
-
// some servers return more than one 'set-cookie' headers. For extractions we will merge them.
|
|
17
|
-
if (lowerCaseHeaderName === 'set-cookie' && Array.isArray(headerValue)) {
|
|
18
|
-
headerValue = headerValue.join('; ');
|
|
19
|
-
}
|
|
20
|
-
// log.debug('Header extraction:', { headerName, headerValue });
|
|
21
|
-
|
|
22
|
-
return headerValue;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { CheerioExtractor } from './cheerio-extractor';
|
|
2
|
-
import { ExpressionExtractor } from './expression-extractor';
|
|
3
|
-
import { Extractor } from './extractor';
|
|
4
|
-
import { HeaderExtractor } from './header-extractor';
|
|
5
|
-
import { JsonPathExtractor } from './json-path-extractor';
|
|
6
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
7
|
-
import { RegexExtractor } from './regex-extractor';
|
|
8
|
-
import { WsExtractor } from './ws-extractor';
|
|
9
|
-
|
|
10
|
-
export { CheerioExtractor, ExpressionExtractor, Extractor, HeaderExtractor, JsonPathExtractor, ParametrizedExtractor, RegexExtractor, WsExtractor };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import jsonpath from 'jsonpath';
|
|
2
|
-
import { Parameters } from '../type';
|
|
3
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
4
|
-
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
5
|
-
import log from '@loadmill/universal/dist/log';
|
|
6
|
-
|
|
7
|
-
export class JsonPathExtractor extends ParametrizedExtractor {
|
|
8
|
-
private obj;
|
|
9
|
-
private parseError?;
|
|
10
|
-
|
|
11
|
-
_validateObj = () => {
|
|
12
|
-
const { parseError } = this;
|
|
13
|
-
if (parseError) {
|
|
14
|
-
throw new PresentableError(parseError);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
constructor(target, parameters: Parameters) {
|
|
19
|
-
super(parameters);
|
|
20
|
-
try {
|
|
21
|
-
this.obj = JSON.parse(target);
|
|
22
|
-
} catch (e) {
|
|
23
|
-
this.parseError = e.message || 'Response body is not a valid JSON';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
extractResolved = (jsonPath: string, full?) => {
|
|
28
|
-
|
|
29
|
-
this._validateObj();
|
|
30
|
-
|
|
31
|
-
let jsonPathResult;
|
|
32
|
-
if (jsonPath === '$') {
|
|
33
|
-
jsonPathResult = this.obj;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
jsonPathResult = full ? jsonpath.query(this.obj, jsonPath) : jsonpath.value(this.obj, jsonPath);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (jsonPathResult != null) {
|
|
40
|
-
return typeof jsonPathResult === 'object'
|
|
41
|
-
? JSON.stringify(jsonPathResult)
|
|
42
|
-
: jsonPathResult.toString();
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
apply = (jsonPath: string, value) => {
|
|
47
|
-
this._validateObj();
|
|
48
|
-
|
|
49
|
-
log.debug('JSONPath parameter apply:', { jsonPath, value });
|
|
50
|
-
|
|
51
|
-
jsonpath.apply(this.obj, jsonPath, () => {
|
|
52
|
-
try {
|
|
53
|
-
return JSON.parse(value);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
log.debug('JSONPath parameter apply result:', { obj: this.obj });
|
|
60
|
-
|
|
61
|
-
return JSON.stringify(this.obj);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Extractor } from './extractor';
|
|
2
|
-
import { parameterUtils } from '../';
|
|
3
|
-
import { Parameters } from '../type';
|
|
4
|
-
|
|
5
|
-
export abstract class ParametrizedExtractor implements Extractor {
|
|
6
|
-
abstract extractResolved: ResolvedExtracter;
|
|
7
|
-
|
|
8
|
-
constructor(private parameters: Parameters) {}
|
|
9
|
-
|
|
10
|
-
extract = (parametrizedQuery: string | string[]) => {
|
|
11
|
-
if (!Array.isArray(parametrizedQuery)) {
|
|
12
|
-
parametrizedQuery = [parametrizedQuery];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const resolvedQuery = parametrizedQuery.map(
|
|
16
|
-
(q) => q && parameterUtils.resolveExpression(q, this.parameters)
|
|
17
|
-
);
|
|
18
|
-
/* log.debug('Parametrized query resolution:', {
|
|
19
|
-
parametrizedQuery,
|
|
20
|
-
resolvedQuery,
|
|
21
|
-
}); */
|
|
22
|
-
|
|
23
|
-
return this.extractResolved(...resolvedQuery);
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
type ResolvedExtracter = ((...resolvedQuery: string[]) => string | undefined | Promise<string | undefined>);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
2
|
-
import { Parameters } from '../type';
|
|
3
|
-
import { matchRegex } from './regex-matcher';
|
|
4
|
-
|
|
5
|
-
export class RegexExtractor extends ParametrizedExtractor {
|
|
6
|
-
constructor(
|
|
7
|
-
private targetProvider: () => string | undefined,
|
|
8
|
-
parameters: Parameters
|
|
9
|
-
) {
|
|
10
|
-
super(parameters);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
extractResolved = (regex: string) => {
|
|
14
|
-
const target = this.targetProvider() || '';
|
|
15
|
-
const regExp = new RegExp(regex);
|
|
16
|
-
return matchRegex(regExp, target);
|
|
17
|
-
};
|
|
18
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import log from '@loadmill/universal/dist/log';
|
|
2
|
-
|
|
3
|
-
const MAX_STR_LENGTH = 500;
|
|
4
|
-
|
|
5
|
-
export const matchRegex = (regExp: RegExp, target: string) => {
|
|
6
|
-
const matchArray = regExp.exec(target);
|
|
7
|
-
|
|
8
|
-
log.debug('Regex parameter extraction:', {
|
|
9
|
-
regExp,
|
|
10
|
-
type: typeof target,
|
|
11
|
-
target: typeof target === 'string' && target.substring(0, MAX_STR_LENGTH),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
if (matchArray && matchArray[1]) {
|
|
15
|
-
return matchArray[1];
|
|
16
|
-
}
|
|
17
|
-
};
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import findLast from 'lodash/findLast';
|
|
2
|
-
import { PresentableError } from '@loadmill/universal/dist/errors';
|
|
3
|
-
import log from '@loadmill/universal/dist/log';
|
|
4
|
-
import { delay } from '@loadmill/universal/dist/promise-utils';
|
|
5
|
-
import { CAPTURE_ALL_REGEX, isCaptureAllRegExp } from '../parameter-regex-providers';
|
|
6
|
-
import { Parameters } from '../type';
|
|
7
|
-
import { ParametrizedExtractor } from './parametrized-extractor';
|
|
8
|
-
import { matchRegex } from './regex-matcher';
|
|
9
|
-
|
|
10
|
-
export class WsExtractor extends ParametrizedExtractor {
|
|
11
|
-
private readonly _messages: string[];
|
|
12
|
-
private readonly _timeLimit: number;
|
|
13
|
-
private _regexp: RegExp;
|
|
14
|
-
constructor(
|
|
15
|
-
extractionData: WSExtractionData = { messages: [], timeLimit: 0 },
|
|
16
|
-
parameters: Parameters,
|
|
17
|
-
) {
|
|
18
|
-
super(parameters);
|
|
19
|
-
this._messages = extractionData.messages;
|
|
20
|
-
this._timeLimit = extractionData.timeLimit;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
extractResolved = async (resolvedRegex: string) => {
|
|
24
|
-
log.debug('Resolved WS extraction regex:', resolvedRegex);
|
|
25
|
-
this._regexp = new RegExp(resolvedRegex || CAPTURE_ALL_REGEX);
|
|
26
|
-
const wsMessage = await this.queryMessage();
|
|
27
|
-
log.debug('WS message queried and found:', wsMessage);
|
|
28
|
-
|
|
29
|
-
return matchRegex(this._regexp, wsMessage);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Querying all received incoming WS messages by regexp.
|
|
34
|
-
* @returns A WS message that satisfies the given regexp. If none found, returns an empty string.
|
|
35
|
-
* @throws RequestFailuresError if the procedure timeouts before any relevant message found.
|
|
36
|
-
*/
|
|
37
|
-
private async queryMessage(): Promise<string> {
|
|
38
|
-
const res = await this.getWSMessageOrTimeout();
|
|
39
|
-
if (!res) {
|
|
40
|
-
throw new PresentableError('Websocket waiting timeout expired');
|
|
41
|
-
}
|
|
42
|
-
return res;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private readonly GIVE_WS_MSG_TIME_MS = 250;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get an incoming ws message that satisfies the given regex, out of all already received incoming ws messages.
|
|
49
|
-
*
|
|
50
|
-
* If can't find that message, then actively waiting, until procedure timeouts.
|
|
51
|
-
* @param regex To test the messages with
|
|
52
|
-
* @param timeout Milliseconds for the procedure to find a message
|
|
53
|
-
* @returns ws message as string or undefined if timed out
|
|
54
|
-
*/
|
|
55
|
-
private async getWSMessageOrTimeout(): Promise<string | undefined> {
|
|
56
|
-
const startTime = Date.now();
|
|
57
|
-
let message = '';
|
|
58
|
-
let elapsedTime = 0;
|
|
59
|
-
|
|
60
|
-
while (!message && elapsedTime < this._timeLimit) {
|
|
61
|
-
message = this.findMessageByRegex() || '';
|
|
62
|
-
if (message) {
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
await delay(this.GIVE_WS_MSG_TIME_MS);
|
|
67
|
-
elapsedTime = Date.now() - startTime;
|
|
68
|
-
}
|
|
69
|
-
return message;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private findMessageByRegex(): string | undefined {
|
|
73
|
-
if (isCaptureAllRegExp(this._regexp)) {
|
|
74
|
-
return this.getLastMessage();
|
|
75
|
-
}
|
|
76
|
-
return this.findLastMatchingMessage();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private getLastMessage(): string | undefined {
|
|
80
|
-
return this._messages[this._messages.length - 1];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private findLastMatchingMessage(): string | undefined {
|
|
84
|
-
return findLast(this._messages, msg => this._regexp.test(msg));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type WSExtractionData = {
|
|
89
|
-
messages: string[];
|
|
90
|
-
timeLimit: number;
|
|
91
|
-
}
|