@jrpool/kilotest 46.1.2 → 47.0.1
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/.claude/settings.local.json +13 -1
- package/.devin/config.local.json +5 -1
- package/.github/workflows/ci.yml +0 -3
- package/.github/workflows/periodic-smoke-tests.yml +30 -0
- package/.github/workflows/publish-mcp.yml +4 -4
- package/{alerts.test.js → alerts.test.ts} +17 -29
- package/{alerts.js → alerts.ts} +10 -13
- package/api/{getReport.test.js → getReport.test.ts} +9 -10
- package/api/{getReport.js → getReport.ts} +23 -24
- package/api/{listDiagnoses.test.js → listDiagnoses.test.ts} +26 -27
- package/api/{listDiagnoses.js → listDiagnoses.ts} +46 -70
- package/api/{listIssues.test.js → listIssues.test.ts} +21 -14
- package/api/{listIssues.js → listIssues.ts} +75 -74
- package/api/{listReports.test.js → listReports.test.ts} +17 -16
- package/api/listReports.ts +87 -0
- package/api/{listViolators.test.js → listViolators.test.ts} +19 -20
- package/api/{listViolators.js → listViolators.ts} +45 -70
- package/api/requestFeature.test.ts +54 -0
- package/api/{requestFeature.js → requestFeature.ts} +18 -15
- package/api/{requestRetest.test.js → requestRetest.test.ts} +33 -28
- package/api/{requestRetest.js → requestRetest.ts} +32 -25
- package/api/{requestTest.test.js → requestTest.test.ts} +35 -30
- package/api/{requestTest.js → requestTest.ts} +35 -28
- package/api/{routes.test.js → routes.test.ts} +5 -5
- package/api/{routes.js → routes.ts} +6 -6
- package/api/{schemas.js → schemas.ts} +69 -50
- package/api/{util.test.js → util.test.ts} +31 -11
- package/api/{util.js → util.ts} +61 -36
- package/api/version.ts +8 -0
- package/{apiIntegration.test.js → apiIntegration.test.ts} +12 -13
- package/balances.ts +87 -0
- package/{checkinfo.js → checkinfo.ts} +24 -20
- package/docs/AI-TOOLS.md +4 -4
- package/docs/BACKLOG.md +356 -0
- package/docs/SERVICE.md +144 -17
- package/docs/decisions/002-coverage-tool.md +40 -0
- package/docs/decisions/003-adr-threshold.md +34 -0
- package/docs/decisions/004-docs-directory.md +23 -0
- package/docs/decisions/005-observability.md +28 -0
- package/docs/decisions/006-git-workflow.md +28 -0
- package/docs/decisions/007-internal-testing.md +34 -0
- package/docs/decisions/008-dependencies.md +28 -0
- package/docs/decisions/009-qai-integration.md +40 -0
- package/docs/decisions/QAI-HISTORICAL-CONTEXT.md +42 -0
- package/eslint.config.mjs +27 -0
- package/index.html +3 -3
- package/{index.test.js → index.test.ts} +380 -143
- package/{index.js → index.ts} +382 -283
- package/{indexBalances.test.js → indexBalances.test.ts} +21 -17
- package/{indexServe.test.js → indexServe.test.ts} +9 -9
- package/linkValidation.test.ts +190 -0
- package/{mcp.test.js → mcp.test.ts} +53 -26
- package/{mcp.js → mcp.ts} +29 -20
- package/memory/MEMORY.md +7 -0
- package/memory/user_maintainer_context.md +24 -0
- package/openapi.yaml +115 -68
- package/package.json +35 -15
- package/pm2.config.cjs +29 -0
- package/{routes.test.js → routes.test.ts} +8 -7
- package/{routing.test.js → routing.test.ts} +11 -12
- package/scripts/{generate-openapi.js → generate-openapi.ts} +13 -13
- package/serve.ts +15 -0
- package/smokeTest.ts +234 -0
- package/test/dbFixture.ts +21 -0
- package/test/fixtures/{buildFixtures.js → buildFixtures.ts} +14 -13
- package/test/{setup.cjs → setup.ts} +2 -2
- package/tsconfig.json +16 -0
- package/{util.test.js → util.test.ts} +350 -257
- package/util.ts +921 -0
- package/web/ai0BalanceForm/{ai0BalanceForm.test.js → ai0BalanceForm.test.ts} +15 -15
- package/web/ai0BalanceForm/{index.js → index.ts} +10 -18
- package/web/enqueue/index.ts +56 -0
- package/web/enqueueForm/index.html +9 -9
- package/web/enqueueForm/index.ts +43 -0
- package/web/expungeReportsForm/{expungeReportsForm.test.js → expungeReportsForm.test.ts} +18 -20
- package/web/expungeReportsForm/index.ts +18 -0
- package/web/hideReportForm/{hideReportForm.test.js → hideReportForm.test.ts} +14 -14
- package/web/hideReportForm/{index.js → index.ts} +17 -21
- package/web/listDiagnoses/{index.js → index.ts} +48 -58
- package/web/listDiagnoses/{listDiagnoses.test.js → listDiagnoses.test.ts} +39 -34
- package/web/listIssues/{index.js → index.ts} +67 -92
- package/web/listIssues/{listIssues.test.js → listIssues.test.ts} +45 -51
- package/web/listReports/index.html +3 -3
- package/web/listReports/{index.js → index.ts} +48 -53
- package/web/listReports/{listReports.test.js → listReports.test.ts} +41 -43
- package/web/listRules/{index.js → index.ts} +18 -23
- package/web/listRules/{listRules.test.js → listRules.test.ts} +26 -19
- package/web/listTopIssues/{index.js → index.ts} +36 -47
- package/web/listTopIssues/{listTopIssues.test.js → listTopIssues.test.ts} +26 -17
- package/web/listViolators/{index.js → index.ts} +51 -71
- package/web/listViolators/{listViolators.test.js → listViolators.test.ts} +43 -36
- package/web/manage/index.ts +22 -0
- package/web/pruneReportsForm/index.ts +18 -0
- package/web/pruneReportsForm/{pruneReportsForm.test.js → pruneReportsForm.test.ts} +11 -11
- package/web/reannotate/index.ts +70 -0
- package/web/reannotate/{reannotate.test.js → reannotate.test.ts} +19 -22
- package/web/reannotateForm/{index.js → index.ts} +41 -50
- package/web/reannotateForm/{reannotateForm.test.js → reannotateForm.test.ts} +13 -16
- package/web/renewWCAG/{index.js → index.ts} +9 -9
- package/web/renewWCAG/{renewWCAG.test.js → renewWCAG.test.ts} +17 -20
- package/web/renewWCAGForm/index.ts +22 -0
- package/web/{expungeReportsForm/index.js → reportDeletion.ts} +56 -38
- package/web/requestRetest/index.html +7 -7
- package/web/requestRetest/index.ts +31 -0
- package/web/requestRetest/requestRetest.test.ts +116 -0
- package/web/requestRetestForm/{index.js → index.ts} +10 -16
- package/web/requestRetestForm/{requestRetestForm.test.js → requestRetestForm.test.ts} +11 -12
- package/web/requestTest/index.html +7 -7
- package/web/requestTest/{index.js → index.ts} +5 -5
- package/web/requestTest/{requestTest.test.js → requestTest.test.ts} +21 -21
- package/web/requestTestForm/index.html +4 -4
- package/web/requestTestForm/index.ts +21 -0
- package/web/requestTestForm/{requestTestForm.test.js → requestTestForm.test.ts} +9 -9
- package/web/rewindReportsForm/index.ts +18 -0
- package/web/rewindReportsForm/{rewindReportsForm.test.js → rewindReportsForm.test.ts} +11 -11
- package/web/tutorialAI/index.html +184 -0
- package/web/tutorialAI/index.ts +80 -0
- package/web/tutorialAI/tutorialAI.test.ts +192 -0
- package/web/{tutorial → tutorialWeb}/index.html +9 -8
- package/web/tutorialWeb/index.ts +78 -0
- package/web/tutorialWeb/tutorialWeb.test.ts +195 -0
- package/web/unhideReportForm/{index.js → index.ts} +17 -21
- package/web/unhideReportForm/{unhideReportForm.test.js → unhideReportForm.test.ts} +16 -16
- package/aceconfig.js +0 -28
- package/api/listReports.js +0 -89
- package/api/requestFeature.test.js +0 -58
- package/api/version.js +0 -8
- package/indexFallback.test.js +0 -33
- package/indexWorkers.test.js +0 -79
- package/jsconfig.json +0 -8
- package/pm2.config.js +0 -18
- package/smokeTest.js +0 -128
- package/util.js +0 -744
- package/web/enqueue/index.js +0 -67
- package/web/enqueueForm/index.js +0 -49
- package/web/manage/index.js +0 -22
- package/web/pruneReportsForm/index.js +0 -123
- package/web/reannotate/index.js +0 -58
- package/web/renewWCAGForm/index.js +0 -22
- package/web/requestRetest/index.js +0 -31
- package/web/requestRetest/requestRetest.test.js +0 -119
- package/web/requestTestForm/index.js +0 -21
- package/web/rewindReportsForm/index.js +0 -121
- package/web/tutorial/index.js +0 -63
- package/web/tutorial/tutorial.test.js +0 -97
- /package/test/fixtures/db/jobs/{recs.json → testRequests.json} +0 -0
- /package/web/{tutorial → tutorialWeb}/images/newsletter-form.png +0 -0
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/*
|
|
2
|
-
util.test.
|
|
3
|
-
Tests for util.
|
|
2
|
+
util.test.ts
|
|
3
|
+
Tests for util.ts data-path injection (Phase 1) and utility functions.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
import {test, before, after} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import fs from 'node:fs/promises';
|
|
12
|
+
import {
|
|
13
|
+
annotateReportObject,
|
|
14
|
+
checkCommentDuplicate,
|
|
15
|
+
checkCommentLength,
|
|
14
16
|
createLock,
|
|
15
17
|
dbPath,
|
|
16
18
|
getAgoDays,
|
|
@@ -18,10 +20,10 @@ const {
|
|
|
18
20
|
getCountString,
|
|
19
21
|
getDateString,
|
|
20
22
|
getDateTime,
|
|
21
|
-
|
|
23
|
+
getEngineNamesString,
|
|
22
24
|
getIssue,
|
|
25
|
+
errorMessage,
|
|
23
26
|
getJSON,
|
|
24
|
-
getLatestReportExtracts,
|
|
25
27
|
getMultiReportWhats,
|
|
26
28
|
getNowStamp,
|
|
27
29
|
getObject,
|
|
@@ -39,7 +41,6 @@ const {
|
|
|
39
41
|
getWeightName,
|
|
40
42
|
hiddenReportsPath,
|
|
41
43
|
htmlSafe,
|
|
42
|
-
isHidden,
|
|
43
44
|
isJobID,
|
|
44
45
|
isReportAvailable,
|
|
45
46
|
isTimeStamp,
|
|
@@ -49,19 +50,74 @@ const {
|
|
|
49
50
|
minifyURL,
|
|
50
51
|
objectSort,
|
|
51
52
|
processTestRequest,
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
testRequestsLock,
|
|
54
|
+
testRequestsPath,
|
|
54
55
|
reportsPath,
|
|
55
|
-
|
|
56
|
-
}
|
|
56
|
+
addTestRequest
|
|
57
|
+
} from './util.ts';
|
|
57
58
|
|
|
58
59
|
// TESTS
|
|
59
60
|
|
|
61
|
+
test('checkCommentLength accepts a comment of exactly 20 characters', () => {
|
|
62
|
+
const result = checkCommentLength('a'.repeat(20));
|
|
63
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('checkCommentLength accepts a comment of exactly 1000 characters', () => {
|
|
67
|
+
const result = checkCommentLength('a'.repeat(1000));
|
|
68
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('checkCommentLength rejects a comment shorter than 20 characters', () => {
|
|
72
|
+
const result = checkCommentLength('a'.repeat(19));
|
|
73
|
+
assert.deepEqual(result, {
|
|
74
|
+
status: 'error',
|
|
75
|
+
message: 'Your comment was shorter than 20 characters'
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('checkCommentLength rejects a comment longer than 1000 characters', () => {
|
|
80
|
+
const result = checkCommentLength('a'.repeat(1001));
|
|
81
|
+
assert.deepEqual(result, {
|
|
82
|
+
status: 'error',
|
|
83
|
+
message: 'Your comment was longer than 1000 characters'
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('checkCommentDuplicate accepts a comment with no existing comments', () => {
|
|
88
|
+
const result = checkCommentDuplicate([], 'a'.repeat(20));
|
|
89
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('checkCommentDuplicate rejects a comment repeating one submitted within the last 1000 seconds', () => {
|
|
93
|
+
const content = 'a'.repeat(20);
|
|
94
|
+
const comments = [{timeStamp: getNowStamp(), content}];
|
|
95
|
+
const result = checkCommentDuplicate(comments, content);
|
|
96
|
+
assert.deepEqual(result, {
|
|
97
|
+
status: 'error',
|
|
98
|
+
message: 'Your comment repeats a recently submitted one, but you are welcome to submit a different comment'
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('checkCommentDuplicate accepts a comment repeating one submitted more than 1000 seconds ago', () => {
|
|
103
|
+
const content = 'a'.repeat(20);
|
|
104
|
+
const oldTimeStamp = getTimeStamp(new Date(Date.now() - 2000000));
|
|
105
|
+
const comments = [{timeStamp: oldTimeStamp, content}];
|
|
106
|
+
const result = checkCommentDuplicate(comments, content);
|
|
107
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('checkCommentDuplicate accepts a comment that differs from a recent one', () => {
|
|
111
|
+
const comments = [{timeStamp: getNowStamp(), content: 'a'.repeat(20)}];
|
|
112
|
+
const result = checkCommentDuplicate(comments, 'b'.repeat(20));
|
|
113
|
+
assert.deepEqual(result, {status: 'ok'});
|
|
114
|
+
});
|
|
115
|
+
|
|
60
116
|
test('dbPath defaults to the project db directory when DB_DIR is unset', () => {
|
|
61
117
|
const saved = process.env.DB_DIR;
|
|
62
118
|
delete process.env.DB_DIR;
|
|
63
119
|
try {
|
|
64
|
-
assert.equal(dbPath(), path.join(
|
|
120
|
+
assert.equal(dbPath(), path.join(import.meta.dirname, 'db'));
|
|
65
121
|
}
|
|
66
122
|
finally {
|
|
67
123
|
if (saved !== undefined) {
|
|
@@ -86,12 +142,12 @@ test('dbPath honors DB_DIR when it is set', () => {
|
|
|
86
142
|
}
|
|
87
143
|
});
|
|
88
144
|
|
|
89
|
-
test('jobsPath,
|
|
145
|
+
test('jobsPath, testRequestsPath, reportsPath, and hiddenReportsPath derive from DB_DIR', () => {
|
|
90
146
|
const saved = process.env.DB_DIR;
|
|
91
147
|
process.env.DB_DIR = '/tmp/kilotest-fixtures';
|
|
92
148
|
try {
|
|
93
149
|
assert.equal(jobsPath(), path.join('/tmp/kilotest-fixtures', 'jobs'));
|
|
94
|
-
assert.equal(
|
|
150
|
+
assert.equal(testRequestsPath(), path.join('/tmp/kilotest-fixtures', 'jobs', 'testRequests.json'));
|
|
95
151
|
assert.equal(reportsPath(), path.join('/tmp/kilotest-fixtures', 'reports'));
|
|
96
152
|
assert.equal(hiddenReportsPath(), path.join('/tmp/kilotest-fixtures', 'hiddenReports'));
|
|
97
153
|
}
|
|
@@ -134,7 +190,7 @@ test('getAgoDays returns null for an invalid string', () => {
|
|
|
134
190
|
});
|
|
135
191
|
|
|
136
192
|
test('getAgoDays returns null for a non-string non-Date argument', () => {
|
|
137
|
-
assert.equal(getAgoDays(42), null);
|
|
193
|
+
assert.equal(getAgoDays(42 as any), null);
|
|
138
194
|
});
|
|
139
195
|
|
|
140
196
|
test('getAgoDays returns 0 for the current time', () => {
|
|
@@ -190,20 +246,26 @@ test('getIssue returns null for an unknown engine', () => {
|
|
|
190
246
|
assert.equal(getIssue('nonexistentEngine', 'anyRule'), null);
|
|
191
247
|
});
|
|
192
248
|
|
|
249
|
+
test('errorMessage returns the message of an Error instance', () => {
|
|
250
|
+
assert.equal(errorMessage(new Error('something went wrong')), 'something went wrong');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('errorMessage returns the string representation of a non-Error value', () => {
|
|
254
|
+
assert.equal(errorMessage('bare string'), 'bare string');
|
|
255
|
+
});
|
|
256
|
+
|
|
193
257
|
test('getJSON returns a JSON string with a trailing newline', () => {
|
|
194
258
|
assert.equal(getJSON({a: 1}), '{\n "a": 1\n}\n');
|
|
195
259
|
});
|
|
196
260
|
|
|
197
261
|
test('getObject returns the parsed object for a valid JSON file', async () => {
|
|
198
|
-
const result = await getObject(path.join(
|
|
199
|
-
assert.ok(typeof result === 'object');
|
|
200
|
-
assert.equal(result.name, '@jrpool/kilotest');
|
|
262
|
+
const result = await getObject(path.join(import.meta.dirname, 'package.json'));
|
|
263
|
+
assert.ok(typeof result === 'object' && result !== null);
|
|
264
|
+
assert.equal((result as {name: unknown}).name, '@jrpool/kilotest');
|
|
201
265
|
});
|
|
202
266
|
|
|
203
|
-
test('getObject
|
|
204
|
-
|
|
205
|
-
assert.equal(typeof result, 'string');
|
|
206
|
-
assert.ok(result.startsWith('ERROR'));
|
|
267
|
+
test('getObject throws for a nonexistent file', async () => {
|
|
268
|
+
await assert.rejects(getObject('/tmp/nonexistent-file.json'), /not readable/);
|
|
207
269
|
});
|
|
208
270
|
|
|
209
271
|
test('getNowStamp returns an 11-character time stamp', () => {
|
|
@@ -264,10 +326,8 @@ test('isTimeStamp returns false for an invalid time stamp', () => {
|
|
|
264
326
|
assert.equal(isTimeStamp('invalid'), false);
|
|
265
327
|
});
|
|
266
328
|
|
|
267
|
-
test('isURL returns
|
|
268
|
-
|
|
269
|
-
assert.ok(result instanceof URL);
|
|
270
|
-
assert.equal(result.hostname, 'example.com');
|
|
329
|
+
test('isURL returns true for a valid HTTPS URL', () => {
|
|
330
|
+
assert.equal(isURL('https://example.com/page'), true);
|
|
271
331
|
});
|
|
272
332
|
|
|
273
333
|
test('isURL returns false for a non-HTTPS URL', () => {
|
|
@@ -304,11 +364,11 @@ test('objectSort sorts objects numerically descending', () => {
|
|
|
304
364
|
assert.deepEqual(sorted.map(i => i.count), [3, 2, 1]);
|
|
305
365
|
});
|
|
306
366
|
|
|
307
|
-
// TESTS FOR processTestRequest
|
|
367
|
+
// TESTS FOR processTestRequest AND isReportAvailable
|
|
308
368
|
|
|
309
369
|
// These tests use the fixture database directory and the real web/requestTest template.
|
|
310
|
-
|
|
311
|
-
const requestTestDir = path.join(
|
|
370
|
+
import {fixtureDBDir as fixtureDbDir} from './test/dbFixture.ts';
|
|
371
|
+
const requestTestDir = path.join(import.meta.dirname, 'web', 'requestTest');
|
|
312
372
|
const savedDbDir = process.env.DB_DIR;
|
|
313
373
|
|
|
314
374
|
before(() => {
|
|
@@ -325,20 +385,20 @@ after(() => {
|
|
|
325
385
|
});
|
|
326
386
|
|
|
327
387
|
test('getPageData returns page data for a valid report', async () => {
|
|
328
|
-
const data = await getPageData('260101T0000', 'mix');
|
|
329
|
-
assert.equal(data.
|
|
388
|
+
const data = await getPageData('260101T0000', 'mix') as any;
|
|
389
|
+
assert.equal(data.description, 'Mixed Outcomes Page');
|
|
330
390
|
assert.equal(data.url, 'https://example.com/mixed');
|
|
331
391
|
assert.equal(typeof data.daysAgo, 'number');
|
|
332
392
|
});
|
|
333
393
|
|
|
334
394
|
test('getPageData returns an error for a nonexistent report', async () => {
|
|
335
|
-
const data = await getPageData('999999T9999', 'xxx');
|
|
395
|
+
const data = await getPageData('999999T9999', 'xxx') as any;
|
|
336
396
|
assert.ok(data.error);
|
|
337
397
|
});
|
|
338
398
|
|
|
339
399
|
test('getPageDataStrings returns HTML strings for a valid report', async () => {
|
|
340
|
-
const strings = await getPageDataStrings('260101T0000', 'mix');
|
|
341
|
-
assert.equal(strings.
|
|
400
|
+
const strings = await getPageDataStrings('260101T0000', 'mix') as any;
|
|
401
|
+
assert.equal(strings.description, 'Mixed Outcomes Page');
|
|
342
402
|
assert.equal(strings.url, 'https://example.com/mixed');
|
|
343
403
|
assert.equal(strings.urlLink, '<a href="https://example.com/mixed">https://example.com/mixed</a>');
|
|
344
404
|
assert.ok(strings.testInfo.includes('by job <code>mix</code>'));
|
|
@@ -346,8 +406,8 @@ test('getPageDataStrings returns HTML strings for a valid report', async () => {
|
|
|
346
406
|
});
|
|
347
407
|
|
|
348
408
|
test('getPageDataStrings returns different testInfo for a different timeStamp', async () => {
|
|
349
|
-
const strings = await getPageDataStrings('260101T0001', 'ct');
|
|
350
|
-
assert.equal(strings.
|
|
409
|
+
const strings = await getPageDataStrings('260101T0001', 'ct') as any;
|
|
410
|
+
assert.equal(strings.description, 'All CantTell Page');
|
|
351
411
|
assert.ok(strings.testInfo.includes('by job <code>ct</code>'));
|
|
352
412
|
assert.ok(strings.testInfo.includes('2026-01-01 at 00:01'));
|
|
353
413
|
});
|
|
@@ -359,84 +419,128 @@ test('getPageDataStrings returns an error for a nonexistent report', async () =>
|
|
|
359
419
|
|
|
360
420
|
test('getPageDataStrings uses provided pageData instead of reading the report', async () => {
|
|
361
421
|
const strings = await getPageDataStrings('260101T0000', 'mix', {
|
|
362
|
-
|
|
422
|
+
description: 'Custom Page',
|
|
363
423
|
url: 'https://custom.com',
|
|
364
424
|
daysAgo: 1
|
|
365
|
-
});
|
|
366
|
-
assert.equal(strings.
|
|
425
|
+
}) as any;
|
|
426
|
+
assert.equal(strings.description, 'Custom Page');
|
|
367
427
|
assert.equal(strings.url, 'https://custom.com');
|
|
368
428
|
assert.ok(strings.testInfo.includes('1 day ago'));
|
|
369
429
|
});
|
|
370
430
|
|
|
371
431
|
test('processTestRequest returns an error for an invalid test type', async () => {
|
|
372
|
-
const result = await processTestRequest('invalid', requestTestDir, 'Page', 'https://example.com', 'because');
|
|
432
|
+
const result: any = await processTestRequest('invalid', requestTestDir, 'Page', 'https://example.com', 'because');
|
|
373
433
|
assert.equal(result.status, 'error');
|
|
374
|
-
assert.equal(result.message, 'Invalid
|
|
434
|
+
assert.equal(result.message, 'Invalid request');
|
|
375
435
|
});
|
|
376
436
|
|
|
377
437
|
test('processTestRequest returns an error for an invalid URL', async () => {
|
|
378
|
-
const result = await processTestRequest('test', requestTestDir, 'Page', 'not-a-url', 'because');
|
|
438
|
+
const result: any = await processTestRequest('test', requestTestDir, 'Page', 'not-a-url', 'because');
|
|
379
439
|
assert.equal(result.status, 'error');
|
|
380
|
-
assert.equal(result.message, 'Invalid
|
|
440
|
+
assert.equal(result.message, 'Invalid request');
|
|
381
441
|
});
|
|
382
442
|
|
|
383
443
|
test('processTestRequest returns an error for a short reason', async () => {
|
|
384
|
-
const result = await processTestRequest('test', requestTestDir, 'Page', 'https://example.com', 'abc');
|
|
444
|
+
const result: any = await processTestRequest('test', requestTestDir, 'Page', 'https://example.com', 'abc');
|
|
385
445
|
assert.equal(result.status, 'error');
|
|
386
|
-
assert.equal(result.message, 'Invalid
|
|
446
|
+
assert.equal(result.message, 'Invalid request');
|
|
387
447
|
});
|
|
388
448
|
|
|
389
449
|
test('processTestRequest returns an error for a missing description', async () => {
|
|
390
|
-
const result = await processTestRequest('test', requestTestDir, '', 'https://example.com', 'because');
|
|
450
|
+
const result: any = await processTestRequest('test', requestTestDir, '', 'https://example.com', 'because');
|
|
391
451
|
assert.equal(result.status, 'error');
|
|
392
|
-
assert.equal(result.message, 'Invalid
|
|
452
|
+
assert.equal(result.message, 'Invalid request');
|
|
393
453
|
});
|
|
394
454
|
|
|
395
455
|
test('processTestRequest returns an error for a mismatched directory name', async () => {
|
|
396
|
-
const result = await processTestRequest('test', '/tmp/wrongDir', 'Page', 'https://example.com', 'because');
|
|
456
|
+
const result: any = await processTestRequest('test', '/tmp/wrongDir', 'Page', 'https://example.com', 'because');
|
|
397
457
|
assert.equal(result.status, 'error');
|
|
398
|
-
assert.equal(result.message, 'Invalid
|
|
458
|
+
assert.equal(result.message, 'Invalid request');
|
|
399
459
|
});
|
|
400
460
|
|
|
461
|
+
|
|
401
462
|
test('processTestRequest succeeds and populates the template for a valid request', {timeout: 500}, async () => {
|
|
402
|
-
// Reset
|
|
403
|
-
await fs.writeFile(
|
|
404
|
-
const result = await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'because accessibility');
|
|
463
|
+
// Reset testRequests.json to empty before the test.
|
|
464
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
465
|
+
const result: any = await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'because accessibility');
|
|
405
466
|
assert.equal(result.status, 'ok');
|
|
406
467
|
assert.ok(result.answerPage.includes('Example Page'));
|
|
407
468
|
assert.ok(result.answerPage.includes('because accessibility'));
|
|
408
|
-
// Clean up
|
|
409
|
-
await fs.writeFile(
|
|
469
|
+
// Clean up testRequests.json.
|
|
470
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
410
471
|
});
|
|
411
472
|
|
|
412
473
|
test('processTestRequest returns a duplicate error for a repeated request', {timeout: 500}, async () => {
|
|
413
|
-
// Reset
|
|
414
|
-
await fs.writeFile(
|
|
474
|
+
// Reset testRequests.json to empty, then make a successful request.
|
|
475
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
415
476
|
await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'because accessibility');
|
|
416
477
|
// Repeat the same request.
|
|
417
|
-
const result = await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'another reason');
|
|
478
|
+
const result: any = await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'another reason');
|
|
418
479
|
assert.equal(result.status, 'error');
|
|
419
|
-
assert.equal(result.message, 'Duplicate
|
|
420
|
-
// Clean up
|
|
421
|
-
await fs.writeFile(
|
|
480
|
+
assert.equal(result.message, 'Duplicate request');
|
|
481
|
+
// Clean up testRequests.json.
|
|
482
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
422
483
|
});
|
|
423
484
|
|
|
424
|
-
test('
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
485
|
+
test('annotateReportObject annotates a report object in place without reading or writing a file', async () => {
|
|
486
|
+
// A report object that was never written to disk (e.g. one just received from a worker,
|
|
487
|
+
// as in index.ts's worker/report handler, which annotates before the first write).
|
|
488
|
+
const report: any = {
|
|
489
|
+
id: '990101T0099-obj',
|
|
490
|
+
target: {what: 'Object Page', url: 'https://example.com/object'},
|
|
491
|
+
acts: [
|
|
492
|
+
{
|
|
493
|
+
type: 'test',
|
|
494
|
+
which: 'alfa',
|
|
495
|
+
result: {
|
|
496
|
+
standardResult: {
|
|
497
|
+
instances: [
|
|
498
|
+
// A classifiable rule.
|
|
499
|
+
{ruleID: 'r11', what: 'The link does not have an accessible name', outcome: 'failed', catalogIndex: '0'},
|
|
500
|
+
// An unclassifiable rule.
|
|
501
|
+
{ruleID: 'unknownRule123', what: 'Unknown', outcome: 'failed', catalogIndex: '0'}
|
|
502
|
+
]
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
],
|
|
507
|
+
jobData: {endTime: '26-01-01T00:00'},
|
|
508
|
+
catalog: {}
|
|
509
|
+
};
|
|
510
|
+
await annotateReportObject(report);
|
|
511
|
+
const instances = report.acts[0].result.standardResult.instances;
|
|
512
|
+
assert.equal(instances[0].issueID, 'linkNoText');
|
|
513
|
+
assert.equal(instances[1].issueID, undefined);
|
|
514
|
+
assert.deepEqual(report.jobData.issuelessRules, ['alfa:unknownRule123']);
|
|
428
515
|
});
|
|
429
516
|
|
|
430
|
-
test('
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
517
|
+
test('annotateReportObject handles a test act with no standardResult instances', async () => {
|
|
518
|
+
const report: any = {
|
|
519
|
+
id: '990101T0098-ni',
|
|
520
|
+
target: {what: 'Test', url: 'https://example.com'},
|
|
521
|
+
acts: [
|
|
522
|
+
{type: 'test', which: 'axe', result: {standardResult: {instances: []}}},
|
|
523
|
+
{type: 'other'}
|
|
524
|
+
],
|
|
525
|
+
jobData: {endTime: '26-01-01T00:00'},
|
|
526
|
+
catalog: {}
|
|
527
|
+
};
|
|
528
|
+
await annotateReportObject(report);
|
|
529
|
+
assert.deepEqual(report.jobData.issuelessRules, []);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('annotateReportObject handles a test act with no standardResult', async () => {
|
|
533
|
+
const report: any = {
|
|
534
|
+
id: '990101T0097-ns',
|
|
535
|
+
target: {what: 'Test', url: 'https://example.com'},
|
|
536
|
+
acts: [
|
|
537
|
+
{type: 'test', which: 'axe', result: {}}
|
|
538
|
+
],
|
|
539
|
+
jobData: {endTime: '26-01-01T00:00'},
|
|
540
|
+
catalog: {}
|
|
541
|
+
};
|
|
542
|
+
await annotateReportObject(report);
|
|
543
|
+
assert.deepEqual(report.jobData.issuelessRules, []);
|
|
440
544
|
});
|
|
441
545
|
|
|
442
546
|
test('isReportAvailable returns true for a known page description', async () => {
|
|
@@ -457,18 +561,19 @@ test('isReportAvailable returns false for an unknown page and URL', async () =>
|
|
|
457
561
|
// TESTS FOR REMAINING BRANCH COVERAGE
|
|
458
562
|
|
|
459
563
|
test('getJobNames creates missing job directories and returns empty arrays', async () => {
|
|
460
|
-
const os =
|
|
564
|
+
const os = await import('node:os');
|
|
461
565
|
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kilotest-jobnames-'));
|
|
462
566
|
const tmpDbDir = path.join(tmpRoot, 'db');
|
|
463
567
|
const savedDbDir = process.env.DB_DIR;
|
|
464
568
|
process.env.DB_DIR = tmpDbDir;
|
|
465
569
|
try {
|
|
466
|
-
const {getJobNames} =
|
|
570
|
+
const {getJobNames} = await import('./util.ts');
|
|
467
571
|
const result = await getJobNames();
|
|
468
|
-
assert.
|
|
469
|
-
|
|
470
|
-
assert.deepEqual(
|
|
471
|
-
assert.deepEqual(
|
|
572
|
+
assert.ok(typeof result === 'object' && result !== null);
|
|
573
|
+
const jobNames = result as Record<string, string[]>;
|
|
574
|
+
assert.deepEqual(jobNames.queue, []);
|
|
575
|
+
assert.deepEqual(jobNames.claimed, []);
|
|
576
|
+
assert.deepEqual(jobNames.failed, []);
|
|
472
577
|
// Verify the directories were created.
|
|
473
578
|
for (const category of ['queue', 'claimed', 'failed']) {
|
|
474
579
|
const stat = await fs.stat(path.join(tmpDbDir, 'jobs', category));
|
|
@@ -481,8 +586,8 @@ test('getJobNames creates missing job directories and returns empty arrays', asy
|
|
|
481
586
|
}
|
|
482
587
|
});
|
|
483
588
|
|
|
484
|
-
test('getJobNames
|
|
485
|
-
const os =
|
|
589
|
+
test('getJobNames throws when a job directory is a file, not a directory', async () => {
|
|
590
|
+
const os = await import('node:os');
|
|
486
591
|
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kilotest-jobnames-err-'));
|
|
487
592
|
const tmpJobsDir = path.join(tmpRoot, 'db', 'jobs');
|
|
488
593
|
await fs.mkdir(tmpJobsDir, {recursive: true});
|
|
@@ -491,10 +596,8 @@ test('getJobNames returns an error when a job directory is a file, not a directo
|
|
|
491
596
|
const savedDbDir = process.env.DB_DIR;
|
|
492
597
|
process.env.DB_DIR = path.join(tmpRoot, 'db');
|
|
493
598
|
try {
|
|
494
|
-
const {getJobNames} =
|
|
495
|
-
|
|
496
|
-
assert.equal(typeof result, 'string');
|
|
497
|
-
assert.ok(result.startsWith('ERROR'));
|
|
599
|
+
const {getJobNames} = await import('./util.ts');
|
|
600
|
+
await assert.rejects(getJobNames(), /not readable/);
|
|
498
601
|
}
|
|
499
602
|
finally {
|
|
500
603
|
process.env.DB_DIR = savedDbDir;
|
|
@@ -502,28 +605,46 @@ test('getJobNames returns an error when a job directory is a file, not a directo
|
|
|
502
605
|
}
|
|
503
606
|
});
|
|
504
607
|
|
|
505
|
-
test('getObject
|
|
506
|
-
const tmpFile = path.join(
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
608
|
+
test('getObject throws for a file that is not valid JSON', async () => {
|
|
609
|
+
const tmpFile = path.join((await import('node:os')).tmpdir(), 'kilotest-test-invalid.json');
|
|
610
|
+
(await import('node:fs')).writeFileSync(tmpFile, 'not json');
|
|
611
|
+
try {
|
|
612
|
+
await assert.rejects(getObject(tmpFile), /not JSON/);
|
|
613
|
+
}
|
|
614
|
+
finally {
|
|
615
|
+
(await import('node:fs')).unlinkSync(tmpFile);
|
|
616
|
+
}
|
|
512
617
|
});
|
|
513
618
|
|
|
514
|
-
test('
|
|
515
|
-
const tmpDir =
|
|
516
|
-
const fsSync =
|
|
619
|
+
test('getTestRequests creates and returns an empty test-requests object when the file is missing', async () => {
|
|
620
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-missing-testrequests-test';
|
|
621
|
+
const fsSync = await import('node:fs');
|
|
517
622
|
fsSync.mkdirSync(tmpDir + '/jobs', {recursive: true});
|
|
518
623
|
const savedDbDir = process.env.DB_DIR;
|
|
519
624
|
process.env.DB_DIR = tmpDir;
|
|
520
625
|
try {
|
|
521
|
-
const {
|
|
522
|
-
const result = await
|
|
523
|
-
assert.
|
|
524
|
-
assert.ok(result.startsWith('ERROR'));
|
|
626
|
+
const {getTestRequests} = await import('./util.ts');
|
|
627
|
+
const result = await getTestRequests();
|
|
628
|
+
assert.deepEqual(result, {});
|
|
525
629
|
// Verify the empty file was created.
|
|
526
|
-
assert.ok(fsSync.existsSync(tmpDir + '/jobs/
|
|
630
|
+
assert.ok(fsSync.existsSync(tmpDir + '/jobs/testRequests.json'));
|
|
631
|
+
}
|
|
632
|
+
finally {
|
|
633
|
+
process.env.DB_DIR = savedDbDir;
|
|
634
|
+
fsSync.rmSync(tmpDir, {recursive: true});
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test('getTestRequests throws when the test-requests file is not readable for a reason other than being missing', async () => {
|
|
639
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-testrequests-unreadable-test';
|
|
640
|
+
const fsSync = await import('node:fs');
|
|
641
|
+
// Create a directory where the test-requests file should be, causing EISDIR rather than ENOENT.
|
|
642
|
+
fsSync.mkdirSync(tmpDir + '/jobs/testRequests.json', {recursive: true});
|
|
643
|
+
const savedDbDir = process.env.DB_DIR;
|
|
644
|
+
process.env.DB_DIR = tmpDir;
|
|
645
|
+
try {
|
|
646
|
+
const {getTestRequests} = await import('./util.ts');
|
|
647
|
+
await assert.rejects(getTestRequests(), /not readable/);
|
|
527
648
|
}
|
|
528
649
|
finally {
|
|
529
650
|
process.env.DB_DIR = savedDbDir;
|
|
@@ -531,27 +652,25 @@ test('getRecs creates an empty recommendations file and returns an error when it
|
|
|
531
652
|
}
|
|
532
653
|
});
|
|
533
654
|
|
|
534
|
-
test('
|
|
535
|
-
const tmpDir =
|
|
536
|
-
|
|
537
|
-
|
|
655
|
+
test('getTestRequests throws when the test-requests file is not JSON', async () => {
|
|
656
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-testrequests-test';
|
|
657
|
+
(await import('node:fs')).mkdirSync(tmpDir + '/jobs', {recursive: true});
|
|
658
|
+
(await import('node:fs')).writeFileSync(tmpDir + '/jobs/testRequests.json', 'not json');
|
|
538
659
|
const savedDbDir = process.env.DB_DIR;
|
|
539
660
|
process.env.DB_DIR = tmpDir;
|
|
540
661
|
try {
|
|
541
|
-
const {
|
|
542
|
-
|
|
543
|
-
assert.equal(typeof result, 'string');
|
|
544
|
-
assert.ok(result.startsWith('ERROR'));
|
|
662
|
+
const {getTestRequests} = await import('./util.ts');
|
|
663
|
+
await assert.rejects(getTestRequests(), /not JSON/);
|
|
545
664
|
}
|
|
546
665
|
finally {
|
|
547
666
|
process.env.DB_DIR = savedDbDir;
|
|
548
|
-
|
|
667
|
+
(await import('node:fs')).rmSync(tmpDir, {recursive: true});
|
|
549
668
|
}
|
|
550
669
|
});
|
|
551
670
|
|
|
552
671
|
test('getPOSTData resolves with parsed query for form-urlencoded requests', async () => {
|
|
553
|
-
const {Readable} =
|
|
554
|
-
const {getPOSTData} =
|
|
672
|
+
const {Readable} = await import('node:stream');
|
|
673
|
+
const {getPOSTData} = await import('./util.ts');
|
|
555
674
|
const body = 'target=Page&why=Because';
|
|
556
675
|
const req = Object.assign(new Readable({
|
|
557
676
|
read() {
|
|
@@ -559,14 +678,14 @@ test('getPOSTData resolves with parsed query for form-urlencoded requests', asyn
|
|
|
559
678
|
this.push(null);
|
|
560
679
|
}
|
|
561
680
|
}), {headers: {'content-type': 'application/x-www-form-urlencoded'}});
|
|
562
|
-
const result = await getPOSTData(req);
|
|
681
|
+
const result: any = await getPOSTData(req as any);
|
|
563
682
|
assert.equal(result.target, 'Page');
|
|
564
683
|
assert.equal(result.why, 'Because');
|
|
565
684
|
});
|
|
566
685
|
|
|
567
|
-
test('
|
|
568
|
-
const tmpDir =
|
|
569
|
-
const fsSync =
|
|
686
|
+
test('getRequestability returns "claimed" for a URL in a claimed job', async () => {
|
|
687
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-requestability-test';
|
|
688
|
+
const fsSync = await import('node:fs');
|
|
570
689
|
fsSync.mkdirSync(tmpDir + '/jobs/claimed', {recursive: true});
|
|
571
690
|
fsSync.mkdirSync(tmpDir + '/jobs/queue', {recursive: true});
|
|
572
691
|
fsSync.mkdirSync(tmpDir + '/jobs/failed', {recursive: true});
|
|
@@ -576,8 +695,8 @@ test('isRecommendable returns "claimed" for a URL in a claimed job', async () =>
|
|
|
576
695
|
const savedDbDir = process.env.DB_DIR;
|
|
577
696
|
process.env.DB_DIR = tmpDir;
|
|
578
697
|
try {
|
|
579
|
-
const {
|
|
580
|
-
const result = await
|
|
698
|
+
const {getRequestability} = await import('./util.ts');
|
|
699
|
+
const result = await getRequestability('https://example.com/test');
|
|
581
700
|
assert.equal(result, 'claimed');
|
|
582
701
|
}
|
|
583
702
|
finally {
|
|
@@ -586,9 +705,9 @@ test('isRecommendable returns "claimed" for a URL in a claimed job', async () =>
|
|
|
586
705
|
}
|
|
587
706
|
});
|
|
588
707
|
|
|
589
|
-
test('
|
|
590
|
-
const tmpDir =
|
|
591
|
-
const fsSync =
|
|
708
|
+
test('getRequestability returns "queued" for a URL in a queued job', async () => {
|
|
709
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-requestability-test';
|
|
710
|
+
const fsSync = await import('node:fs');
|
|
592
711
|
fsSync.mkdirSync(tmpDir + '/jobs/claimed', {recursive: true});
|
|
593
712
|
fsSync.mkdirSync(tmpDir + '/jobs/queue', {recursive: true});
|
|
594
713
|
fsSync.mkdirSync(tmpDir + '/jobs/failed', {recursive: true});
|
|
@@ -598,8 +717,8 @@ test('isRecommendable returns "queued" for a URL in a queued job', async () => {
|
|
|
598
717
|
const savedDbDir = process.env.DB_DIR;
|
|
599
718
|
process.env.DB_DIR = tmpDir;
|
|
600
719
|
try {
|
|
601
|
-
const {
|
|
602
|
-
const result = await
|
|
720
|
+
const {getRequestability} = await import('./util.ts');
|
|
721
|
+
const result = await getRequestability('https://example.com/test');
|
|
603
722
|
assert.equal(result, 'queued');
|
|
604
723
|
}
|
|
605
724
|
finally {
|
|
@@ -608,17 +727,17 @@ test('isRecommendable returns "queued" for a URL in a queued job', async () => {
|
|
|
608
727
|
}
|
|
609
728
|
});
|
|
610
729
|
|
|
611
|
-
test('
|
|
612
|
-
const tmpDir =
|
|
613
|
-
const fsSync =
|
|
730
|
+
test('getRequestability returns empty string for a URL with no matching jobs', async () => {
|
|
731
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-requestability-test';
|
|
732
|
+
const fsSync = await import('node:fs');
|
|
614
733
|
fsSync.mkdirSync(tmpDir + '/jobs/claimed', {recursive: true});
|
|
615
734
|
fsSync.mkdirSync(tmpDir + '/jobs/queue', {recursive: true});
|
|
616
735
|
fsSync.mkdirSync(tmpDir + '/jobs/failed', {recursive: true});
|
|
617
736
|
const savedDbDir = process.env.DB_DIR;
|
|
618
737
|
process.env.DB_DIR = tmpDir;
|
|
619
738
|
try {
|
|
620
|
-
const {
|
|
621
|
-
const result = await
|
|
739
|
+
const {getRequestability} = await import('./util.ts');
|
|
740
|
+
const result = await getRequestability('https://example.com/no-match');
|
|
622
741
|
assert.equal(result, '');
|
|
623
742
|
}
|
|
624
743
|
finally {
|
|
@@ -633,22 +752,22 @@ test('isURL returns false for a malformed URL', () => {
|
|
|
633
752
|
|
|
634
753
|
test('objectSort returns 0 for an unknown sort type', () => {
|
|
635
754
|
const items = [{name: 'a'}, {name: 'b'}];
|
|
636
|
-
const sorted = objectSort(items, 'name', 'unknownType');
|
|
755
|
+
const sorted = objectSort(items, 'name', 'unknownType' as any);
|
|
637
756
|
assert.equal(sorted.length, 2);
|
|
638
757
|
});
|
|
639
758
|
|
|
640
759
|
test('getReport returns an error for an invalid report', async () => {
|
|
641
|
-
const tmpDir =
|
|
642
|
-
const fsSync =
|
|
760
|
+
const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-invalid-report-test';
|
|
761
|
+
const fsSync = await import('node:fs');
|
|
643
762
|
fsSync.mkdirSync(tmpDir + '/reports', {recursive: true});
|
|
644
763
|
fsSync.writeFileSync(tmpDir + '/reports/260101T0000-bad.json', JSON.stringify({notValid: true}));
|
|
645
764
|
const savedDbDir = process.env.DB_DIR;
|
|
646
765
|
process.env.DB_DIR = tmpDir;
|
|
647
766
|
try {
|
|
648
|
-
const {getReport} =
|
|
649
|
-
const result = await getReport('260101T0000', 'bad');
|
|
767
|
+
const {getReport} = await import('./util.ts');
|
|
768
|
+
const result = await getReport('260101T0000', 'bad') as any;
|
|
650
769
|
assert.ok(result.error);
|
|
651
|
-
assert.ok(result.error.includes('
|
|
770
|
+
assert.ok(result.error.includes('not usable'));
|
|
652
771
|
}
|
|
653
772
|
finally {
|
|
654
773
|
process.env.DB_DIR = savedDbDir;
|
|
@@ -657,11 +776,27 @@ test('getReport returns an error for an invalid report', async () => {
|
|
|
657
776
|
});
|
|
658
777
|
|
|
659
778
|
test('getReportData returns an error for a nonexistent report', async () => {
|
|
660
|
-
const {getReportData} =
|
|
661
|
-
const result = await getReportData('990101T0000', 'xxx');
|
|
779
|
+
const {getReportData} = await import('./util.ts');
|
|
780
|
+
const result: any = await getReportData('990101T0000', 'xxx');
|
|
662
781
|
assert.ok(result.error);
|
|
663
782
|
});
|
|
664
783
|
|
|
784
|
+
test('getReportData falls back to the engine ID for an unknown prevented engine', async () => {
|
|
785
|
+
const {getReportData} = await import('./util.ts');
|
|
786
|
+
const prvJSON = await fs.readFile(path.join(reportsPath(), '260101T0006-prv.json'), 'utf8');
|
|
787
|
+
const report = JSON.parse(prvJSON);
|
|
788
|
+
report.jobData.preventions.unknownEngine = 'mystery failure';
|
|
789
|
+
const reportPath = path.join(reportsPath(), '260103T0000-unk.json');
|
|
790
|
+
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
791
|
+
try {
|
|
792
|
+
const result: any = await getReportData('260103T0000', 'unk');
|
|
793
|
+
assert.ok(result.preventedEngineNames.includes('unknownEngine'));
|
|
794
|
+
}
|
|
795
|
+
finally {
|
|
796
|
+
await fs.unlink(reportPath);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
|
|
665
800
|
// TESTS FOR REMAINING BRANCH COVERAGE IN util.js
|
|
666
801
|
|
|
667
802
|
test('getAgoDays returns null for an invalid Date object', () => {
|
|
@@ -685,15 +820,15 @@ test('getIssue returns an issue ID for a variable rule pattern match', () => {
|
|
|
685
820
|
assert.equal(result, 'duplicateAttribute');
|
|
686
821
|
});
|
|
687
822
|
|
|
688
|
-
test('
|
|
689
|
-
const {getDateTimeString} =
|
|
823
|
+
test('getDateTimeString describes an invalid time stamp as unknown', async () => {
|
|
824
|
+
const {getDateTimeString} = await import('./util.ts');
|
|
690
825
|
const result = getDateTimeString('999999T9999');
|
|
691
|
-
assert.
|
|
826
|
+
assert.equal(result, 'an unknown date at an unknown time');
|
|
692
827
|
});
|
|
693
828
|
|
|
694
829
|
test('getPOSTData resolves with parsed JSON for application/json requests', async () => {
|
|
695
|
-
const {Readable} =
|
|
696
|
-
const {getPOSTData} =
|
|
830
|
+
const {Readable} = await import('node:stream');
|
|
831
|
+
const {getPOSTData} = await import('./util.ts');
|
|
697
832
|
const body = JSON.stringify({target: 'Page', why: 'Because'});
|
|
698
833
|
const req = Object.assign(new Readable({
|
|
699
834
|
read() {
|
|
@@ -701,13 +836,13 @@ test('getPOSTData resolves with parsed JSON for application/json requests', asyn
|
|
|
701
836
|
this.push(null);
|
|
702
837
|
}
|
|
703
838
|
}), {headers: {'content-type': 'application/json'}});
|
|
704
|
-
const result = await getPOSTData(req);
|
|
839
|
+
const result: any = await getPOSTData(req as any);
|
|
705
840
|
assert.equal(result.target, 'Page');
|
|
706
841
|
});
|
|
707
842
|
|
|
708
843
|
test('getPOSTData resolves with parsed query for body-type form-urlencoded requests', async () => {
|
|
709
|
-
const {Readable} =
|
|
710
|
-
const {getPOSTData} =
|
|
844
|
+
const {Readable} = await import('node:stream');
|
|
845
|
+
const {getPOSTData} = await import('./util.ts');
|
|
711
846
|
const body = 'target=Page&why=Because';
|
|
712
847
|
const req = Object.assign(new Readable({
|
|
713
848
|
read() {
|
|
@@ -715,131 +850,93 @@ test('getPOSTData resolves with parsed query for body-type form-urlencoded reque
|
|
|
715
850
|
this.push(null);
|
|
716
851
|
}
|
|
717
852
|
}), {headers: {'body-type': 'application/x-www-form-urlencoded'}});
|
|
718
|
-
const result = await getPOSTData(req);
|
|
853
|
+
const result: any = await getPOSTData(req as any);
|
|
719
854
|
assert.equal(result.target, 'Page');
|
|
720
855
|
assert.equal(result.why, 'Because');
|
|
721
856
|
});
|
|
722
857
|
|
|
723
858
|
test('getPOSTData resolves with null for an unknown content type', async () => {
|
|
724
|
-
const {Readable} =
|
|
725
|
-
const {getPOSTData} =
|
|
859
|
+
const {Readable} = await import('node:stream');
|
|
860
|
+
const {getPOSTData} = await import('./util.ts');
|
|
726
861
|
const req = Object.assign(new Readable({
|
|
727
862
|
read() {
|
|
728
863
|
this.push(Buffer.from('data'));
|
|
729
864
|
this.push(null);
|
|
730
865
|
}
|
|
731
866
|
}), {headers: {}});
|
|
732
|
-
const result = await getPOSTData(req);
|
|
867
|
+
const result: any = await getPOSTData(req as any);
|
|
868
|
+
assert.equal(result, null);
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
test('getPOSTData resolves with null for a malformed JSON body', async () => {
|
|
872
|
+
const {Readable} = await import('node:stream');
|
|
873
|
+
const {getPOSTData} = await import('./util.ts');
|
|
874
|
+
const req = Object.assign(new Readable({
|
|
875
|
+
read() {
|
|
876
|
+
this.push(Buffer.from('{not valid json'));
|
|
877
|
+
this.push(null);
|
|
878
|
+
}
|
|
879
|
+
}), {headers: {'content-type': 'application/json'}});
|
|
880
|
+
const result: any = await getPOSTData(req as any);
|
|
733
881
|
assert.equal(result, null);
|
|
734
882
|
});
|
|
735
883
|
|
|
736
|
-
test('getEngineNamesString falls back to the engine ID for an unknown engine', () => {
|
|
737
|
-
const {getEngineNamesString} =
|
|
884
|
+
test('getEngineNamesString falls back to the engine ID for an unknown engine', async () => {
|
|
885
|
+
const {getEngineNamesString} = await import('./util.ts');
|
|
738
886
|
const result = getEngineNamesString(new Set(['unknownEngine']));
|
|
739
887
|
assert.equal(result, 'unknownEngine');
|
|
740
888
|
});
|
|
741
889
|
|
|
742
|
-
test('getPathID returns the catalog pathID when catalogIndex is truthy', () => {
|
|
743
|
-
const {getPathID} =
|
|
744
|
-
const catalog = {'0': {pathID: '/html/body/div'}};
|
|
890
|
+
test('getPathID returns the catalog pathID when catalogIndex is truthy', async () => {
|
|
891
|
+
const {getPathID} = await import('./util.ts');
|
|
892
|
+
const catalog = {'0': {tagName: 'div', pathID: '/html/body/div'}};
|
|
745
893
|
assert.equal(getPathID(catalog, '0', '/fallback'), '/html/body/div');
|
|
746
894
|
});
|
|
747
895
|
|
|
748
|
-
test('getPathID returns the fallback pathID when catalogIndex is truthy but catalogItem has no pathID', () => {
|
|
749
|
-
const {getPathID} =
|
|
750
|
-
const catalog = {'0': {tagName: 'div'}};
|
|
896
|
+
test('getPathID returns the fallback pathID when catalogIndex is truthy but catalogItem has no pathID', async () => {
|
|
897
|
+
const {getPathID} = await import('./util.ts');
|
|
898
|
+
const catalog = {'0': {tagName: 'div', pathID: ''}};
|
|
751
899
|
assert.equal(getPathID(catalog, '0', '/fallback'), '/fallback');
|
|
752
900
|
});
|
|
753
901
|
|
|
754
|
-
test('getPathID returns /html when catalogIndex is truthy but catalogItem and pathID are both missing', () => {
|
|
755
|
-
const {getPathID} =
|
|
902
|
+
test('getPathID returns /html when catalogIndex is truthy but catalogItem and pathID are both missing', async () => {
|
|
903
|
+
const {getPathID} = await import('./util.ts');
|
|
756
904
|
const catalog = {};
|
|
757
|
-
assert.equal(getPathID(catalog, '0', null), '/html');
|
|
905
|
+
assert.equal(getPathID(catalog, '0', null as any), '/html');
|
|
758
906
|
});
|
|
759
907
|
|
|
760
|
-
test('getPathID returns /html when catalogIndex is falsy and pathID is null', () => {
|
|
761
|
-
const {getPathID} =
|
|
762
|
-
assert.equal(getPathID({}, null, null), '/html');
|
|
908
|
+
test('getPathID returns /html when catalogIndex is falsy and pathID is null', async () => {
|
|
909
|
+
const {getPathID} = await import('./util.ts');
|
|
910
|
+
assert.equal(getPathID({}, null as any, null as any), '/html');
|
|
763
911
|
});
|
|
764
912
|
|
|
765
|
-
test('
|
|
766
|
-
const {
|
|
913
|
+
test('isUsableReport returns false for a report with a test act using an unknown engine', async () => {
|
|
914
|
+
const {isUsableReport} = await import('./util.ts');
|
|
767
915
|
const report = {
|
|
768
916
|
target: {what: 'Test', url: 'https://example.com'},
|
|
769
917
|
acts: [{type: 'test', which: 'unknownEngine'}],
|
|
770
918
|
jobData: {endTime: '26-01-01T00:00'},
|
|
771
919
|
catalog: {}
|
|
772
920
|
};
|
|
773
|
-
assert.equal(
|
|
921
|
+
assert.equal(isUsableReport(report), false);
|
|
774
922
|
});
|
|
775
923
|
|
|
776
|
-
test('
|
|
777
|
-
const {
|
|
924
|
+
test('isUsableReport returns true for a report with a non-test act', async () => {
|
|
925
|
+
const {isUsableReport} = await import('./util.ts');
|
|
778
926
|
const report = {
|
|
779
927
|
target: {what: 'Test', url: 'https://example.com'},
|
|
780
928
|
acts: [{type: 'other'}],
|
|
781
929
|
jobData: {endTime: '26-01-01T00:00'},
|
|
782
930
|
catalog: {}
|
|
783
931
|
};
|
|
784
|
-
assert.equal(
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
test('annotateReport handles a test act with no standardResult instances', async () => {
|
|
788
|
-
const tmpDir = require('node:os').tmpdir() + '/kilotest-no-instances-test';
|
|
789
|
-
const fsSync = require('node:fs');
|
|
790
|
-
fsSync.mkdirSync(tmpDir + '/reports', {recursive: true});
|
|
791
|
-
const report = {
|
|
792
|
-
target: {what: 'Test', url: 'https://example.com'},
|
|
793
|
-
acts: [
|
|
794
|
-
{type: 'test', which: 'axe', result: {standardResult: {instances: []}}},
|
|
795
|
-
{type: 'other'}
|
|
796
|
-
],
|
|
797
|
-
jobData: {endTime: '26-01-01T00:00'},
|
|
798
|
-
catalog: {}
|
|
799
|
-
};
|
|
800
|
-
fsSync.writeFileSync(tmpDir + '/reports/260101T0000-ni.json', JSON.stringify(report));
|
|
801
|
-
const savedDbDir = process.env.DB_DIR;
|
|
802
|
-
process.env.DB_DIR = tmpDir;
|
|
803
|
-
try {
|
|
804
|
-
const result = await annotateReport('260101T0000', 'ni');
|
|
805
|
-
assert.equal(result, '');
|
|
806
|
-
}
|
|
807
|
-
finally {
|
|
808
|
-
process.env.DB_DIR = savedDbDir;
|
|
809
|
-
fsSync.rmSync(tmpDir, {recursive: true});
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
test('annotateReport handles a test act with no standardResult', async () => {
|
|
814
|
-
const tmpDir = require('node:os').tmpdir() + '/kilotest-no-standard-test';
|
|
815
|
-
const fsSync = require('node:fs');
|
|
816
|
-
fsSync.mkdirSync(tmpDir + '/reports', {recursive: true});
|
|
817
|
-
const report = {
|
|
818
|
-
target: {what: 'Test', url: 'https://example.com'},
|
|
819
|
-
acts: [
|
|
820
|
-
{type: 'test', which: 'axe', result: {}}
|
|
821
|
-
],
|
|
822
|
-
jobData: {endTime: '26-01-01T00:00'},
|
|
823
|
-
catalog: {}
|
|
824
|
-
};
|
|
825
|
-
fsSync.writeFileSync(tmpDir + '/reports/260101T0000-ns.json', JSON.stringify(report));
|
|
826
|
-
const savedDbDir = process.env.DB_DIR;
|
|
827
|
-
process.env.DB_DIR = tmpDir;
|
|
828
|
-
try {
|
|
829
|
-
const result = await annotateReport('260101T0000', 'ns');
|
|
830
|
-
assert.equal(result, '');
|
|
831
|
-
}
|
|
832
|
-
finally {
|
|
833
|
-
process.env.DB_DIR = savedDbDir;
|
|
834
|
-
fsSync.rmSync(tmpDir, {recursive: true});
|
|
835
|
-
}
|
|
932
|
+
assert.equal(isUsableReport(report), true);
|
|
836
933
|
});
|
|
837
934
|
|
|
838
935
|
// UNIT TESTS FOR PREVIOUSLY UNTESTED EXPORTED FUNCTIONS
|
|
839
936
|
|
|
840
937
|
test('createLock returns a function that runs tasks sequentially', async () => {
|
|
841
938
|
const lock = createLock();
|
|
842
|
-
const order = [];
|
|
939
|
+
const order: string[] = [];
|
|
843
940
|
const p1 = lock(async () => {
|
|
844
941
|
order.push('start 1');
|
|
845
942
|
await new Promise(r => setTimeout(r, 10));
|
|
@@ -876,34 +973,38 @@ test('getTimeStamp returns an 11-character stamp from a Date', () => {
|
|
|
876
973
|
assert.equal(stamp.slice(7), '1430');
|
|
877
974
|
});
|
|
878
975
|
|
|
879
|
-
test('
|
|
880
|
-
const result =
|
|
976
|
+
test('getEngineNamesString returns a sorted +-delimited list of engine names', () => {
|
|
977
|
+
const result = getEngineNamesString(new Set(['axe', 'wave', 'nuVal']));
|
|
881
978
|
const names = result.split(' + ');
|
|
882
979
|
assert.ok(names.length === 3);
|
|
883
980
|
assert.ok(names.includes('WAVE'));
|
|
884
981
|
});
|
|
885
982
|
|
|
886
|
-
test('
|
|
887
|
-
assert.equal(
|
|
983
|
+
test('getEngineNamesString falls back to the ID for an unknown engine', () => {
|
|
984
|
+
assert.equal(getEngineNamesString(new Set(['unknownEngine'])), 'unknownEngine');
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
test('testRequestsLock is a function (the lock returned by createLock)', () => {
|
|
988
|
+
assert.equal(typeof testRequestsLock, 'function');
|
|
888
989
|
});
|
|
889
990
|
|
|
890
|
-
test('
|
|
891
|
-
await fs.writeFile(
|
|
892
|
-
const result = await
|
|
991
|
+
test('addTestRequest adds a request and returns success', async () => {
|
|
992
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
993
|
+
const result = await addTestRequest('Test Page', 'https://example.com/test', 'because');
|
|
893
994
|
assert.equal(result.error, undefined);
|
|
894
|
-
const
|
|
895
|
-
assert.ok(
|
|
896
|
-
assert.equal(
|
|
897
|
-
assert.equal(
|
|
898
|
-
await fs.writeFile(
|
|
995
|
+
const testRequests = JSON.parse(await fs.readFile(testRequestsPath(), 'utf8'));
|
|
996
|
+
assert.ok(testRequests['https://example.com/test']);
|
|
997
|
+
assert.equal(testRequests['https://example.com/test'].length, 1);
|
|
998
|
+
assert.equal(testRequests['https://example.com/test'][0].description, 'Test Page');
|
|
999
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
899
1000
|
});
|
|
900
1001
|
|
|
901
|
-
test('
|
|
902
|
-
await fs.writeFile(
|
|
903
|
-
await
|
|
904
|
-
const result = await
|
|
1002
|
+
test('addTestRequest returns a duplicate error for a repeated request', async () => {
|
|
1003
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
1004
|
+
await addTestRequest('Test Page', 'https://example.com/test', 'because');
|
|
1005
|
+
const result = await addTestRequest('Test Page', 'https://example.com/test', 'another reason');
|
|
905
1006
|
assert.equal(result.error, 'duplicate');
|
|
906
|
-
await fs.writeFile(
|
|
1007
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
907
1008
|
});
|
|
908
1009
|
|
|
909
1010
|
test('getReportPath returns the path of a report file', () => {
|
|
@@ -913,6 +1014,7 @@ test('getReportPath returns the path of a report file', () => {
|
|
|
913
1014
|
|
|
914
1015
|
test('getReportStats returns reportTime and reportSize for a valid report', async () => {
|
|
915
1016
|
const stats = await getReportStats('260101T0000', 'mix');
|
|
1017
|
+
assert.ok(stats);
|
|
916
1018
|
assert.ok(stats.reportTime instanceof Date);
|
|
917
1019
|
assert.equal(typeof stats.reportSize, 'number');
|
|
918
1020
|
assert.ok(stats.reportSize > 0);
|
|
@@ -923,27 +1025,18 @@ test('getReportStats returns null for a nonexistent report', async () => {
|
|
|
923
1025
|
assert.equal(stats, null);
|
|
924
1026
|
});
|
|
925
1027
|
|
|
926
|
-
test('isHidden returns false for a non-hidden report', async () => {
|
|
927
|
-
const result = await isHidden('260101T0000', 'mix');
|
|
928
|
-
assert.equal(result, false);
|
|
929
|
-
});
|
|
930
|
-
|
|
931
|
-
test('isHidden returns true for a hidden report', async () => {
|
|
932
|
-
const result = await isHidden('260101T0007', 'hid');
|
|
933
|
-
assert.equal(result, true);
|
|
934
|
-
});
|
|
935
1028
|
|
|
936
1029
|
test('getReportExtract returns an extract for a valid report', async () => {
|
|
937
|
-
const extract = await getReportExtract('260101T0000', 'mix');
|
|
1030
|
+
const extract = await getReportExtract('260101T0000', 'mix') as any;
|
|
938
1031
|
assert.equal(extract.timeStamp, '260101T0000');
|
|
939
1032
|
assert.equal(extract.jobID, 'mix');
|
|
940
|
-
assert.equal(extract.
|
|
1033
|
+
assert.equal(extract.description, 'Mixed Outcomes Page');
|
|
941
1034
|
assert.equal(extract.url, 'https://example.com/mixed');
|
|
942
1035
|
assert.ok(extract.reportTime);
|
|
943
1036
|
});
|
|
944
1037
|
|
|
945
1038
|
test('getReportExtract returns an error for a nonexistent report', async () => {
|
|
946
|
-
const extract = await getReportExtract('999999T9999', 'xxx');
|
|
1039
|
+
const extract = await getReportExtract('999999T9999', 'xxx') as any;
|
|
947
1040
|
assert.ok(extract.error);
|
|
948
1041
|
});
|
|
949
1042
|
|
|
@@ -955,11 +1048,11 @@ test('getReportExtracts returns extracts of all available reports', async () =>
|
|
|
955
1048
|
assert.ok(ids.includes('260101T0001-ct'));
|
|
956
1049
|
});
|
|
957
1050
|
|
|
958
|
-
test('
|
|
959
|
-
const latest = await
|
|
960
|
-
const mixReports = latest.filter(e => e.
|
|
1051
|
+
test('getReportExtracts with onlyLatest returns only the latest report for each page', async () => {
|
|
1052
|
+
const latest = await getReportExtracts(true);
|
|
1053
|
+
const mixReports = latest.filter(e => e.description === 'Mixed Outcomes Page');
|
|
961
1054
|
assert.equal(mixReports.length, 1);
|
|
962
|
-
assert.equal(mixReports[0]
|
|
1055
|
+
assert.equal(mixReports[0]!.timeStamp, '260202T0000');
|
|
963
1056
|
});
|
|
964
1057
|
|
|
965
1058
|
test('getMultiReportWhats returns descriptions that have multiple reports', async () => {
|