@loadmill/core 0.3.38 → 0.3.42

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.
Files changed (38) hide show
  1. package/dist/conf/types.d.ts +5 -0
  2. package/dist/conf/types.js +6 -1
  3. package/dist/conf/types.js.map +1 -1
  4. package/dist/har/index.d.ts +8 -1
  5. package/dist/har/index.js.map +1 -1
  6. package/dist/multipart-form-data/form-data-utils.d.ts +19 -0
  7. package/dist/multipart-form-data/form-data-utils.js +116 -0
  8. package/dist/multipart-form-data/form-data-utils.js.map +1 -0
  9. package/dist/multipart-form-data/multipart-text-to-post-form-data.d.ts +2 -0
  10. package/dist/multipart-form-data/multipart-text-to-post-form-data.js +77 -0
  11. package/dist/multipart-form-data/multipart-text-to-post-form-data.js.map +1 -0
  12. package/dist/parameters/index.d.ts +5 -1
  13. package/dist/parameters/index.js +41 -53
  14. package/dist/parameters/index.js.map +1 -1
  15. package/dist/parameters/parameter-functions/crypto.d.ts +10 -0
  16. package/dist/parameters/parameter-functions/crypto.js +56 -0
  17. package/dist/parameters/parameter-functions/crypto.js.map +1 -0
  18. package/dist/parameters/parameter-functions/textual-parameter-functions.d.ts +4 -0
  19. package/dist/parameters/parameter-functions/textual-parameter-functions.js +24 -5
  20. package/dist/parameters/parameter-functions/textual-parameter-functions.js.map +1 -1
  21. package/dist/request/index.d.ts +12 -0
  22. package/dist/request/index.js +16 -2
  23. package/dist/request/index.js.map +1 -1
  24. package/package.json +9 -6
  25. package/src/conf/types.ts +6 -0
  26. package/src/har/index.ts +9 -1
  27. package/src/multipart-form-data/form-data-utils.ts +81 -0
  28. package/src/multipart-form-data/multipart-text-to-post-form-data.ts +89 -0
  29. package/src/parameters/index.ts +44 -56
  30. package/src/parameters/parameter-functions/crypto.ts +55 -0
  31. package/src/parameters/parameter-functions/textual-parameter-functions.ts +32 -7
  32. package/src/request/index.ts +20 -0
  33. package/test/har/is-har.spec.js +1 -0
  34. package/test/multipart-form-data/form-data-utils.spec.ts +121 -0
  35. package/test/multipart-form-data/resources/multipart-form-data-file-text-content.json +5 -0
  36. package/test/parameters/parameter-functions.spec.js +2 -0
  37. package/test/parameters/parameter-utils.spec.js +2 -0
  38. package/tsconfig.json +1 -1
@@ -0,0 +1,55 @@
1
+ const CryptoJS = require('crypto-js');
2
+
3
+ import { PresentableError } from '@loadmill/universal/dist/errors';
4
+
5
+ export const FORMATS = {
6
+ UTF8: 'utf-8',
7
+ HEX: 'hex'
8
+ };
9
+
10
+ export const HASH = {
11
+ SHA1: 'SHA1',
12
+ SHA256: 'SHA256',
13
+ };
14
+
15
+ export const toSha = (secret: string, alg: string, secretFormat: string) => {
16
+ try {
17
+ const hashFunction = toHashFunction(alg);
18
+ const hashed = hashFunction(toCryptoFormat(secret, secretFormat));
19
+ return hashed.toString(CryptoJS.enc.Hex);
20
+ } catch (_e) {
21
+ throw new PresentableError(`Can't digest ${secret}`);
22
+ }
23
+ };
24
+
25
+ export const toHMAC = (secret: string, alg: string, secretFormat: string, salt: string) => {
26
+ try {
27
+ const converted = toCryptoFormat(secret, secretFormat);
28
+ const hmaced = CryptoJS.algo.HMAC.create(CryptoJS.algo[(alg || HASH.SHA256)], converted);
29
+ salt && hmaced.update(salt);
30
+ const hashed = hmaced.finalize();
31
+ return hashed.toString(CryptoJS.enc.Hex);
32
+ } catch (_e) {
33
+ throw new PresentableError(`Can't digest ${secret}`);
34
+ }
35
+ };
36
+
37
+ const toHashFunction = (type) => {
38
+ switch (type) {
39
+ case HASH.SHA1:
40
+ return CryptoJS.SHA1;
41
+ case HASH.SHA256:
42
+ default:
43
+ return CryptoJS.SHA256;
44
+ }
45
+ };
46
+
47
+ const toCryptoFormat = (from, base) => {
48
+ switch (base) {
49
+ case FORMATS.HEX:
50
+ return CryptoJS.enc.Hex.parse(from);
51
+ case FORMATS.UTF8:
52
+ default:
53
+ return CryptoJS.enc.Utf8.parse(from);
54
+ }
55
+ };
@@ -1,3 +1,9 @@
1
+ import defaultTo = require('lodash/defaultTo');
2
+ import isEmpty = require('lodash/isEmpty');
3
+ import isEqual = require('lodash/isEqual');
4
+ import uniqWith = require('lodash/uniqWith');
5
+ import escapeRegExp = require('lodash/escapeRegExp');
6
+ import isNaN = require('lodash/isNaN');
1
7
 
