@jrpool/kilotest 46.1.2 → 48.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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.ts +144 -0
- package/api/requestRetest.ts +119 -0
- package/api/requestTest.test.ts +153 -0
- package/api/requestTest.ts +123 -0
- 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} +35 -14
- package/api/{util.js → util.ts} +54 -51
- 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 +360 -0
- package/docs/SERVICE.md +170 -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} +397 -152
- package/{index.js → index.ts} +386 -285
- 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.ts +1129 -0
- package/util.ts +1017 -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} +74 -58
- 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 +71 -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} +57 -39
- package/web/requestRetest/index.html +7 -7
- package/web/requestRetest/index.ts +62 -0
- package/web/requestRetest/requestRetest.test.ts +130 -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.ts +52 -0
- package/web/requestTest/{requestTest.test.js → requestTest.test.ts} +39 -24
- 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} +18 -22
- 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/requestRetest.js +0 -96
- package/api/requestRetest.test.js +0 -80
- package/api/requestTest.js +0 -106
- package/api/requestTest.test.js +0 -92
- 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/util.test.js +0 -968
- 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/requestTest/index.js +0 -24
- 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
|
@@ -3,7 +3,19 @@
|
|
|
3
3
|
"allow": [
|
|
4
4
|
"WebSearch",
|
|
5
5
|
"Bash(gh api *)",
|
|
6
|
-
"WebFetch(domain:raw.githubusercontent.com)"
|
|
6
|
+
"WebFetch(domain:raw.githubusercontent.com)",
|
|
7
|
+
"Bash(ls -la /Users/pool/Documents/Topics/repos/a11yTesting/kilotest/*.json)",
|
|
8
|
+
"Bash(grep -n \"^import\\\\|^export\" /Users/pool/Documents/Topics/repos/a11yTesting/qai/src/*.ts)",
|
|
9
|
+
"Bash(find /Users/pool/Documents/Topics/repos/a11yTesting/kilotest -type f \\\\\\( -name \"*.test.js\" -o -name \"*.test.cjs\" -o -name \"test.js\" \\\\\\) ! -path \"*/node_modules/*\" -exec wc -l {} +)",
|
|
10
|
+
"Bash(find /Users/pool/Documents/Topics/repos/a11yTesting/kilotest -type f \\\\\\( -name \"*.test.js\" -o -name \"*.test.cjs\" -o -name \"test.js\" \\\\\\) ! -path \"*/node_modules/*\" -exec grep -h \"^test\\(\" {} +)",
|
|
11
|
+
"Bash(npm install:*)",
|
|
12
|
+
"Bash(npm run:*)",
|
|
13
|
+
"Bash(npm test:*)",
|
|
14
|
+
"Bash(git add:*)",
|
|
15
|
+
"Bash(git rm:*)",
|
|
16
|
+
"Bash(git commit -m ':*)",
|
|
17
|
+
"Bash(git push:*)",
|
|
18
|
+
"Bash(gh auth:*)"
|
|
7
19
|
]
|
|
8
20
|
}
|
|
9
21
|
}
|
package/.devin/config.local.json
CHANGED
package/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Periodic Smoke Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
# Run daily at 14:14 UTC
|
|
6
|
+
- cron: '14 14 * * *'
|
|
7
|
+
workflow_dispatch: # Allow manual triggering
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
smoke-tests:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout code
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set Node.js version
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: '>=24.16.0'
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Run smoke tests against deployed service
|
|
28
|
+
run: npm exec -- node smokeTest.ts
|
|
29
|
+
env:
|
|
30
|
+
SMOKE_HOST: kilotest.com
|
|
@@ -5,7 +5,7 @@ on:
|
|
|
5
5
|
branches: [main]
|
|
6
6
|
paths:
|
|
7
7
|
- 'server.json'
|
|
8
|
-
- 'api/version.
|
|
8
|
+
- 'api/version.ts'
|
|
9
9
|
|
|
10
10
|
jobs:
|
|
11
11
|
publish:
|
|
@@ -23,12 +23,12 @@ jobs:
|
|
|
23
23
|
with:
|
|
24
24
|
node-version: '>=24.16.0'
|
|
25
25
|
|
|
26
|
-
- name: Verify server.json version matches api/version.
|
|
26
|
+
- name: Verify server.json version matches api/version.ts
|
|
27
27
|
run: |
|
|
28
|
-
FILE_VERSION=$(node -e "console.log(require('./api/version').version)")
|
|
28
|
+
FILE_VERSION=$(node -e "console.log(require('./api/version.ts').version)")
|
|
29
29
|
JSON_VERSION=$(node -e "console.log(require('./server.json').version)")
|
|
30
30
|
if [ "$FILE_VERSION" != "$JSON_VERSION" ]; then
|
|
31
|
-
echo "::error::server.json version ($JSON_VERSION) does not match api/version.
|
|
31
|
+
echo "::error::server.json version ($JSON_VERSION) does not match api/version.ts version ($FILE_VERSION)"
|
|
32
32
|
exit 1
|
|
33
33
|
fi
|
|
34
34
|
echo "Versions match: $FILE_VERSION"
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
/*
|
|
2
|
-
alerts.test.
|
|
3
|
-
Tests for alerts.
|
|
2
|
+
alerts.test.ts
|
|
3
|
+
Tests for alerts.ts, covering the success, failure, error, timeout, and unconfigured paths.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
import {test, before, after} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import https from 'node:https';
|
|
11
|
+
import {EventEmitter} from 'node:events';
|
|
12
|
+
import {sendAlert} from './alerts.ts';
|
|
12
13
|
|
|
13
14
|
// SETUP AND TEARDOWN
|
|
14
15
|
|
|
15
16
|
const originalRequest = https.request;
|
|
16
|
-
const savedEnv = {};
|
|
17
|
+
const savedEnv: Record<string, any> = {};
|
|
17
18
|
|
|
18
19
|
before(() => {
|
|
19
20
|
for (const key of ['MANAGER_EMAIL', 'ALERT_API_HOST', 'ALERT_API_PATH', 'ALERT_API_KEY', 'ALERT_FROM']) {
|
|
@@ -31,29 +32,27 @@ after(() => {
|
|
|
31
32
|
delete process.env[key];
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
delete require.cache[require.resolve('./alerts')];
|
|
35
|
-
require('./alerts');
|
|
36
35
|
});
|
|
37
36
|
|
|
38
37
|
// HELPER
|
|
39
38
|
|
|
40
39
|
// Replaces https.request with a function that returns a fake request and invokes the callback with a fake response. The mockHandler is called on next tick so that event listeners are attached first.
|
|
41
|
-
const mockRequest = mockHandler => {
|
|
42
|
-
https.request = (options, callback) => {
|
|
43
|
-
const req = new EventEmitter();
|
|
40
|
+
const mockRequest = (mockHandler: any) => {
|
|
41
|
+
https.request = ((options: any, callback: any) => {
|
|
42
|
+
const req: any = new EventEmitter();
|
|
44
43
|
req.write = () => {};
|
|
45
44
|
req.end = () => {};
|
|
46
45
|
req.destroy = () => {
|
|
47
46
|
req.emit('error', new Error('Socket destroyed'));
|
|
48
47
|
};
|
|
49
|
-
req.setTimeout = (ms, fn) => {
|
|
48
|
+
req.setTimeout = (ms: any, fn: any) => {
|
|
50
49
|
process.nextTick(fn);
|
|
51
50
|
};
|
|
52
|
-
const res = new EventEmitter();
|
|
51
|
+
const res: any = new EventEmitter();
|
|
53
52
|
res.statusCode = 200;
|
|
54
53
|
process.nextTick(() => mockHandler(req, res, callback));
|
|
55
54
|
return req;
|
|
56
|
-
};
|
|
55
|
+
}) as any;
|
|
57
56
|
};
|
|
58
57
|
|
|
59
58
|
// Sets the alert configuration env vars so that sendAlert enters the configured path.
|
|
@@ -72,29 +71,21 @@ const clearAlertConfig = () => {
|
|
|
72
71
|
}
|
|
73
72
|
};
|
|
74
73
|
|
|
75
|
-
// Loads alerts.js fresh so it picks up the current env vars and https.request.
|
|
76
|
-
const loadAlerts = () => {
|
|
77
|
-
delete require.cache[require.resolve('./alerts')];
|
|
78
|
-
return require('./alerts');
|
|
79
|
-
};
|
|
80
|
-
|
|
81
74
|
// TESTS
|
|
82
75
|
|
|
83
76
|
test('sendAlert resolves without sending when configuration is incomplete', async () => {
|
|
84
77
|
clearAlertConfig();
|
|
85
|
-
const {sendAlert} = loadAlerts();
|
|
86
78
|
await sendAlert('Test', 'Body');
|
|
87
79
|
assert.ok(true);
|
|
88
80
|
});
|
|
89
81
|
|
|
90
82
|
test('sendAlert logs success when the API responds with a 2xx status', async () => {
|
|
91
83
|
setAlertConfig();
|
|
92
|
-
mockRequest((req, res, callback) => {
|
|
84
|
+
mockRequest((req: any, res: any, callback: any) => {
|
|
93
85
|
res.statusCode = 200;
|
|
94
86
|
callback(res);
|
|
95
87
|
res.emit('end');
|
|
96
88
|
});
|
|
97
|
-
const {sendAlert} = loadAlerts();
|
|
98
89
|
await sendAlert('Test Success', 'Body');
|
|
99
90
|
https.request = originalRequest;
|
|
100
91
|
assert.ok(true);
|
|
@@ -102,13 +93,12 @@ test('sendAlert logs success when the API responds with a 2xx status', async ()
|
|
|
102
93
|
|
|
103
94
|
test('sendAlert logs an error when the API responds with a non-2xx status', async () => {
|
|
104
95
|
setAlertConfig();
|
|
105
|
-
mockRequest((req, res, callback) => {
|
|
96
|
+
mockRequest((req: any, res: any, callback: any) => {
|
|
106
97
|
res.statusCode = 500;
|
|
107
98
|
callback(res);
|
|
108
99
|
res.emit('data', 'Server error');
|
|
109
100
|
res.emit('end');
|
|
110
101
|
});
|
|
111
|
-
const {sendAlert} = loadAlerts();
|
|
112
102
|
await sendAlert('Test Failure', 'Body');
|
|
113
103
|
https.request = originalRequest;
|
|
114
104
|
assert.ok(true);
|
|
@@ -116,10 +106,9 @@ test('sendAlert logs an error when the API responds with a non-2xx status', asyn
|
|
|
116
106
|
|
|
117
107
|
test('sendAlert logs an error when the request errors', async () => {
|
|
118
108
|
setAlertConfig();
|
|
119
|
-
mockRequest((req) => {
|
|
109
|
+
mockRequest((req: any) => {
|
|
120
110
|
req.emit('error', new Error('Connection refused'));
|
|
121
111
|
});
|
|
122
|
-
const {sendAlert} = loadAlerts();
|
|
123
112
|
await sendAlert('Test Error', 'Body');
|
|
124
113
|
https.request = originalRequest;
|
|
125
114
|
assert.ok(true);
|
|
@@ -130,7 +119,6 @@ test('sendAlert logs an error when the request times out', async () => {
|
|
|
130
119
|
mockRequest(() => {
|
|
131
120
|
// Do nothing; the setTimeout mock will fire and call req.destroy().
|
|
132
121
|
});
|
|
133
|
-
const {sendAlert} = loadAlerts();
|
|
134
122
|
await sendAlert('Test Timeout', 'Body');
|
|
135
123
|
https.request = originalRequest;
|
|
136
124
|
assert.ok(true);
|
package/{alerts.js → alerts.ts}
RENAMED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
/*
|
|
2
|
-
alerts.
|
|
2
|
+
alerts.ts
|
|
3
3
|
Sends alert emails to a Kilotest manager when required.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
// CONSTANTS
|
|
11
|
-
|
|
12
|
-
// Alert configuration.
|
|
13
|
-
const MANAGER_EMAIL = process.env.MANAGER_EMAIL;
|
|
14
|
-
const ALERT_API_HOST = process.env.ALERT_API_HOST;
|
|
15
|
-
const ALERT_API_PATH = process.env.ALERT_API_PATH;
|
|
16
|
-
const ALERT_API_KEY = process.env.ALERT_API_KEY;
|
|
17
|
-
const ALERT_FROM = process.env.ALERT_FROM;
|
|
8
|
+
import https from 'node:https';
|
|
18
9
|
|
|
19
10
|
// FUNCTIONS
|
|
20
11
|
|
|
21
12
|
// Sends an email alert to a manager.
|
|
22
|
-
|
|
13
|
+
export const sendAlert = (subject: string, body: string): Promise<void> => new Promise(resolve => {
|
|
14
|
+
// Alert configuration, read at call time so it is not frozen when this module is loaded.
|
|
15
|
+
const MANAGER_EMAIL = process.env['MANAGER_EMAIL'];
|
|
16
|
+
const ALERT_API_HOST = process.env['ALERT_API_HOST'];
|
|
17
|
+
const ALERT_API_PATH = process.env['ALERT_API_PATH'];
|
|
18
|
+
const ALERT_API_KEY = process.env['ALERT_API_KEY'];
|
|
19
|
+
const ALERT_FROM = process.env['ALERT_FROM'];
|
|
23
20
|
// If the alert configuration is complete:
|
|
24
21
|
if (MANAGER_EMAIL && ALERT_API_HOST && ALERT_API_PATH && ALERT_API_KEY && ALERT_FROM) {
|
|
25
22
|
const payload = JSON.stringify({
|
|
@@ -45,7 +42,7 @@ exports.sendAlert = (subject, body) => new Promise(resolve => {
|
|
|
45
42
|
});
|
|
46
43
|
res.on('end', () => {
|
|
47
44
|
// If this succeeded:
|
|
48
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
45
|
+
if (res.statusCode !== undefined && res.statusCode >= 200 && res.statusCode < 300) {
|
|
49
46
|
// Report this.
|
|
50
47
|
console.log(`Alert sent (${subject})`);
|
|
51
48
|
}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
/*
|
|
2
|
-
getReport.test.
|
|
3
|
-
Tests for api/getReport.
|
|
2
|
+
getReport.test.ts
|
|
3
|
+
Tests for api/getReport.ts using the fixture corpus.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const {response} = require('./getReport');
|
|
8
|
+
import {test, before, after} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import {response} from './getReport.ts';
|
|
12
11
|
|
|
13
12
|
// SETUP AND TEARDOWN
|
|
14
13
|
|
|
15
14
|
const savedDBDir = process.env.DB_DIR;
|
|
16
15
|
|
|
17
|
-
before(() => {
|
|
18
|
-
process.env.DB_DIR =
|
|
16
|
+
before(async () => {
|
|
17
|
+
process.env.DB_DIR = (await import('../test/dbFixture.ts')).fixtureDBDir;
|
|
19
18
|
});
|
|
20
19
|
|
|
21
20
|
after(() => {
|
|
@@ -33,8 +32,8 @@ test('getReport returns the full report for a valid report', async () => {
|
|
|
33
32
|
const body = await response(['260101T0000', 'mix']);
|
|
34
33
|
const content = body['response content'];
|
|
35
34
|
assert.equal(typeof content['size of the report in bytes'], 'number');
|
|
36
|
-
assert.ok(content['size of the report in bytes'] > 0);
|
|
37
|
-
const report = content['full report'];
|
|
35
|
+
assert.ok((content['size of the report in bytes'] as number) > 0);
|
|
36
|
+
const report = content['full report'] as any;
|
|
38
37
|
assert.equal(report.id, '260101T0000-mix');
|
|
39
38
|
assert.equal(report.target.what, 'Mixed Outcomes Page');
|
|
40
39
|
assert.equal(report.target.url, 'https://example.com/mixed');
|
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
/*
|
|
2
|
-
getReport.
|
|
2
|
+
getReport.ts
|
|
3
3
|
Returns one report.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import {z} from 'zod';
|
|
9
|
+
import {
|
|
9
10
|
getResponseMetadata,
|
|
10
11
|
getToolsFacts,
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
12
|
+
getThisHost
|
|
13
|
+
} from './util.ts';
|
|
14
|
+
import {getReport, getReportStats} from '../util.ts';
|
|
15
|
+
import {getReportResponseSchema} from './schemas.ts';
|
|
16
|
+
|
|
17
|
+
// TYPES
|
|
18
|
+
|
|
19
|
+
// The response content defined by the response schema.
|
|
20
|
+
type ResponseContent = z.infer<typeof getReportResponseSchema>['response content'];
|
|
14
21
|
|
|
15
22
|
// FUNCTIONS
|
|
16
23
|
|
|
17
24
|
// Returns the response body.
|
|
18
|
-
|
|
25
|
+
export const response = async (args: string[]) => {
|
|
19
26
|
const [timeStamp = '', jobID = ''] = args;
|
|
20
|
-
|
|
21
|
-
const responseContent = {
|
|
22
|
-
'size of the report in bytes': null,
|
|
23
|
-
'full report': null
|
|
24
|
-
};
|
|
27
|
+
const thisHost = getThisHost();
|
|
25
28
|
// Get the report size.
|
|
26
29
|
const reportStats = await getReportStats(timeStamp, jobID);
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
// Otherwise, i.e. if it succeeded:
|
|
33
|
-
else {
|
|
34
|
-
const {reportSize} = reportStats;
|
|
35
|
-
// Add the size to the response content.
|
|
36
|
-
responseContent['size of the report in bytes'] = reportSize;
|
|
30
|
+
// Create the response content.
|
|
31
|
+
const responseContent: ResponseContent = reportStats
|
|
32
|
+
? {
|
|
33
|
+
'size of the report in bytes': reportStats.reportSize,
|
|
37
34
|
// Get the report (which may be only an error message).
|
|
38
|
-
|
|
39
|
-
// Add it to the response content.
|
|
40
|
-
responseContent['full report'] = report;
|
|
35
|
+
'full report': await getReport(timeStamp, jobID)
|
|
41
36
|
}
|
|
37
|
+
: {
|
|
38
|
+
'size of the report in bytes': 'Error: The report could not be accessed for an unknown reason.',
|
|
39
|
+
'full report': null
|
|
40
|
+
};
|
|
42
41
|
// Create a response body.
|
|
43
42
|
const body = {
|
|
44
43
|
'tool collection': getToolsFacts(),
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
/*
|
|
2
|
-
listDiagnoses.test.
|
|
3
|
-
Tests for api/listDiagnoses.
|
|
2
|
+
listDiagnoses.test.ts
|
|
3
|
+
Tests for api/listDiagnoses.ts using the fixture corpus, with emphasis on outcome handling.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const {response} = require('./listDiagnoses');
|
|
8
|
+
import {test, before, after} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import {response} from './listDiagnoses.ts';
|
|
12
11
|
|
|
13
12
|
// SETUP AND TEARDOWN
|
|
14
13
|
|
|
15
14
|
const savedDBDir = process.env.DB_DIR;
|
|
16
15
|
|
|
17
|
-
before(() => {
|
|
18
|
-
process.env.DB_DIR =
|
|
16
|
+
before(async () => {
|
|
17
|
+
process.env.DB_DIR = (await import('../test/dbFixture.ts')).fixtureDBDir;
|
|
19
18
|
});
|
|
20
19
|
|
|
21
20
|
after(() => {
|
|
@@ -31,23 +30,23 @@ after(() => {
|
|
|
31
30
|
|
|
32
31
|
test('listDiagnoses returns 2 diagnoses for catalogIndex 0, linkNoText, in the mixed report (axe and alfa, excluding cantTell)', async () => {
|
|
33
32
|
const body = await response(['0', 'linkNoText', '260101T0000', 'mix']);
|
|
34
|
-
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
33
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
35
34
|
assert.equal(diagnoses.length, 2);
|
|
36
|
-
const rules = diagnoses.map(d => d['identifier of the violated rule']).sort();
|
|
35
|
+
const rules = diagnoses.map((d: any) => d['identifier of the violated rule']).sort();
|
|
37
36
|
assert.deepEqual(rules, ['r11', 'r11']);
|
|
38
|
-
const descriptions = diagnoses.map(d => d['description of the violation']);
|
|
39
|
-
assert.ok(descriptions.every(d => d === 'The link does not have an accessible name'));
|
|
37
|
+
const descriptions = diagnoses.map((d: any) => d['description of the violation']);
|
|
38
|
+
assert.ok(descriptions.every((d: any) => d === 'The link does not have an accessible name'));
|
|
40
39
|
});
|
|
41
40
|
|
|
42
41
|
test('listDiagnoses returns 0 diagnoses when all instances are cantTell', async () => {
|
|
43
42
|
const body = await response(['0', 'focusIndicationBad', '260101T0001', 'ct']);
|
|
44
|
-
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
43
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
45
44
|
assert.equal(diagnoses.length, 0);
|
|
46
45
|
});
|
|
47
46
|
|
|
48
47
|
test('listDiagnoses treats missing outcome as a violation', async () => {
|
|
49
48
|
const body = await response(['0', 'linkNoText', '260101T0002', 'no']);
|
|
50
|
-
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
49
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
51
50
|
assert.equal(diagnoses.length, 1);
|
|
52
51
|
assert.equal(diagnoses[0]['description of the violation'], 'Button has no accessible name');
|
|
53
52
|
assert.equal(diagnoses[0]['severity of the violation on a 0-to-3 scale'], 3);
|
|
@@ -55,7 +54,7 @@ test('listDiagnoses treats missing outcome as a violation', async () => {
|
|
|
55
54
|
|
|
56
55
|
test('listDiagnoses returns element basics from the catalog', async () => {
|
|
57
56
|
const body = await response(['0', 'linkNoText', '260101T0000', 'mix']);
|
|
58
|
-
const elementBasics = body['response content']['basics about the element'];
|
|
57
|
+
const elementBasics = body['response content']['basics about the element'] as any;
|
|
59
58
|
assert.equal(elementBasics.identifier, '0');
|
|
60
59
|
assert.equal(elementBasics['tag name'], 'A');
|
|
61
60
|
assert.equal(elementBasics['inner text'], 'About Us');
|
|
@@ -63,7 +62,7 @@ test('listDiagnoses returns element basics from the catalog', async () => {
|
|
|
63
62
|
|
|
64
63
|
test('listDiagnoses returns element details from the catalog', async () => {
|
|
65
64
|
const body = await response(['0', 'linkNoText', '260101T0000', 'mix']);
|
|
66
|
-
const elementDetails = body['response content']['details about the element'];
|
|
65
|
+
const elementDetails = body['response content']['details about the element'] as any;
|
|
67
66
|
assert.equal(elementDetails['start tag'], '<a>');
|
|
68
67
|
assert.equal(elementDetails['XPath'], '/html/body/a[1]');
|
|
69
68
|
assert.equal(elementDetails['x, y, width, and height of bounding box'], '10:20:80:30');
|
|
@@ -71,55 +70,55 @@ test('listDiagnoses returns element details from the catalog', async () => {
|
|
|
71
70
|
|
|
72
71
|
test('listDiagnoses returns an error for an unknown issue ID', async () => {
|
|
73
72
|
const body = await response(['0', 'nonexistentIssue', '260101T0000', 'mix']);
|
|
74
|
-
const basics = body['response content']['basics about the issue'];
|
|
73
|
+
const basics = body['response content']['basics about the issue'] as any;
|
|
75
74
|
assert.ok(basics.error);
|
|
76
75
|
});
|
|
77
76
|
|
|
78
77
|
test('listDiagnoses returns an error for a nonexistent catalog index', async () => {
|
|
79
78
|
const body = await response(['999', 'linkNoText', '260101T0000', 'mix']);
|
|
80
|
-
const basics = body['response content']['basics about the element'];
|
|
79
|
+
const basics = body['response content']['basics about the element'] as any;
|
|
81
80
|
assert.ok(basics.error);
|
|
82
81
|
});
|
|
83
82
|
|
|
84
83
|
test('listDiagnoses returns an error for a nonexistent report', async () => {
|
|
85
84
|
const body = await response(['0', 'linkNoText', '999999T9999', 'xyz']);
|
|
86
|
-
const basics = body['response content']['basics about the report'];
|
|
85
|
+
const basics = body['response content']['basics about the report'] as any;
|
|
87
86
|
assert.ok(basics.error);
|
|
88
87
|
});
|
|
89
88
|
|
|
90
89
|
test('listDiagnoses returns guideline layer for an issue with a short WCAG code', async () => {
|
|
91
90
|
const body = await response(['0', 'duplicateID', '260101T0009', 'brd']);
|
|
92
|
-
const issueBasics = body['response content']['basics about the issue'];
|
|
91
|
+
const issueBasics = body['response content']['basics about the issue'] as any;
|
|
93
92
|
assert.equal(issueBasics['related WCAG standard'].layer, 'guideline');
|
|
94
93
|
assert.equal(issueBasics['related WCAG standard'].identifier, '4.1');
|
|
95
94
|
});
|
|
96
95
|
|
|
97
96
|
test('listDiagnoses returns null tag name and inner text for a catalog item missing them', async () => {
|
|
98
97
|
const body = await response(['1', 'duplicateID', '260101T0009', 'brd']);
|
|
99
|
-
const elementBasics = body['response content']['basics about the element'];
|
|
98
|
+
const elementBasics = body['response content']['basics about the element'] as any;
|
|
100
99
|
assert.equal(elementBasics['tag name'], null);
|
|
101
100
|
assert.equal(elementBasics['inner text'], null);
|
|
102
101
|
});
|
|
103
102
|
|
|
104
103
|
test('listDiagnoses returns null rule ID when ruleID equals what', async () => {
|
|
105
104
|
const body = await response(['0', 'duplicateID', '260101T0009', 'brd']);
|
|
106
|
-
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
107
|
-
const axeDiagnosis = diagnoses.find(d => d['description of the violation'] === 'r99');
|
|
105
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
106
|
+
const axeDiagnosis = diagnoses.find((d: any) => d['description of the violation'] === 'r99');
|
|
108
107
|
assert.ok(axeDiagnosis);
|
|
109
108
|
assert.equal(axeDiagnosis['identifier of the violated rule'], null);
|
|
110
109
|
});
|
|
111
110
|
|
|
112
111
|
test('listDiagnoses defaults count to 1 when count is missing', async () => {
|
|
113
112
|
const body = await response(['1', 'duplicateID', '260101T0009', 'brd']);
|
|
114
|
-
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
115
|
-
const ibmDiagnosis = diagnoses.find(d => d['description of the violation'] === 'Element has no role');
|
|
113
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
114
|
+
const ibmDiagnosis = diagnoses.find((d: any) => d['description of the violation'] === 'Element has no role');
|
|
116
115
|
assert.ok(ibmDiagnosis);
|
|
117
116
|
assert.equal(ibmDiagnosis['count of violations of the rule by the element'], 1);
|
|
118
117
|
});
|
|
119
118
|
|
|
120
119
|
test('listDiagnoses returns null for missing start tag, XPath, and bounding box', async () => {
|
|
121
120
|
const body = await response(['2', 'duplicateID', '260101T0009', 'brd']);
|
|
122
|
-
const elementDetails = body['response content']['details about the element'];
|
|
121
|
+
const elementDetails = body['response content']['details about the element'] as any;
|
|
123
122
|
assert.equal(elementDetails['start tag'], null);
|
|
124
123
|
assert.equal(elementDetails['XPath'], null);
|
|
125
124
|
assert.equal(elementDetails['x, y, width, and height of bounding box'], null);
|
|
@@ -127,6 +126,6 @@ test('listDiagnoses returns null for missing start tag, XPath, and bounding box'
|
|
|
127
126
|
|
|
128
127
|
test('listDiagnoses returns issue error for a falsy issue ID', async () => {
|
|
129
128
|
const body = await response(['0', '', '260101T0009', 'brd']);
|
|
130
|
-
const issueBasics = body['response content']['basics about the issue'];
|
|
129
|
+
const issueBasics = body['response content']['basics about the issue'] as any;
|
|
131
130
|
assert.ok(issueBasics.error);
|
|
132
131
|
});
|