testaro 41.0.0 → 41.0.2
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/dirWatch.js +2 -7
- package/netWatch.js +71 -78
- package/package.json +1 -1
- package/procs/dateOf.js +47 -0
- package/procs/job.js +268 -0
- package/run.js +697 -964
package/dirWatch.js
CHANGED
|
@@ -33,6 +33,8 @@ require('dotenv').config();
|
|
|
33
33
|
const fs = require('fs/promises');
|
|
34
34
|
// Module to perform jobs.
|
|
35
35
|
const {doJob} = require('./run');
|
|
36
|
+
// Module to get dates from time stamps.
|
|
37
|
+
const {dateOf} = require('./procs/dateOf');
|
|
36
38
|
|
|
37
39
|
// ########## CONSTANTS
|
|
38
40
|
|
|
@@ -45,13 +47,6 @@ const reportDir = process.env.REPORTDIR;
|
|
|
45
47
|
const tsPart = (timeStamp, startIndex) => timeStamp.slice(startIndex, startIndex + 2);
|
|
46
48
|
// Returns a string representing the date and time.
|
|
47
49
|
const nowString = () => (new Date()).toISOString().slice(2, 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
50
|
// Writes a directory report.
|
|
56
51
|
const writeDirReport = async report => {
|
|
57
52
|
const jobID = report && report.id;
|
package/netWatch.js
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
// Module to keep secrets.
|
|
31
31
|
require('dotenv').config();
|
|
32
|
+
// Module to validate jobs.
|
|
33
|
+
const {isValidJob} = require('./procs/job');
|
|
32
34
|
// Modules to make requests to servers.
|
|
33
35
|
const httpClient = require('http');
|
|
34
36
|
const httpsClient = require('https');
|
|
@@ -132,6 +134,7 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
|
|
|
132
134
|
try {
|
|
133
135
|
// If there was no job to do:
|
|
134
136
|
let contentObj = JSON.parse(content);
|
|
137
|
+
let jobInvalidity = '';
|
|
135
138
|
if (! Object.keys(contentObj).length) {
|
|
136
139
|
// Report this.
|
|
137
140
|
console.log(`No job to do at ${url}`);
|
|
@@ -139,7 +142,8 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
|
|
|
139
142
|
}
|
|
140
143
|
// Otherwise, i.e. if there was a job or a message:
|
|
141
144
|
else {
|
|
142
|
-
const {id, message,
|
|
145
|
+
const {id, message, sources} = contentObj;
|
|
146
|
+
const sendReportTo = sources ? sources.sendReportTo : '';
|
|
143
147
|
// If the server sent a message, not a job:
|
|
144
148
|
if (message) {
|
|
145
149
|
// Report it.
|
|
@@ -147,98 +151,89 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
|
|
|
147
151
|
resolve(true);
|
|
148
152
|
}
|
|
149
153
|
// Otherwise, if the server sent a valid job:
|
|
150
|
-
else if (
|
|
154
|
+
else if (
|
|
155
|
+
id && sendReportTo && sources && ! (jobInvalidity = isValidJob(contentObj))
|
|
156
|
+
) {
|
|
151
157
|
// Restart the cycle.
|
|
152
158
|
cycleIndex = -1;
|
|
153
159
|
// Prevent further watching, if unwanted.
|
|
154
160
|
noJobYet = false;
|
|
155
161
|
// Add the agent to the job.
|
|
156
162
|
sources.agent = agent;
|
|
157
|
-
//
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
contentObj = {};
|
|
196
|
-
resolve(true);
|
|
197
|
-
}
|
|
198
|
-
// Otherwise, i.e. if the server sent anything else:
|
|
199
|
-
else {
|
|
200
|
-
// Report it.
|
|
201
|
-
console.log(
|
|
202
|
-
`ERROR: ${reportLogStart}status ${repResponse.statusCode} and error message ${JSON.stringify(ackObj, null, 2)}\n`
|
|
203
|
-
);
|
|
204
|
-
resolve(true);
|
|
205
|
-
}
|
|
163
|
+
// Perform the job, adding result data to it.
|
|
164
|
+
console.log(`${logStart}job ${id} (${nowString()})`);
|
|
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://')
|
|
172
|
+
? httpsClient
|
|
173
|
+
: httpClient;
|
|
174
|
+
const reportLogStart = `Sent report ${id} to ${sendReportTo} and got `;
|
|
175
|
+
reportClient.request(sendReportTo, {method: 'POST'}, repResponse => {
|
|
176
|
+
const chunks = [];
|
|
177
|
+
repResponse
|
|
178
|
+
// If the response to the report threw an error:
|
|
179
|
+
.on('error', async error => {
|
|
180
|
+
// Report this.
|
|
181
|
+
console.log(`${reportLogStart}error message ${error.message}\n`);
|
|
182
|
+
resolve(true);
|
|
183
|
+
})
|
|
184
|
+
.on('data', chunk => {
|
|
185
|
+
chunks.push(chunk);
|
|
186
|
+
})
|
|
187
|
+
// When the response arrives:
|
|
188
|
+
.on('end', async () => {
|
|
189
|
+
const content = chunks.join('');
|
|
190
|
+
try {
|
|
191
|
+
// If the server sent a message, as expected:
|
|
192
|
+
const ackObj = JSON.parse(content);
|
|
193
|
+
const {message} = ackObj;
|
|
194
|
+
if (message) {
|
|
195
|
+
// Report it.
|
|
196
|
+
console.log(`${reportLogStart}message ${message}\n`);
|
|
197
|
+
// Free the memory used by the report.
|
|
198
|
+
reportJSON = '';
|
|
199
|
+
contentObj = {};
|
|
200
|
+
resolve(true);
|
|
206
201
|
}
|
|
207
|
-
//
|
|
208
|
-
|
|
202
|
+
// Otherwise, i.e. if the server sent anything else:
|
|
203
|
+
else {
|
|
209
204
|
// Report it.
|
|
210
205
|
console.log(
|
|
211
|
-
`ERROR: ${reportLogStart}status ${repResponse.statusCode}
|
|
206
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode} and error message ${JSON.stringify(ackObj, null, 2)}\n`
|
|
212
207
|
);
|
|
213
208
|
resolve(true);
|
|
214
209
|
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Otherwise, i.e. if the job specifies no report destination:
|
|
229
|
-
else {
|
|
210
|
+
}
|
|
211
|
+
// If processing the server message throws an error:
|
|
212
|
+
catch(error) {
|
|
213
|
+
// Report it.
|
|
214
|
+
console.log(
|
|
215
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode}, error message ${error.message}, and response ${content.slice(0, 1000)}\n`
|
|
216
|
+
);
|
|
217
|
+
resolve(true);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
})
|
|
221
|
+
// If the report submission throws an error:
|
|
222
|
+
.on('error', async error => {
|
|
230
223
|
// Report this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
224
|
+
console.log(
|
|
225
|
+
`ERROR in report submission: ${reportLogStart}error message ${error.message}\n`
|
|
226
|
+
);
|
|
234
227
|
resolve(true);
|
|
235
|
-
}
|
|
228
|
+
})
|
|
229
|
+
// Finish submitting the report.
|
|
230
|
+
.end(reportJSON);
|
|
236
231
|
}
|
|
237
232
|
// Otherwise, i.e. if the server sent an invalid job:
|
|
238
233
|
else {
|
|
239
234
|
// Report this.
|
|
240
|
-
const
|
|
241
|
-
= `ERROR: ${logStart}invalid job
|
|
235
|
+
const errorSuffix = jobInvalidity ? ` (${jobInvalidity})` : '';
|
|
236
|
+
const message = `ERROR: ${logStart}invalid job${errorSuffix}`;
|
|
242
237
|
console.log(message);
|
|
243
238
|
serveObject({message}, response);
|
|
244
239
|
resolve(true);
|
|
@@ -248,9 +243,7 @@ exports.netWatch = async (isForever, intervalInSeconds, isCertTolerant = true) =
|
|
|
248
243
|
// If processing the server response throws an error:
|
|
249
244
|
catch(error) {
|
|
250
245
|
// Report this.
|
|
251
|
-
console.log(
|
|
252
|
-
`ERROR processing server response: ${error.message} (response ${content.slice(0, 1000)})`
|
|
253
|
-
);
|
|
246
|
+
console.log(`ERROR processing server response: ${error.message})`);
|
|
254
247
|
resolve(true);
|
|
255
248
|
}
|
|
256
249
|
});
|
package/package.json
CHANGED
package/procs/dateOf.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2024 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
|
+
dateOf
|
|
25
|
+
Returns the date represented by a time stamp.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Inserts a character periodically in a string.
|
|
29
|
+
const punctuate = (string, insertion, chunkSize) => {
|
|
30
|
+
const segments = [];
|
|
31
|
+
let startIndex = 0;
|
|
32
|
+
while (startIndex < string.length) {
|
|
33
|
+
segments.push(string.slice(startIndex, startIndex + chunkSize));
|
|
34
|
+
startIndex += chunkSize;
|
|
35
|
+
}
|
|
36
|
+
return segments.join(insertion);
|
|
37
|
+
};
|
|
38
|
+
// Gets the date of a timestamp.
|
|
39
|
+
exports.dateOf = timeStamp => {
|
|
40
|
+
if (/^\d{6}T\d{4}$/.test(timeStamp)) {
|
|
41
|
+
const dateString = punctuate(timeStamp.slice(0, 6), '-', 2);
|
|
42
|
+
const timeString = punctuate(timeStamp.slice(7, 11), ':', 2);
|
|
43
|
+
return new Date(`20${dateString}T${timeString}Z`);
|
|
44
|
+
} else {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
package/procs/job.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2024 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
|
+
job
|
|
25
|
+
Utilities about jobs and acts.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// IMPORTS
|
|
29
|
+
|
|
30
|
+
// Requirements for acts.
|
|
31
|
+
const {actSpecs} = require('../actSpecs');
|
|
32
|
+
// Module to validate device IDs.
|
|
33
|
+
const {isDeviceID} = require('./device');
|
|
34
|
+
// Module to get dates from time stamps.
|
|
35
|
+
const {dateOf} = require('./dateOf');
|
|
36
|
+
|
|
37
|
+
// CONSTANTS
|
|
38
|
+
|
|
39
|
+
// Names and descriptions of tools.
|
|
40
|
+
const tools = exports.tools = {
|
|
41
|
+
alfa: 'alfa',
|
|
42
|
+
aslint: 'ASLint',
|
|
43
|
+
axe: 'Axe',
|
|
44
|
+
ed11y: 'Editoria11y',
|
|
45
|
+
htmlcs: 'HTML CodeSniffer WCAG 2.1 AA ruleset',
|
|
46
|
+
ibm: 'IBM Accessibility Checker',
|
|
47
|
+
nuVal: 'Nu Html Checker',
|
|
48
|
+
qualWeb: 'QualWeb',
|
|
49
|
+
testaro: 'Testaro',
|
|
50
|
+
wave: 'WAVE',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// FUNCTIONS
|
|
54
|
+
|
|
55
|
+
// Returns whether a variable has a specified type.
|
|
56
|
+
const hasType = (variable, type) => {
|
|
57
|
+
if (type === 'string') {
|
|
58
|
+
return typeof variable === 'string';
|
|
59
|
+
}
|
|
60
|
+
else if (type === 'array') {
|
|
61
|
+
return Array.isArray(variable);
|
|
62
|
+
}
|
|
63
|
+
else if (type === 'boolean') {
|
|
64
|
+
return typeof variable === 'boolean';
|
|
65
|
+
}
|
|
66
|
+
else if (type === 'number') {
|
|
67
|
+
return typeof variable === 'number';
|
|
68
|
+
}
|
|
69
|
+
else if (type === 'object') {
|
|
70
|
+
return typeof variable === 'object' && ! Array.isArray(variable);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
// Returns whether a variable has a specified subtype.
|
|
77
|
+
const hasSubtype = (variable, subtype) => {
|
|
78
|
+
if (subtype) {
|
|
79
|
+
if (subtype === 'hasLength') {
|
|
80
|
+
return variable.length > 0;
|
|
81
|
+
}
|
|
82
|
+
else if (subtype === 'isURL') {
|
|
83
|
+
return isURL(variable);
|
|
84
|
+
}
|
|
85
|
+
else if (subtype === 'isDeviceID') {
|
|
86
|
+
return isDeviceID(variable);
|
|
87
|
+
}
|
|
88
|
+
else if (subtype === 'isBrowserID') {
|
|
89
|
+
return isBrowserID(variable);
|
|
90
|
+
}
|
|
91
|
+
else if (subtype === 'isFocusable') {
|
|
92
|
+
return isFocusable(variable);
|
|
93
|
+
}
|
|
94
|
+
else if (subtype === 'isTest') {
|
|
95
|
+
return tools[variable];
|
|
96
|
+
}
|
|
97
|
+
else if (subtype === 'isWaitable') {
|
|
98
|
+
return ['url', 'title', 'body'].includes(variable);
|
|
99
|
+
}
|
|
100
|
+
else if (subtype === 'areNumbers') {
|
|
101
|
+
return areNumbers(variable);
|
|
102
|
+
}
|
|
103
|
+
else if (subtype === 'areStrings') {
|
|
104
|
+
return areStrings(variable);
|
|
105
|
+
}
|
|
106
|
+
else if (subtype === 'areArrays') {
|
|
107
|
+
return areArrays(variable);
|
|
108
|
+
}
|
|
109
|
+
else if (subtype === 'isState') {
|
|
110
|
+
return isState(variable);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log(`ERROR: ${subtype} not a known subtype`);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
// Validates a browser type.
|
|
122
|
+
const isBrowserID = type => ['chromium', 'firefox', 'webkit'].includes(type);
|
|
123
|
+
// Validates a load state.
|
|
124
|
+
const isState = string => ['loaded', 'idle'].includes(string);
|
|
125
|
+
// Validates a URL.
|
|
126
|
+
const isURL = string => /^(?:https?|file):\/\/[^\s]+$/.test(string);
|
|
127
|
+
// Validates a focusable tag name.
|
|
128
|
+
const isFocusable = string => ['a', 'button', 'input', 'select'].includes(string);
|
|
129
|
+
// Returns whether all elements of an array are numbers.
|
|
130
|
+
const areNumbers = array => array.every(element => typeof element === 'number');
|
|
131
|
+
// Returns whether all elements of an array are strings.
|
|
132
|
+
const areStrings = array => array.every(element => typeof element === 'string');
|
|
133
|
+
// Returns whether all properties of an object have array values.
|
|
134
|
+
const areArrays = object => Object.values(object).every(value => Array.isArray(value));
|
|
135
|
+
// Validates an act by reference to actSpecs.js.
|
|
136
|
+
const isValidAct = exports.isValidAct = act => {
|
|
137
|
+
// Identify the type of the act.
|
|
138
|
+
const type = act.type;
|
|
139
|
+
// If the type exists and is known:
|
|
140
|
+
if (type && actSpecs.etc[type]) {
|
|
141
|
+
// Copy the validator of the type for possible expansion.
|
|
142
|
+
const validator = Object.assign({}, actSpecs.etc[type][1]);
|
|
143
|
+
// If the type is test:
|
|
144
|
+
if (type === 'test') {
|
|
145
|
+
// Identify the test.
|
|
146
|
+
const toolName = act.which;
|
|
147
|
+
// If one was specified and is known:
|
|
148
|
+
if (toolName && tools[toolName]) {
|
|
149
|
+
// If it has special properties:
|
|
150
|
+
if (actSpecs.tools[toolName]) {
|
|
151
|
+
// Expand the validator by adding them.
|
|
152
|
+
Object.assign(validator, actSpecs.tools[toolName][1]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Otherwise, i.e. if no or an unknown test was specified:
|
|
156
|
+
else {
|
|
157
|
+
// Return invalidity.
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Return whether the act is valid.
|
|
162
|
+
return Object.keys(validator).every(property => {
|
|
163
|
+
if (property === 'name') {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const vP = validator[property];
|
|
168
|
+
const aP = act[property];
|
|
169
|
+
// If it is optional and omitted or is present and valid:
|
|
170
|
+
const optAndNone = ! vP[0] && ! aP;
|
|
171
|
+
const isValid = aP !== undefined && hasType(aP, vP[1]) && hasSubtype(aP, vP[2]);
|
|
172
|
+
return optAndNone || isValid;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Otherwise, i.e. if the act has an unknown or no type:
|
|
177
|
+
else {
|
|
178
|
+
// Return invalidity.
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
// Returns blank if a job is valid, or an error message.
|
|
183
|
+
exports.isValidJob = job => {
|
|
184
|
+
// If any job was provided:
|
|
185
|
+
if (job) {
|
|
186
|
+
// Get its properties.
|
|
187
|
+
const {
|
|
188
|
+
id,
|
|
189
|
+
strict,
|
|
190
|
+
isolate,
|
|
191
|
+
standard,
|
|
192
|
+
observe,
|
|
193
|
+
deviceID,
|
|
194
|
+
browserID,
|
|
195
|
+
lowMotion,
|
|
196
|
+
timeLimit,
|
|
197
|
+
creationTimeStamp,
|
|
198
|
+
executionTimeStamp,
|
|
199
|
+
sources,
|
|
200
|
+
acts
|
|
201
|
+
} = job;
|
|
202
|
+
// Return an error for the first missing or invalid property.
|
|
203
|
+
if (! id || typeof id !== 'string') {
|
|
204
|
+
return 'Bad job ID';
|
|
205
|
+
}
|
|
206
|
+
if (typeof strict !== 'boolean') {
|
|
207
|
+
return 'Bad job strict';
|
|
208
|
+
}
|
|
209
|
+
if (typeof isolate !== 'boolean') {
|
|
210
|
+
return 'Bad job isolate';
|
|
211
|
+
}
|
|
212
|
+
if (! ['also', 'only', 'no'].includes(standard)) {
|
|
213
|
+
return 'Bad job standard';
|
|
214
|
+
}
|
|
215
|
+
if (typeof observe !== 'boolean') {
|
|
216
|
+
return 'Bad job observe';
|
|
217
|
+
}
|
|
218
|
+
if (! isDeviceID(deviceID)) {
|
|
219
|
+
return 'Bad job deviceID';
|
|
220
|
+
}
|
|
221
|
+
if (! ['chromium', 'firefox', 'webkit'].includes(browserID)) {
|
|
222
|
+
return 'Bad job browserID';
|
|
223
|
+
}
|
|
224
|
+
if (typeof lowMotion !== 'boolean') {
|
|
225
|
+
return 'Bad job lowMotion';
|
|
226
|
+
}
|
|
227
|
+
if (typeof timeLimit !== 'number' || timeLimit < 1) {
|
|
228
|
+
return 'Bad job timeLimit';
|
|
229
|
+
}
|
|
230
|
+
if (
|
|
231
|
+
! (creationTimeStamp && typeof creationTimeStamp === 'string' && dateOf(creationTimeStamp))
|
|
232
|
+
) {
|
|
233
|
+
return 'bad job creationTimeStamp';
|
|
234
|
+
}
|
|
235
|
+
if (
|
|
236
|
+
! (executionTimeStamp && typeof executionTimeStamp === 'string') && dateOf(executionTimeStamp)
|
|
237
|
+
) {
|
|
238
|
+
return 'bad job executionTimeStamp';
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
! sources
|
|
242
|
+
|| typeof sources !== 'object'
|
|
243
|
+
|| ! ['script', 'batch', 'target'].every(key => sources[key])
|
|
244
|
+
|| ! ['what', 'url'].every(key => sources.target[key])
|
|
245
|
+
) {
|
|
246
|
+
return 'Bad job sources';
|
|
247
|
+
}
|
|
248
|
+
if (
|
|
249
|
+
! acts
|
|
250
|
+
|| ! Array.isArray(acts)
|
|
251
|
+
|| acts.length < 2
|
|
252
|
+
|| ! acts.every(act => act.type && typeof act.type === 'string')
|
|
253
|
+
|| acts[0].type !== 'launch'
|
|
254
|
+
) {
|
|
255
|
+
return 'Bad job acts';
|
|
256
|
+
}
|
|
257
|
+
const invalidAct = acts.find(act => ! isValidAct(act));
|
|
258
|
+
if (invalidAct) {
|
|
259
|
+
return `Invalid act:\n${JSON.stringify(invalidAct, null, 2)}`;
|
|
260
|
+
}
|
|
261
|
+
return '';
|
|
262
|
+
}
|
|
263
|
+
// Otherwise, i.e. if no job was provided:
|
|
264
|
+
else {
|
|
265
|
+
// Return this.
|
|
266
|
+
return 'no job';
|
|
267
|
+
}
|
|
268
|
+
};
|