testilo 23.0.0 → 24.0.0

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/README.md CHANGED
@@ -65,8 +65,7 @@ Testilo can, however, make job preparation more efficient in these scenarios:
65
65
 
66
66
  The simplest version of a list of targets is a _target list_. It is an array of arrays defining 1 or more targets. It can be stored as a tab-delimited text file.
67
67
 
68
- A target is defined by 3 items:
69
- - An ID
68
+ A target is defined by 2 items:
70
69
  - A description
71
70
  - A URL
72
71
 
@@ -74,16 +73,16 @@ For example, a target list might be:
74
73
 
75
74
  ```javaScript
76
75
  [
77
- ['w3c', 'World Wide Web Consortium', 'https://www.w3.org/'],
78
- ['moz', 'Mozilla Foundation', 'https://foundation.mozilla.org/en/']
76
+ ['World Wide Web Consortium', 'https://www.w3.org/'],
77
+ ['Mozilla Foundation', 'https://foundation.mozilla.org/en/']
79
78
  ]
80
79
  ```
81
80
 
82
81
  If this target list were stored as a file, its content would be this (with “→” representing the Tab character):
83
82
 
84
83
  ```text
85
- w3c→World Wide Web Consortium→https://www.w3.org/
86
- moz→Mozilla Foundation→https://foundation.mozilla.org/en/
84
+ World Wide Web Consortium→https://www.w3.org/
85
+ Mozilla Foundation→https://foundation.mozilla.org/en/
87
86
  ```
88
87
 
89
88
  ### Batches
