@jrpool/kilotest 40.0.0 → 41.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/.devin/config.local.json +7 -0
- package/README.md +2 -2
- package/alerts.js +5 -0
- package/docs/SERVICE.md +3 -3
- package/env.example +15 -18
- package/env.testaro +35 -9
- package/index.js +153 -87
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Testing for Kilotest is performed by one or more testing agents that obtain jobs
|
|
|
18
18
|
|
|
19
19
|
An active production instance of Kilotest may require multiple testing agents to handle the load, because testing one web page typically takes about 3 minutes and agents test only one page at a time.
|
|
20
20
|
|
|
21
|
-
## Getting started locally with 1 testing
|
|
21
|
+
## Getting started locally with 1 testing worker
|
|
22
22
|
|
|
23
23
|
### Installation
|
|
24
24
|
|
|
@@ -30,7 +30,7 @@ In the steps below, hosts `T` and `K` may be the same host or two different host
|
|
|
30
30
|
1. Clone the Kilotest repository into a new directory on host `K`.
|
|
31
31
|
1. In that directory, install the Kilotest dependencies: `npm install`.
|
|
32
32
|
1. Copy the `env.testaro` file from the `kilotest` directory to `.env` in the `testaro` directory and replace the `__placeholder__` values in `.env` with actual values.
|
|
33
|
-
1. Copy the `env.example` file in the `kilotest` directory to a new `.env` file in the same directory and replace the `__placeholder__` values in `.env` with actual values.
|
|
33
|
+
1. Copy the `env.example` file in the `kilotest` directory to a new `.env` file in the same directory and replace the `__placeholder__` values in `.env` with actual values. `TESTARO_WORKERS` is a JSON object mapping each Testaro worker's ID to an object with its `secret` and its `name`; the ID and secret for a worker must match the `NETWATCH_URL_WORKER_ID` and `NETWATCH_URL_AUTH` values in that worker's `.env`. A worker authenticates each request with an HTTP Basic `Authorization` header (`id:secret`, base64-encoded); as of this writing that header is not yet sent by the published Testaro package, so this scheme requires a matching update to Testaro's `netWatch.js` request code (only to send the header; no change to Testaro's handling of jobs or reports is needed). The ID is a credential and is never published; the `name` is written by Kilotest into a job's and report's `sources.worker` property, which Testaro does not read or alter, so it is a distinct property from `sources.agent` (Testaro's own, untrusted, self-reported label, which Kilotest ignores). Choose a `name` that is safe to appear in every report Kilotest serves.
|
|
34
34
|
|
|
35
35
|
### Usage
|
|
36
36
|
|
package/alerts.js
CHANGED
|
@@ -28,6 +28,7 @@ exports.sendAlert = (subject, body) => new Promise(resolve => {
|
|
|
28
28
|
subject,
|
|
29
29
|
text: body
|
|
30
30
|
});
|
|
31
|
+
// Ask the alerting host to send an alert to the manager.
|
|
31
32
|
const req = https.request({
|
|
32
33
|
hostname: ALERT_API_HOST,
|
|
33
34
|
path: ALERT_API_PATH,
|
|
@@ -43,10 +44,14 @@ exports.sendAlert = (subject, body) => new Promise(resolve => {
|
|
|
43
44
|
data += chunk;
|
|
44
45
|
});
|
|
45
46
|
res.on('end', () => {
|
|
47
|
+
// If this succeeded:
|
|
46
48
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
49
|
+
// Report this.
|
|
47
50
|
console.log(`Alert sent (${subject})`);
|
|
48
51
|
}
|
|
52
|
+
// Otherwise, i.e. if it failed:
|
|
49
53
|
else {
|
|
54
|
+
// Report this.
|
|
50
55
|
console.log(`ERROR: Alert API responded ${res.statusCode}: ${data}`);
|
|
51
56
|
}
|
|
52
57
|
resolve();
|
package/docs/SERVICE.md
CHANGED
|
@@ -203,7 +203,7 @@ kilotest.com {
|
|
|
203
203
|
}
|
|
204
204
|
@allowedPOST {
|
|
205
205
|
method POST
|
|
206
|
-
path /mcp /retestRec.html/* /testRec.html /recAction.html /reannotate.html /wcagRenew.html /api/* /tutorialComment.html
|
|
206
|
+
path /mcp /retestRec.html/* /testRec.html /recAction.html /reannotate.html /wcagRenew.html /api/* /worker/* /tutorialComment.html
|
|
207
207
|
}
|
|
208
208
|
# Respond to OPTIONS requests.
|
|
209
209
|
@allowedOPTIONS method OPTIONS
|
|
@@ -244,7 +244,7 @@ kilotest.com {
|
|
|
244
244
|
|
|
245
245
|
This configuration can be futher tightened if experience warrants.
|
|
246
246
|
|
|
247
|
-
The `flush_interval` setting prevents granular reporting by Testaro
|
|
247
|
+
The `flush_interval` setting prevents granular reporting by Testaro workers from being buffered, so the updates reach the browser without delay.
|
|
248
248
|
|
|
249
249
|
## Version management
|
|
250
250
|
|
|
@@ -326,7 +326,7 @@ After that the maintainer can inspect the original and restored directories and
|
|
|
326
326
|
|
|
327
327
|
### Possible future Testaro integration
|
|
328
328
|
|
|
329
|
-
Kilotest uses Testaro to run jobs. In previous versions of Kilotest, Testaro was a dependency. It is currently not a
|
|
329
|
+
Kilotest uses Testaro to run jobs. In previous versions of Kilotest, Testaro was a dependency. It is currently not a dependency. Instead, Testaro instances are installed on one or more other hosts, and each instance polls Kilotest to ask for jobs to run.
|
|
330
330
|
|
|
331
331
|
In case Testaro again becomes a dependency of Kilotest, the notes below on security issues will be useful.
|
|
332
332
|
|
package/env.example
CHANGED
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
#
|
|
1
|
+
# test (local) or production (deployed host).
|
|
2
2
|
ENVIRONMENT=__placeholder__
|
|
3
|
-
#
|
|
3
|
+
# http (local or reverse-proxied server) or https.
|
|
4
4
|
PROTOCOL=__placeholder__
|
|
5
|
-
#
|
|
5
|
+
# 3-character password for manager operations.
|
|
6
6
|
AUTH_CODE=__placeholder__
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
TESTARO_AGENT_PW=__placeholder__
|
|
11
|
-
# Name of research agent (AI agent calling Kilotest as a tool).
|
|
12
|
-
RESEARCH_AGENT=research-agent
|
|
13
|
-
# Research agent password.
|
|
14
|
-
RESEARCH_AGENT_PW=__placeholder__
|
|
15
|
-
# URL of this Kilotest host.
|
|
7
|
+
# ID of each Testaro worker and, for each, its secret and published name.
|
|
8
|
+
TESTARO_WORKERS={"__placeholder__":{"secret":"__placeholder__","name":"__placeholder__"}}
|
|
9
|
+
# URL of this host.
|
|
16
10
|
THIS_KILOTEST_HOST=__placeholder__
|
|
17
|
-
# URL of a dev/test
|
|
11
|
+
# URL of a dev/test host.
|
|
18
12
|
LOCAL_KILOTEST_HOST=http://localhost:3000
|
|
19
|
-
# URL of the deployed
|
|
13
|
+
# URL of the deployed host.
|
|
20
14
|
DEPLOYED_KILOTEST_HOST=https://kilotest.com
|
|
21
|
-
|
|
15
|
+
|
|
16
|
+
# Alert configuration:
|
|
17
|
+
|
|
22
18
|
MANAGER_EMAIL=__placeholder__
|
|
23
19
|
ALERT_API_HOST=api.resend.com
|
|
24
20
|
ALERT_API_PATH=/emails
|
|
25
21
|
ALERT_API_KEY=__placeholder__
|
|
26
22
|
ALERT_FROM=__placeholder__
|
|
27
|
-
#
|
|
28
|
-
WAVE_BALANCE_THRESHOLD=
|
|
29
|
-
|
|
23
|
+
# Threshold in credits.
|
|
24
|
+
WAVE_BALANCE_THRESHOLD=__placeholder__
|
|
25
|
+
# Threshold in dollars.
|
|
26
|
+
AI_SERVICE0_BALANCE_THRESHOLD=__placeholder__
|
|
30
27
|
# Per-million-token prices (Claude Haiku 4.5) on 2026-05-01 were $1 in, $5 out.
|
|
31
28
|
AI_MODEL0_INPUT_PRICE=0.000001
|
|
32
29
|
AI_MODEL0_OUTPUT_PRICE=0.000005
|
package/env.testaro
CHANGED
|
@@ -1,17 +1,43 @@
|
|
|
1
1
|
# You can get a WAVE API key at https://wave.webaim.org/api.
|
|
2
2
|
WAVE_KEY=__placeholder__
|
|
3
|
-
|
|
3
|
+
# You can get an Anthropic API key at https://console.anthropic.com/.
|
|
4
|
+
ANTHROPIC_API_KEY=__placeholder__
|
|
5
|
+
# Any message from this instance to the server (optional).
|
|
6
|
+
AGENT=__placeholder__
|
|
7
|
+
# Whether to make the browser visible (normally false).
|
|
4
8
|
HEADED_BROWSER=false
|
|
9
|
+
# Whether to output even forked-job logging to the primary log (normally false).
|
|
5
10
|
DEBUG=false
|
|
11
|
+
# Number of seconds to wait between actions (normally 0).
|
|
6
12
|
WAITS=0
|
|
13
|
+
# See https://nodejs.org/api/cli.html#options for all permitted options.
|
|
7
14
|
NODE_OPTIONS='--trace-uncaught --trace-warnings'
|
|
15
|
+
# Whether to suppress a routine warning from QualWeb.
|
|
8
16
|
PUPPETEER_DISABLE_HEADLESS_WARNING=true
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# NETWATCH_AUTH must be identical to TESTARO_AGENT_PW in the Kilotest .env.
|
|
15
|
-
NETWATCH_AUTH=__placeholder__
|
|
17
|
+
# Directory (relative to project root) to watch for jobs.
|
|
18
|
+
JOBDIR=__placeholder__
|
|
19
|
+
# Directory (relative to project root) to write reports to when watching a directory for jobs.
|
|
20
|
+
REPORTDIR=__placeholder__
|
|
21
|
+
# Multiplier for time limits (normally 1).
|
|
16
22
|
TIMEOUT_MULTIPLIER=1
|
|
17
|
-
|
|
23
|
+
# Whether to abort the job when launch retries are exhausted or a test act crashes (default false).
|
|
24
|
+
ABORT_ASSERTIVELY=false
|
|
25
|
+
# URL of any non-default (e.g., self-hosted) Nu Html Checker API (see ghcr.io/validator/validator).
|
|
26
|
+
TESTARO_NU_URL=__placeholder__
|
|
27
|
+
# Whether to launch the Playwright chromium browser without its sandbox (normally false).
|
|
28
|
+
# The sandbox requires unprivileged user-namespace cloning, which default container
|
|
29
|
+
# seccomp policies and some hardened hosts prohibit. Set this to true in containers,
|
|
30
|
+
# or run containers with a seccomp profile that permits user-namespace cloning.
|
|
31
|
+
TESTARO_CHROMIUM_NO_SANDBOX=false
|
|
32
|
+
|
|
33
|
+
# netWatch variables
|
|
34
|
+
|
|
35
|
+
# Replace the next 2 placeholders with the hostname of the server (e.g., http://localhost:3000 or https://kilotest.com).
|
|
36
|
+
# URL to poll for available jobs.
|
|
37
|
+
NETWATCH_URL_JOB=__placeholder__/worker/job
|
|
38
|
+
# URL to send a job report.
|
|
39
|
+
NETWATCH_URL_REPORT=__placeholder__/worker/report
|
|
40
|
+
|
|
41
|
+
# The server requires an authorization header. Identifier and secret of this worker.
|
|
42
|
+
WORKER_ID=__placeholder__
|
|
43
|
+
WORKER_SECRET=__placeholder__
|
package/index.js
CHANGED
|
@@ -69,8 +69,18 @@ const protocol = process.env.PROTOCOL || 'http';
|
|
|
69
69
|
const queuePath = path.join(jobsPath, 'queue');
|
|
70
70
|
const claimedPath = path.join(jobsPath, 'claimed');
|
|
71
71
|
const failedPath = path.join(jobsPath, 'failed');
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
// Credentials of the Testaro workers, by worker ID, from a JSON-object environment variable.
|
|
73
|
+
// Each worker ID maps to a secret (used only to authenticate the worker, never published) and a
|
|
74
|
+
// name (a non-secret label safe to publish, e.g. in report data and logs).
|
|
75
|
+
const workerCredentials = (() => {
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(process.env.TESTARO_WORKERS || '{}');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`ERROR: TESTARO_WORKERS is not valid JSON (${error.message})`);
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
})();
|
|
74
84
|
// Values that may require alerts.
|
|
75
85
|
const balancePath = path.join(__dirname, 'ai0Balance.json');
|
|
76
86
|
const jobLock = createLock();
|
|
@@ -82,11 +92,11 @@ const AI_MODEL0_OUTPUT_PRICE = Number(process.env.AI_MODEL0_OUTPUT_PRICE);
|
|
|
82
92
|
// FUNCTIONS
|
|
83
93
|
|
|
84
94
|
// Serves or sends an error message.
|
|
85
|
-
const serveError = async (error, response, isHumanUser = true) => {
|
|
95
|
+
const serveError = async (error, response, isHumanUser = true, statusCode = 400) => {
|
|
86
96
|
const errorLines = Object.entries(error).map(pair => `${pair[0]}: ${pair[1]}`);
|
|
87
97
|
console.log(errorLines.join('\n') || 'ERROR');
|
|
88
98
|
if (!response.writableEnded) {
|
|
89
|
-
response.statusCode =
|
|
99
|
+
response.statusCode = statusCode;
|
|
90
100
|
// If the request is from a human user:
|
|
91
101
|
if (isHumanUser) {
|
|
92
102
|
// Serve an HTML page containing the message property of the error.
|
|
@@ -189,19 +199,53 @@ const getAbuseError = (request, reason) => {
|
|
|
189
199
|
time: new Date().toISOString()
|
|
190
200
|
};
|
|
191
201
|
};
|
|
192
|
-
//
|
|
193
|
-
|
|
202
|
+
// Gets the ID and secret from a request's HTTP Basic Authorization header, or null if the header
|
|
203
|
+
// is absent or malformed.
|
|
204
|
+
const getBasicAuth = request => {
|
|
205
|
+
const header = request.headers['authorization'] || '';
|
|
206
|
+
const match = header.match(/^Basic\s+(\S+)$/i);
|
|
207
|
+
if (!match) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
let decoded;
|
|
211
|
+
try {
|
|
212
|
+
decoded = Buffer.from(match[1], 'base64').toString('utf8');
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const sepIndex = decoded.indexOf(':');
|
|
218
|
+
if (sepIndex === -1) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return {id: decoded.slice(0, sepIndex), secret: decoded.slice(sepIndex + 1)};
|
|
222
|
+
};
|
|
223
|
+
// Gets the published name of the Testaro worker that made a request, or null if it is not
|
|
224
|
+
// authenticated. The ID authenticates the worker and is never published; the name is a
|
|
225
|
+
// non-secret label safe to write into job/report data and log messages.
|
|
226
|
+
const getAuthorizedWorkerName = request => {
|
|
227
|
+
const credentials = getBasicAuth(request);
|
|
228
|
+
if (credentials) {
|
|
229
|
+
const worker = workerCredentials[credentials.id];
|
|
230
|
+
if (worker && worker.secret === credentials.secret && worker.name) {
|
|
231
|
+
return worker.name;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
};
|
|
236
|
+
// Processes a job request from a Testaro worker.
|
|
237
|
+
const processJobRequest = async (request, response, workerName) => jobLock(async () => {
|
|
194
238
|
let clean = true;
|
|
195
|
-
const messageStart = `Testaro
|
|
239
|
+
const messageStart = `Testaro worker ${workerName} requested a job, `;
|
|
196
240
|
const jobNames = await getJobNames();
|
|
197
241
|
const claimedJobNames = jobNames.claimed;
|
|
198
242
|
// For each claimed job:
|
|
199
243
|
for (const jobName of claimedJobNames) {
|
|
200
244
|
const job = await getObject(path.join(jobsPath, 'claimed', jobName));
|
|
201
245
|
const {id, sources} = job;
|
|
202
|
-
const {
|
|
203
|
-
// If its assignee is the
|
|
204
|
-
if (
|
|
246
|
+
const {worker} = sources;
|
|
247
|
+
// If its assignee is the worker:
|
|
248
|
+
if (worker === workerName) {
|
|
205
249
|
const messageEnd = `but has not completed job ${id}`;
|
|
206
250
|
// Report this.
|
|
207
251
|
await serveError({message: `${messageStart}${messageEnd}`}, response, false);
|
|
@@ -214,7 +258,7 @@ const processJobRequest = async (request, response, agentID) => jobLock(async ()
|
|
|
214
258
|
break;
|
|
215
259
|
}
|
|
216
260
|
}
|
|
217
|
-
// If no aborted-job error was found for the
|
|
261
|
+
// If no aborted-job error was found for the worker:
|
|
218
262
|
if (clean) {
|
|
219
263
|
const queuedJobNames = jobNames.queue;
|
|
220
264
|
// If any jobs are queued:
|
|
@@ -222,18 +266,18 @@ const processJobRequest = async (request, response, agentID) => jobLock(async ()
|
|
|
222
266
|
const oldestJobName = queuedJobNames[0];
|
|
223
267
|
// Get the first one.
|
|
224
268
|
const firstJob = await getObject(path.join(queuePath, oldestJobName));
|
|
225
|
-
// Add the
|
|
226
|
-
firstJob.sources.
|
|
269
|
+
// Add the public worker name to the job, in a property Testaro does not read or alter.
|
|
270
|
+
firstJob.sources.worker = workerName;
|
|
227
271
|
console.log(
|
|
228
|
-
`Job ${firstJob.id} (${firstJob.target.what}) is being sent to the
|
|
272
|
+
`Job ${firstJob.id} (${firstJob.target.what}) is being sent to the worker.`
|
|
229
273
|
);
|
|
230
|
-
// Assign the job to the
|
|
274
|
+
// Assign the job to the worker.
|
|
231
275
|
response.writeHead(200, {
|
|
232
276
|
'content-type': 'application/json; charset=utf-8'
|
|
233
277
|
});
|
|
234
278
|
response.end(JSON.stringify(firstJob));
|
|
235
279
|
const messageEnd
|
|
236
|
-
= `and job ${firstJob.id} (${firstJob.target.what}) was assigned to the
|
|
280
|
+
= `and job ${firstJob.id} (${firstJob.target.what}) was assigned to the worker`;
|
|
237
281
|
console.log(`${messageStart}${messageEnd}`);
|
|
238
282
|
// Save the job in the claimed-jobs directory.
|
|
239
283
|
await fs.writeFile(
|
|
@@ -247,7 +291,7 @@ const processJobRequest = async (request, response, agentID) => jobLock(async ()
|
|
|
247
291
|
response.writeHead(200, {
|
|
248
292
|
'content-type': 'application/json; charset=utf-8'
|
|
249
293
|
});
|
|
250
|
-
// Send a no-jobs response to the
|
|
294
|
+
// Send a no-jobs response to the worker.
|
|
251
295
|
response.end(JSON.stringify({}));
|
|
252
296
|
const messageEnd = 'but no job was in the queue';
|
|
253
297
|
console.log(`${messageStart}${messageEnd}`);
|
|
@@ -674,47 +718,67 @@ const requestHandler = async (request, response) => {
|
|
|
674
718
|
await serveError({message: answerData.message}, response, true);
|
|
675
719
|
}
|
|
676
720
|
}
|
|
677
|
-
// Otherwise, if it is a request from
|
|
678
|
-
else if (pageName === '
|
|
679
|
-
//
|
|
680
|
-
const
|
|
681
|
-
// If
|
|
682
|
-
if (
|
|
683
|
-
const agentID = segments[0];
|
|
721
|
+
// Otherwise, if it is a request from a Testaro worker:
|
|
722
|
+
else if (pageName === 'worker') {
|
|
723
|
+
// Authenticate the worker and get its public name.
|
|
724
|
+
const workerName = getAuthorizedWorkerName(request);
|
|
725
|
+
// If this succeeded:
|
|
726
|
+
if (workerName) {
|
|
684
727
|
// Get the requested service from the path.
|
|
685
|
-
const service =
|
|
728
|
+
const service = pathTail;
|
|
686
729
|
// If the service is job assignment:
|
|
687
730
|
if (service === 'job') {
|
|
688
731
|
// Process the job request as a transaction.
|
|
689
|
-
await processJobRequest(request, response,
|
|
732
|
+
await processJobRequest(request, response, workerName);
|
|
690
733
|
}
|
|
691
|
-
// Otherwise, if
|
|
734
|
+
// Otherwise, if it is report acquisition:
|
|
692
735
|
else if (service === 'report') {
|
|
693
736
|
const {report} = postData;
|
|
694
737
|
const {id, target} = report;
|
|
695
738
|
const {what, url} = target;
|
|
696
739
|
const [timeStamp, jobID] = id?.split('-') ?? ['', ''];
|
|
697
|
-
// If the request is valid:
|
|
740
|
+
// If the request is syntactically valid:
|
|
698
741
|
if (id && isTimeStamp(timeStamp) && isJobID(jobID) && what && url) {
|
|
699
742
|
const [timeStamp, jobID] = id.split('-');
|
|
700
|
-
//
|
|
701
|
-
await
|
|
702
|
-
//
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
743
|
+
// Get the job the report is from.
|
|
744
|
+
const claimedJob = await getObject(path.join(claimedPath, `${id}.json`));
|
|
745
|
+
// If the job was actually assigned to this worker:
|
|
746
|
+
if (typeof claimedJob === 'object' && claimedJob.sources?.worker === workerName) {
|
|
747
|
+
console.log(`Testaro report ${id} was received from worker ${workerName}`);
|
|
748
|
+
// Add the public worker name to the report.
|
|
749
|
+
report.sources = {...report.sources, worker: workerName};
|
|
750
|
+
// Save the report.
|
|
751
|
+
await fs.writeFile(getReportPath(timeStamp, jobID), getJSON(report));
|
|
752
|
+
// Update the data on the available reports.
|
|
753
|
+
await makeReportsExtract();
|
|
754
|
+
// Annotate the report.
|
|
755
|
+
await annotateReport(ruleIDs, timeStamp, jobID);
|
|
756
|
+
console.log(`Testaro report ${id} was annotated, saved, and indexed`);
|
|
757
|
+
// Check the monetary balances and send alerts if nearing exhaustion.
|
|
758
|
+
await checkBalancesForAlerts(report);
|
|
759
|
+
// Delete the job.
|
|
760
|
+
await fs.unlink(path.join(claimedPath, `${id}.json`));
|
|
761
|
+
console.log(`Completed job ${id} deleted`);
|
|
762
|
+
// Acknowledge receipt.
|
|
763
|
+
response.setHeader('content-type', 'application/json; charset=utf-8');
|
|
764
|
+
response.end(JSON.stringify({status: 'ok'}));
|
|
765
|
+
console.log(
|
|
766
|
+
`Testaro report ${id} was received from Testaro worker ${workerName}`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
// Otherwise, i.e. if the job was not assigned to this worker:
|
|
770
|
+
else {
|
|
771
|
+
await serveError(
|
|
772
|
+
getAbuseError(
|
|
773
|
+
request,
|
|
774
|
+
`Worker ${workerName} submitted a report for job ${id}, which was not currently assigned to it`
|
|
775
|
+
),
|
|
776
|
+
response,
|
|
777
|
+
false
|
|
778
|
+
);
|
|
779
|
+
}
|
|
716
780
|
}
|
|
717
|
-
// Otherwise, i.e. if the request is invalid:
|
|
781
|
+
// Otherwise, i.e. if the request is syntactically invalid:
|
|
718
782
|
else {
|
|
719
783
|
await serveError({message: 'ERROR: Request invalid'}, response, false);
|
|
720
784
|
}
|
|
@@ -722,54 +786,56 @@ const requestHandler = async (request, response) => {
|
|
|
722
786
|
// Otherwise, i.e. if the service is invalid:
|
|
723
787
|
else {
|
|
724
788
|
await serveError(
|
|
725
|
-
{message: 'ERROR: Invalid service request from Testaro
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
// Otherwise, if the first segment is not the ID of the Testaro agent:
|
|
730
|
-
else if (segments[0] !== testaroAgent) {
|
|
731
|
-
// If the service is to receive a test request:
|
|
732
|
-
if (segments[0] === 'requestTest') {
|
|
733
|
-
const {description, URL, reason} = postData;
|
|
734
|
-
// Get the response body.
|
|
735
|
-
const responseBody = await require(path.join(__dirname, 'api', 'requestTest'))
|
|
736
|
-
.response([description, URL, reason]);
|
|
737
|
-
// Send it.
|
|
738
|
-
setHeaders('application/json', null, 'ultra');
|
|
739
|
-
response.end(JSON.stringify(responseBody));
|
|
740
|
-
}
|
|
741
|
-
// Otherwise, if the service is to receive a retest request:
|
|
742
|
-
else if (segments[0] === 'requestRetest') {
|
|
743
|
-
const {reason} = postData;
|
|
744
|
-
// Get the response body.
|
|
745
|
-
const responseBody = await require(path.join(__dirname, 'api', 'requestRetest'))
|
|
746
|
-
.response(segments.slice(1).concat(reason));
|
|
747
|
-
// Send it.
|
|
748
|
-
setHeaders('application/json', null, 'ultra');
|
|
749
|
-
response.end(JSON.stringify(responseBody));
|
|
750
|
-
}
|
|
751
|
-
// Otherwise, if the service is to receive a feature request:
|
|
752
|
-
else if (segments[0] === 'requestFeature') {
|
|
753
|
-
const {feature} = postData;
|
|
754
|
-
// Get the response body.
|
|
755
|
-
const responseBody = await require(path.join(__dirname, 'api', 'requestFeature'))
|
|
756
|
-
.response([feature]);
|
|
757
|
-
// Send it.
|
|
758
|
-
setHeaders('application/json', null, 'ultra');
|
|
759
|
-
response.end(JSON.stringify(responseBody));
|
|
760
|
-
}
|
|
761
|
-
// Otherwise, i.e. if the service is invalid:
|
|
762
|
-
else {
|
|
763
|
-
await serveError(
|
|
764
|
-
{message: 'ERROR: Invalid request'}, response, false
|
|
789
|
+
{message: 'ERROR: Invalid service request from Testaro worker'}, response, false
|
|
765
790
|
);
|
|
766
791
|
}
|
|
767
792
|
}
|
|
768
|
-
// Otherwise, i.e. if
|
|
793
|
+
// Otherwise, i.e. if it is not authenticated:
|
|
769
794
|
else {
|
|
770
795
|
// Report this.
|
|
771
796
|
await serveError(
|
|
772
|
-
{message: 'ERROR:
|
|
797
|
+
{message: 'ERROR: Unauthorized worker request'}, response, false, 401
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// Otherwise, if it is a request from an API consumer:
|
|
802
|
+
else if (pageName === 'api') {
|
|
803
|
+
// Get the segments of the path after api.
|
|
804
|
+
const segments = pathTail.split('/');
|
|
805
|
+
// If the service is to receive a test request:
|
|
806
|
+
if (segments[0] === 'requestTest') {
|
|
807
|
+
const {description, URL, reason} = postData;
|
|
808
|
+
// Get the response body.
|
|
809
|
+
const responseBody = await require(path.join(__dirname, 'api', 'requestTest'))
|
|
810
|
+
.response([description, URL, reason]);
|
|
811
|
+
// Send it.
|
|
812
|
+
setHeaders('application/json', null, 'ultra');
|
|
813
|
+
response.end(JSON.stringify(responseBody));
|
|
814
|
+
}
|
|
815
|
+
// Otherwise, if the service is to receive a retest request:
|
|
816
|
+
else if (segments[0] === 'requestRetest') {
|
|
817
|
+
const {reason} = postData;
|
|
818
|
+
// Get the response body.
|
|
819
|
+
const responseBody = await require(path.join(__dirname, 'api', 'requestRetest'))
|
|
820
|
+
.response(segments.slice(1).concat(reason));
|
|
821
|
+
// Send it.
|
|
822
|
+
setHeaders('application/json', null, 'ultra');
|
|
823
|
+
response.end(JSON.stringify(responseBody));
|
|
824
|
+
}
|
|
825
|
+
// Otherwise, if the service is to receive a feature request:
|
|
826
|
+
else if (segments[0] === 'requestFeature') {
|
|
827
|
+
const {feature} = postData;
|
|
828
|
+
// Get the response body.
|
|
829
|
+
const responseBody = await require(path.join(__dirname, 'api', 'requestFeature'))
|
|
830
|
+
.response([feature]);
|
|
831
|
+
// Send it.
|
|
832
|
+
setHeaders('application/json', null, 'ultra');
|
|
833
|
+
response.end(JSON.stringify(responseBody));
|
|
834
|
+
}
|
|
835
|
+
// Otherwise, i.e. if the service is invalid:
|
|
836
|
+
else {
|
|
837
|
+
await serveError(
|
|
838
|
+
{message: 'ERROR: Invalid request'}, response, false
|
|
773
839
|
);
|
|
774
840
|
}
|
|
775
841
|
}
|