@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,45 +1,39 @@
|
|
|
1
1
|
/*
|
|
2
|
-
listIssues.test.
|
|
3
|
-
UI tests for web/listIssues/index.
|
|
2
|
+
listIssues.test.ts
|
|
3
|
+
UI tests for web/listIssues/index.ts using the fixture corpus.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
8
|
+
import {test, before, after, mock} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import {parse} from 'node-html-parser';
|
|
12
|
+
|
|
13
|
+
// Mock the util functions called by index.ts before requiring it, so that
|
|
14
|
+
// it imports the mocked versions. Other exports delegate to the real module.
|
|
15
|
+
import * as realUtil from '../../util.ts';
|
|
16
|
+
let pageDataStringsOverride: any = null;
|
|
17
|
+
mock.module('../../util.ts', {
|
|
18
|
+
exports: {
|
|
19
|
+
...realUtil,
|
|
20
|
+
getPageDataStrings: async (...args: any[]) => {
|
|
21
|
+
if (pageDataStringsOverride !== null) {
|
|
22
|
+
return pageDataStringsOverride;
|
|
23
|
+
}
|
|
24
|
+
return (realUtil.getPageDataStrings as any)(...args);
|
|
25
|
+
}
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
};
|
|
28
|
-
util.getPageDataStrings = async (...args) => {
|
|
29
|
-
if (pageDataStringsOverride !== null) {
|
|
30
|
-
return pageDataStringsOverride;
|
|
31
|
-
}
|
|
32
|
-
return realGetPageDataStrings(...args);
|
|
33
|
-
};
|
|
27
|
+
} as any);
|
|
34
28
|
|
|
35
|
-
const {answer} =
|
|
29
|
+
const {answer} = await import('./index.ts') as any;
|
|
36
30
|
|
|
37
31
|
// SETUP AND TEARDOWN
|
|
38
32
|
|
|
39
33
|
const savedDBDir = process.env.DB_DIR;
|
|
40
34
|
|
|
41
|
-
before(() => {
|
|
42
|
-
process.env.DB_DIR =
|
|
35
|
+
before(async () => {
|
|
36
|
+
process.env.DB_DIR = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
43
37
|
});
|
|
44
38
|
|
|
45
39
|
after(() => {
|
|
@@ -72,13 +66,8 @@ test('listIssues includes links to listViolators for issues in the mixed report'
|
|
|
72
66
|
const violatorLinks = html.querySelectorAll('a[href*="listViolators.html"]');
|
|
73
67
|
assert.ok(violatorLinks.length >= 2);
|
|
74
68
|
const hrefs = violatorLinks.map(a => a.getAttribute('href'));
|
|
75
|
-
assert.ok(hrefs.some(href => href
|
|
76
|
-
assert.ok(hrefs.some(href => href
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('listIssues returns an error status for a hidden report', async () => {
|
|
80
|
-
const result = await answer('260101T0007/hid');
|
|
81
|
-
assert.equal(result.status, 'error');
|
|
69
|
+
assert.ok(hrefs.some(href => href?.includes('linkNoText')));
|
|
70
|
+
assert.ok(hrefs.some(href => href?.includes('allCaps')));
|
|
82
71
|
});
|
|
83
72
|
|
|
84
73
|
test('listIssues returns an error status for a nonexistent report', async () => {
|
|
@@ -102,18 +91,6 @@ test('listIssues for the empty report shows no issue links', async () => {
|
|
|
102
91
|
assert.equal(violatorLinks.length, 0);
|
|
103
92
|
});
|
|
104
93
|
|
|
105
|
-
test('listIssues returns an error when a report becomes hidden during processing', async () => {
|
|
106
|
-
isHiddenCallCount = 0;
|
|
107
|
-
forceHiddenOnCall = 2;
|
|
108
|
-
try {
|
|
109
|
-
const result = await answer('260101T0000/mix');
|
|
110
|
-
assert.equal(result.status, 'error');
|
|
111
|
-
assert.equal(result.message, 'Report is not available');
|
|
112
|
-
}
|
|
113
|
-
finally {
|
|
114
|
-
forceHiddenOnCall = -1;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
94
|
|
|
118
95
|
test('listIssues includes prevention notices for the prevented report', async () => {
|
|
119
96
|
const result = await answer('260101T0006/prv');
|
|
@@ -122,6 +99,23 @@ test('listIssues includes prevention notices for the prevented report', async ()
|
|
|
122
99
|
assert.ok(result.answerPage.includes('page timed out'));
|
|
123
100
|
});
|
|
124
101
|
|
|
102
|
+
test('listIssues falls back to the engine ID for an unknown prevented engine', async () => {
|
|
103
|
+
const fs = await import('node:fs/promises');
|
|
104
|
+
const prvJSON = await fs.readFile(path.join(realUtil.reportsPath(), '260101T0006-prv.json'), 'utf8');
|
|
105
|
+
const report = JSON.parse(prvJSON);
|
|
106
|
+
report.jobData.preventions.unknownEngine = 'mystery failure';
|
|
107
|
+
const reportPath = path.join(realUtil.reportsPath(), '260103T0000-unk.json');
|
|
108
|
+
await fs.writeFile(reportPath, JSON.stringify(report));
|
|
109
|
+
try {
|
|
110
|
+
const result = await answer('260103T0000/unk');
|
|
111
|
+
assert.equal(result.status, 'ok');
|
|
112
|
+
assert.ok(result.answerPage.includes('unknownEngine (unknown sponsor)'));
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await fs.unlink(reportPath);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
125
119
|
test('listIssues returns an error when getPageDataStrings fails after getData succeeds', async () => {
|
|
126
120
|
pageDataStringsOverride = {error: 'Page data strings error'};
|
|
127
121
|
try {
|
|
@@ -159,8 +153,8 @@ test('listIssues shows plural violator count for an issue with multiple violator
|
|
|
159
153
|
|
|
160
154
|
test('listIssues handles acts with no standardResult instances', async () => {
|
|
161
155
|
// Create a temporary report with an act that has no standardResult.
|
|
162
|
-
const fs =
|
|
163
|
-
const dbDir =
|
|
156
|
+
const fs = await import('node:fs/promises');
|
|
157
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
164
158
|
const reportPath = path.join(dbDir, 'reports', '260101T0004-nsi.json');
|
|
165
159
|
const report = {
|
|
166
160
|
id: '260101T0004-nsi',
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
<body>
|
|
14
14
|
<main>
|
|
15
15
|
<h1>Pages tested by <a href="/">Kilotest</a></h1>
|
|
16
|
-
<h2>
|
|
16
|
+
<h2>Requested for testing</h2>
|
|
17
17
|
<ol>
|
|
18
|
-
|
|
18
|
+
__requests__
|
|
19
19
|
</ol>
|
|
20
|
-
<p>
|
|
20
|
+
<p>__noRequests__</p>
|
|
21
21
|
<p>Should Kilotest <a href="requestTestForm.html">test a page</a> that is not listed below?</p>
|
|
22
22
|
<h2>Queued for testing</h2>
|
|
23
23
|
<ol>
|
|
@@ -1,66 +1,66 @@
|
|
|
1
1
|
/*
|
|
2
|
-
index.
|
|
2
|
+
index.ts
|
|
3
3
|
Lists all available reports.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {
|
|
9
9
|
getAgoDays,
|
|
10
10
|
getCountString,
|
|
11
11
|
getJobNames,
|
|
12
12
|
getMultiReportWhats,
|
|
13
13
|
getObject,
|
|
14
14
|
getPageDataStrings,
|
|
15
|
-
|
|
15
|
+
getTestRequests,
|
|
16
16
|
getReportData,
|
|
17
17
|
getReportExtracts,
|
|
18
|
-
|
|
18
|
+
getRequestability,
|
|
19
19
|
jobsPath,
|
|
20
|
-
objectSort
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
objectSort,
|
|
21
|
+
populateTemplate
|
|
22
|
+
} from '../../util.ts';
|
|
23
|
+
import path from 'node:path';
|
|
24
24
|
|
|
25
25
|
// FUNCTIONS
|
|
26
26
|
|
|
27
27
|
// Adds parameters to a query for the answer page.
|
|
28
|
-
const populateQuery = async query => {
|
|
28
|
+
const populateQuery = async (query: Record<string, any>) => {
|
|
29
29
|
const margin = ' '.repeat(8);
|
|
30
30
|
// Initialize the classes of lines.
|
|
31
|
-
const lines = {
|
|
32
|
-
|
|
31
|
+
const lines: {requests: string[], queue: string[], claimed: string[], tested: string[]} = {
|
|
32
|
+
requests: [],
|
|
33
33
|
queue: [],
|
|
34
34
|
claimed: [],
|
|
35
35
|
tested: []
|
|
36
36
|
};
|
|
37
|
-
// Get the
|
|
38
|
-
const
|
|
39
|
-
// For each
|
|
40
|
-
Object.keys(
|
|
41
|
-
// For each of its
|
|
42
|
-
|
|
43
|
-
const {
|
|
37
|
+
// Get the test requests.
|
|
38
|
+
const testRequests = await getTestRequests() as Record<string, {description: string, why: string}[]>;
|
|
39
|
+
// For each requested URL:
|
|
40
|
+
Object.keys(testRequests).forEach(url => {
|
|
41
|
+
// For each of its requests:
|
|
42
|
+
testRequests[url]!.forEach((req: any) => {
|
|
43
|
+
const {description, why} = req;
|
|
44
44
|
// Add a line.
|
|
45
|
-
lines.
|
|
45
|
+
lines.requests.push(`${margin}<li><code>${url}</code> (${description}): ${why}</li>`);
|
|
46
46
|
});
|
|
47
47
|
});
|
|
48
48
|
// Sort the lines in alphabetical order by URL and secondarily by proposed name.
|
|
49
|
-
lines.
|
|
49
|
+
lines.requests.sort();
|
|
50
50
|
// Add the lines to the query.
|
|
51
|
-
query.
|
|
52
|
-
// Add a no-
|
|
53
|
-
query.
|
|
54
|
-
? 'Kilotest managers can <a href="enqueueForm.html">approve or reject a
|
|
55
|
-
: 'No
|
|
51
|
+
query.requests = lines.requests.join('\n');
|
|
52
|
+
// Add a no-requests message, if applicable, to the query.
|
|
53
|
+
query.noRequests = lines.requests.length
|
|
54
|
+
? 'Kilotest managers can <a href="enqueueForm.html">approve or reject a request</a>.'
|
|
55
|
+
: 'No requests await approval now.';
|
|
56
56
|
// Get the file names of all queued and claimed jobs.
|
|
57
57
|
const jobFileNames = await getJobNames();
|
|
58
58
|
// For each job category:
|
|
59
|
-
for (const category of ['queue', 'claimed']) {
|
|
59
|
+
for (const category of ['queue', 'claimed'] as const) {
|
|
60
60
|
// For each job in the category:
|
|
61
61
|
for (const fileName of jobFileNames[category]) {
|
|
62
62
|
// Get the job.
|
|
63
|
-
const job = await getObject(path.join(jobsPath(), category, fileName));
|
|
63
|
+
const job = await getObject(path.join(jobsPath(), category, fileName)) as {target: {url: string, what: string}};
|
|
64
64
|
// Add a line.
|
|
65
65
|
lines[category].push(`${margin}<li><code>${job.target.url}</code> (${job.target.what})</li>`);
|
|
66
66
|
}
|
|
@@ -81,14 +81,21 @@ const populateQuery = async query => {
|
|
|
81
81
|
const multiReportWhats = await getMultiReportWhats();
|
|
82
82
|
// Sort them primarily by page description and secondarily by completion time.
|
|
83
83
|
let sortedExtracts = objectSort(reportExtracts, 'reportTime', 'alpha');
|
|
84
|
-
sortedExtracts = objectSort(sortedExtracts, '
|
|
84
|
+
sortedExtracts = objectSort(sortedExtracts, 'description', 'alpha');
|
|
85
85
|
// For each report:
|
|
86
86
|
for (const extract of sortedExtracts) {
|
|
87
|
-
const {jobID, timeStamp, url,
|
|
87
|
+
const {jobID, timeStamp, url, description} = extract;
|
|
88
88
|
// Get data about it.
|
|
89
89
|
const reportData = await getReportData(timeStamp, jobID);
|
|
90
|
+
// If this failed:
|
|
91
|
+
if (reportData.error !== undefined) {
|
|
92
|
+
console.error(reportData.error);
|
|
93
|
+
// Populate the query with the reason.
|
|
94
|
+
query.error = reportData.error;
|
|
95
|
+
// Stop populating the query.
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
90
98
|
const {
|
|
91
|
-
error,
|
|
92
99
|
issueCount,
|
|
93
100
|
preventedEngineCount,
|
|
94
101
|
preventedEngineNames,
|
|
@@ -96,21 +103,13 @@ const populateQuery = async query => {
|
|
|
96
103
|
reporterCount,
|
|
97
104
|
violatorCount
|
|
98
105
|
} = reportData;
|
|
99
|
-
// If this failed:
|
|
100
|
-
if (error) {
|
|
101
|
-
console.error(error);
|
|
102
|
-
// Populate the query with the reason.
|
|
103
|
-
query.error = error;
|
|
104
|
-
// Stop populating the query.
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
106
|
// Otherwise, i.e. if it succeeded, add lines about the report.
|
|
108
107
|
lines.tested.push(`${margin}<details>`);
|
|
109
108
|
const daysAgo = getAgoDays(timeStamp);
|
|
110
|
-
const pageDataStrings = await getPageDataStrings(timeStamp, jobID, {
|
|
111
|
-
const {urlLink, testInfo} = pageDataStrings;
|
|
112
|
-
const testText = multiReportWhats.includes(
|
|
113
|
-
lines.tested.push(`${margin} <summary>${
|
|
109
|
+
const pageDataStrings = await getPageDataStrings(timeStamp, jobID, {description, url, daysAgo});
|
|
110
|
+
const {urlLink, testInfo}: any = pageDataStrings;
|
|
111
|
+
const testText = multiReportWhats.includes(description) ? ` (${testInfo.toLowerCase()})` : '';
|
|
112
|
+
lines.tested.push(`${margin} <summary>${description}${testText}</summary>`);
|
|
114
113
|
lines.tested.push(`${margin} <ul>`);
|
|
115
114
|
// Add the URL of the target to the lines.
|
|
116
115
|
lines.tested.push(`${margin} <li>URL: ${urlLink}</li>`);
|
|
@@ -125,7 +124,7 @@ const populateQuery = async query => {
|
|
|
125
124
|
);
|
|
126
125
|
}
|
|
127
126
|
// Add facts about the test results to the lines.
|
|
128
|
-
|
|
127
|
+
let reporterString = `${getCountString(reporterCount, 'rule engine', 'rule engines')} reported issues`;
|
|
129
128
|
if (reporterCount) {
|
|
130
129
|
const reporterNamesString = reporterNames.join(' + ');
|
|
131
130
|
reporterString = `${reporterString} (${reporterNamesString})`;
|
|
@@ -141,14 +140,14 @@ const populateQuery = async query => {
|
|
|
141
140
|
if (issueCount) {
|
|
142
141
|
// Add a question link about the reported issues to the lines.
|
|
143
142
|
const href = `href="listIssues.html/${timeStamp}/${jobID}"`;
|
|
144
|
-
const label = `aria-label="What ${issueCountString} reported for the ${
|
|
143
|
+
const label = `aria-label="What ${issueCountString} reported for the ${description} page?"`;
|
|
145
144
|
const questionString = issueCount === 1 ? 'was the issue' : 'were the issues';
|
|
146
145
|
const link = `<a ${href} ${label}>What ${questionString}?</a>`;
|
|
147
146
|
lines.tested.push(`${margin} <li>${link}</li>`);
|
|
148
147
|
}
|
|
149
148
|
// Add the status of, and if necessary a question link about, retesting to the lines.
|
|
150
|
-
const status = await
|
|
151
|
-
let retestString;
|
|
149
|
+
const status = await getRequestability(url);
|
|
150
|
+
let retestString: string;
|
|
152
151
|
if (status === 'claimed') {
|
|
153
152
|
retestString = 'Currently being retested';
|
|
154
153
|
}
|
|
@@ -167,8 +166,8 @@ const populateQuery = async query => {
|
|
|
167
166
|
query.testedPages = lines.tested.join('\n');
|
|
168
167
|
};
|
|
169
168
|
// Returns a page answering the targets question.
|
|
170
|
-
|
|
171
|
-
const query = {};
|
|
169
|
+
export const answer = async () => {
|
|
170
|
+
const query: Record<string, any> = {};
|
|
172
171
|
// Create a query to replace placeholders.
|
|
173
172
|
await populateQuery(query);
|
|
174
173
|
// If the query reports an error:
|
|
@@ -180,11 +179,7 @@ exports.answer = async () => {
|
|
|
180
179
|
};
|
|
181
180
|
}
|
|
182
181
|
// Otherwise, i.e. if it does not report an error, get the template.
|
|
183
|
-
|
|
184
|
-
// Replace its placeholders.
|
|
185
|
-
Object.keys(query).forEach(param => {
|
|
186
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
187
|
-
});
|
|
182
|
+
const answerPage = await populateTemplate(import.meta.dirname, query);
|
|
188
183
|
// Return the populated page.
|
|
189
184
|
return {
|
|
190
185
|
status: 'ok',
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/*
|
|
2
|
-
listReports.test.
|
|
3
|
-
UI tests for web/listReports/index.
|
|
2
|
+
listReports.test.ts
|
|
3
|
+
UI tests for web/listReports/index.ts using the fixture corpus.
|
|
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 {parse} from 'node-html-parser';
|
|
13
|
+
import {answer} from './index.ts';
|
|
14
14
|
|
|
15
15
|
// SETUP AND TEARDOWN
|
|
16
16
|
|
|
17
17
|
const savedDBDir = process.env.DB_DIR;
|
|
18
18
|
|
|
19
19
|
before(async () => {
|
|
20
|
-
process.env.DB_DIR =
|
|
20
|
+
process.env.DB_DIR = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
21
21
|
// Ensure job subdirectories exist (they are empty and not tracked by Git).
|
|
22
22
|
for (const sub of ['queue', 'claimed', 'failed']) {
|
|
23
23
|
await fs.mkdir(path.join(process.env.DB_DIR, 'jobs', sub), {recursive: true});
|
|
@@ -36,22 +36,22 @@ after(() => {
|
|
|
36
36
|
// TESTS
|
|
37
37
|
|
|
38
38
|
test('listReports returns an ok status with valid HTML', async () => {
|
|
39
|
-
const result = await answer();
|
|
39
|
+
const result: any = await answer();
|
|
40
40
|
assert.equal(result.status, 'ok');
|
|
41
41
|
assert.ok(result.answerPage);
|
|
42
42
|
const html = parse(result.answerPage);
|
|
43
|
-
assert.equal(html.querySelector('title')
|
|
43
|
+
assert.equal(html.querySelector('title')?.text, 'Pages tested | Kilotest');
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test('listReports includes the 8 non-hidden fixture reports as details elements', async () => {
|
|
47
|
-
const result = await answer();
|
|
47
|
+
const result: any = await answer();
|
|
48
48
|
const html = parse(result.answerPage);
|
|
49
49
|
const details = html.querySelectorAll('details');
|
|
50
50
|
assert.equal(details.length, 8);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
test('listReports includes the page descriptions in summary elements', async () => {
|
|
54
|
-
const result = await answer();
|
|
54
|
+
const result: any = await answer();
|
|
55
55
|
const html = parse(result.answerPage);
|
|
56
56
|
const summaries = html.querySelectorAll('details > summary').map(s => s.text);
|
|
57
57
|
assert.ok(summaries.some(s => s.startsWith('Mixed Outcomes Page')));
|
|
@@ -62,55 +62,55 @@ test('listReports includes the page descriptions in summary elements', async ()
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test('listReports does not include the hidden report', async () => {
|
|
65
|
-
const result = await answer();
|
|
65
|
+
const result: any = await answer();
|
|
66
66
|
assert.ok(!result.answerPage.includes('Hidden Page'));
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
test('listReports includes links to listIssues for reports with issues', async () => {
|
|
70
|
-
const result = await answer();
|
|
70
|
+
const result: any = await answer();
|
|
71
71
|
const html = parse(result.answerPage);
|
|
72
72
|
const issueLinks = html.querySelectorAll('a[href*="listIssues.html"]');
|
|
73
73
|
assert.ok(issueLinks.length > 0);
|
|
74
74
|
const hrefs = issueLinks.map(a => a.getAttribute('href'));
|
|
75
|
-
assert.ok(hrefs.some(href => href
|
|
75
|
+
assert.ok(hrefs.some(href => href?.includes('260101T0000/mix')));
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
test('listReports includes the page URLs in the report details', async () => {
|
|
79
|
-
const result = await answer();
|
|
79
|
+
const result: any = await answer();
|
|
80
80
|
assert.ok(result.answerPage.includes('https://example.com/mixed'));
|
|
81
81
|
assert.ok(result.answerPage.includes('https://example.com/canttell'));
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
test('listReports includes a link to request testing a new page', async () => {
|
|
85
|
-
const result = await answer();
|
|
85
|
+
const result: any = await answer();
|
|
86
86
|
const html = parse(result.answerPage);
|
|
87
87
|
const testLink = html.querySelector('a[href="requestTestForm.html"]');
|
|
88
88
|
assert.ok(testLink);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
test('listReports shows
|
|
92
|
-
const dbDir =
|
|
93
|
-
const
|
|
94
|
-
const
|
|
91
|
+
test('listReports shows requests when testRequests.json has entries', {timeout: 500}, async () => {
|
|
92
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
93
|
+
const testRequestsPath = path.join(dbDir, 'jobs', 'testRequests.json');
|
|
94
|
+
const originalTestRequests = await fs.readFile(testRequestsPath, 'utf8');
|
|
95
95
|
try {
|
|
96
|
-
const
|
|
96
|
+
const testRequests = {
|
|
97
97
|
'https://example.com/mixed': [
|
|
98
|
-
{
|
|
98
|
+
{description: 'Mixed Outcomes Page', why: 'Needs retesting for accessibility'}
|
|
99
99
|
]
|
|
100
100
|
};
|
|
101
|
-
await fs.writeFile(
|
|
102
|
-
const result = await answer();
|
|
101
|
+
await fs.writeFile(testRequestsPath, JSON.stringify(testRequests, null, 2));
|
|
102
|
+
const result: any = await answer();
|
|
103
103
|
assert.equal(result.status, 'ok');
|
|
104
104
|
assert.ok(result.answerPage.includes('https://example.com/mixed'));
|
|
105
105
|
assert.ok(result.answerPage.includes('Needs retesting for accessibility'));
|
|
106
106
|
}
|
|
107
107
|
finally {
|
|
108
|
-
await fs.writeFile(
|
|
108
|
+
await fs.writeFile(testRequestsPath, originalTestRequests);
|
|
109
109
|
}
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
test('listReports shows queued and claimed jobs when they exist', async () => {
|
|
113
|
-
const dbDir =
|
|
113
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
114
114
|
const queueDir = path.join(dbDir, 'jobs', 'queue');
|
|
115
115
|
const claimedDir = path.join(dbDir, 'jobs', 'claimed');
|
|
116
116
|
const queueFile = path.join(queueDir, 'queuedJob.json');
|
|
@@ -124,7 +124,7 @@ test('listReports shows queued and claimed jobs when they exist', async () => {
|
|
|
124
124
|
};
|
|
125
125
|
await fs.writeFile(queueFile, JSON.stringify(queuedJob, null, 2));
|
|
126
126
|
await fs.writeFile(claimedFile, JSON.stringify(claimedJob, null, 2));
|
|
127
|
-
const result = await answer();
|
|
127
|
+
const result: any = await answer();
|
|
128
128
|
assert.equal(result.status, 'ok');
|
|
129
129
|
assert.ok(result.answerPage.includes('https://example.com/queued'));
|
|
130
130
|
assert.ok(result.answerPage.includes('Queued Page'));
|
|
@@ -138,7 +138,7 @@ test('listReports shows queued and claimed jobs when they exist', async () => {
|
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
test('listReports shows claimed retest status for a report with a matching claimed job', async () => {
|
|
141
|
-
const dbDir =
|
|
141
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
142
142
|
const claimedDir = path.join(dbDir, 'jobs', 'claimed');
|
|
143
143
|
const claimedFile = path.join(claimedDir, 'claimedRetest.json');
|
|
144
144
|
try {
|
|
@@ -146,7 +146,7 @@ test('listReports shows claimed retest status for a report with a matching claim
|
|
|
146
146
|
target: {url: 'https://example.com/mixed', what: 'Mixed Outcomes Page'}
|
|
147
147
|
};
|
|
148
148
|
await fs.writeFile(claimedFile, JSON.stringify(claimedJob, null, 2));
|
|
149
|
-
const result = await answer();
|
|
149
|
+
const result: any = await answer();
|
|
150
150
|
assert.equal(result.status, 'ok');
|
|
151
151
|
assert.ok(result.answerPage.includes('Currently being retested'));
|
|
152
152
|
}
|
|
@@ -156,7 +156,7 @@ test('listReports shows claimed retest status for a report with a matching claim
|
|
|
156
156
|
});
|
|
157
157
|
|
|
158
158
|
test('listReports shows queued retest status for a report with a matching queued job', async () => {
|
|
159
|
-
const dbDir =
|
|
159
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
160
160
|
const queueDir = path.join(dbDir, 'jobs', 'queue');
|
|
161
161
|
const queueFile = path.join(queueDir, 'queuedRetest.json');
|
|
162
162
|
try {
|
|
@@ -164,7 +164,7 @@ test('listReports shows queued retest status for a report with a matching queued
|
|
|
164
164
|
target: {url: 'https://example.com/mixed', what: 'Mixed Outcomes Page'}
|
|
165
165
|
};
|
|
166
166
|
await fs.writeFile(queueFile, JSON.stringify(queuedJob, null, 2));
|
|
167
|
-
const result = await answer();
|
|
167
|
+
const result: any = await answer();
|
|
168
168
|
assert.equal(result.status, 'ok');
|
|
169
169
|
assert.ok(result.answerPage.includes('Currently in the queue for retesting'));
|
|
170
170
|
}
|
|
@@ -173,18 +173,18 @@ test('listReports shows queued retest status for a report with a matching queued
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
test('listReports returns an error when a report file is invalid', async () => {
|
|
177
|
-
const dbDir =
|
|
176
|
+
test('listReports returns an error when a report file is invalid', {timeout: 500}, async () => {
|
|
177
|
+
const dbDir = (await import('../../test/dbFixture.ts')).fixtureDBDir;
|
|
178
178
|
const reportsDir = path.join(dbDir, 'reports');
|
|
179
179
|
const invalidReportPath = path.join(reportsDir, '260101T9999-bad.json');
|
|
180
180
|
try {
|
|
181
|
-
// Valid for getReportExtract (has target and jobData.endTime) but
|
|
181
|
+
// Valid for getReportExtract (has target and jobData.endTime) but not usable by isUsableReport (no acts or catalog).
|
|
182
182
|
const invalidReport = {
|
|
183
183
|
target: {what: 'Invalid Report', url: 'https://example.com/invalid'},
|
|
184
184
|
jobData: {endTime: '26-01-01T00:10'}
|
|
185
185
|
};
|
|
186
186
|
await fs.writeFile(invalidReportPath, JSON.stringify(invalidReport, null, 2));
|
|
187
|
-
const result = await answer();
|
|
187
|
+
const result: any = await answer();
|
|
188
188
|
assert.equal(result.status, 'error');
|
|
189
189
|
assert.ok(result.message);
|
|
190
190
|
}
|
|
@@ -195,26 +195,24 @@ test('listReports returns an error when a report file is invalid', async () => {
|
|
|
195
195
|
|
|
196
196
|
test('listReports shows no-reports message when the database is empty', async () => {
|
|
197
197
|
// Create a temp DB with empty reports and jobs directories.
|
|
198
|
-
const os =
|
|
198
|
+
const os = await import('node:os');
|
|
199
199
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'listreports-empty-'));
|
|
200
200
|
await fs.mkdir(path.join(tmpDir, 'reports'), {recursive: true});
|
|
201
201
|
await fs.mkdir(path.join(tmpDir, 'jobs', 'queue'), {recursive: true});
|
|
202
202
|
await fs.mkdir(path.join(tmpDir, 'jobs', 'claimed'), {recursive: true});
|
|
203
203
|
await fs.mkdir(path.join(tmpDir, 'jobs', 'failed'), {recursive: true});
|
|
204
|
-
await fs.writeFile(path.join(tmpDir, 'jobs', '
|
|
204
|
+
await fs.writeFile(path.join(tmpDir, 'jobs', 'testRequests.json'), '{}\n');
|
|
205
205
|
const savedDBDir = process.env.DB_DIR;
|
|
206
206
|
process.env.DB_DIR = tmpDir;
|
|
207
207
|
try {
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
const result = await answer();
|
|
208
|
+
const {answer} = await import('./index.ts');
|
|
209
|
+
const result: any = await answer();
|
|
211
210
|
assert.equal(result.status, 'ok');
|
|
212
211
|
assert.ok(result.answerPage.includes('no'));
|
|
213
212
|
assert.ok(result.answerPage.includes(' a '));
|
|
214
213
|
}
|
|
215
214
|
finally {
|
|
216
215
|
process.env.DB_DIR = savedDBDir;
|
|
217
|
-
delete require.cache[require.resolve('./index')];
|
|
218
216
|
await fs.rm(tmpDir, {recursive: true}).catch(() => {});
|
|
219
217
|
}
|
|
220
218
|
});
|