testaro 32.3.5 → 32.4.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 +17 -19
- package/actSpecs.js +1 -1
- package/call.js +23 -8
- package/dirWatch.js +117 -28
- package/netWatch-old.js +397 -0
- package/netWatch.js +303 -0
- package/package.json +1 -1
- package/procs/standardize.js +1 -1
- package/run.js +10 -8
- package/tests/qualWeb.js +2 -6
- package/watch-old.js +77 -0
- package/watch.js +36 -24
package/README.md
CHANGED
|
@@ -150,7 +150,7 @@ Here is an example of a job:
|
|
|
150
150
|
|
|
151
151
|
```javascript
|
|
152
152
|
{
|
|
153
|
-
id: '
|
|
153
|
+
id: '241213T1200-ts25-w3c',
|
|
154
154
|
what: 'Test W3C with 2 alfa rules',
|
|
155
155
|
strict: true,
|
|
156
156
|
timeLimit: 65,
|
|
@@ -178,8 +178,8 @@ Here is an example of a job:
|
|
|
178
178
|
},
|
|
179
179
|
requester: 'user@domain.org'
|
|
180
180
|
},
|
|
181
|
-
creationTime: '
|
|
182
|
-
timeStamp: '
|
|
181
|
+
creationTime: '2024-12-10T14:28Z',
|
|
182
|
+
timeStamp: '241213T1200'
|
|
183
183
|
}
|
|
184
184
|
```
|
|
185
185
|
|
|
@@ -198,8 +198,8 @@ Job properties:
|
|
|
198
198
|
- `batch` (optional): a set of targets (URLs) from which the target of this job was drawn.
|
|
199
199
|
- `target` (optional): an object describing the target being tested by this job.
|
|
200
200
|
- `requester` (optional): the email address that should receive a notice of completion of the job.
|
|
201
|
-
- `creationTime`: the time when the job was created.
|
|
202
|
-
- `timeStamp`: a string
|
|
201
|
+
- `creationTime`: the time in ISO 8601 format when the job was created.
|
|
202
|
+
- `timeStamp`: a string representing the date and time before which the job is not to be performed.
|
|
203
203
|
|
|
204
204
|
### Reports
|
|
205
205
|
|
|
@@ -595,7 +595,7 @@ When no string pertains to a module, then QualWeb will test for all of the rules
|
|
|
595
595
|
|
|
596
596
|
Thus, when the `rules` argument is omitted, QualWeb will test for all of the rules in all of these modules.
|
|
597
597
|
|
|
598
|
-
|
|
598
|
+
The target can be provided to QualWeb either as an existing page or as a URL. Experience indicates that the results can differ between these methods, with each method reporting some rule violations or some instances that the other method does not report.
|
|
599
599
|
|
|
600
600
|
###### Testaro
|
|
601
601
|
|
|
@@ -774,7 +774,7 @@ Testaro can watch for a job in a directory, with the `dirWatch` function, which
|
|
|
774
774
|
###### By a module
|
|
775
775
|
|
|
776
776
|
```javaScript
|
|
777
|
-
const {dirWatch} = require('./
|
|
777
|
+
const {dirWatch} = require('./dirWatch');
|
|
778
778
|
dirWatch(true, 300);
|
|
779
779
|
```
|
|
780
780
|
|
|
@@ -786,20 +786,12 @@ Testaro creates a report for each job and saves the report in the directory spec
|
|
|
786
786
|
|
|
787
787
|
###### By a user
|
|
788
788
|
|
|
789
|
-
A user can choose between two methods:
|
|
790
|
-
|
|
791
789
|
```javaScript
|
|
792
790
|
node call dirWatch true 300
|
|
793
791
|
```
|
|
794
792
|
|
|
795
|
-
```javaScript
|
|
796
|
-
node dirWatch true 300
|
|
797
|
-
```
|
|
798
|
-
|
|
799
793
|
The arguments and behaviors described above for execution by a module apply here, too.
|
|
800
794
|
|
|
801
|
-
The second, shorter method spawns a new watch subprocess after each job performance, to decrease the risk of process corruption involving bogus timeout messages from Playwright during jobs. That method requires you to enter `CTRL-c` to stop the watching.
|
|
802
|
-
|
|
803
795
|
##### Network watch
|
|
804
796
|
|
|
805
797
|
Testaro can poll servers for jobs to be performed.
|
|
@@ -808,21 +800,27 @@ An instance of Testaro is an _agent_ and has an identifier specified by `process
|
|
|
808
800
|
|
|
809
801
|
The URLs polled by Testaro are specified by `process.env.JOB_URLS`. The format of that environment variable is a `+`-delimited list of URLs, including schemes. If one of the URLs is `https://testrunner.org/a11ytest/api/job`, and if a Testaro instance has the agent ID `tester3`, then a job request is a `GET` request to `https://testrunner.org/a11ytest/api/job?agent=tester3`.
|
|
810
802
|
|
|
811
|
-
Once a Testaro instance obtains a network job, the report
|
|
803
|
+
Once a Testaro instance obtains a network job, Testaro performs it and adds the result data to the job, which then becomes the job report. Testaro sends the report in a `POST` request to the URL specified by the `sources.sendReportTo` property of the job.
|
|
804
|
+
|
|
805
|
+
Network watching can be repeated or 1-job. One-job watching stops after 1 job has been performed.
|
|
806
|
+
|
|
807
|
+
After checking all the URLs in succession without getting a job from any of them, Testaro waits for a prescribed time before continuing to check.
|
|
812
808
|
|
|
813
809
|
###### By a module
|
|
814
810
|
|
|
815
811
|
```javaScript
|
|
816
|
-
const {netWatch} = require('./
|
|
817
|
-
netWatch(true, 300);
|
|
812
|
+
const {netWatch} = require('./netWatch');
|
|
813
|
+
netWatch(true, 300, true);
|
|
818
814
|
```
|
|
819
815
|
|
|
820
816
|
In this example, a module asks Testaro to check the servers for a job every 300 seconds, to perform any jobs obtained from the servers, and then to continue checking until the process is stopped. If the first argument is `false`, Testaro will stop checking after performing 1 job.
|
|
821
817
|
|
|
818
|
+
The third argument specifies whether Testaro should be certificate-tolerant. A `true` value makes Testaro accept SSL certificates that fail verification against a list of certificate authorities. This allows testing of `https` targets that, for example, use self-signed certificates. If the third argument is omitted, the default for that argument is implemented. The default is `true`.
|
|
819
|
+
|
|
822
820
|
###### By a user
|
|
823
821
|
|
|
824
822
|
```javaScript
|
|
825
|
-
node call netWatch true 300
|
|
823
|
+
node call netWatch true 300 true
|
|
826
824
|
```
|
|
827
825
|
|
|
828
826
|
The arguments and behaviors described above for execution by a module apply here, too. If the first argument is `true`, you can terminate the process by entering `CTRL-c`.
|
package/actSpecs.js
CHANGED
|
@@ -145,7 +145,7 @@ exports.actSpecs = {
|
|
|
145
145
|
'Perform tests of a tool',
|
|
146
146
|
{
|
|
147
147
|
which: [true, 'string', 'isTest', 'tool name'],
|
|
148
|
-
rules: [false, 'array', 'areStrings', 'rule IDs or specifications, if not all']
|
|
148
|
+
rules: [false, 'array', 'areStrings', 'rule IDs or (for nuVal) specifications, if not all']
|
|
149
149
|
}
|
|
150
150
|
],
|
|
151
151
|
text: [
|
package/call.js
CHANGED
|
@@ -41,7 +41,8 @@ const fs = require('fs/promises');
|
|
|
41
41
|
// Function to process a testing request.
|
|
42
42
|
const {doJob} = require('./run');
|
|
43
43
|
// Function to watch for jobs.
|
|
44
|
-
const {dirWatch
|
|
44
|
+
const {dirWatch} = require('./dirWatch');
|
|
45
|
+
const {netWatch} = require('./netWatch');
|
|
45
46
|
|
|
46
47
|
// CONSTANTS
|
|
47
48
|
|
|
@@ -81,12 +82,17 @@ const callRun = async jobIDStart => {
|
|
|
81
82
|
}
|
|
82
83
|
};
|
|
83
84
|
// Starts a directory watch, converting the interval argument to a number.
|
|
84
|
-
const callDirWatch = async (isForever,
|
|
85
|
-
await dirWatch(isForever === 'true', Math.max(5, Number.parseInt(
|
|
85
|
+
const callDirWatch = async (isForever, intervalInSeconds) => {
|
|
86
|
+
await dirWatch(isForever === 'true', Math.max(5, Number.parseInt(intervalInSeconds, 10)));
|
|
86
87
|
};
|
|
87
88
|
// Starts a network watch, converting the interval argument to a number.
|
|
88
|
-
const callNetWatch = async(isForever,
|
|
89
|
-
netWatch(
|
|
89
|
+
const callNetWatch = async (isForever, intervalInSeconds, isCertTolerant) => {
|
|
90
|
+
await netWatch(
|
|
91
|
+
isForever === 'true',
|
|
92
|
+
Number.parseInt(intervalInSeconds, 10),
|
|
93
|
+
isCertTolerant ? isCertTolerant === 'true' : undefined
|
|
94
|
+
);
|
|
95
|
+
console.log('netWatch run');
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
// OPERATION
|
|
@@ -100,11 +106,20 @@ if (fn === 'run' && fnArgs.length < 2) {
|
|
|
100
106
|
});
|
|
101
107
|
}
|
|
102
108
|
else if (fn === 'dirWatch' && fnArgs.length === 2) {
|
|
103
|
-
callDirWatch(... fnArgs)
|
|
109
|
+
callDirWatch(... fnArgs)
|
|
110
|
+
.then(() => {
|
|
111
|
+
console.log('Directory watch ended');
|
|
112
|
+
process.exit(0);
|
|
113
|
+
});
|
|
104
114
|
}
|
|
105
|
-
else if (fn === 'netWatch' && fnArgs.length
|
|
106
|
-
callNetWatch(... fnArgs)
|
|
115
|
+
else if (fn === 'netWatch' && [2, 3].includes(fnArgs.length)) {
|
|
116
|
+
callNetWatch(... fnArgs)
|
|
117
|
+
.then(() => {
|
|
118
|
+
console.log('Network watch ended');
|
|
119
|
+
process.exit(0);
|
|
120
|
+
});
|
|
107
121
|
}
|
|
108
122
|
else {
|
|
109
123
|
console.log('ERROR: Invalid statement');
|
|
124
|
+
process.exit(1);
|
|
110
125
|
}
|
package/dirWatch.js
CHANGED
|
@@ -22,48 +22,137 @@
|
|
|
22
22
|
|
|
23
23
|
/*
|
|
24
24
|
dirWatch.js
|
|
25
|
-
Module for
|
|
25
|
+
Module for watching a directory for jobs.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
// ########## IMPORTS
|
|
29
29
|
|
|
30
|
-
// Module to
|
|
31
|
-
|
|
30
|
+
// Module to keep secrets.
|
|
31
|
+
require('dotenv').config();
|
|
32
|
+
// Module to read and write files.
|
|
33
|
+
const fs = require('fs/promises');
|
|
34
|
+
// Module to perform jobs.
|
|
35
|
+
const {doJob} = require('./run');
|
|
32
36
|
|
|
33
37
|
// ########## CONSTANTS
|
|
34
38
|
|
|
35
|
-
const
|
|
39
|
+
const jobDir = process.env.JOBDIR;
|
|
40
|
+
const reportDir = process.env.REPORTDIR;
|
|
36
41
|
|
|
37
42
|
// ########## FUNCTIONS
|
|
38
43
|
|
|
39
|
-
//
|
|
40
|
-
const
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Gets a segment of a timestamp.
|
|
45
|
+
const tsPart = (timeStamp, startIndex) => timeStamp.slice(startIndex, startIndex + 2);
|
|
46
|
+
// Returns a string representing the date and time.
|
|
47
|
+
const nowString = () => (new Date()).toISOString().slice(0, 16);
|
|
48
|
+
// Gets date of a timestamp.
|
|
49
|
+
const dateOf = ts => {
|
|
50
|
+
const dateString = `20${tsPart(ts, 0)}-${tsPart(ts, 2)}-${tsPart(ts, 4)}`;
|
|
51
|
+
const timeString = `${tsPart(ts, 7)}:${tsPart(ts, 9)}:00`;
|
|
52
|
+
const dateTimeString = `${dateString}T${timeString}Z`;
|
|
53
|
+
return new Date(dateTimeString);
|
|
54
|
+
};
|
|
55
|
+
// Writes a directory report.
|
|
56
|
+
const writeDirReport = async report => {
|
|
57
|
+
const jobID = report && report.id;
|
|
58
|
+
if (jobID) {
|
|
59
|
+
try {
|
|
60
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
61
|
+
const reportName = `${jobID}.json`;
|
|
62
|
+
await fs.mkdir(`${reportDir}/raw`, {recursive: true});
|
|
63
|
+
await fs.writeFile(`${reportDir}/raw/${reportName}`, `${reportJSON}\n`);
|
|
64
|
+
console.log(`Report ${jobID} saved in ${reportDir}/raw`);
|
|
65
|
+
}
|
|
66
|
+
catch(error) {
|
|
67
|
+
console.log(`ERROR: Failed to save report ${jobID} in ${reportDir}/raw (${error.message})`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log('ERROR: Job has no ID');
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
// Archives a job.
|
|
75
|
+
const archiveJob = async (job, isFile) => {
|
|
76
|
+
// Save the job in the done subdirectory.
|
|
77
|
+
const {id} = job;
|
|
78
|
+
const jobJSON = JSON.stringify(job, null, 2);
|
|
79
|
+
await fs.mkdir(`${jobDir}/done`, {recursive: true});
|
|
80
|
+
await fs.writeFile(`${jobDir}/done/${id}.json`, `${jobJSON}\n`);
|
|
81
|
+
// If the job had been saved as a file in the todo subdirectory:
|
|
82
|
+
if (isFile) {
|
|
83
|
+
// Delete the file.
|
|
84
|
+
await fs.rm(`${jobDir}/todo/${id}.json`);
|
|
85
|
+
}
|
|
86
|
+
console.log(`Job ${id} archived in ${jobDir}/done (${nowString()})`);
|
|
87
|
+
};
|
|
88
|
+
// Waits.
|
|
89
|
+
const wait = ms => {
|
|
90
|
+
return new Promise(resolve => {
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
resolve('');
|
|
93
|
+
}, ms);
|
|
47
94
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
95
|
+
};
|
|
96
|
+
/*
|
|
97
|
+
Checks for a directory job and, when found, performs and reports it.
|
|
98
|
+
Arguments:
|
|
99
|
+
0. Whether to continue watching after a job is run.
|
|
100
|
+
1: interval in seconds from a no-job check to the next check.
|
|
101
|
+
*/
|
|
102
|
+
exports.dirWatch = async (isForever, intervalInSeconds) => {
|
|
103
|
+
console.log(`Starting to watch directory ${jobDir}/todo for jobs`);
|
|
104
|
+
let notYetRun = true;
|
|
105
|
+
// As long as watching as to continue:
|
|
106
|
+
while (isForever || notYetRun) {
|
|
107
|
+
try {
|
|
108
|
+
// If there are any jobs in the watched directory:
|
|
109
|
+
const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
|
|
110
|
+
const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
|
|
111
|
+
if (jobFileNames.length) {
|
|
112
|
+
// If the first one is ready to do:
|
|
113
|
+
const firstJobTimeStamp = jobFileNames[0].replace(/-.+$/, '');
|
|
114
|
+
if (Date.now() > dateOf(firstJobTimeStamp)) {
|
|
115
|
+
// Get it.
|
|
116
|
+
const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
|
|
117
|
+
try {
|
|
118
|
+
const job = JSON.parse(jobJSON);
|
|
119
|
+
const report = JSON.parse(jobJSON);
|
|
120
|
+
const {id} = job;
|
|
121
|
+
console.log(`Directory job ${id} ready to do (${nowString()})`);
|
|
122
|
+
// Perform it.
|
|
123
|
+
await doJob(report);
|
|
124
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
125
|
+
// Report it.
|
|
126
|
+
await writeDirReport(report);
|
|
127
|
+
// Archive it.
|
|
128
|
+
await archiveJob(job, true);
|
|
129
|
+
}
|
|
130
|
+
catch(error) {
|
|
131
|
+
console.log(`ERROR processing directory job (${error.message})`);
|
|
132
|
+
}
|
|
133
|
+
notYetRun = false;
|
|
134
|
+
}
|
|
135
|
+
// Otherwise, i.e. if the first one is not yet ready to do:
|
|
136
|
+
else {
|
|
137
|
+
// Report this.
|
|
138
|
+
console.log(`All jobs in ${jobDir} not yet ready to do (${nowString()})`);
|
|
139
|
+
// Wait for the specified interval.
|
|
140
|
+
await wait(1000 * intervalInSeconds);
|
|
141
|
+
}
|
|
52
142
|
}
|
|
143
|
+
// Otherwise, i.e. if there are no jobs in the watched directory:
|
|
53
144
|
else {
|
|
54
|
-
console.log(`
|
|
145
|
+
console.log(`No job in ${jobDir} (${nowString()})`);
|
|
146
|
+
// Wait for the specified interval.
|
|
147
|
+
await wait(1000 * intervalInSeconds);
|
|
55
148
|
}
|
|
56
149
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
150
|
+
// If a fatal error was thrown:
|
|
151
|
+
catch(error) {
|
|
152
|
+
// Report this.
|
|
153
|
+
console.log(`ERROR: Directory watching failed (${error.message}); watching aborted`);
|
|
154
|
+
// Quit watching.
|
|
155
|
+
break;
|
|
63
156
|
}
|
|
64
|
-
}
|
|
157
|
+
}
|
|
65
158
|
};
|
|
66
|
-
|
|
67
|
-
// ########## OPERATION
|
|
68
|
-
|
|
69
|
-
reWatch();
|
package/netWatch-old.js
ADDED
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
netWatch.js
|
|
25
|
+
Module for watching for a network job and running it when found.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ########## IMPORTS
|
|
29
|
+
|
|
30
|
+
// Module to keep secrets.
|
|
31
|
+
require('dotenv').config();
|
|
32
|
+
// Module to read and write files.
|
|
33
|
+
const fs = require('fs/promises');
|
|
34
|
+
// Module to make requests to servers.
|
|
35
|
+
const httpClient = require('http');
|
|
36
|
+
const httpsClient = require('https');
|
|
37
|
+
// Module to perform jobs.
|
|
38
|
+
const {doJob} = require('./run');
|
|
39
|
+
|
|
40
|
+
// CONSTANTS
|
|
41
|
+
|
|
42
|
+
const jobDir = process.env.JOBDIR;
|
|
43
|
+
const jobURLs = process.env.JOB_URLS;
|
|
44
|
+
const reportDir = process.env.REPORTDIR;
|
|
45
|
+
const agent = process.env.AGENT;
|
|
46
|
+
|
|
47
|
+
// ########## FUNCTIONS
|
|
48
|
+
|
|
49
|
+
// Returns a string representing the date and time.
|
|
50
|
+
const nowString = () => (new Date()).toISOString().slice(0, 19);
|
|
51
|
+
// Waits.
|
|
52
|
+
const wait = ms => {
|
|
53
|
+
return new Promise(resolve => {
|
|
54
|
+
setTimeout(() => {
|
|
55
|
+
resolve('');
|
|
56
|
+
}, ms);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
// Serves an object in JSON format.
|
|
60
|
+
const serveObject = (object, response) => {
|
|
61
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
62
|
+
response.end(JSON.stringify(object));
|
|
63
|
+
};
|
|
64
|
+
// Writes a directory report.
|
|
65
|
+
const writeDirReport = async report => {
|
|
66
|
+
const jobID = report && report.id;
|
|
67
|
+
if (jobID) {
|
|
68
|
+
try {
|
|
69
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
70
|
+
const reportName = `${jobID}.json`;
|
|
71
|
+
await fs.mkdir(reportDir, {recursive: true});
|
|
72
|
+
await fs.writeFile(`${reportDir}/${reportName}`, reportJSON);
|
|
73
|
+
console.log(`Report ${reportName} saved in ${reportDir}`);
|
|
74
|
+
}
|
|
75
|
+
catch(error) {
|
|
76
|
+
console.log(`ERROR: Failed to write report ${jobID} in ${reportDir} (${error.message})`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log('ERROR: Job has no ID');
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
// Archives a job.
|
|
84
|
+
const archiveJob = async (job, isFile) => {
|
|
85
|
+
// Save the job in the done subdirectory.
|
|
86
|
+
const {id} = job;
|
|
87
|
+
const jobJSON = JSON.stringify(job, null, 2);
|
|
88
|
+
await fs.mkdir(`${jobDir}/done`, {recursive: true});
|
|
89
|
+
await fs.writeFile(`${jobDir}/done/${id}.json`, jobJSON);
|
|
90
|
+
// If the job had been saved as a file in the todo subdirectory:
|
|
91
|
+
if (isFile) {
|
|
92
|
+
// Delete the file.
|
|
93
|
+
await fs.rm(`${jobDir}/todo/${id}.json`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
// Checks for a directory job and, if found, performs and reports it, once or repeatedly.
|
|
97
|
+
const checkDirJob = async (isForever, interval) => {
|
|
98
|
+
try {
|
|
99
|
+
// If there are any jobs in the watched directory:
|
|
100
|
+
const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
|
|
101
|
+
const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
|
|
102
|
+
if (jobFileNames.length) {
|
|
103
|
+
// If the first one is ready to do:
|
|
104
|
+
const firstJobTime = jobFileNames[0].replace(/-.+$/, '');
|
|
105
|
+
if (Date.now() > dateOf(firstJobTime)) {
|
|
106
|
+
// Perform it.
|
|
107
|
+
const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
|
|
108
|
+
try {
|
|
109
|
+
const job = JSON.parse(jobJSON, null, 2);
|
|
110
|
+
const {id} = job;
|
|
111
|
+
console.log(`Directory job ${id} found (${nowString()})`);
|
|
112
|
+
await doJob(job);
|
|
113
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
114
|
+
// Report it.
|
|
115
|
+
await writeDirReport(job);
|
|
116
|
+
// Archive it.
|
|
117
|
+
await archiveJob(job, true);
|
|
118
|
+
console.log(`Job ${id} archived in ${jobDir} (${nowString()})`);
|
|
119
|
+
// If watching is repetitive:
|
|
120
|
+
if (isForever) {
|
|
121
|
+
// Wait 2 seconds.
|
|
122
|
+
await wait(2000);
|
|
123
|
+
// Check the directory again.
|
|
124
|
+
checkDirJob(true, interval);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch(error) {
|
|
128
|
+
console.log(`ERROR processing directory job (${error.message})`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Otherwise, i.e. if the first one is not yet ready to do:
|
|
132
|
+
else {
|
|
133
|
+
// Report this.
|
|
134
|
+
console.log(`All jobs in ${jobDir} not yet ready to do (${nowString()})`);
|
|
135
|
+
// Wait for the specified interval.
|
|
136
|
+
await wait(1000 * interval);
|
|
137
|
+
// Check the directory again.
|
|
138
|
+
await checkDirJob(true, interval);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Otherwise, i.e. if there are no more jobs in the watched directory:
|
|
142
|
+
else {
|
|
143
|
+
console.log(`No job in ${jobDir} (${nowString()})`);
|
|
144
|
+
// Wait for the specified interval.
|
|
145
|
+
await wait(1000 * interval);
|
|
146
|
+
// Check the directory again.
|
|
147
|
+
await checkDirJob(true, interval);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch(error) {
|
|
151
|
+
console.log(`ERROR: Directory watching failed (${error.message})`);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
// Checks servers for a network job.
|
|
155
|
+
const checkNetJob = async (servers, serverIndex, isForever, interval, noJobCount) => {
|
|
156
|
+
// If all servers are jobless:
|
|
157
|
+
if (noJobCount === servers.length) {
|
|
158
|
+
// Wait for the specified interval.
|
|
159
|
+
await wait(1000 * interval);
|
|
160
|
+
// Reset the count of jobless servers.
|
|
161
|
+
noJobCount = 0;
|
|
162
|
+
}
|
|
163
|
+
// Otherwise, i.e. if any server may still have a job:
|
|
164
|
+
else {
|
|
165
|
+
// Wait 2 seconds.
|
|
166
|
+
await wait(2000);
|
|
167
|
+
}
|
|
168
|
+
// If the last server has been checked:
|
|
169
|
+
serverIndex = serverIndex % servers.length;
|
|
170
|
+
if (serverIndex === 0) {
|
|
171
|
+
// Report this.
|
|
172
|
+
console.log('--');
|
|
173
|
+
}
|
|
174
|
+
// Check the next server.
|
|
175
|
+
const server = servers[serverIndex];
|
|
176
|
+
const client = server.startsWith('https://') ? httpsClient : httpClient;
|
|
177
|
+
const fullURL = `${server}?agent=${agent}`;
|
|
178
|
+
const logStart = `Requested job from server ${server} and got `;
|
|
179
|
+
// Tolerate unrecognized certificate authorities if the environment specifies.
|
|
180
|
+
const ruOpt = process.env.REJECT_UNAUTHORIZED === 'false' ? {rejectUnauthorized: false} : {};
|
|
181
|
+
client.request(fullURL, ruOpt, response => {
|
|
182
|
+
const chunks = [];
|
|
183
|
+
response
|
|
184
|
+
// If the response to the job request threw an error:
|
|
185
|
+
.on('error', async error => {
|
|
186
|
+
// Report it.
|
|
187
|
+
console.log(`${logStart}error message ${error.message}`);
|
|
188
|
+
// Check the next server.
|
|
189
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
190
|
+
})
|
|
191
|
+
.on('data', chunk => {
|
|
192
|
+
chunks.push(chunk);
|
|
193
|
+
})
|
|
194
|
+
// When the response arrives:
|
|
195
|
+
.on('end', async () => {
|
|
196
|
+
const content = chunks.join('');
|
|
197
|
+
try {
|
|
198
|
+
// If there was no job to do:
|
|
199
|
+
let contentObj = JSON.parse(content);
|
|
200
|
+
if (! Object.keys(contentObj).length) {
|
|
201
|
+
// Report this.
|
|
202
|
+
console.log(`No job to do at ${server}`);
|
|
203
|
+
// Check the next server.
|
|
204
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
205
|
+
}
|
|
206
|
+
// Otherwise, i.e. if there was a job or a message:
|
|
207
|
+
else {
|
|
208
|
+
const {message, id, sources} = contentObj;
|
|
209
|
+
// If the server sent a message, not a job:
|
|
210
|
+
if (message) {
|
|
211
|
+
// Report it.
|
|
212
|
+
console.log(`${logStart}${message}`);
|
|
213
|
+
// Check the next server.
|
|
214
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
215
|
+
}
|
|
216
|
+
// Otherwise, if the server sent a valid job:
|
|
217
|
+
else if (id && sources && sources.target && sources.target.which) {
|
|
218
|
+
// Add the agent to it.
|
|
219
|
+
sources.agent = agent;
|
|
220
|
+
// If the job specifies a report destination:
|
|
221
|
+
const {sendReportTo} = sources;
|
|
222
|
+
if (sendReportTo) {
|
|
223
|
+
// Perform the job, adding result data to it.
|
|
224
|
+
const testee = sources.target.which;
|
|
225
|
+
console.log(
|
|
226
|
+
`${logStart}job ${id} (${nowString()})\n>> It will test ${testee}\n>> It will send report to ${sendReportTo}\n`
|
|
227
|
+
);
|
|
228
|
+
await doJob(contentObj);
|
|
229
|
+
let reportJSON = JSON.stringify(contentObj, null, 2);
|
|
230
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
231
|
+
// Send the report to the specified server.
|
|
232
|
+
console.log(`Sending report ${id} to ${sendReportTo}`);
|
|
233
|
+
const reportClient = sendReportTo.startsWith('https://') ? httpsClient : httpClient;
|
|
234
|
+
const reportLogStart = `Sent report ${id} to ${sendReportTo} and got `;
|
|
235
|
+
reportClient.request(sendReportTo, {method: 'POST'}, repResponse => {
|
|
236
|
+
const chunks = [];
|
|
237
|
+
repResponse
|
|
238
|
+
// If the response to the report threw an error:
|
|
239
|
+
.on('error', async error => {
|
|
240
|
+
// Report this.
|
|
241
|
+
console.log(`${reportLogStart}error message ${error.message}\n`);
|
|
242
|
+
// Check the next server.
|
|
243
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
244
|
+
})
|
|
245
|
+
.on('data', chunk => {
|
|
246
|
+
chunks.push(chunk);
|
|
247
|
+
})
|
|
248
|
+
// When the response arrives:
|
|
249
|
+
.on('end', async () => {
|
|
250
|
+
const content = chunks.join('');
|
|
251
|
+
try {
|
|
252
|
+
// If the server sent a message, as expected:
|
|
253
|
+
const ackObj = JSON.parse(content);
|
|
254
|
+
const {message} = ackObj;
|
|
255
|
+
if (message) {
|
|
256
|
+
// Report it.
|
|
257
|
+
console.log(`${reportLogStart}${message}\n`);
|
|
258
|
+
// Free the memory used by the report.
|
|
259
|
+
reportJSON = '';
|
|
260
|
+
contentObj = {};
|
|
261
|
+
// Check the next server.
|
|
262
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, 0);
|
|
263
|
+
}
|
|
264
|
+
// Otherwise, i.e. if the server sent anything else:
|
|
265
|
+
else {
|
|
266
|
+
// Report it.
|
|
267
|
+
console.log(
|
|
268
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode} and error message ${JSON.stringify(ackObj, null, 2)}\n`
|
|
269
|
+
);
|
|
270
|
+
// Check the next server, disregarding the failed job.
|
|
271
|
+
await checkNetJob(
|
|
272
|
+
servers, serverIndex + 1, isForever, interval, noJobCount + 1
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// If processing the report threw an error:
|
|
277
|
+
catch(error) {
|
|
278
|
+
// Report it.
|
|
279
|
+
console.log(
|
|
280
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode}, error message ${error.message}, and response ${content.slice(0, 1000)}\n`
|
|
281
|
+
);
|
|
282
|
+
// Check the next server, disregarding the failed job.
|
|
283
|
+
await checkNetJob(
|
|
284
|
+
servers, serverIndex + 1, isForever, interval, noJobCount + 1
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
})
|
|
289
|
+
// If the report submission throws an error:
|
|
290
|
+
.on('error', async error => {
|
|
291
|
+
// Report this.
|
|
292
|
+
console.log(`ERROR: ${reportLogStart}error message ${error.message}\n`);
|
|
293
|
+
// Check the next server, disregarding the failed job.
|
|
294
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
295
|
+
})
|
|
296
|
+
// Finish submitting the report.
|
|
297
|
+
.end(reportJSON);
|
|
298
|
+
}
|
|
299
|
+
// Otherwise, i.e. if the job specifies no report destination:
|
|
300
|
+
else {
|
|
301
|
+
// Report this.
|
|
302
|
+
const message = `ERROR: ${logStart}job with no report destination`;
|
|
303
|
+
serveObject({message}, response);
|
|
304
|
+
console.log(message);
|
|
305
|
+
// Check the next server, disregarding the defective job.
|
|
306
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Otherwise, if the server sent an invalid job:
|
|
310
|
+
else {
|
|
311
|
+
// Report this.
|
|
312
|
+
const message
|
|
313
|
+
= `ERROR: ${logStart}invalid job:\n${JSON.stringify(contentObj, null, 2)}`;
|
|
314
|
+
console.log(message);
|
|
315
|
+
serveObject({message}, response);
|
|
316
|
+
// Check the next server, disregarding the defective job.
|
|
317
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// If an error is thrown:
|
|
322
|
+
catch(error) {
|
|
323
|
+
// Report this.
|
|
324
|
+
console.log(`ERROR: ${error.message} (response ${content.slice(0, 1000)})`);
|
|
325
|
+
// Check the next server.
|
|
326
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
})
|
|
330
|
+
// If the job request throws an error:
|
|
331
|
+
.on('error', async error => {
|
|
332
|
+
// If it was a refusal to connect:
|
|
333
|
+
const {message} = error;
|
|
334
|
+
if (message.includes('ECONNREFUSED')) {
|
|
335
|
+
// Report this.
|
|
336
|
+
console.log(`${logStart}no connection`);
|
|
337
|
+
// Check the next server.
|
|
338
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
339
|
+
}
|
|
340
|
+
// Otherwise, if it was a DNS failure:
|
|
341
|
+
else if (message.includes('ENOTFOUND')) {
|
|
342
|
+
// Report this.
|
|
343
|
+
console.log(`${logStart}no domain name resolution`);
|
|
344
|
+
// Check the next server.
|
|
345
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
346
|
+
}
|
|
347
|
+
// Otherwise, i.e. if it was any other error:
|
|
348
|
+
else {
|
|
349
|
+
// Report this.
|
|
350
|
+
console.log(
|
|
351
|
+
`ERROR: ${logStart}no response, but got error message ${error.message.slice(0, 200)}`
|
|
352
|
+
);
|
|
353
|
+
// Check the next server.
|
|
354
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
// Finish sending the request.
|
|
358
|
+
.end();
|
|
359
|
+
};
|
|
360
|
+
// Composes an interval description.
|
|
361
|
+
const intervalSpec = interval => {
|
|
362
|
+
if (interval > -1) {
|
|
363
|
+
return `repeatedly, with ${interval}-second intervals `;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
return '';
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
// Checks for a directory job, performs it, and submits a report, once or repeatedly.
|
|
370
|
+
exports.dirWatch = async (isForever, interval = 300) => {
|
|
371
|
+
console.log(`Directory watching started ${intervalSpec(interval)}(${nowString()})\n`);
|
|
372
|
+
// Start the checking.
|
|
373
|
+
await checkDirJob(isForever, interval);
|
|
374
|
+
};
|
|
375
|
+
// Checks for a network job, performs it, and submits a report, once or repeatedly.
|
|
376
|
+
exports.netWatch = async (isForever, interval = 300) => {
|
|
377
|
+
console.log('Starting netWatch');
|
|
378
|
+
// If the servers to be checked are valid:
|
|
379
|
+
const servers = jobURLs
|
|
380
|
+
.split('+')
|
|
381
|
+
.map(url => [Math.random(), url])
|
|
382
|
+
.sort((a, b) => a[0] - b[0])
|
|
383
|
+
.map(pair => pair[1]);
|
|
384
|
+
if (
|
|
385
|
+
servers
|
|
386
|
+
&& servers.length
|
|
387
|
+
&& servers
|
|
388
|
+
.every(server => ['http://', 'https://'].some(prefix => server.startsWith(prefix)))
|
|
389
|
+
) {
|
|
390
|
+
console.log(`Network watching started ${intervalSpec(interval)}(${nowString()})\n`);
|
|
391
|
+
// Start checking.
|
|
392
|
+
await checkNetJob(servers, 0, isForever, interval, 0);
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
console.log('ERROR: List of job URLs invalid');
|
|
396
|
+
}
|
|
397
|
+
};
|
package/netWatch.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2022–2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
netWatch.js
|
|
25
|
+
Module for watching for a network job and running it when found.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// IMPORTS
|
|
29
|
+
|
|
30
|
+
// Module to keep secrets.
|
|
31
|
+
require('dotenv').config();
|
|
32
|
+
// Module to read and write files.
|
|
33
|
+
const fs = require('fs/promises');
|
|
34
|
+
// Modules to make requests to servers.
|
|
35
|
+
const httpClient = require('http');
|
|
36
|
+
const httpsClient = require('https');
|
|
37
|
+
// Module to perform jobs.
|
|
38
|
+
const {doJob} = require('./run');
|
|
39
|
+
|
|
40
|
+
// CONSTANTS
|
|
41
|
+
|
|
42
|
+
const jobURLSpec = process.env.JOB_URLS;
|
|
43
|
+
const agent = process.env.AGENT;
|
|
44
|
+
|
|
45
|
+
// FUNCTIONS
|
|
46
|
+
|
|
47
|
+
// Returns a string representing the date and time.
|
|
48
|
+
const nowString = () => (new Date()).toISOString().slice(2, 15);
|
|
49
|
+
// Waits.
|
|
50
|
+
const wait = ms => {
|
|
51
|
+
return new Promise(resolve => {
|
|
52
|
+
setTimeout(() => {
|
|
53
|
+
resolve('');
|
|
54
|
+
}, ms);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
// Serves an object in JSON format.
|
|
58
|
+
const serveObject = (object, response) => {
|
|
59
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
60
|
+
response.end(JSON.stringify(object));
|
|
61
|
+
};
|
|
62
|
+
/*
|
|
63
|
+
Requests a network job and, when found, performs and reports it.
|
|
64
|
+
Arguments:
|
|
65
|
+
0. whether to continue watching after a job is run.
|
|
66
|
+
1: interval in seconds from a cycle of no-job checks to the next cycle.
|
|
67
|
+
*/
|
|
68
|
+
exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) => {
|
|
69
|
+
const urls = jobURLSpec
|
|
70
|
+
.split('+')
|
|
71
|
+
.map(url => [Math.random(), url])
|
|
72
|
+
.sort((a, b) => a[0] - b[0])
|
|
73
|
+
.map(pair => pair[1]);
|
|
74
|
+
const urlCount = urls.length;
|
|
75
|
+
// If the job URLs exist and are valid:
|
|
76
|
+
if (
|
|
77
|
+
urls
|
|
78
|
+
&& urlCount
|
|
79
|
+
&& urls.every(url => ['http://', 'https://'].some(prefix => url.startsWith(prefix)))
|
|
80
|
+
) {
|
|
81
|
+
// Configure the watch.
|
|
82
|
+
let cycleIndex = -1;
|
|
83
|
+
let urlIndex = -1;
|
|
84
|
+
let noJobYet = true;
|
|
85
|
+
let abort = false;
|
|
86
|
+
const certOpt = isCertTolerant ? {rejectUnauthorized: false} : {};
|
|
87
|
+
const certInfo = `Certificate-${isCertTolerant ? '' : 'in'}tolerant`;
|
|
88
|
+
const foreverInfo = isForever ? 'repeating' : 'one-job';
|
|
89
|
+
const intervalInfo = `with ${intervalInSeconds}-second intervals`;
|
|
90
|
+
console.log(
|
|
91
|
+
`${certInfo} ${foreverInfo} network watching started ${intervalInfo} (${nowString()})\n`
|
|
92
|
+
);
|
|
93
|
+
// As long as watching is to continue:
|
|
94
|
+
while ((isForever || noJobYet) && ! abort) {
|
|
95
|
+
// If the cycle is complete:
|
|
96
|
+
if (cycleIndex === urlCount - 1) {
|
|
97
|
+
// Wait for the specified interval.
|
|
98
|
+
await wait(1000 * intervalInSeconds);
|
|
99
|
+
// Log the start of a cycle.
|
|
100
|
+
console.log('--');
|
|
101
|
+
}
|
|
102
|
+
// Otherwise, i.e. if the cycle is incomplete:
|
|
103
|
+
else {
|
|
104
|
+
// Wait briefly.
|
|
105
|
+
await wait(1000);
|
|
106
|
+
}
|
|
107
|
+
// Configure the next check.
|
|
108
|
+
cycleIndex = ++cycleIndex % urlCount;
|
|
109
|
+
urlIndex = ++urlIndex % urlCount;
|
|
110
|
+
const url = urls[urlIndex];
|
|
111
|
+
const logStart = `Requested job from server ${url} and got `;
|
|
112
|
+
const fullURL = `${url}?agent=${agent}`;
|
|
113
|
+
// Perform it.
|
|
114
|
+
await new Promise(resolve => {
|
|
115
|
+
try {
|
|
116
|
+
const client = url.startsWith('https://') ? httpsClient : httpClient;
|
|
117
|
+
// Request a job.
|
|
118
|
+
client.request(fullURL, certOpt, response => {
|
|
119
|
+
const chunks = [];
|
|
120
|
+
response
|
|
121
|
+
// If the response throws an error:
|
|
122
|
+
.on('error', async error => {
|
|
123
|
+
// Report it.
|
|
124
|
+
console.log(`${logStart}error message ${error.message}`);
|
|
125
|
+
resolve(true);
|
|
126
|
+
})
|
|
127
|
+
.on('data', chunk => {
|
|
128
|
+
chunks.push(chunk);
|
|
129
|
+
})
|
|
130
|
+
// When the response arrives:
|
|
131
|
+
.on('end', async () => {
|
|
132
|
+
const content = chunks.join('');
|
|
133
|
+
try {
|
|
134
|
+
// If there was no job to do:
|
|
135
|
+
let contentObj = JSON.parse(content);
|
|
136
|
+
if (! Object.keys(contentObj).length) {
|
|
137
|
+
// Report this.
|
|
138
|
+
console.log(`No job to do at ${url}`);
|
|
139
|
+
resolve(true);
|
|
140
|
+
}
|
|
141
|
+
// Otherwise, i.e. if there was a job or a message:
|
|
142
|
+
else {
|
|
143
|
+
const {message, id, sources} = contentObj;
|
|
144
|
+
// If the server sent a message, not a job:
|
|
145
|
+
if (message) {
|
|
146
|
+
// Report it.
|
|
147
|
+
console.log(`${logStart}${message}`);
|
|
148
|
+
resolve(true);
|
|
149
|
+
}
|
|
150
|
+
// Otherwise, if the server sent a valid job:
|
|
151
|
+
else if (id && sources && sources.target && sources.target.which) {
|
|
152
|
+
// Restart the cycle.
|
|
153
|
+
cycleIndex = -1;
|
|
154
|
+
// Prevent further watching, if unwanted.
|
|
155
|
+
noJobYet = false;
|
|
156
|
+
// Add the agent to the job.
|
|
157
|
+
sources.agent = agent;
|
|
158
|
+
// If the job specifies a report destination:
|
|
159
|
+
const {sendReportTo} = sources;
|
|
160
|
+
if (sendReportTo) {
|
|
161
|
+
// Perform the job, adding result data to it.
|
|
162
|
+
const target = sources.target.which;
|
|
163
|
+
console.log(`${logStart}job ${id} (${nowString()}`);
|
|
164
|
+
console.log(`>> It will test ${target}`);
|
|
165
|
+
console.log(`>> It will send report to ${sendReportTo}`);
|
|
166
|
+
await doJob(contentObj);
|
|
167
|
+
let reportJSON = JSON.stringify(contentObj, null, 2);
|
|
168
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
169
|
+
// Send the report to the specified server.
|
|
170
|
+
console.log(`Sending report ${id} to ${sendReportTo}`);
|
|
171
|
+
const reportClient = sendReportTo.startsWith('https://') ? httpsClient : httpClient;
|
|
172
|
+
const reportLogStart = `Sent report ${id} to ${sendReportTo} and got `;
|
|
173
|
+
reportClient.request(sendReportTo, {method: 'POST'}, repResponse => {
|
|
174
|
+
const chunks = [];
|
|
175
|
+
repResponse
|
|
176
|
+
// If the response to the report threw an error:
|
|
177
|
+
.on('error', async error => {
|
|
178
|
+
// Report this.
|
|
179
|
+
console.log(`${reportLogStart}error message ${error.message}\n`);
|
|
180
|
+
resolve(true);
|
|
181
|
+
})
|
|
182
|
+
.on('data', chunk => {
|
|
183
|
+
chunks.push(chunk);
|
|
184
|
+
})
|
|
185
|
+
// When the response arrives:
|
|
186
|
+
.on('end', async () => {
|
|
187
|
+
const content = chunks.join('');
|
|
188
|
+
try {
|
|
189
|
+
// If the server sent a message, as expected:
|
|
190
|
+
const ackObj = JSON.parse(content);
|
|
191
|
+
const {message} = ackObj;
|
|
192
|
+
if (message) {
|
|
193
|
+
// Report it.
|
|
194
|
+
console.log(`${reportLogStart}message ${message}\n`);
|
|
195
|
+
// Free the memory used by the report.
|
|
196
|
+
reportJSON = '';
|
|
197
|
+
contentObj = {};
|
|
198
|
+
resolve(true);
|
|
199
|
+
}
|
|
200
|
+
// Otherwise, i.e. if the server sent anything else:
|
|
201
|
+
else {
|
|
202
|
+
// Report it.
|
|
203
|
+
console.log(
|
|
204
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode} and error message ${JSON.stringify(ackObj, null, 2)}\n`
|
|
205
|
+
);
|
|
206
|
+
resolve(true);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// If processing the server message throws an error:
|
|
210
|
+
catch(error) {
|
|
211
|
+
// Report it.
|
|
212
|
+
console.log(
|
|
213
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode}, error message ${error.message}, and response ${content.slice(0, 1000)}\n`
|
|
214
|
+
);
|
|
215
|
+
resolve(true);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
})
|
|
219
|
+
// If the report submission throws an error:
|
|
220
|
+
.on('error', async error => {
|
|
221
|
+
// Report this.
|
|
222
|
+
console.log(`ERROR: ${reportLogStart}error message ${error.message}\n`);
|
|
223
|
+
resolve(true);
|
|
224
|
+
})
|
|
225
|
+
// Finish submitting the report.
|
|
226
|
+
.end(reportJSON);
|
|
227
|
+
}
|
|
228
|
+
// Otherwise, i.e. if the job specifies no report destination:
|
|
229
|
+
else {
|
|
230
|
+
// Report this.
|
|
231
|
+
const message = `ERROR: ${logStart}job with no report destination`;
|
|
232
|
+
serveObject({message}, response);
|
|
233
|
+
console.log(message);
|
|
234
|
+
resolve(true);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Otherwise, i.e. if the server sent an invalid job:
|
|
238
|
+
else {
|
|
239
|
+
// Report this.
|
|
240
|
+
const message
|
|
241
|
+
= `ERROR: ${logStart}invalid job:\n${JSON.stringify(contentObj, null, 2)}`;
|
|
242
|
+
console.log(message);
|
|
243
|
+
serveObject({message}, response);
|
|
244
|
+
resolve(true);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// If processing the server response throws an error:
|
|
249
|
+
catch(error) {
|
|
250
|
+
// Report this.
|
|
251
|
+
console.log(`ERROR: ${error.message} (response ${content.slice(0, 1000)})`);
|
|
252
|
+
resolve(true);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
})
|
|
256
|
+
// If the job request throws an error:
|
|
257
|
+
.on('error', async error => {
|
|
258
|
+
// If it is a refusal to connect:
|
|
259
|
+
if (error.code && error.code.includes('ECONNREFUSED')) {
|
|
260
|
+
// Report this.
|
|
261
|
+
console.log(`${logStart}no connection`);
|
|
262
|
+
}
|
|
263
|
+
// Otherwise, if it was a DNS failure:
|
|
264
|
+
else if (error.code && error.code.includes('ENOTFOUND')) {
|
|
265
|
+
// Report this.
|
|
266
|
+
console.log(`${logStart}no domain name resolution`);
|
|
267
|
+
}
|
|
268
|
+
// Otherwise, if it was any other error with a message:
|
|
269
|
+
else if (error.message) {
|
|
270
|
+
// Report this.
|
|
271
|
+
console.log(`ERROR: ${logStart}got error message ${error.message.slice(0, 200)}`);
|
|
272
|
+
// Abort the watch.
|
|
273
|
+
abort = true;
|
|
274
|
+
}
|
|
275
|
+
// Otherwise, i.e. if it was any other error with no message:
|
|
276
|
+
else {
|
|
277
|
+
// Report this.
|
|
278
|
+
console.log(`ERROR: ${logStart}got an error with no message`);
|
|
279
|
+
// Abort the watch.
|
|
280
|
+
abort = true;
|
|
281
|
+
}
|
|
282
|
+
resolve(true);
|
|
283
|
+
})
|
|
284
|
+
// Finish sending the job request.
|
|
285
|
+
.end();
|
|
286
|
+
}
|
|
287
|
+
// If requesting a job throws an error:
|
|
288
|
+
catch(error) {
|
|
289
|
+
// Report this.
|
|
290
|
+
console.log(`ERROR requesting a network job (${error.message})`);
|
|
291
|
+
abort = true;
|
|
292
|
+
resolve(true);
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
console.log('Watching complete');
|
|
297
|
+
}
|
|
298
|
+
// Otherwise, i.e. if the job URLs do not exist or are invalid:
|
|
299
|
+
else {
|
|
300
|
+
// Report this.
|
|
301
|
+
console.log('ERROR: List of job URLs invalid');
|
|
302
|
+
}
|
|
303
|
+
};
|
package/package.json
CHANGED
package/procs/standardize.js
CHANGED
|
@@ -282,7 +282,7 @@ const doQualWeb = (result, standardResult, ruleClassName) => {
|
|
|
282
282
|
const instance = {
|
|
283
283
|
ruleID,
|
|
284
284
|
what: item.description,
|
|
285
|
-
ordinalSeverity: severities[ruleClassName][item.verdict],
|
|
285
|
+
ordinalSeverity: severities[ruleClassName][item.verdict] || 0,
|
|
286
286
|
tagName: identifiers[0],
|
|
287
287
|
id: identifiers[1],
|
|
288
288
|
location: {
|
package/run.js
CHANGED
|
@@ -277,14 +277,10 @@ const isValidReport = report => {
|
|
|
277
277
|
if (typeof sources.script !== 'string') {
|
|
278
278
|
return 'Bad source script';
|
|
279
279
|
}
|
|
280
|
-
if (
|
|
281
|
-
! creationTime
|
|
282
|
-
|| typeof creationTime !== 'string'
|
|
283
|
-
|| ! /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(creationTime)
|
|
284
|
-
) {
|
|
280
|
+
if (! (creationTime && typeof creationTime === 'string' && Date.parse(creationTime))) {
|
|
285
281
|
return 'bad job creation time';
|
|
286
282
|
}
|
|
287
|
-
if (! timeStamp
|
|
283
|
+
if (! (timeStamp && typeof timeStamp === 'string')) {
|
|
288
284
|
return 'bad report timestamp';
|
|
289
285
|
}
|
|
290
286
|
return '';
|
|
@@ -772,12 +768,18 @@ const doActs = async (report, actIndex, page) => {
|
|
|
772
768
|
actInfo = act.which;
|
|
773
769
|
}
|
|
774
770
|
}
|
|
771
|
+
const message = `>>>> ${act.type}: ${actInfo}`;
|
|
775
772
|
// If granular reporting has been specified:
|
|
776
773
|
if (report.observe) {
|
|
777
|
-
// Notify the observer of the act.
|
|
774
|
+
// Notify the observer of the act and log it.
|
|
778
775
|
const whichParam = act.which ? `&which=${act.which}` : '';
|
|
779
776
|
const messageParams = `act=${act.type}${whichParam}`;
|
|
780
|
-
tellServer(report, messageParams,
|
|
777
|
+
tellServer(report, messageParams, message);
|
|
778
|
+
}
|
|
779
|
+
// Otherwise, i.e. if granular reporting has not been specified:
|
|
780
|
+
else {
|
|
781
|
+
// Log the act.
|
|
782
|
+
console.log(message);
|
|
781
783
|
}
|
|
782
784
|
// Increment the count of acts performed.
|
|
783
785
|
actCount++;
|
package/tests/qualWeb.js
CHANGED
|
@@ -114,15 +114,11 @@ exports.reporter = async (page, options) => {
|
|
|
114
114
|
else {
|
|
115
115
|
const bestPractices = bestSpec.slice(5).split(',').map(num => `QW-BP${num}`);
|
|
116
116
|
qualWebOptions['best-practices'] = {bestPractices};
|
|
117
|
-
|
|
118
|
-
// Temporarily disable best practices, because they crash QualWeb.
|
|
119
|
-
qualWebOptions.execute.bp = false;
|
|
117
|
+
qualWebOptions.execute.bp = true;
|
|
120
118
|
}
|
|
121
119
|
}
|
|
122
120
|
else {
|
|
123
|
-
|
|
124
|
-
// Temporarily disable best practices, because they crash QualWeb.
|
|
125
|
-
qualWebOptions.execute.bp = false;
|
|
121
|
+
qualWebOptions.execute.bp = true;
|
|
126
122
|
}
|
|
127
123
|
// Get the report.
|
|
128
124
|
let actReports = await qualWeb.evaluate(qualWebOptions);
|
package/watch-old.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2023 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
dirWatch.js
|
|
25
|
+
Module for launching a one-time directory watch.
|
|
26
|
+
Argument:
|
|
27
|
+
1: interval in seconds from a no-job check to the next check.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
// ########## IMPORTS
|
|
31
|
+
|
|
32
|
+
// Module to spawn a child process.
|
|
33
|
+
const {spawn} = require('node:child_process');
|
|
34
|
+
|
|
35
|
+
// ########## CONSTANTS
|
|
36
|
+
|
|
37
|
+
const repeat = process.argv[2];
|
|
38
|
+
const interval = process.argv[3];
|
|
39
|
+
|
|
40
|
+
// ########## FUNCTIONS
|
|
41
|
+
|
|
42
|
+
// Spawns a one-time directory watch.
|
|
43
|
+
const spawnWatch = () => spawn(
|
|
44
|
+
'node', 'dirWatch', 'false', interval, {stdio: ['inherit', 'inherit', 'pipe']}
|
|
45
|
+
);
|
|
46
|
+
// Repeatedly spawns a one-time directory watch.
|
|
47
|
+
const reWatch = () => {
|
|
48
|
+
const watcher = spawnWatch('node', ['call', 'dirWatch', 'false', interval]);
|
|
49
|
+
let error = '';
|
|
50
|
+
watcher.stderr.on('data', data => {
|
|
51
|
+
error += data.toString();
|
|
52
|
+
});
|
|
53
|
+
watcher.on('close', async code => {
|
|
54
|
+
if (error) {
|
|
55
|
+
if (error.startsWith('Navigation timeout of 30000 ms exceeded')) {
|
|
56
|
+
console.log('ERROR: Playwright claims 30-second timeout exceeded');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(`ERROR watching: ${error.slice(0, 200)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (! error && code === 0) {
|
|
63
|
+
console.log('Watcher exited successfully\n');
|
|
64
|
+
reWatch();
|
|
65
|
+
}
|
|
66
|
+
else if (code) {
|
|
67
|
+
console.log(`Watcher exited with error code ${code}`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.log('Watch aborted');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ########## OPERATION
|
|
76
|
+
|
|
77
|
+
reWatch();
|
package/watch.js
CHANGED
|
@@ -96,39 +96,51 @@ const archiveJob = async (job, isFile) => {
|
|
|
96
96
|
// Checks for a directory job and, if found, performs and reports it, once or repeatedly.
|
|
97
97
|
const checkDirJob = async (isForever, interval) => {
|
|
98
98
|
try {
|
|
99
|
-
// If there are any jobs
|
|
99
|
+
// If there are any jobs in the watched directory:
|
|
100
100
|
const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
|
|
101
101
|
const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
|
|
102
102
|
if (jobFileNames.length) {
|
|
103
|
-
//
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
const job = JSON.parse(jobJSON, null, 2);
|
|
107
|
-
const {id} = job;
|
|
103
|
+
// If the first one is ready to do:
|
|
104
|
+
const firstJobTime = jobFileNames[0].replace(/-.+$/, '');
|
|
105
|
+
if (Date.now() > dateOf(firstJobTime)) {
|
|
108
106
|
// Perform it.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
107
|
+
const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
|
|
108
|
+
try {
|
|
109
|
+
const job = JSON.parse(jobJSON, null, 2);
|
|
110
|
+
const {id} = job;
|
|
111
|
+
console.log(`Directory job ${id} found (${nowString()})`);
|
|
112
|
+
await doJob(job);
|
|
113
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
114
|
+
// Report it.
|
|
115
|
+
await writeDirReport(job);
|
|
116
|
+
// Archive it.
|
|
117
|
+
await archiveJob(job, true);
|
|
118
|
+
console.log(`Job ${id} archived in ${jobDir} (${nowString()})`);
|
|
119
|
+
// If watching is repetitive:
|
|
120
|
+
if (isForever) {
|
|
121
|
+
// Wait 2 seconds.
|
|
122
|
+
await wait(2000);
|
|
123
|
+
// Check the directory again.
|
|
124
|
+
checkDirJob(true, interval);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch(error) {
|
|
128
|
+
console.log(`ERROR processing directory job (${error.message})`);
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
|
-
|
|
126
|
-
|
|
131
|
+
// Otherwise, i.e. if the first one is not yet ready to do:
|
|
132
|
+
else {
|
|
133
|
+
// Report this.
|
|
134
|
+
console.log(`All jobs in ${jobDir} not yet ready to do (${nowString()})`);
|
|
135
|
+
// Wait for the specified interval.
|
|
136
|
+
await wait(1000 * interval);
|
|
137
|
+
// Check the directory again.
|
|
138
|
+
await checkDirJob(true, interval);
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
|
-
// Otherwise, i.e. if there are no more jobs
|
|
141
|
+
// Otherwise, i.e. if there are no more jobs in the watched directory:
|
|
130
142
|
else {
|
|
131
|
-
console.log(`No job
|
|
143
|
+
console.log(`No job in ${jobDir} (${nowString()})`);
|
|
132
144
|
// Wait for the specified interval.
|
|
133
145
|
await wait(1000 * interval);
|
|
134
146
|
// Check the directory again.
|