@@ -97,27 +96,21 @@ Targets can be specified in a more complex way, too. That allows you to create j
97
96
  targets: [
98
97
  {
99
98
  id: 'acme',
100
- which: 'https://acmeclothes.com/',
101
99
  what: 'Acme Clothes',
100
+ which: 'https://acmeclothes.com/',
102
101
  acts: {
103
102
  public: [
104
103
  {
105
- type: 'launch'
106
- },
107
- {
108
- type: 'url',
109
- which: 'https://acmeclothes.com/',
110
- what: 'Acme Clothes home page'
104
+ type: 'launch',
105
+ what: 'Acme Clothes home page',
106
+ url: 'https://acmeclothes.com/'
111
107
  }
112
108
  ],
113
109
  private: [
114
110
  {
115
- type: 'launch'
116
- },
117
- {
118
- type: 'url',
119
- which: 'https://acmeclothes.com/login.html',
120
- what: 'Acme Clothes login page'
111
+ type: 'launch',
112
+ what: 'Acme Clothes login page',
113
+ url: 'https://acmeclothes.com/login.html'
121
114
  },
122
115
  {
123
116
  type: 'text',
@@ -164,12 +157,6 @@ Here is a script:
164
157
  strict: true,
165
158
  isolate: true,
166
159
  timeLimit: 60,
167
- standard: 'also',
168
- observe: false,
169
- timeStamp: '240115T1200',
170
- requester: 'you@yourdomain.com',
171
- urlPrefix: '/file/reports',
172
- urlSuffix: '.json',
173
160
  acts: [
174
161
  {
175
162
  type: 'placeholder',
@@ -199,17 +186,12 @@ A script has several properties that specify facts about the jobs to be created.
199
186
  - `what`: a description of the script.
200
187
  - `strict`: `true` if Testaro is to abort jobs when a target redirects a request to a URL differing substantially from the one specified. If `false` Testaro is to allow redirection. All differences are considered substantial unless the URLs differ only in the presence and absence of a trailing slash.
201
188
  - `isolate`: If `true`, Testilo, before creating a job, will isolate test acts, as needed, from effects of previous test acts, by inserting a copy of the latest placeholder after each target-modifying test act other than the final act. If `false`, placeholders will not be duplicated.
202
- - `standard`: If `also`, jobs will tell Testaro to include in its reports both the original results of the tests of tools and the Testaro-standardized results. If `only`, reports are to include only the standardized test results. If `no`, reports are to include only the original results, without standardization.
203
- - `observe`: If `true`, jobs will tell Testaro to allow granular observation of job progress. If `false`, jobs will tell Testaro not to permit granular observation, but only to send the report to the server when the report is completed. It is generally user-friendly to allow granular observation, and for user applications to implement it, if they make users wait while jobs are assigned and performed, since that process typically takes about 3 minutes.
204
- - `timeStamp`: This string specifies a UTC date and time when jobs created with the script are to be permitted to be assigned to agents. Thus, jobs can be created from a script for later performance. The value of `timeStamp` is a compact representation in the format `yymmddThhMM`. If the value is an empty string, Testilo will make the date and time equal to the time when jobs are created.
205
- - `requester`: the email address that any notices of job completion can be sent to, or an empty string if there is a `REQUESTER` environment variable and it is to be used.
206
- - `urlPrefix`: the start of a URL that the Testaro reports will be retrievable at.
207
- - `urlSuffix`: the end of that URL. The job ID will be inserted between the `urlPrefix` and the `urlSuffix`.
189
+ - `timeLimit`: This specifies the maximum duration, in seconds, of a job. Testaro will abort jobs that are not completed within that time.
208
190
  - `acts`: an array of acts.
209
191
 
210
192
  The first act in this example script is a placeholder, whose `which` property is `'private'`. If the above batch were merged with this script, in each job the placeholder would be replaced with the `private` acts of a target. For example, the first act of the first job would launch a Chromium browser, navigate to the Acme login page, complete and submit the login form, wait for the account page to load, run the Axe tests, and then run the QualWeb tests. If the batch contained additional targets, additional jobs would be created, with the login actions for each target specified in the `private` array of the `acts` object of that target.
211
193
 
212
- As shown in this example, when a browser is launched by placeholder substitution, the script can determine the browser type (`chromium`, `firefox`, or `webkit`) by assigning a value to a `launch` property of the placeholder.
194
+ As shown in this example, when a browser is launched by placeholder substitution, the script can determine the browser type (`chromium`, `firefox`, or `webkit`) by assigning a value to a `launch` property of the placeholder. This allows a script to ensure that the tests it requires are performed with appropriate browser types. Some browser types are incompatible with some tests.
213
195
 
214
196
  ### Target list to batch
215
197
 
@@ -225,13 +207,15 @@ A module can invoke `batch` in this way:
225
207
 
226
208
  ```javaScript
227
209
  const {batch} = require('testilo/batch');
228
- const batchObj = batch(listID, what, targets);
210
+ const batchObj = batch(id, what, targets);
229
211
  ```
230
212
 
231
- This invocation references `listID`, `what`, and `targets` variables that the module must have already defined. The `listID` variable is a unique identifier for the target list. the `what` variable describes the target list. The `targets` variable is an array of arrays, with each array containing the 3 items (ID, description, and URL) defining one target.
213
+ This invocation references `id`, `what`, and `targets` variables that the module must have already defined. The `id` variable is a unique identifier for the target list. The `what` variable describes the target list. The `targets` variable is an array of arrays, with each array containing the 2 items (description and URL) defining one target.
232
214
 
233
215
  The `batch()` function of the `batch` module generates a batch and returns it as an object. The invoking module can further dispose of the batch as needed.
234
216
 
217
+ The ID assigned to each target by the `batch()` function is a sequential (base-62 alphanumeric) string, rather than a mnemonic like the one (`'acme'`) in the above example.
218
+
235
219
  ##### By a user
236
220
 
237
221
  A user can invoke `batch` in this way:
@@ -299,6 +283,8 @@ To create a script without any issue restrictions, a user can execute the statem
299
283
 
300
284
  #### Options
301
285
 
286
+ The `script` module will use the value of the `SEND_REPORT_TO` environment variable as the value of the `sendReportTo` property of the script, if that variable exists, and otherwise will leave that property with an empty-string value.
287
+
302
288
  When the `script` module creates a script for you, it does not ask you for all of the options that the script may require. Instead, it chooses default options. After you invoke `script`, you can edit the script that it creates to revise options.
303
289
 
304
290
  ### Merge
@@ -317,16 +303,14 @@ Suppose you ask for a merger of the above batch and script. Then the first job p
317
303
  timeLimit: 60,
318
304
  standard: 'also',
319
305
  observe: false,
306
+ sendReportTo: 'https://ourdomain.com/testman/api/report'
320
307
  timeStamp: '240115T1200',
321
308
  acts: [
322
309
  {
323
310
  type: 'launch',
324
- which: 'chromium'
325
- },
326
- {
327
- type: 'url',
328
- which: 'https://acmeclothes.com/login.html',
329
- what: 'Acme Clothes login page'
311
+ which: 'chromium',
312
+ what: 'Acme Clothes login page',
313
+ url: 'https://acmeclothes.com/login.html'
330
314
  },
331
315
  {
332
316
  type: 'text',
@@ -357,12 +341,9 @@ Suppose you ask for a merger of the above batch and script. Then the first job p
357
341
  },
358
342
  {
359
343
  type: 'launch',
360
- which: 'chromium'
361
- },
362
- {
363
- type: 'url',
364
- which: 'https://acmeclothes.com/login.html',
365
- what: 'Acme Clothes login page'
344
+ which: 'chromium',
345
+ what: 'Acme Clothes login page',
346
+ url: 'https://acmeclothes.com/login.html'
366
347
  },
367
348
  {
368
349
  type: 'text',
@@ -399,19 +380,18 @@ Suppose you ask for a merger of the above batch and script. Then the first job p
399
380
  id: 'acme',
400
381
  what: 'Acme Clothes'
401
382
  },
402
- requester: 'you@yourdomain.tld',
403
- url: '/file/reports/240115T1200-4Rw-acme.json'
383
+ requester: 'you@yourdomain.tld'
404
384
  },
405
- creationTime: '2023-11-20T15:50:27'
385
+ creationTime: '241120T1550'
406
386
  }
407
387
  ```
408
388
 
409
389
  Testilo has substituted the `private` acts from the `acme` target of the batch for the placeholder when creating the job. Testilo also has:
410
390
  - inserted a copy of those same acts after the `axe` test act, because `axe` is a target-modifying tool.
411
391
  - let the script determine the browser type of the `launch` act.
412
- - added the creation time to the job.
413
392
  - given the job an ID that combines the time stamp with a differentiator and the batch ID.
414
393
  - inserted a `sources` property into the job, recording facts about the script, the batch, the target, the requester, and the report URL.
394
+ - added a time stamp describing the creation time to the job.
415
395
 
416
396
  This is a valid Testaro job.
417
397
 
@@ -425,25 +405,18 @@ A module can invoke `merge` in this way:
425
405
 
426
406
  ```javaScript
427
407
  const {merge} = require('testilo/merge');
428
- const jobs = merge(
429
- script, batch, requester, isolate, standard, isGranular, timeStamp, 'file/reports/', '.json'
430
- );
408
+ const jobs = merge(script, batch, standard, observe, requester, timeStamp);
431
409
  ```
432
410
 
433
- The `merge` module uses these 9 arguments to create jobs from a script and a batch.
411
+ The `merge` module uses these 4 arguments to create jobs from a script and a batch.
434
412
 
435
413
  The arguments are:
436
414
  - `script`: a script.
437
415
  - `batch`: a batch.
438
- - `requester`: an email address, or an empty string if there is a `REQUESTER` environment variable to be used.
439
- - `isolate`: `true` if test isolation is to be performed, or `false` if not.
440
- - `standard`: one of the standardization options (`'also'`, `'only'`, or `'no'`).
441
- - `isGranular`: `true` if Testaro is to allow granular observation of the job when performed under a network watch, or `false` if not.
442
- - `timeStamp`: a time stamp in the format `240115T1200`.
443
- - `urlPnrefix`: the start of an absolute or relative report URL.
444
- - `urlSuffix`: the end of a report URL.
445
-
446
- In this case, a job with ID `240115T1200-4Rw-acme` will produce a report that can be retrieved at the relative URL `/file/reports/240115T1200-4Rw-acme.json`.
416
+ - `standard`: how to handle standardization. If `also`, jobs will tell Testaro to include in its reports both the original results of the tests of tools and the Testaro-standardized results. If `only`, reports are to include only the standardized test results. If `no`, reports are to include only the original results, without standardization.
417
+ - `observe`: whether to allow granular observation. If `true`, jobs will tell Testaro to permit granular observation of job progress. If `false`, jobs will tell Testaro not to permit granular observation, but only to send the report to the server when the report is completed. It is generally user-friendly to allow granular observation, and for user applications to implement it, if they make users wait while jobs are assigned and performed, since that process typically takes about 3 minutes.
418
+ - `requester`: an email address.
419
+ - `timeStamp`: the earliest UTC date and time when the jobs may be assigned (format `240415T1230`), or an empty string if now.
447
420
 
448
421
  The `merge()` function of the `merge` module generates jobs and returns them in an array. The invoking module can further dispose of the jobs as needed.
449
422
 
@@ -453,21 +426,20 @@ A user can invoke `merge` in this way:
453
426
 
454
427
  - Create a script and save it as a JSON file in the `scripts` subdirectory of the `SPECDIR` directory.
455
428
  - Create a batch and save it as a JSON file in the `batches` subdirectory of the `SPECDIR` directory.
456
- - In the Testilo project directory, execute the statement `node call merge scriptName batchName requester isolate standard observe todoDir urlPrefix urlSuffix`.
429
+ - In the Testilo project directory, execute the statement:
430
+
431
+ ```javascript
432
+ node call merge scriptID batchID standard observe requester timeStamp todoDir
433
+ ```
457
434
 
458
435
  In this statement, replace:
459
- - `scriptName` with the base name of the script file.
460
- - `batchName` with the base name of the batch file.
461
- - `requester` with an email address, or with an empty string if the environment variable `REQUESTER` exists and you want to use it.
462
- - `isolate` with `true` if you want test isolation or `false` if not.
463
- - `standard` with `'also'`, `'only'`, or `'no'` to specify the treatment of standard-format results.
464
- - `observe` with `true` if granular observation is to be permitted, or `false` if not.
465
- - `todoDir` with `true` if the job is to be saved in the `todo` subdirectory, or `false` if it is to be saved in the `pending` subdirectory, of the `JOBDIR` directory.
466
- - `urlPrefix` with the pre-ID part of the report URL.
467
- - `urlSuffix` with the post-ID part of the report URL.
436
+ - `scriptID` with the ID (which is also the base of the file name) of the script.
437
+ - `batchID` with the ID (which is also the base of the file name) of the batch.
438
+ - `standard`, `observe`, `requester`, and `timeStamp` as described above.
439
+ - `todoDir`: `true` if the jobs are to be saved in the `todo` subdirectory, or `false` if they are to be saved in the `pending` subdirectory, of the `JOBDIR` directory.
468
440
 
469
441
  The `call` module will retrieve the named script and batch from their respective directories.
470
- The `merge` module will create an array of jobs, with or without test isolation.
442
+ The `merge` module will create an array of jobs.
471
443
  The `call` module will save the jobs as JSON files in the `todo` or `pending` subdirectory of the `JOBDIR` directory.
472
444
 
473
445
  #### Validation
@@ -733,3 +705,11 @@ The third argument to `call` (`23pl` in this example) is optional. If it is omit
733
705
  ### Validation
734
706
 
735
707
  To test the `compare` module, in the project directory you can execute the statement `node validation/compare/validate`. If `compare` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
708
+
709
+ ## Origin
710
+
711
+ Work on the functionalities of Testaro and Testilo began in 2017. It was named [Autotest](https://github.com/jrpool/autotest) in early 2021 and then partitioned into the more single-purpose packages Testaro and Testilo in January 2022.
712
+
713
+ ## Etymology
714
+
715
+ “Testilo” means “testing tool” in Esperanto.
package/batch.js CHANGED
@@ -3,7 +3,11 @@
3
3
  Converts a target list to a batch.
4
4
  */
5
5
 
6
- // ########## FUNCTIONS
6
+ // IMPORTS
7
+
8
+ const {alphaNumOf} = require('./procs/util');
9
+
10
+ // FUNCTIONS
7
11
 
8
12
  // Converts a target list to a batch and returns the batch.
9
13
  exports.batch = (id, what, targetList) => {
@@ -17,9 +21,9 @@ exports.batch = (id, what, targetList) => {
17
21
  && targetList.length
18
22
  && targetList.every(
19
23
  target => Array.isArray(target)
24
+ && target.length === 2
20
25
  && target.every(item => typeof item === 'string')
21
26
  )
22
- && targetList.some(target => target.length === 3)
23
27
  ) {
24
28
  // Initialize the batch.
25
29
  const batch = {
@@ -27,25 +31,23 @@ exports.batch = (id, what, targetList) => {
27
31
  what,
28
32
  targets: []
29
33
  };
30
- // For each valid target:
31
- targetList.forEach(target => {
32
- if (target.length === 3 && target.every(item => item.length)) {
33
- // Add it to the batch.
34
- batch.targets.push({
35
- id: target[0],
36
- which: target[2],
37
- what: target[1],
38
- acts: {
39
- main: [
40
- {
41
- type: 'launch',
42
- url: target[2],
43
- what: target[1]
44
- }
45
- ]
46
- }
47
- });
48
- }
34
+ // For each target:
35
+ targetList.forEach((target, index) => {
36
+ // Add it to the batch.
37
+ batch.targets.push({
38
+ id: alphaNumOf(index),
39
+ what: target[0],
40
+ which: target[1],
41
+ acts: {
42
+ main: [
43
+ {
44
+ type: 'launch',
45
+ what: target[0],
46
+ url: target[1]
47
+ }
48
+ ]
49
+ }
50
+ });
49
51
  });
50
52
  // Return the batch.
51
53
  return batch;
@@ -53,6 +55,7 @@ exports.batch = (id, what, targetList) => {
53
55
  // Otherwise, i.e. if the arguments are invalid:
54
56
  else {
55
57
  // Return this.
58
+ console.log('ERROR: Batch information missing or invalid');
56
59
  return null;
57
60
  }
58
61
  };
package/call.js CHANGED
@@ -43,16 +43,21 @@ const fnArgs = process.argv.slice(3);
43
43
  // ########## FUNCTIONS
44
44
 
45
45
  // Converts a target list to a batch.
46
- const callBatch = async (listID, what) => {
46
+ const callBatch = async (id, what) => {
47
47
  // Get the target list.
48
- const listString = await fs.readFile(`${specDir}/targetLists/${listID}.tsv`, 'utf8');
49
- const list = listString.split('\n').map(target => target.split('\t'));
48
+ const listString = await fs.readFile(`${specDir}/targetLists/${id}.tsv`, 'utf8');
49
+ const list = listString
50
+ .split('\n')
51
+ .filter(target => target.length)
52
+ .map(target => target.split('\t'));
50
53
  // Convert it to a batch.
51
- const batchObj = batch(listID, what, list);
54
+ const batchObj = batch(id, what, list);
52
55
  // Save the batch.
53
- const batchJSON = JSON.stringify(batchObj, null, 2);
54
- await fs.writeFile(`${specDir}/batches/${listID}.json`, `${batchJSON}\n`);
55
- console.log(`Target list ${listID} converted to a batch and saved in ${specDir}/batches`);
56
+ if (batchObj) {
57
+ const batchJSON = JSON.stringify(batchObj, null, 2);
58
+ await fs.writeFile(`${specDir}/batches/${id}.json`, `${batchJSON}\n`);
59
+ console.log(`Target list ${id} converted to a batch and saved in ${specDir}/batches`);
60
+ }
56
61
  };
57
62
  // Fulfills a script-creation request.
58
63
  const callScript = async (scriptID, classificationID = null, ... issueIDs) => {
@@ -71,11 +76,11 @@ const callScript = async (scriptID, classificationID = null, ... issueIDs) => {
71
76
  const callMerge = async (
72
77
  scriptID,
73
78
  batchID,
74
- requester,
75
- withIsolation,
76
79
  standard,
77
- isGranular,
78
- todo
80
+ observe,
81
+ requester,
82
+ timeStamp,
83
+ todoDir
79
84
  ) => {
80
85
  // Get the script and the batch.
81
86
  const scriptJSON = await fs.readFile(`${specDir}/scripts/${scriptID}.json`, 'utf8');
@@ -83,19 +88,15 @@ const callMerge = async (
83
88
  const batchJSON = await fs.readFile(`${specDir}/batches/${batchID}.json`, 'utf8');
84
89
  const batch = JSON.parse(batchJSON);
85
90
  // Merge them into an array of jobs.
86
- const jobs = merge(
87
- script, batch, requester, withIsolation, standard, isGranular, null, urlPrefix, urlSuffix
88
- );
91
+ const jobs = merge(script, batch, standard, observe === 'true', requester, timeStamp);
89
92
  // Save the jobs.
90
- const destination = todo === 'true' ? 'todo' : 'pending';
93
+ const subdir = `${jobDir}/${todoDir === 'true' ? 'todo' : 'pending'}`;
91
94
  for (const job of jobs) {
92
95
  const jobJSON = JSON.stringify(job, null, 2);
93
- await fs.writeFile(`${jobDir}/${destination}/${job.id}.json`, `${jobJSON}\n`);
96
+ await fs.writeFile(`${subdir}/${job.id}.json`, `${jobJSON}\n`);
94
97
  }
95
- const {timeStamp} = jobs[0];
96
- console.log(
97
- `Script ${scriptID} and batch ${batchID} merged as ${timeStamp}-… in ${jobDir}/${destination}`
98
- );
98
+ const truncatedID = `${jobs[0].timeStamp}-${jobs[0].mergeID}-…`;
99
+ console.log(`Script ${scriptID} and batch ${batchID} merged as ${truncatedID} in ${subdir}`);
99
100
  };
100
101
  // Gets selected reports.
101
102
  const getReports = async (type, selector = '') => {
@@ -222,7 +223,7 @@ else if (fn === 'script' && fnArgs.length) {
222
223
  console.log('Execution completed');
223
224
  });
224
225
  }
225
- else if (fn === 'merge' && fnArgs.length === 9) {
226
+ else if (fn === 'merge' && fnArgs.length === 7) {
226
227
  callMerge(... fnArgs)
227
228
  .then(() => {
228
229
  console.log('Execution completed');
package/merge.js CHANGED
@@ -1,83 +1,34 @@
1
1
  /*
2
2
  merge.js
3
3
  Merges a script and a batch and returns jobs.
4
- Arguments:
5
- 0. script
6
- 1. batch
7
- 2. requester
8
- 3. whether to provide test isolation
9
- 4. value of the standard property
10
- 5. whether reporting is to be granular
11
- 6. date and time as a compact timestamp for job execution, if not now
12
4
  */
13
5
 
14
6
  // ########## IMPORTS
15
7
 
16
8
  // Module to keep secrets.
17
9
  require('dotenv').config();
10
+ // Module to perform common actions.
11
+ const {alphaNumOf, dateOf, getRandomString, getNowStamp} = require('./procs/util');
18
12
 
19
13
  // ########## CONSTANTS
20
14
 
21
- // Standard requester.
22
- const stdRequester = process.env.REQUESTER || 'nobody@nodomain.tld';
23
- // Length of the random part of a job ID, as a string.
24
- const randomIDLength = process.env.RANDOM_ID_LENGTH || '3';
25
15
  // Tools that alter the page.
26
16
  const contaminantNames = new Set([
27
17
  'alfa',
28
18
  'aslint',
29
19
  'axe',
20
+ 'ed11y',
30
21
  'htmlcs',
31
22
  'testaro'
32
23
  ]);
33
- const randomIDChars = (() => {
34
- const digits = Array(10).fill('').map((digit, index) => index.toString());
35
- const uppers = Array(26).fill('').map((letter, index) => String.fromCodePoint(65 + index));
36
- const lowers = Array(26).fill('').map((letter, index) => String.fromCodePoint(97 + index));
37
- return digits.concat(uppers, lowers);
38
- })();
39
-
24
+ // Length of the random merger ID.
25
+ const mergeIDLength = 2;
40
26
 
41
27
  // ########## FUNCTIONS
42
28
 
43
- // Inserts a character periodically in a string.
44
- const punctuate = (string, insertion, chunkSize) => {
45
- const segments = [];
46
- let startIndex = 0;
47
- while (startIndex < string.length) {
48
- segments.push(string.slice(startIndex, startIndex + chunkSize));
49
- startIndex += chunkSize;
50
- }
51
- return segments.join(insertion);
52
- };
53
- // Converts a compact timestamp to a date.
54
- const dateOf = timeStamp => {
55
- if (/^\d{6}T\d{4}$/.test(timeStamp)) {
56
- const dateString = punctuate(timeStamp.slice(0, 6), '-', 2);
57
- const timeString = punctuate(timeStamp.slice(7, 11), ':', 2);
58
- return new Date(`20${dateString}T${timeString}Z`);
59
- } else {
60
- return null;
61
- }
62
- };
63
- // Converts a date and time to a compact timestamp.
64
- const stampTime = date => date.toISOString().replace(/[-:]/g, '').slice(2, 13);
65
- // Generates a random string.
66
- const getRandomID = length => {
67
- const chars = [];
68
- for (let i = 0; i < length; i++) {
69
- chars.push(randomIDChars[Math.floor(62 * Math.random())]);
70
- }
71
- return chars.join('');
72
- };
73
29
  // Merges a script and a batch and returns jobs.
74
- exports.merge = (
75
- script, batch, requester, isolate, standard, observe, timeStamp, urlPrefix, urlSuffix
76
- ) => {
77
- if (isolate === 'false') {
78
- isolate = false;
79
- }
80
- // If a timestamp was specified:
30
+ exports.merge = (script, batch, standard, observe, requester, timeStamp) => {
31
+ // If a time stamp was specified:
81
32
  if (timeStamp) {
82
33
  // If it is invalid:
83
34
  if (! dateOf(timeStamp)) {
@@ -86,77 +37,79 @@ exports.merge = (
86
37
  return [];
87
38
  }
88
39
  }
89
- // Otherwise, i.e. if no timestamp was specified:
40
+ // Otherwise, i.e. if no time stamp was specified:
90
41
  else {
91
42
  // Create one for the job.
92
- timeStamp = stampTime(new Date());
43
+ timeStamp = getNowStamp();
93
44
  }
94
- // If the requester is blank or unspecified, make it the standard requester.
95
- requester ||= stdRequester;
96
- // Create a creation-time description.
97
- const creationTime = (new Date()).toISOString().slice(0, 16);
98
- // Initialize a target-independent job.
45
+ // Initialize a job as a copy of the script.
99
46
  const protoJob = JSON.parse(JSON.stringify(script));
100
- // Make the timestamp and a random string the start of the job ID.
101
- protoJob.id = `${timeStamp}-${getRandomID(Number.parseInt(randomIDLength, 10))}-`;
102
- // Add a sources property to the job.
47
+ // Add an initialized sources property to it.
103
48
  protoJob.sources = {
104
49
  script: script.id,
105
50
  batch: batch.id,
106
51
  target: {
107
52
  id: '',
108
- which: '',
109
- what: ''
53
+ what: '',
54
+ which: ''
110
55
  },
111
- requester,
112
- sendReportTo: process.env.REPORT_URL || '',
113
- url: ''
56
+ requester
114
57
  };
115
58
  // Add properties to the job.
116
- protoJob.creationTime = creationTime;
59
+ protoJob.standard = standard;
60
+ protoJob.observe = observe;
117
61
  protoJob.timeStamp = timeStamp;
118
- protoJob.standard = standard || 'only';
119
- protoJob.observe = observe || false;
62
+ protoJob.creationTimeStamp = getNowStamp();
120
63
  // If isolation was requested:
121
- if (isolate) {
122
- // Configure the job for it.
64
+ if (script.isolate) {
65
+ // For each act:
123
66
  let {acts} = protoJob;
124
67
  let lastPlaceholder = {};
125
68
  for (const actIndexString in acts) {
69
+ // If it is a placeholder:
126
70
  const actIndex = Number.parseInt(actIndexString);
127
71
  const act = acts[actIndex];
128
- const nextAct = acts[actIndex + 1];
129
72
  if (act.type === 'placeholder') {
73
+ // Identify it as the current one.
130
74
  lastPlaceholder = act;
131
75
  }
76
+ // Otherwise, if it is a non-final target-modifying test act:
132
77
  else if (
133
78
  act.type === 'test'
134
79
  && contaminantNames.has(act.which)
135
80
  && actIndex < acts.length - 1
136
- && (nextAct.type === 'test')
137
81
  ) {
82
+ // Change it to an array of itself and the current placeholder.
138
83
  acts[actIndex] = JSON.parse(JSON.stringify([act, lastPlaceholder]));
139
84
  }
140
85
  };
86
+ // Flatten the acts.
141
87
  protoJob.acts = acts.flat();
142
88
  }
89
+ // Delete the no-longer-necessary job property.
90
+ delete protoJob.isolate;
143
91
  // Initialize an array of jobs.
144
92
  const jobs = [];
93
+ // Get an ID for the merger.
94
+ const mergeID = getRandomString(mergeIDLength);
145
95
  // For each target in the batch:
146
96
  const {targets} = batch;
147
- for (const target of targets) {
97
+ targets.forEach((target, index) => {
148
98
  // If the target has the required identifiers:
149
- const {id, which, what} = target;
150
- if (id && which && what) {
99
+ const {id, what, which} = target;
100
+ if (id && what && which) {
151
101
  // Initialize a job.
152
102
  const job = JSON.parse(JSON.stringify(protoJob));
153
- // Append the target ID to the job ID.
154
- job.id += target.id;
103
+ // Make the job ID unique.
104
+ const targetID = alphaNumOf(index);
105
+ job.id = `${timeStamp}-${mergeID}-${targetID}`;
106
+ // Add properties to the job.
107
+ job.mergeID = mergeID;
108
+ job.sendReportTo = process.env.SEND_REPORT_TO || '';
155
109
  // Add data to the sources property of the job.
156
- job.sources.target.id = target.id;
157
- job.sources.target.which = target.which;
110
+ job.sources.target.id = targetID;
158
111
  job.sources.target.what = target.what;
159
- job.sources.url = `${urlPrefix}${job.id}${urlSuffix}`;
112
+ job.sources.target.which = target.which;
160
113
  // Replace each placeholder object in the job with the named replacer array of the target.
161
114
  let {acts} = job;
162
115
  for (const actIndex in acts) {
@@ -184,13 +137,14 @@ exports.merge = (
184
137
  }
185
138
  }
186
139
  }
140
+ // Flatten the acts.
187
141
  job.acts = acts.flat();
188
142
  // Append the job to the array of jobs.
189
143
  jobs.push(job);
190
144
  }
191
145
  else {
192
- console.log('ERROR: Target in batch missing id, which, or what property');
146
+ console.log('ERROR: Target in batch missing id, what, or which property');
193
147
  }
194
- };
148
+ });
195
149
  return jobs;
196
150
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "23.0.0",
3
+ "version": "24.0.0",
4
4
  "description": "Prepares and processes Testaro reports",
5
5
  "main": "aim.js",
6
6
  "scripts": {
package/procs/test.js ADDED
@@ -0,0 +1,20 @@
1
+ // Array of 62 alphanumeric characters.
2
+ const alphaNumChars = (() => {
3
+ const digits = Array(10).fill('').map((digit, index) => index.toString());
4
+ const uppers = Array(26).fill('').map((letter, index) => String.fromCodePoint(65 + index));
5
+ const lowers = Array(26).fill('').map((letter, index) => String.fromCodePoint(97 + index));
6
+ return digits.concat(uppers, lowers);
7
+ })();
8
+
9
+ // Returns an alphanumeric representation of an integer.
10
+ const alphaNumOf = num => {
11
+ let resultDigits = [];
12
+ while (num) {
13
+ const remainder = num % 62;
14
+ resultDigits.unshift(alphaNumChars[remainder]);
15
+ num = Math.floor(num / 62);
16
+ }
17
+ return resultDigits.join('');
18
+ };
19
+
20
+ console.log(alphaNumOf(process.argv[2]));
package/procs/util.js ADDED
@@ -0,0 +1,63 @@
1
+ /*
2
+ util.js
3
+ Utility functions.
4
+ */
5
+
6
+ // CONSTANTS
7
+
8
+ // Array of 62 alphanumeric characters.
9
+ const alphaNumChars = (() => {
10
+ const digits = Array(10).fill('').map((digit, index) => index.toString());
11
+ const uppers = Array(26).fill('').map((letter, index) => String.fromCodePoint(65 + index));
12
+ const lowers = Array(26).fill('').map((letter, index) => String.fromCodePoint(97 + index));
13
+ return digits.concat(uppers, lowers);
14
+ })();
15
+
16
+ // FUNCTIONS
17
+
18
+ // Returns a string representing a date and time.
19
+ const getTimeString = date => date.toISOString().slice(0, 19);
20
+ // Returns a string representing the date and time.
21
+ exports.getNowString = () => getTimeString(new Date());
22
+ // Returns a time stamp representing a date and time.
23
+ const getTimeStamp = date => getTimeString(date).replace(/[-:]/g, '').slice(2, 13);
24
+ // Returns a time stamp representing the date and time.
25
+ exports.getNowStamp = () => getTimeStamp(new Date());
26
+ // Inserts a character periodically in a string.
27
+ const punctuate = (string, insertion, chunkSize) => {
28
+ const segments = [];
29
+ let startIndex = 0;
30
+ while (startIndex < string.length) {
31
+ segments.push(string.slice(startIndex, startIndex + chunkSize));
32
+ startIndex += chunkSize;
33
+ }
34
+ return segments.join(insertion);
35
+ };
36
+ // Converts a compact timestamp to a date.
37
+ exports.dateOf = timeStamp => {
38
+ if (/^\d{6}T\d{4}$/.test(timeStamp)) {
39
+ const dateString = punctuate(timeStamp.slice(0, 6), '-', 2);
40
+ const timeString = punctuate(timeStamp.slice(7, 11), ':', 2);
41
+ return new Date(`20${dateString}T${timeString}Z`);
42
+ } else {
43
+ return null;
44
+ }
45
+ };
46
+ // Returns a base-62 alphanumeric representation of an integer.
47
+ exports.alphaNumOf = num => {
48
+ let resultDigits = [];
49
+ while (num || ! resultDigits.length) {
50
+ const remainder = num % 62;
51
+ resultDigits.unshift(alphaNumChars[remainder]);
52
+ num = Math.floor(num / 62);
53
+ }
54
+ return resultDigits.join('');
55
+ };
56
+ // Returns a random string.
57
+ exports.getRandomString = length => {
58
+ const chars = [];
59
+ for (let i = 0; i < length; i++) {
60
+ chars.push(alphaNumChars[Math.floor(62 * Math.random())]);
61
+ }
62
+ return chars.join('');
63
+ };
package/script.js CHANGED
@@ -71,9 +71,6 @@ exports.script = (id, issues = null, ... issueIDs) => {
71
71
  strict: true,
72
72
  isolate: true,
73
73
  timeLimit: 30 + (10 * issueIDs.length || 30 * toolIDs.length),
74
- standard: 'only',
75
- observe: true,
76
- timeStamp: '',
77
74
  acts: [
78
75
  {
79
76
  "type": "placeholder",
@@ -99,7 +96,7 @@ exports.script = (id, issues = null, ... issueIDs) => {
99
96
  toolAct.rules.unshift('y');
100
97
  }
101
98
  }
102
- // Add any needed option specifications to the act.
99
+ // Add any needed option defaults to the act.
103
100
  if (toolID === 'axe') {
104
101
  toolAct.detailLevel = 2;
105
102
  }