2
8
  import { RegexExtractor } from '../extractors/regex-extractor';
3
9
  import { CheerioExtractor } from '../extractors/cheerio-extractor';
@@ -9,21 +15,16 @@ import {
9
15
  pickRandom,
10
16
  RANDOM_SELECT_OPT,
11
17
  } from '../value-utils';
12
- import defaultTo = require('lodash/defaultTo');
13
- import isEmpty = require('lodash/isEmpty');
14
- import isEqual = require('lodash/isEqual');
15
- import uniqWith = require('lodash/uniqWith');
16
- import escapeRegExp = require('lodash/escapeRegExp');
17
- import isNaN = require('lodash/isNaN');
18
18
  import isUUID from 'validator/lib/isUUID';
19
19
  import * as uriUtils from '@loadmill/universal/dist/uri-utils';
20
20
  import { PresentableError } from '@loadmill/universal/dist/errors';
21
21
  import { nodeBtoa } from '@loadmill/universal/dist/string-utils';
22
22
  import { applyJSONSchema } from './json-schema';
23
+ import { toSha, toHMAC, FORMATS, HASH } from './crypto';
23
24
 
24
25
  const MAX_NOW_LENGTH = 13; // max length UTC until 2286
25
26
 
26
- const nowParameterFunction = new BasicParameterFunction((length) =>{
27
+ const nowParameterFunction = new BasicParameterFunction((length) => {
27
28
  const now = Date.now();
28
29
  if (length && +length > 0) {
29
30
  return ('' + now).substring(0, Math.min(+length, MAX_NOW_LENGTH));
@@ -205,6 +206,30 @@ export const textualParameterFunctions = {
205
206
  1
206
207
  ),
207
208
 
209
+ __sha1: new BasicParameterFunction(
210
+ (secret, secretFormat = FORMATS.UTF8) => toSha(secret, HASH.SHA1, secretFormat),
211
+ 1,
212
+ 2
213
+ ),
214
+
215
+ __sha256: new BasicParameterFunction(
216
+ (secret, secretFormat = FORMATS.UTF8) => toSha(secret, HASH.SHA256, secretFormat),
217
+ 1,
218
+ 2
219
+ ),
220
+
221
+ __hmacSha1: new BasicParameterFunction(
222
+ (secret, secretFormat = FORMATS.UTF8, salt = '') => toHMAC(secret, HASH.SHA1, secretFormat, salt),
223
+ 1,
224
+ 3
225
+ ),
226
+
227
+ __hmacSha256: new BasicParameterFunction(
228
+ (secret, secretFormat = FORMATS.UTF8, salt = '') => toHMAC(secret, HASH.SHA256, secretFormat, salt),
229
+ 1,
230
+ 3
231
+ ),
232
+
208
233
  __split_pick: new BasicParameterFunction(
209
234
  (str: string, delim: string, selection = '0') =>
210
235
  pick(selection, str.split(delim)),
@@ -38,6 +38,7 @@ export class LoadmillRequest implements RequestLike {
38
38
  description?: string;
39
39
  delay?: number | string;
40
40
  postData?: RequestPostData;
41
+ postFormData?: PostFormData;
41
42
  cachePenetration?: CachePenetration;
42
43
 
43
44
  method: HttpMethod = 'GET';
@@ -77,6 +78,7 @@ export function createRequest(from: RequestLike): LoadmillRequest {
77
78
  extract,
78
79
  headers,
79
80
  postData,
81
+ postFormData,
80
82
  stopBefore,
81
83
  skipBefore,
82
84
  loop,
@@ -106,6 +108,10 @@ export function createRequest(from: RequestLike): LoadmillRequest {
106
108
  request.postData = { text, mimeType };
107
109
  }
108
110
 
111
+ if (postFormData) {
112
+ request.postFormData = postFormData;
113
+ }
114
+
109
115
  if (delay !== null && delay !== undefined) {
110
116
  request.delay = delay;
111
117
  }
@@ -346,6 +352,7 @@ export interface RequestLike {
346
352
  description?: string;
347
353
  delay?: number | string;
348
354
  postData?: RequestPostData;
355
+ postFormData?: PostFormData;
349
356
  extract?: Extractions | Extractions[];
350
357
  headers?: LoadmillHeaders | LoadmillHeaders[];
351
358
  expectedStatus?: HttpResponseStatus;
@@ -370,6 +377,19 @@ export type RequestPostData = {
370
377
  text: string;
371
378
  };
372
379
 
380
+ export type PostFormData = PostFormDataEntry[];
381
+
382
+ export type PostFormDataEntry = {
383
+ name: string;
384
+ value: string;
385
+ fileName?: string;
386
+ };
387
+
388
+ export enum BodyTypes {
389
+ POST_DATA = 'postData',
390
+ FORM_DATA = 'postFormData',
391
+ }
392
+
373
393
  export type LoadmillHeaders = { [headerName: string]: string };
374
394
 
375
395
  export type Extractions = { [parameter: string]: Extraction };
@@ -1,4 +1,5 @@
1
1
  const { expect } = require('chai');
2
+ const { describe, it } = require('mocha');
2
3
  const { isHar } = require('../../dist/har');
3
4
 
4
5
  describe('har-utils isHar', () => {
@@ -0,0 +1,121 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ import { suite, describe, it } from 'mocha';
4
+ import { expect } from 'chai';
5
+
6
+ import { PostFormData } from '../../src/request/index';
7
+ import { Form, FormEntry, mapToMultipartFormData, multipartFormDataTextToPostFormData } from '../../src/multipart-form-data/form-data-utils';
8
+ const multipartPostDataWithText = JSON.parse(fs.readFileSync(path.join(__dirname, './resources/multipart-form-data-file-text-content.json')));
9
+
10
+ const { text } = multipartPostDataWithText;
11
+
12
+ const params: FormEntry[] = [
13
+ {
14
+ 'name': 'the_name_of_the_thing',
15
+ 'contentType': 'application/vnd.mspowerpoint-sniffing.x-rar-compressed',
16
+ 'fileName': 'some file name.ext',
17
+ 'value': 'the_contents_of_the_file'
18
+ },
19
+ {
20
+ 'name': 'entityId',
21
+ 'value': '123456789123456789123456789'
22
+ },
23
+ {
24
+ 'name': 'shouldDoStuff',
25
+ 'value': 'true'
26
+ },
27
+ {
28
+ 'name': 'content-transfer-encoding',
29
+ 'value': 'quoted-printable'
30
+ },
31
+ ];
32
+
33
+ suite('form-data-utils - multipart form data', () => {
34
+ describe('Form Class - manages and manipulates multipart form data', () => {
35
+ it('to string', () => {
36
+ const boundary = '------WebKitFormBoundary12345678ASDFGHJ';
37
+ // eslint-disable-next-line max-len
38
+ const expectedText = '--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="the_name_of_the_thing"; filename="some file name.ext"\r\ncontent-type: application/vnd.mspowerpoint-sniffing.x-rar-compressed\r\n\r\nthe_contents_of_the_file\r\n--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="entityId"\r\n\r\n123456789123456789123456789\r\n--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="shouldDoStuff"\r\n\r\ntrue\r\n--------WebKitFormBoundary12345678ASDFGHJ\r\nContent-Disposition: form-data; name="content-transfer-encoding"\r\n\r\nquoted-printable\r\n--------WebKitFormBoundary12345678ASDFGHJ--\r\n';
39
+ const form = new Form(params, boundary);
40
+ const result = form.toString();
41
+ expect(result).equals(expectedText);
42
+ });
43
+ });
44
+
45
+ describe('mapToMultipartFormData', () => {
46
+ it ('should convert HAR parameters to PostFormData', () => {
47
+ const requestUnderTest = { postData: { params } } as { postData?: { params?, text?, mimeType? }, postFormData?: PostFormData };
48
+ const expectedResult = {
49
+ postFormData: [
50
+ {
51
+ name: 'the_name_of_the_thing',
52
+ value: '',
53
+ fileName: 'some file name.ext',
54
+ },
55
+ {
56
+ name: 'entityId',
57
+ value: '123456789123456789123456789'
58
+ },
59
+ {
60
+ name: 'shouldDoStuff',
61
+ value: 'true'
62
+ },
63
+ {
64
+ name: 'content-transfer-encoding',
65
+ value: 'quoted-printable'
66
+ },
67
+ ]
68
+ };
69
+ mapToMultipartFormData(requestUnderTest); // mutate request under test
70
+ expect(requestUnderTest.postFormData).to.have.deep.ordered.members(expectedResult.postFormData);
71
+ });
72
+ it ('should convert HAR multipart/form-data body TEXT to PostFormData', () => {
73
+ const requestUnderTest = { postData: { text } } as { postData?: { params?, text?, mimeType? }, postFormData?: PostFormData };
74
+ const expectedResult = {
75
+ postFormData: [
76
+ {
77
+ name: 'file',
78
+ value: '',
79
+ fileName: 'silk_sonic.jpeg',
80
+ },
81
+ ]
82
+ };
83
+ mapToMultipartFormData(requestUnderTest); // mutate request under test
84
+ expect(requestUnderTest.postFormData).to.have.deep.ordered.members(expectedResult.postFormData);
85
+ });
86
+ });
87
+
88
+ describe('multipartFormDataTextToPostFormData', () => {
89
+ it('should return empty array when text is empty', () => {
90
+ const result = multipartFormDataTextToPostFormData('');
91
+ expect(result).to.have.deep.ordered.members([]);
92
+ });
93
+
94
+ it('should return name-value', () => {
95
+ const text = '----10453916712809342873442003634\r\nContent-Disposition: form-data; name="arnon"\r\n\r\npeleg\r\n----10453916712809342873442003634--\r\n';
96
+ const result = multipartFormDataTextToPostFormData(text);
97
+ expect(result).to.have.deep.ordered.members([{ name: 'arnon', value: 'peleg' }]);
98
+ });
99
+
100
+ it('should return 2 name-values', () => {
101
+ // eslint-disable-next-line max-len
102
+ const text = '----10453916712\r\nContent-Disposition: form-data; name="arnon"\r\n\r\npeleg\r\n----10453916712\r\nContent-Disposition: form-data; name="rivi"\r\n\r\nshimi\r\n----10453916712--\r\n';
103
+ const result = multipartFormDataTextToPostFormData(text);
104
+ expect(result).to.have.deep.ordered.members([{ name: 'arnon', value: 'peleg' }, { name: 'rivi', value: 'shimi' }]);
105
+ });
106
+
107
+ it('should return name-value-filename', () => {
108
+ // eslint-disable-next-line max-len
109
+ const text = '-----------------------------10453916712809342873442003634\r\nContent-Disposition: form-data; name="file"; filename="silk_sonic.jpeg"\r\nContent-Type: image/jpeg\r\n\r\nXCXCXCXCXCXCXCXCXCXCXCXCXCXC\t\r\n\t \n\n\nXXC101010101010101101010101010\r\n-----------------------------10453916712809342873442003634--\r\n';
110
+ const result = multipartFormDataTextToPostFormData(text);
111
+ expect(result).to.have.deep.ordered.members([
112
+ {
113
+ name: 'file',
114
+ value: '',
115
+ fileName: 'silk_sonic.jpeg'
116
+ }
117
+ ]);
118
+ });
119
+ });
120
+
121
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "mimeType": "multipart/form-data; boundary=---------------------------10453916712809342873442003634",
3
+ "params": [],
4
+ "text": "-----------------------------10453916712809342873442003634\r\nContent-Disposition: form-data; name=\"file\"; filename=\"silk_sonic.jpeg\"\r\nContent-Type: image/jpeg\r\n\r\nÿØÿà\u0000\u0010JFIF\u0000\u0001\u0001\u0000\u0000\u0001\u0000\u0001\u0000\u0000ÿÛ\u0000„\u0000\u0005\u0003\u0004\t\t\b\t\t\t\t\t\t\t\t\t\u0005u001c\u0017\t\b\u001a\t\t\u0006\u0018!\u0018\u001a\u001d\u001d\u001f\u001f\u001f\u0007\u000b\"\u0018\"\u001e\u0018\u001c\u001e\u001f\u001e\u0001\u0005\u0005\u0005\b\u0007\b\u000e\b\b\r\u0012\r\u000e\r\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012\u0012R#B?\u00005Ù\r\n-----------------------------10453916712809342873442003634--\r\n"
5
+ }
@@ -1,4 +1,6 @@
1
1
  const { expect } = require('chai');
2
+ const { describe, it } = require('mocha');
3
+
2
4
  require('../../dist/parameters');
3
5
  const { canHaveNullParameterValue } = require('../../dist/parameters/parameter-functions/parameter-functions');
4
6
 
@@ -1,4 +1,5 @@
1
1
  const { expect } = require('chai');
2
+ const { describe, it } = require('mocha');
2
3
 
3
4
  const { parameterUtils, ParameterError } = require('../../dist/parameters');
4
5
  const _ = require('lodash');
@@ -180,4 +181,5 @@ describe('parameter utils', () => {
180
181
  expect(res).to.have.ordered.members([]);
181
182
  });
182
183
  });
184
+
183
185
  });
package/tsconfig.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "compilerOptions": {
4
4
  "noEmitHelpers": false,
5
5
  "outDir": "dist",
6
- "declaration": true,
6
+ "declaration": true
7
7
  },
8
8
  "include": ["src"]
9
9
  }