@jrpool/kilotest 46.0.8 → 46.0.11

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.
Files changed (51) hide show
  1. package/.devin/config.local.json +3 -1
  2. package/.github/workflows/{smoke-test.yml → ci.yml} +8 -2
  3. package/alerts.test.js +137 -0
  4. package/api/listDiagnoses.test.js +44 -0
  5. package/api/listIssues.js +1 -1
  6. package/api/listIssues.test.js +10 -0
  7. package/api/listReports.test.js +6 -2
  8. package/api/listViolators.test.js +35 -0
  9. package/api/requestTest.test.js +7 -0
  10. package/api/routes.test.js +63 -0
  11. package/api/util.js +8 -10
  12. package/api/util.test.js +68 -2
  13. package/apiIntegration.test.js +13 -3
  14. package/docs/{Smoke Test.json → CI.json} +4 -3
  15. package/index.js +64 -77
  16. package/index.test.js +1152 -0
  17. package/indexBalances.test.js +188 -0
  18. package/indexFallback.test.js +33 -0
  19. package/indexServe.test.js +59 -0
  20. package/indexWorkers.test.js +79 -0
  21. package/mcp.js +2 -2
  22. package/mcp.test.js +281 -0
  23. package/package.json +10 -5
  24. package/routing.test.js +13 -3
  25. package/test/fixtures/buildFixtures.js +83 -0
  26. package/test/fixtures/db/reports/260101T0008-mul.json +137 -0
  27. package/test/fixtures/db/reports/260101T0009-brd.json +147 -0
  28. package/test/setup.cjs +14 -0
  29. package/util.js +16 -6
  30. package/util.test.js +886 -3
  31. package/web/ai0BalanceForm/ai0BalanceForm.test.js +88 -0
  32. package/web/expungeReportsForm/expungeReportsForm.test.js +114 -0
  33. package/web/hideReportForm/hideReportForm.test.js +82 -0
  34. package/web/listDiagnoses/listDiagnoses.test.js +160 -0
  35. package/web/listIssues/listIssues.test.js +109 -0
  36. package/web/listReports/listReports.test.js +139 -3
  37. package/web/listRules/listRules.test.js +103 -0
  38. package/web/listTopIssues/listTopIssues.test.js +126 -0
  39. package/web/listViolators/index.js +1 -2
  40. package/web/listViolators/listViolators.test.js +256 -0
  41. package/web/pruneReportsForm/pruneReportsForm.test.js +86 -0
  42. package/web/reannotate/reannotate.test.js +144 -0
  43. package/web/reannotateForm/reannotateForm.test.js +134 -0
  44. package/web/renewWCAG/renewWCAG.test.js +124 -0
  45. package/web/requestRetest/requestRetest.test.js +119 -0
  46. package/web/requestRetestForm/requestRetestForm.test.js +62 -0
  47. package/web/requestTest/requestTest.test.js +115 -0
  48. package/web/requestTestForm/requestTestForm.test.js +40 -0
  49. package/web/rewindReportsForm/rewindReportsForm.test.js +86 -0
  50. package/web/tutorial/tutorial.test.js +97 -0
  51. package/web/unhideReportForm/unhideReportForm.test.js +135 -0
package/routing.test.js CHANGED
@@ -23,7 +23,7 @@ let server;
23
23
  before(async () => {
24
24
  process.env.DB_DIR = path.join(__dirname, 'test', 'fixtures', 'db');
25
25
  server = http.createServer(requestHandler);
26
- await new Promise(resolve => server.listen(port, resolve));
26
+ await new Promise(resolve => server.listen(port, () => resolve()));
27
27
  });
28
28
 
