@jrpool/kilotest 46.1.0 → 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 +8 -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} +75 -46
- package/api/version.ts +8 -0
- package/{apiIntegration.test.js → apiIntegration.test.ts} +13 -14
- 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} +462 -137
- package/{index.js → index.ts} +388 -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 -87
- 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 -126
- package/util.js +0 -733
- 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
package/web/enqueue/index.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Implements a test request approval.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
getJSON, getNowStamp, getRandomString, getRecs, isURL, jobsPath, recsLock
|
|
10
|
-
} = require('../../util');
|
|
11
|
-
const fs = require('fs/promises');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
|
|
14
|
-
// FUNCTIONS
|
|
15
|
-
|
|
16
|
-
// Implements a test request approval and returns a revised request page.
|
|
17
|
-
exports.answer = async (url, what, authCode) => {
|
|
18
|
-
// If the arguments are valid:
|
|
19
|
-
if (isURL(url) && what && authCode === process.env.AUTH_CODE) {
|
|
20
|
-
// Get the job template.
|
|
21
|
-
const jobTemplateJSON = await fs.readFile(path.join(__dirname, '..', '..', 'job.json'), 'utf8');
|
|
22
|
-
const job = JSON.parse(jobTemplateJSON);
|
|
23
|
-
const nowStamp = getNowStamp();
|
|
24
|
-
// Populate the template with job properties.
|
|
25
|
-
const jobIDSuffix = getRandomString(3);
|
|
26
|
-
const jobName = `${nowStamp}-${jobIDSuffix}`;
|
|
27
|
-
job.id = jobName;
|
|
28
|
-
job.creationTimeStamp = nowStamp;
|
|
29
|
-
job.executionTimeStamp = nowStamp;
|
|
30
|
-
job.target.what = what;
|
|
31
|
-
job.target.url = url;
|
|
32
|
-
const query = {
|
|
33
|
-
target: what,
|
|
34
|
-
jobName
|
|
35
|
-
};
|
|
36
|
-
// Save the job in the queue.
|
|
37
|
-
await fs.writeFile(
|
|
38
|
-
path.join(jobsPath(), 'queue', `${jobName}.json`), getJSON(job)
|
|
39
|
-
);
|
|
40
|
-
console.log(`Retest queued for ${what} as job ${jobName}`);
|
|
41
|
-
// Isolate this revision.
|
|
42
|
-
await recsLock(async () => {
|
|
43
|
-
// Get the recommendations.
|
|
44
|
-
const recs = await getRecs();
|
|
45
|
-
// Delete the recommendations to test the target.
|
|
46
|
-
delete recs[url];
|
|
47
|
-
// Save the revised recommendations.
|
|
48
|
-
await fs.writeFile(path.join(jobsPath(), 'recs.json'), getJSON(recs));
|
|
49
|
-
});
|
|
50
|
-
// Get the answer template.
|
|
51
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
52
|
-
// Replace its placeholders.
|
|
53
|
-
Object.keys(query).forEach(param => {
|
|
54
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
55
|
-
});
|
|
56
|
-
// Return the populated page.
|
|
57
|
-
return {
|
|
58
|
-
status: 'ok',
|
|
59
|
-
answerPage
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
// Otherwise, i.e. if the authorization code is invalid, return an error page.
|
|
63
|
-
return {
|
|
64
|
-
status: 'error',
|
|
65
|
-
message: 'Invalid authorization code'
|
|
66
|
-
};
|
|
67
|
-
};
|
package/web/enqueueForm/index.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Serves a form for approving or rejecting a test request.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {getRecs} = require('../../util');
|
|
9
|
-
const fs = require('fs/promises');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// FUNCTIONS
|
|
13
|
-
|
|
14
|
-
// Returns a test order form.
|
|
15
|
-
exports.answer = async () => {
|
|
16
|
-
const recs = await getRecs();
|
|
17
|
-
const urls = Object.keys(recs);
|
|
18
|
-
const margin = ' '.repeat(12);
|
|
19
|
-
const lines = [];
|
|
20
|
-
// For each page with any recommendations:
|
|
21
|
-
urls.forEach(url => {
|
|
22
|
-
// Add a radio button and its URL to the lines.
|
|
23
|
-
lines.push(`${margin}<h2><input type="radio" name="target" value="${url}" required> ${url}</h2>`);
|
|
24
|
-
// Get the recommended target names for the page.
|
|
25
|
-
const targetNames = new Set(recs[url].map(rec => rec.what));
|
|
26
|
-
// For each recommended target name:
|
|
27
|
-
targetNames.forEach(what => {
|
|
28
|
-
const radio = `<input type="radio" name="target" value="${url}\t${what}" required>`;
|
|
29
|
-
// Add a radio button and the recommended page name to the lines.
|
|
30
|
-
lines.push(`${margin} <p>${radio} ${what}</p>`);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
const query = {
|
|
34
|
-
recs: lines.join('\n'),
|
|
35
|
-
noRecs: urls.length ? '' : 'No recommendations exist now.',
|
|
36
|
-
disabled: urls.length ? '' : ' disabled'
|
|
37
|
-
};
|
|
38
|
-
// Get the order form template.
|
|
39
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
40
|
-
// Replace its placeholders.
|
|
41
|
-
Object.keys(query).forEach(param => {
|
|
42
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
43
|
-
});
|
|
44
|
-
// Return the populated page.
|
|
45
|
-
return {
|
|
46
|
-
status: 'ok',
|
|
47
|
-
answerPage
|
|
48
|
-
};
|
|
49
|
-
};
|
package/web/manage/index.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Lists the actions that managers can take.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const fs = require('fs/promises');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
// FUNCTIONS
|
|
12
|
-
|
|
13
|
-
// Returns the answer page.
|
|
14
|
-
exports.answer = async () => {
|
|
15
|
-
// Get the answer page.
|
|
16
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
17
|
-
// Return it.
|
|
18
|
-
return {
|
|
19
|
-
status: 'ok',
|
|
20
|
-
answerPage
|
|
21
|
-
};
|
|
22
|
-
};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Serves a form for deleting superseded reports.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {getReportData, reportsPath} = require('../../util');
|
|
9
|
-
const fs = require('fs/promises');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// FUNCTIONS
|
|
13
|
-
|
|
14
|
-
// Returns a form for deleting non-latest reports.
|
|
15
|
-
exports.answer = async (_, search) => {
|
|
16
|
-
const searchParams = new URLSearchParams(search);
|
|
17
|
-
const authCode = searchParams?.get('authCode');
|
|
18
|
-
const jobNames = searchParams?.getAll('report');
|
|
19
|
-
// If the form has displayed itself after a submission and any reports are to be deleted:
|
|
20
|
-
if (jobNames?.length) {
|
|
21
|
-
// If the authorization code is valid:
|
|
22
|
-
if (authCode === process.env.AUTH_CODE) {
|
|
23
|
-
try {
|
|
24
|
-
// For each report to be deleted:
|
|
25
|
-
for (const jobName of jobNames) {
|
|
26
|
-
// Delete it.
|
|
27
|
-
await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// If this failed:
|
|
31
|
-
catch (error) {
|
|
32
|
-
// Return why.
|
|
33
|
-
return {
|
|
34
|
-
status: 'error',
|
|
35
|
-
message: `Deleting superseded reports failed (${error.message})`
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Otherwise, i.e. if the authorization code is invalid:
|
|
40
|
-
else {
|
|
41
|
-
// Return this.
|
|
42
|
-
return {
|
|
43
|
-
status: 'error',
|
|
44
|
-
message: 'Invalid authorization code'
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const reportNames = await fs.readdir(reportsPath());
|
|
49
|
-
// Initialize an array of report summaries.
|
|
50
|
-
const reportSpecs = [];
|
|
51
|
-
// For each report:
|
|
52
|
-
for (const reportName of reportNames) {
|
|
53
|
-
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
54
|
-
// Get a summary of it.
|
|
55
|
-
const reportFacts = await getReportData(timeStamp, jobID);
|
|
56
|
-
const {error, issueCount, preventedEngineCount, url} = reportFacts;
|
|
57
|
-
// If this failed:
|
|
58
|
-
if (error) {
|
|
59
|
-
// Return why.
|
|
60
|
-
return {
|
|
61
|
-
status: 'error',
|
|
62
|
-
message: error.message
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Otherwise, i.e. if it succeeded, add the summary to the array.
|
|
66
|
-
reportSpecs.push({
|
|
67
|
-
timeStamp,
|
|
68
|
-
jobID,
|
|
69
|
-
issueCount,
|
|
70
|
-
preventedEngineCount,
|
|
71
|
-
url
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
// Sort the summaries by URL and then by time stamp.
|
|
75
|
-
reportSpecs.sort((a, b) => {
|
|
76
|
-
if (a.url === b.url) {
|
|
77
|
-
return a.timeStamp.localeCompare(b.timeStamp);
|
|
78
|
-
}
|
|
79
|
-
return a.url.localeCompare(b.url);
|
|
80
|
-
});
|
|
81
|
-
const lines = [];
|
|
82
|
-
const margin = ' '.repeat(12);
|
|
83
|
-
let anyDeletable = false;
|
|
84
|
-
// For each summary:
|
|
85
|
-
reportSpecs.forEach((spec, index) => {
|
|
86
|
-
const {timeStamp, jobID, issueCount, preventedEngineCount, url} = spec;
|
|
87
|
-
const jobName = `${timeStamp}-${jobID}`;
|
|
88
|
-
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${preventedEngineCount}, issues ${issueCount}`;
|
|
89
|
-
// If its report is a non-latest report:
|
|
90
|
-
if (reportSpecs[index + 1]?.url === url) {
|
|
91
|
-
// Add a line with a deletion checkbox.
|
|
92
|
-
lines.push(
|
|
93
|
-
`${margin}<p><input type="checkbox" name="report" value="${jobName}"> ${specString}</p>`
|
|
94
|
-
);
|
|
95
|
-
anyDeletable = true;
|
|
96
|
-
}
|
|
97
|
-
// Otherwise, i.e. if its report is a latest report:
|
|
98
|
-
else {
|
|
99
|
-
// Add a line without a deletion checkbox.
|
|
100
|
-
lines.push(`${margin}<p>${specString}</p>`);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
const intro = anyDeletable
|
|
104
|
-
? 'Choose the superseded reports to delete.'
|
|
105
|
-
: 'Each target has only 1 report, so there are no superseded reports to delete.';
|
|
106
|
-
const disabled = anyDeletable ? '' : ' disabled';
|
|
107
|
-
const query = {
|
|
108
|
-
reports: lines.join('\n'),
|
|
109
|
-
intro,
|
|
110
|
-
disabled
|
|
111
|
-
};
|
|
112
|
-
// Get the order form template.
|
|
113
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
114
|
-
// Replace its placeholders.
|
|
115
|
-
Object.keys(query).forEach(param => {
|
|
116
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
117
|
-
});
|
|
118
|
-
// Return the populated page.
|
|
119
|
-
return {
|
|
120
|
-
status: 'ok',
|
|
121
|
-
answerPage
|
|
122
|
-
};
|
|
123
|
-
};
|
package/web/reannotate/index.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Reannotates all available latest reports.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {annotateReport, getReportExtracts} = require('../../util');
|
|
9
|
-
const fs = require('fs/promises');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// FUNCTIONS
|
|
13
|
-
|
|
14
|
-
// Implements a reannotation order and returns an acknowledgement page.
|
|
15
|
-
exports.answer = async authCode => {
|
|
16
|
-
// If the authorization code is valid:
|
|
17
|
-
if (authCode === process.env.AUTH_CODE) {
|
|
18
|
-
// Get data on the available reports.
|
|
19
|
-
const reportExtracts = await getReportExtracts();
|
|
20
|
-
// If any exist:
|
|
21
|
-
if (reportExtracts.length) {
|
|
22
|
-
// For each report:
|
|
23
|
-
for (const reportExtract of reportExtracts) {
|
|
24
|
-
const {timeStamp, jobID} = reportExtract;
|
|
25
|
-
// Reannotate it.
|
|
26
|
-
const annotationError = await annotateReport(timeStamp, jobID);
|
|
27
|
-
// If this failed:
|
|
28
|
-
if (annotationError) {
|
|
29
|
-
// Return an error page.
|
|
30
|
-
return {
|
|
31
|
-
status: 'error',
|
|
32
|
-
message: annotationError
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// Otherwis, i.e. if it failed:
|
|
38
|
-
else {
|
|
39
|
-
// Return an error page.
|
|
40
|
-
return {
|
|
41
|
-
status: 'error',
|
|
42
|
-
message: 'Got data on no available reports'
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
// If every annotation succeeded, get the answer page.
|
|
46
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
47
|
-
// Return it.
|
|
48
|
-
return {
|
|
49
|
-
status: 'ok',
|
|
50
|
-
answerPage
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
// Otherwise, i.e. if the authorization code is invalid, return an error page.
|
|
54
|
-
return {
|
|
55
|
-
status: 'error',
|
|
56
|
-
message: 'Invalid authorization code'
|
|
57
|
-
};
|
|
58
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Serves a form for renewing the WCAG map.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const fs = require('fs/promises');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
// FUNCTIONS
|
|
12
|
-
|
|
13
|
-
// Returns a form for renewing the WCAG map.
|
|
14
|
-
exports.answer = async () => {
|
|
15
|
-
// Get the map renewal form.
|
|
16
|
-
const answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
17
|
-
// Return it.
|
|
18
|
-
return {
|
|
19
|
-
status: 'ok',
|
|
20
|
-
answerPage
|
|
21
|
-
};
|
|
22
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Records a retest request.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {getLatestReportExtracts, processTestRequest} = require('../../util');
|
|
9
|
-
|
|
10
|
-
// FUNCTIONS
|
|
11
|
-
|
|
12
|
-
exports.answer = async (pageArgs, why) => {
|
|
13
|
-
const [timeStamp, jobID] = pageArgs.split('/');
|
|
14
|
-
// Get data on the latest available reports.
|
|
15
|
-
const reportExtracts = await getLatestReportExtracts();
|
|
16
|
-
// Get data on the report whose page is to be retested.
|
|
17
|
-
const reportExtract = reportExtracts.find(
|
|
18
|
-
reportExtract => reportExtract.timeStamp === timeStamp && reportExtract.jobID === jobID
|
|
19
|
-
);
|
|
20
|
-
const {error, url, what} = reportExtract;
|
|
21
|
-
// If this failed:
|
|
22
|
-
if (error) {
|
|
23
|
-
// Return why.
|
|
24
|
-
return {
|
|
25
|
-
status: 'error',
|
|
26
|
-
message: error
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
// Otherwise, i.e. if it succeeded, process the request.
|
|
30
|
-
return await processTestRequest('retest', __dirname, what, url, why);
|
|
31
|
-
};
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
requestRetest.test.js
|
|
3
|
-
Unit tests for web/requestRetest/index.js, covering the success path of answer.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {test, before, after, beforeEach} = require('node:test');
|
|
9
|
-
const assert = require('node:assert/strict');
|
|
10
|
-
const fs = require('node:fs/promises');
|
|
11
|
-
const path = require('node:path');
|
|
12
|
-
const {parse} = require('node-html-parser');
|
|
13
|
-
|
|
14
|
-
// CONSTANTS
|
|
15
|
-
|
|
16
|
-
const fixtureDBDir = path.join(__dirname, '..', '..', 'test', 'fixtures', 'db');
|
|
17
|
-
const recsPath = path.join(fixtureDBDir, 'jobs', 'recs.json');
|
|
18
|
-
|
|
19
|
-
// SETUP AND TEARDOWN
|
|
20
|
-
|
|
21
|
-
before(async () => {
|
|
22
|
-
process.env.DB_DIR = fixtureDBDir;
|
|
23
|
-
process.env.AUTH_CODE = 'test-auth-code';
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
// Reset recs.json and clean job directories before each test.
|
|
28
|
-
await fs.writeFile(recsPath, '{}\n');
|
|
29
|
-
for (const sub of ['claimed', 'queue', 'failed']) {
|
|
30
|
-
const dir = path.join(fixtureDBDir, 'jobs', sub);
|
|
31
|
-
await fs.mkdir(dir, {recursive: true});
|
|
32
|
-
const files = await fs.readdir(dir).catch(() => []);
|
|
33
|
-
for (const file of files) {
|
|
34
|
-
try {
|
|
35
|
-
await fs.unlink(path.join(dir, file));
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Ignore cleanup errors for files that may not exist.
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
after(async () => {
|
|
45
|
-
// Restore recs.json and clean job directories after all tests.
|
|
46
|
-
await fs.writeFile(recsPath, '{}\n');
|
|
47
|
-
for (const sub of ['claimed', 'queue', 'failed']) {
|
|
48
|
-
const dir = path.join(fixtureDBDir, 'jobs', sub);
|
|
49
|
-
await fs.mkdir(dir, {recursive: true});
|
|
50
|
-
const files = await fs.readdir(dir).catch(() => []);
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
try {
|
|
53
|
-
await fs.unlink(path.join(dir, file));
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// Ignore cleanup errors for files that may not exist.
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// TESTS
|
|
63
|
-
|
|
64
|
-
test('answer returns ok with a populated answer page for a valid retest request', async () => {
|
|
65
|
-
// Require the module after DB_DIR is set.
|
|
66
|
-
const {answer} = require('./index');
|
|
67
|
-
// Use the 260101T0001-ct report (All CantTell Page, https://example.com/canttell).
|
|
68
|
-
const result = await answer('260101T0001/ct', 'Because changes were made');
|
|
69
|
-
assert.equal(result.status, 'ok');
|
|
70
|
-
assert.ok(result.answerPage, 'answerPage should be present');
|
|
71
|
-
// Verify the placeholders were replaced with the report target.
|
|
72
|
-
assert.ok(result.answerPage.includes('All CantTell Page'));
|
|
73
|
-
assert.ok(result.answerPage.includes('Because changes were made'));
|
|
74
|
-
assert.ok(!result.answerPage.includes('__target__'));
|
|
75
|
-
assert.ok(!result.answerPage.includes('__why__'));
|
|
76
|
-
// Verify the HTML is well-formed.
|
|
77
|
-
const doc = parse(result.answerPage);
|
|
78
|
-
const h1 = doc.querySelector('h1');
|
|
79
|
-
assert.ok(h1);
|
|
80
|
-
assert.ok(h1.textContent.includes('All CantTell Page'));
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('answer throws when the report does not exist', async () => {
|
|
84
|
-
const {answer} = require('./index');
|
|
85
|
-
// Use a nonexistent report timestamp and jobID.
|
|
86
|
-
await assert.rejects(
|
|
87
|
-
() => answer('990101T0000/xxx', 'Because changes were made'),
|
|
88
|
-
/Cannot destructure property 'error'/
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('answer returns an error for an invalid retest recommendation', async () => {
|
|
93
|
-
const {answer} = require('./index');
|
|
94
|
-
// Use a valid report but a too-short reason.
|
|
95
|
-
const result = await answer('260101T0001/ct', 'why');
|
|
96
|
-
assert.equal(result.status, 'error');
|
|
97
|
-
assert.equal(result.message, 'Invalid recommendation');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('answer returns an error when the report extract has an error', async () => {
|
|
101
|
-
// Mock getLatestReportExtracts to return an extract with an error.
|
|
102
|
-
const util = require('../../util');
|
|
103
|
-
const original = util.getLatestReportExtracts;
|
|
104
|
-
util.getLatestReportExtracts = async () => [
|
|
105
|
-
{timeStamp: '260101T0001', jobID: 'ct', error: 'Report data unavailable'}
|
|
106
|
-
];
|
|
107
|
-
try {
|
|
108
|
-
// Re-require to pick up the mocked function.
|
|
109
|
-
delete require.cache[require.resolve('./index')];
|
|
110
|
-
const {answer} = require('./index');
|
|
111
|
-
const result = await answer('260101T0001/ct', 'Because changes were made');
|
|
112
|
-
assert.equal(result.status, 'error');
|
|
113
|
-
assert.equal(result.message, 'Report data unavailable');
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
util.getLatestReportExtracts = original;
|
|
117
|
-
delete require.cache[require.resolve('./index')];
|
|
118
|
-
}
|
|
119
|
-
});
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Serves a form for requesting a test.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const fs = require('fs/promises');
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
// FUNCTIONS
|
|
12
|
-
|
|
13
|
-
exports.answer = async () => {
|
|
14
|
-
// Get the test request form template.
|
|
15
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
16
|
-
// Return it.
|
|
17
|
-
return {
|
|
18
|
-
status: 'ok',
|
|
19
|
-
answerPage
|
|
20
|
-
};
|
|
21
|
-
};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
index.js
|
|
3
|
-
Serves a form for deleting latest superseding reports.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {getReportData, reportsPath} = require('../../util');
|
|
9
|
-
const fs = require('fs/promises');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
|
|
12
|
-
// FUNCTIONS
|
|
13
|
-
|
|
14
|
-
// Returns a form for deleting latest superseding reports.
|
|
15
|
-
exports.answer = async (_, search) => {
|
|
16
|
-
const searchParams = new URLSearchParams(search);
|
|
17
|
-
const authCode = searchParams?.get('authCode');
|
|
18
|
-
const jobNames = searchParams?.getAll('report');
|
|
19
|
-
// If the form has been displayed by itself after a submission and any reports are to be deleted:
|
|
20
|
-
if (jobNames?.length) {
|
|
21
|
-
// If the authorization code is valid:
|
|
22
|
-
if (authCode === process.env.AUTH_CODE) {
|
|
23
|
-
try {
|
|
24
|
-
// For each report to be deleted:
|
|
25
|
-
for (const jobName of jobNames) {
|
|
26
|
-
// Delete it.
|
|
27
|
-
await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// If this failed:
|
|
31
|
-
catch (error) {
|
|
32
|
-
// Return why.
|
|
33
|
-
return {
|
|
34
|
-
status: 'error',
|
|
35
|
-
message: `Deleting latest superseding reports failed (${error.message})`
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Otherwise, i.e. if the authorization code is invalid:
|
|
40
|
-
else {
|
|
41
|
-
// Report the error.
|
|
42
|
-
return {
|
|
43
|
-
status: 'error',
|
|
44
|
-
message: 'Invalid authorization code'
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
const reportNames = await fs.readdir(reportsPath());
|
|
49
|
-
const reportSpecs = [];
|
|
50
|
-
// For each report:
|
|
51
|
-
for (const reportName of reportNames) {
|
|
52
|
-
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
53
|
-
// Get a summary of it.
|
|
54
|
-
const reportFacts = await getReportData(timeStamp, jobID);
|
|
55
|
-
const {error, issueCount, preventedEngineCount, url} = reportFacts;
|
|
56
|
-
// If this failed:
|
|
57
|
-
if (error) {
|
|
58
|
-
// Return why.
|
|
59
|
-
return {
|
|
60
|
-
status: 'error',
|
|
61
|
-
message: error
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
reportSpecs.push({
|
|
65
|
-
timeStamp,
|
|
66
|
-
jobID,
|
|
67
|
-
issueCount,
|
|
68
|
-
preventedEngineCount,
|
|
69
|
-
url
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
// Sort the summaries by URL and then by time stamp.
|
|
73
|
-
reportSpecs.sort((a, b) => {
|
|
74
|
-
if (a.url === b.url) {
|
|
75
|
-
return a.timeStamp.localeCompare(b.timeStamp);
|
|
76
|
-
}
|
|
77
|
-
return a.url.localeCompare(b.url);
|
|
78
|
-
});
|
|
79
|
-
const lines = [];
|
|
80
|
-
const margin = ' '.repeat(12);
|
|
81
|
-
let anyDeletable = false;
|
|
82
|
-
// For each summary:
|
|
83
|
-
reportSpecs.forEach((spec, index) => {
|
|
84
|
-
const {timeStamp, jobID, issueCount, preventedEngineCount, url} = spec;
|
|
85
|
-
const jobName = `${timeStamp}-${jobID}`;
|
|
86
|
-
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${preventedEngineCount}, issues ${issueCount}`;
|
|
87
|
-
// If its report is the latest report on a target with at least 2 reports:
|
|
88
|
-
if (reportSpecs[index - 1]?.url === url && reportSpecs[index + 1]?.url !== url) {
|
|
89
|
-
// Add a line with a deletion checkbox.
|
|
90
|
-
lines.push(
|
|
91
|
-
`${margin}<p><input type="checkbox" name="report" value="${jobName}"> ${specString}</p>`
|
|
92
|
-
);
|
|
93
|
-
anyDeletable = true;
|
|
94
|
-
}
|
|
95
|
-
// Otherwise, i.e. if its report is a superseded report or the only report on its target:
|
|
96
|
-
else {
|
|
97
|
-
// Add a line without a deletion checkbox.
|
|
98
|
-
lines.push(`${margin}<p>${specString}</p>`);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
const intro = anyDeletable
|
|
102
|
-
? 'Choose the latest superseding reports to delete.'
|
|
103
|
-
: 'Each target has only 1 report, so there are no reports to delete.';
|
|
104
|
-
const disabled = anyDeletable ? '' : ' disabled';
|
|
105
|
-
const query = {
|
|
106
|
-
reports: lines.join('\n'),
|
|
107
|
-
intro,
|
|
108
|
-
disabled
|
|
109
|
-
};
|
|
110
|
-
// Get the order form template.
|
|
111
|
-
let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
|
|
112
|
-
// Replace its placeholders.
|
|
113
|
-
Object.keys(query).forEach(param => {
|
|
114
|
-
answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
|
|
115
|
-
});
|
|
116
|
-
// Return the populated page.
|
|
117
|
-
return {
|
|
118
|
-
status: 'ok',
|
|
119
|
-
answerPage
|
|
120
|
-
};
|
|
121
|
-
};
|