29
29
  after(async () => {
@@ -33,7 +33,17 @@ after(async () => {
33
33
  else {
34
34
  delete process.env.DB_DIR;
35
35
  }
36
- await new Promise(resolve => server.close(() => resolve()));
36
+ server.closeAllConnections?.();
37
+ await new Promise(resolve => {
38
+ const timer = setTimeout(() => {
39
+ server.closeAllConnections?.();
40
+ resolve();
41
+ }, 1000);
42
+ server.close(() => {
43
+ clearTimeout(timer);
44
+ resolve();
45
+ });
46
+ });
37
47
  });
38
48
 
39
49
  // HELPERS
@@ -73,5 +83,5 @@ test('GET /api/listReports returns a valid JSON response', async () => {
73
83
  const body = JSON.parse(result.body);
74
84
  assert.equal(body['tool name'], 'listReports');
75
85
  const reports = body['response content']['basics about all available reports'];
76
- assert.equal(reports.length, 6);
86
+ assert.equal(reports.length, 8);
77
87
  });
@@ -233,6 +233,89 @@ const main = async () => {
233
233
  hiddenReport
234
234
  );
235
235
 
236
+ // Fixture 8: multiViolator. Three violators for the same issue: two with 1 reporter each (exercising the catalogIndex tiebreaker) and one with 2 reporters (exercising the reporter-count descending sort).
237
+ const multiViolatorCatalog = {
238
+ '0': catalogItem('A', 'Home', '/html/body/a[1]', '10:10:40:20'),
239
+ '1': catalogItem('A', 'Contact', '/html/body/a[2]', '10:40:40:20'),
240
+ '2': catalogItem('A', 'Help', '/html/body/a[3]', '10:70:40:20')
241
+ };
242
+ const multiViolatorActs = [
243
+ testAct(engineAxe, [
244
+ instance('r11', 'The link does not have an accessible name', 'failed',
245
+ issueLinkNoText, 0, 2, 1),
246
+ instance('r11', 'The link does not have an accessible name', 'failed',
247
+ issueLinkNoText, 2, 2, 1)
248
+ ]),
249
+ testAct(engineAlfa, [
250
+ instance('r11', 'The link does not have an accessible name', 'failed',
251
+ issueLinkNoText, 1, 2, 1),
252
+ instance('r11', 'The link does not have an accessible name', 'failed',
253
+ issueLinkNoText, 2, 2, 1)
254
+ ])
255
+ ];
256
+ await writeJSON(
257
+ path.join(reportsDir, '260101T0008-mul.json'),
258
+ report('260101T0008-mul', 'Multi Violator Page',
259
+ 'https://example.com/multiviolator', multiViolatorActs, multiViolatorCatalog)
260
+ );
261
+
262
+ // Fixture 9: branchCoverage. Covers remaining branch gaps in listDiagnoses and listIssues: an issue with a short WCAG code (guideline layer), a catalog item with no tagName or text, an instance where ruleID equals what and count is missing, a test act with null instances, and an instance with no issueID.
263
+ const issueDuplicateID = 'duplicateID';
264
+ const branchCatalog = {
265
+ '0': catalogItem('DIV', '', '/html/body/div[1]', '0:0:100:50'),
266
+ '1': {id: '', startTag: '<div>', text: null, textLinkable: false, boxID: '5:5:90:40', pathID: '/html/body/div[2]', headingIndex: '', checkpoint: 0},
267
+ '2': {id: '', text: 'No tags', textLinkable: false, headingIndex: '', checkpoint: 0}
268
+ };
269
+ const branchActs = [
270
+ testAct(engineAxe, [
271
+ instance('r99', 'r99', 'failed',
272
+ issueDuplicateID, 0, 2, 1),
273
+ {
274
+ ruleID: 'r100',
275
+ what: 'Element has no role',
276
+ ordinalSeverity: 1,
277
+ outcome: 'failed',
278
+ catalogIndex: '1',
279
+ checkpoint: 0,
280
+ issueID: issueDuplicateID
281
+ },
282
+ instance('r101', 'Duplicate ID found', 'failed',
283
+ issueDuplicateID, 2, 2, 1),
284
+ instance('r102', 'Duplicate ID in orphan', 'failed',
285
+ issueDuplicateID, 3, 2, 1)
286
+ ]),
287
+ {
288
+ type: 'test',
289
+ which: engineAlfa,
290
+ startTime: '26-01-01T00:00',
291
+ endTime: '26-01-01T00:01',
292
+ result: {
293
+ standardResult: {}
294
+ }
295
+ },
296
+ testAct(engineIbm, [
297
+ {
298
+ ruleID: 'r50',
299
+ what: 'Link has no text',
300
+ ordinalSeverity: 2,
301
+ outcome: 'failed',
302
+ catalogIndex: '0',
303
+ checkpoint: 0
304
+ }
305
+ ])
306
+ ];
307
+ await writeJSON(
308
+ path.join(reportsDir, '260101T0009-brd.json'),
309
+ report('260101T0009-brd', 'Branch Coverage Page',
310
+ 'https://example.com/branch', branchActs, branchCatalog)
311
+ );
312
+ // Remove preventions from the brd fixture to cover the preventions ?? {} branch in listIssues.
313
+ const brdReport = JSON.parse(
314
+ await fs.readFile(path.join(reportsDir, '260101T0009-brd.json'), 'utf8')
315
+ );
316
+ delete brdReport.jobData.preventions;
317
+ await writeJSON(path.join(reportsDir, '260101T0009-brd.json'), brdReport);
318
+
236
319
  // Write an empty recs.json so getRecs does not try to create one.
237
320
  await writeJSON(path.join(targetDir, 'jobs', 'recs.json'), {});
238
321
 
@@ -0,0 +1,137 @@
1
+ {
2
+ "id": "260101T0008-mul",
3
+ "what": "Multi Violator Page",
4
+ "strict": false,
5
+ "standard": "only",
6
+ "device": {
7
+ "id": "default"
8
+ },
9
+ "browserID": "chromium",
10
+ "creationTimeStamp": "260101T0008",
11
+ "executionTimeStamp": "260101T0008",
12
+ "target": {
13
+ "what": "Multi Violator Page",
14
+ "url": "https://example.com/multiviolator"
15
+ },
16
+ "sources": {
17
+ "worker": "test-worker"
18
+ },
19
+ "acts": [
20
+ {
21
+ "type": "test",
22
+ "which": "axe",
23
+ "startTime": "26-01-01T00:00",
24
+ "endTime": "26-01-01T00:01",
25
+ "result": {
26
+ "standardResult": {
27
+ "instances": [
28
+ {
29
+ "ruleID": "r11",
30
+ "what": "The link does not have an accessible name",
31
+ "ordinalSeverity": 2,
32
+ "outcome": "failed",
33
+ "count": 1,
34
+ "catalogIndex": "0",
35
+ "checkpoint": 0,
36
+ "issueID": "linkNoText"
37
+ },
38
+ {
39
+ "ruleID": "r11",
40
+ "what": "The link does not have an accessible name",
41
+ "ordinalSeverity": 2,
42
+ "outcome": "failed",
43
+ "count": 1,
44
+ "catalogIndex": "2",
45
+ "checkpoint": 0,
46
+ "issueID": "linkNoText"
47
+ }
48
+ ],
49
+ "outcomeTotals": {
50
+ "failed": 2,
51
+ "cantTell": 0
52
+ }
53
+ }
54
+ }
55
+ },
56
+ {
57
+ "type": "test",
58
+ "which": "alfa",
59
+ "startTime": "26-01-01T00:00",
60
+ "endTime": "26-01-01T00:01",
61
+ "result": {
62
+ "standardResult": {
63
+ "instances": [
64
+ {
65
+ "ruleID": "r11",
66
+ "what": "The link does not have an accessible name",
67
+ "ordinalSeverity": 2,
68
+ "outcome": "failed",
69
+ "count": 1,
70
+ "catalogIndex": "1",
71
+ "checkpoint": 0,
72
+ "issueID": "linkNoText"
73
+ },
74
+ {
75
+ "ruleID": "r11",
76
+ "what": "The link does not have an accessible name",
77
+ "ordinalSeverity": 2,
78
+ "outcome": "failed",
79
+ "count": 1,
80
+ "catalogIndex": "2",
81
+ "checkpoint": 0,
82
+ "issueID": "linkNoText"
83
+ }
84
+ ],
85
+ "outcomeTotals": {
86
+ "failed": 2,
87
+ "cantTell": 0
88
+ }
89
+ }
90
+ }
91
+ }
92
+ ],
93
+ "jobData": {
94
+ "startTime": "26-01-01T00:00",
95
+ "endTime": "26-01-01T00:10",
96
+ "elapsedSeconds": 600,
97
+ "preventions": {},
98
+ "issuelessRules": []
99
+ },
100
+ "catalog": {
101
+ "0": {
102
+ "tagName": "A",
103
+ "id": "",
104
+ "startTag": "<a>",
105
+ "text": "Home",
106
+ "textLinkable": false,
107
+ "boxID": "10:10:40:20",
108
+ "pathID": "/html/body/a[1]",
109
+ "headingIndex": "",
110
+ "checkpoint": 0
111
+ },
112
+ "1": {
113
+ "tagName": "A",
114
+ "id": "",
115
+ "startTag": "<a>",
116
+ "text": "Contact",
117
+ "textLinkable": false,
118
+ "boxID": "10:40:40:20",
119
+ "pathID": "/html/body/a[2]",
120
+ "headingIndex": "",
121
+ "checkpoint": 0
122
+ },
123
+ "2": {
124
+ "tagName": "A",
125
+ "id": "",
126
+ "startTag": "<a>",
127
+ "text": "Help",
128
+ "textLinkable": false,
129
+ "boxID": "10:70:40:20",
130
+ "pathID": "/html/body/a[3]",
131
+ "headingIndex": "",
132
+ "checkpoint": 0
133
+ }
134
+ },
135
+ "images": {},
136
+ "checkpoints": []
137
+ }
@@ -0,0 +1,147 @@
1
+ {
2
+ "id": "260101T0009-brd",
3
+ "what": "Branch Coverage Page",
4
+ "strict": false,
5
+ "standard": "only",
6
+ "device": {
7
+ "id": "default"
8
+ },
9
+ "browserID": "chromium",
10
+ "creationTimeStamp": "260101T0009",
11
+ "executionTimeStamp": "260101T0009",
12
+ "target": {
13
+ "what": "Branch Coverage Page",
14
+ "url": "https://example.com/branch"
15
+ },
16
+ "sources": {
17
+ "worker": "test-worker"
18
+ },
19
+ "acts": [
20
+ {
21
+ "type": "test",
22
+ "which": "axe",
23
+ "startTime": "26-01-01T00:00",
24
+ "endTime": "26-01-01T00:01",
25
+ "result": {
26
+ "standardResult": {
27
+ "instances": [
28
+ {
29
+ "ruleID": "r99",
30
+ "what": "r99",
31
+ "ordinalSeverity": 2,
32
+ "outcome": "failed",
33
+ "count": 1,
34
+ "catalogIndex": "0",
35
+ "checkpoint": 0,
36
+ "issueID": "duplicateID"
37
+ },
38
+ {
39
+ "ruleID": "r100",
40
+ "what": "Element has no role",
41
+ "ordinalSeverity": 1,
42
+ "outcome": "failed",
43
+ "catalogIndex": "1",
44
+ "checkpoint": 0,
45
+ "issueID": "duplicateID"
46
+ },
47
+ {
48
+ "ruleID": "r101",
49
+ "what": "Duplicate ID found",
50
+ "ordinalSeverity": 2,
51
+ "outcome": "failed",
52
+ "count": 1,
53
+ "catalogIndex": "2",
54
+ "checkpoint": 0,
55
+ "issueID": "duplicateID"
56
+ },
57
+ {
58
+ "ruleID": "r102",
59
+ "what": "Duplicate ID in orphan",
60
+ "ordinalSeverity": 2,
61
+ "outcome": "failed",
62
+ "count": 1,
63
+ "catalogIndex": "3",
64
+ "checkpoint": 0,
65
+ "issueID": "duplicateID"
66
+ }
67
+ ],
68
+ "outcomeTotals": {
69
+ "failed": 4,
70
+ "cantTell": 0
71
+ }
72
+ }
73
+ }
74
+ },
75
+ {
76
+ "type": "test",
77
+ "which": "alfa",
78
+ "startTime": "26-01-01T00:00",
79
+ "endTime": "26-01-01T00:01",
80
+ "result": {
81
+ "standardResult": {}
82
+ }
83
+ },
84
+ {
85
+ "type": "test",
86
+ "which": "ibm",
87
+ "startTime": "26-01-01T00:00",
88
+ "endTime": "26-01-01T00:01",
89
+ "result": {
90
+ "standardResult": {
91
+ "instances": [
92
+ {
93
+ "ruleID": "r50",
94
+ "what": "Link has no text",
95
+ "ordinalSeverity": 2,
96
+ "outcome": "failed",
97
+ "catalogIndex": "0",
98
+ "checkpoint": 0
99
+ }
100
+ ],
101
+ "outcomeTotals": {
102
+ "failed": 1,
103
+ "cantTell": 0
104
+ }
105
+ }
106
+ }
107
+ }
108
+ ],
109
+ "jobData": {
110
+ "startTime": "26-01-01T00:00",
111
+ "endTime": "26-01-01T00:10",
112
+ "elapsedSeconds": 600,
113
+ "issuelessRules": []
114
+ },
115
+ "catalog": {
116
+ "0": {
117
+ "tagName": "DIV",
118
+ "id": "",
119
+ "startTag": "<div>",
120
+ "text": "",
121
+ "textLinkable": false,
122
+ "boxID": "0:0:100:50",
123
+ "pathID": "/html/body/div[1]",
124
+ "headingIndex": "",
125
+ "checkpoint": 0
126
+ },
127
+ "1": {
128
+ "id": "",
129
+ "startTag": "<div>",
130
+ "text": null,
131
+ "textLinkable": false,
132
+ "boxID": "5:5:90:40",
133
+ "pathID": "/html/body/div[2]",
134
+ "headingIndex": "",
135
+ "checkpoint": 0
136
+ },
137
+ "2": {
138
+ "id": "",
139
+ "text": "No tags",
140
+ "textLinkable": false,
141
+ "headingIndex": "",
142
+ "checkpoint": 0
143
+ }
144
+ },
145
+ "images": {},
146
+ "checkpoints": []
147
+ }
package/test/setup.cjs ADDED
@@ -0,0 +1,14 @@
1
+ /*
2
+ test/setup.cjs
3
+ Clears alert configuration env vars before any modules are required,
4
+ so that tests calling sendAlert indirectly never trigger real email alerts.
5
+ This file is loaded via --require before any test file runs.
6
+
7
+ The env vars are set to empty strings rather than deleted, because
8
+ dotenv does not override existing env vars. This prevents the .env
9
+ file loaded by index.js from re-arming the alert configuration.
10
+ */
11
+
12
+ for (const key of ['MANAGER_EMAIL', 'ALERT_API_HOST', 'ALERT_API_PATH', 'ALERT_API_KEY', 'ALERT_FROM']) {
13
+ process.env[key] = '';
14
+ }
package/util.js CHANGED
@@ -142,11 +142,18 @@ const getJobNames = exports.getJobNames = async () => {
142
142
  const jobNames = {};
143
143
  let fileNames;
144
144
  for (const category of ['queue', 'claimed', 'failed']) {
145
+ const categoryPath = path.join(jobsPath(), category);
145
146
  try {
146
- fileNames = await fs.readdir(path.join(jobsPath(), category));
147
+ fileNames = await fs.readdir(categoryPath);
147
148
  }
148
149
  catch(error) {
149
- return `ERROR: Job directory ${category} not readable (${error.message})`;
150
+ if (error.code === 'ENOENT') {
151
+ await fs.mkdir(categoryPath, {recursive: true});
152
+ fileNames = [];
153
+ }
154
+ else {
155
+ return `ERROR: Job directory ${category} not readable (${error.message})`;
156
+ }
150
157
  }
151
158
  jobNames[category] = fileNames;
152
159
  }
@@ -220,6 +227,9 @@ exports.getPOSTData = request => new Promise(resolve => {
220
227
  const query = querystring.parse(body);
221
228
  resolve(query);
222
229
  }
230
+ else {
231
+ resolve(null);
232
+ }
223
233
  });
224
234
  });
225
235
  // Returns the waiting test and retest recommendations.
@@ -604,9 +614,9 @@ const getPageData = exports.getPageData = async (timeStamp, jobID) => {
604
614
  return report;
605
615
  }
606
616
  const {url, what} = report.target;
607
- const reportStats = await getReportStats(timeStamp, jobID)
608
- // Otherwise, i.e. if it succeeded, get the elapsed time in days since the report was created.
609
- const daysAgo = getAgoDays(reportStats.reportTime);
617
+ // Get the elapsed time in days since the report was completed, using the
618
+ // report content rather than the file system birth time.
619
+ const daysAgo = getAgoDays(new Date(`20${report.jobData.endTime}Z`));
610
620
  // Return the data.
611
621
  return {
612
622
  what,
@@ -640,7 +650,7 @@ exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
640
650
  };
641
651
  };
642
652
  // Returns the creation time and size of a report.
643
- const getReportStats = exports.getReportStats = async (timeStamp, jobID) => {
653
+ exports.getReportStats = async (timeStamp, jobID) => {
644
654
  let reportStat;
645
655
  try {
646
656
  reportStat = await fs.stat(path.join(reportsPath(), `${timeStamp}-${jobID}.json`));