@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,17 +1,17 @@
|
|
|
1
1
|
/*
|
|
2
|
-
unhideReportForm.test.
|
|
3
|
-
Unit tests for web/unhideReportForm/index.
|
|
2
|
+
unhideReportForm.test.ts
|
|
3
|
+
Unit tests for web/unhideReportForm/index.ts.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
import {test, before, after, beforeEach} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import fs from 'fs/promises';
|
|
12
|
+
import {parse} from 'node-html-parser';
|
|
13
|
+
import {answer} from './index.ts';
|
|
14
|
+
import {reportsPath, hiddenReportsPath} from '../../util.ts';
|
|
15
15
|
|
|
16
16
|
// CONSTANTS
|
|
17
17
|
|
|
@@ -21,7 +21,7 @@ const hiddenFile = '260101T0007-hid.json';
|
|
|
21
21
|
|
|
22
22
|
const savedAuthCode = process.env.AUTH_CODE;
|
|
23
23
|
const savedDBDir = process.env.DB_DIR;
|
|
24
|
-
|
|
24
|
+
import {fixtureDBDir} from '../../test/dbFixture.ts';
|
|
25
25
|
|
|
26
26
|
const getPaths = () => ({
|
|
27
27
|
hidden: path.join(hiddenReportsPath(), hiddenFile),
|
|
@@ -64,7 +64,7 @@ after(async () => {
|
|
|
64
64
|
// TESTS
|
|
65
65
|
|
|
66
66
|
test('unhideReportForm displays a list of hidden reports when no submission', async () => {
|
|
67
|
-
const result = await answer(null, '');
|
|
67
|
+
const result: any = await answer(null, '');
|
|
68
68
|
assert.equal(result.status, 'ok');
|
|
69
69
|
assert.ok(result.answerPage);
|
|
70
70
|
const html = parse(result.answerPage);
|
|
@@ -73,21 +73,21 @@ test('unhideReportForm displays a list of hidden reports when no submission', as
|
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('unhideReportForm returns an error for an invalid auth code', async () => {
|
|
76
|
-
const result = await answer(null, 'authCode=wrong&report=260101T0007-hid');
|
|
76
|
+
const result: any = await answer(null, 'authCode=wrong&report=260101T0007-hid');
|
|
77
77
|
assert.equal(result.status, 'error');
|
|
78
78
|
assert.equal(result.message, 'Invalid authorization code');
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
test('unhideReportForm unhides a report with valid auth code', async () => {
|
|
82
82
|
const {report} = getPaths();
|
|
83
|
-
const result = await answer(null, 'authCode=test-auth-code&report=260101T0007-hid');
|
|
83
|
+
const result: any = await answer(null, 'authCode=test-auth-code&report=260101T0007-hid');
|
|
84
84
|
assert.equal(result.status, 'ok');
|
|
85
85
|
// The report should now be in the reports directory.
|
|
86
86
|
await fs.access(report);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test('unhideReportForm returns an error when unhiding a nonexistent report', async () => {
|
|
90
|
-
const result = await answer(null, 'authCode=test-auth-code&report=999999T9999-nope');
|
|
90
|
+
const result: any = await answer(null, 'authCode=test-auth-code&report=999999T9999-nope');
|
|
91
91
|
assert.equal(result.status, 'error');
|
|
92
92
|
assert.ok(result.message.includes('Unhiding report'));
|
|
93
93
|
});
|
|
@@ -102,7 +102,7 @@ test('unhideReportForm sorts hidden reports with the same page name by timeStamp
|
|
|
102
102
|
const copyPath = path.join(hiddenReportsPath(), '260101T0010-hid2.json');
|
|
103
103
|
await fs.writeFile(copyPath, JSON.stringify(copy));
|
|
104
104
|
try {
|
|
105
|
-
const result = await answer(null, '');
|
|
105
|
+
const result: any = await answer(null, '');
|
|
106
106
|
assert.equal(result.status, 'ok');
|
|
107
107
|
// Both reports should appear in the form.
|
|
108
108
|
assert.ok(result.answerPage.includes('260101T0007-hid'));
|
|
@@ -123,7 +123,7 @@ test('unhideReportForm sorts hidden reports with different page names alphabetic
|
|
|
123
123
|
const copyPath = path.join(hiddenReportsPath(), '260101T0011-hid3.json');
|
|
124
124
|
await fs.writeFile(copyPath, JSON.stringify(copy));
|
|
125
125
|
try {
|
|
126
|
-
const result = await answer(null, '');
|
|
126
|
+
const result: any = await answer(null, '');
|
|
127
127
|
assert.equal(result.status, 'ok');
|
|
128
128
|
// Both reports should appear in the form.
|
|
129
129
|
assert.ok(result.answerPage.includes('260101T0007-hid'));
|
package/aceconfig.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
© 2021–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
-
© 2025 Jonathan Robert Pool.
|
|
4
|
-
|
|
5
|
-
Licensed under the MIT License. See LICENSE file at the project root or
|
|
6
|
-
https://opensource.org/license/mit/ for details.
|
|
7
|
-
|
|
8
|
-
SPDX-License-Identifier: MIT
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/*
|
|
12
|
-
aceconfig
|
|
13
|
-
Configuration for the ibm rule engine.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const os = require('os');
|
|
17
|
-
|
|
18
|
-
const tmpDir = os.tmpdir();
|
|
19
|
-
|
|
20
|
-
module.exports = {
|
|
21
|
-
reportLevels: [
|
|
22
|
-
'violation',
|
|
23
|
-
'recommendation'
|
|
24
|
-
],
|
|
25
|
-
cacheFolder: tmpDir,
|
|
26
|
-
outputFolder: tmpDir,
|
|
27
|
-
puppeteerArgs: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
28
|
-
};
|
package/api/listReports.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
listReports.js
|
|
3
|
-
Returns basics about all the available reports.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {getReportBasics, getResponseMetadata, getToolsFacts, thisHost} = require('./util');
|
|
9
|
-
const {getLatestReportExtracts, getReportExtracts} = require('../util');
|
|
10
|
-
|
|
11
|
-
// FUNCTIONS
|
|
12
|
-
|
|
13
|
-
// Returns the response body.
|
|
14
|
-
exports.response = async () => {
|
|
15
|
-
// Initialize the response content.
|
|
16
|
-
const responseContent = {
|
|
17
|
-
'basics about all available reports': null,
|
|
18
|
-
'how to request that a page with no report be tested': null,
|
|
19
|
-
'how a web user can request that the page be tested': null
|
|
20
|
-
};
|
|
21
|
-
// Initialize an array of basics about the reports.
|
|
22
|
-
const reportsBasics = [];
|
|
23
|
-
// Get extracts of all available reports.
|
|
24
|
-
const reportExtracts = await getReportExtracts();
|
|
25
|
-
// Get the latest report extracts once, to avoid recomputing them for every report.
|
|
26
|
-
const latestReportExtracts = await getLatestReportExtracts();
|
|
27
|
-
// For each report:
|
|
28
|
-
for (const extract of reportExtracts) {
|
|
29
|
-
const {error, jobID, timeStamp} = extract;
|
|
30
|
-
// Get the basics about it (which may be only an error message).
|
|
31
|
-
const reportBasics = await getReportBasics(timeStamp, jobID, extract, latestReportExtracts);
|
|
32
|
-
// If this succeeded:
|
|
33
|
-
if (!error) {
|
|
34
|
-
// Add instructions for getting details to the basics.
|
|
35
|
-
reportBasics['how to get details about the report'] = {
|
|
36
|
-
method: 'GET',
|
|
37
|
-
URL: `${thisHost}/api/listIssues/${timeStamp}/${jobID}`
|
|
38
|
-
};
|
|
39
|
-
reportBasics['web users can get details about the report at'] = `${thisHost}/listIssues.html/${timeStamp}/${jobID}`;
|
|
40
|
-
// Add the basics to the array.
|
|
41
|
-
reportsBasics.push(reportBasics);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
// Sort the array by page description and secondarily by completion recency.
|
|
45
|
-
reportsBasics.sort((a, b) => {
|
|
46
|
-
if (a['tested web page'].description !== b['tested web page'].description) {
|
|
47
|
-
return a['tested web page']
|
|
48
|
-
.description
|
|
49
|
-
.localeCompare(b['tested web page'].description, 'en', {sensitivity: 'base'});
|
|
50
|
-
}
|
|
51
|
-
return a['completion date and time'].localeCompare(b['completion date and time']);
|
|
52
|
-
});
|
|
53
|
-
// Add the sorted basics about the reports to the response content.
|
|
54
|
-
responseContent['basics about all available reports'] = reportsBasics;
|
|
55
|
-
// Add instructions for requesting a test to the response content.
|
|
56
|
-
responseContent['how to request that a page with no report be tested'] = {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
URL: `${thisHost}/api/requestTest`,
|
|
59
|
-
'request body': {
|
|
60
|
-
description: '10- to 100-character description of the page conforming to the naming convention used in this list of reports',
|
|
61
|
-
URL: '12- to 300-character URL of the page, including the https:// scheme and any query',
|
|
62
|
-
reason: '20- to 100-character reason why the page should be tested'
|
|
63
|
-
},
|
|
64
|
-
'how to check whether the request has been fulfilled': 'use this listReports tool to determine whether a report about the page has become available (typical wait time: 1 hour to 1 day)'
|
|
65
|
-
};
|
|
66
|
-
// Add instructions for a web user to request a test to the response content.
|
|
67
|
-
responseContent['how a web user can request that the page be tested'] = {
|
|
68
|
-
URL: `${thisHost}/requestTestForm.html`
|
|
69
|
-
};
|
|
70
|
-
// Create a response body.
|
|
71
|
-
const body = {
|
|
72
|
-
'tool collection': getToolsFacts(),
|
|
73
|
-
'tool name': 'listReports',
|
|
74
|
-
'this request': {
|
|
75
|
-
description: 'Provide basics about all available reports.',
|
|
76
|
-
method: 'GET',
|
|
77
|
-
URL: `${thisHost}/api/listReports`,
|
|
78
|
-
'closest ancestor request': null
|
|
79
|
-
},
|
|
80
|
-
'URLs of similar requests for web users': {
|
|
81
|
-
'this request': `${thisHost}/listReports.html`,
|
|
82
|
-
'closest ancestor request': null
|
|
83
|
-
},
|
|
84
|
-
'response metadata': getResponseMetadata(),
|
|
85
|
-
'response content': responseContent
|
|
86
|
-
};
|
|
87
|
-
// Return it.
|
|
88
|
-
return body;
|
|
89
|
-
};
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
requestFeature.test.js
|
|
3
|
-
Tests for api/requestFeature.js with mocked side effects.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const {test, before, beforeEach, after} = require('node:test');
|
|
9
|
-
const assert = require('node:assert/strict');
|
|
10
|
-
const alerts = require('../alerts');
|
|
11
|
-
|
|
12
|
-
// SETUP AND TEARDOWN
|
|
13
|
-
|
|
14
|
-
let sendAlertCalls = [];
|
|
15
|
-
|
|
16
|
-
before(() => {
|
|
17
|
-
// @ts-expect-error: Replacing the real function with a mock for testing.
|
|
18
|
-
alerts.sendAlert = async (subject, body) => {
|
|
19
|
-
sendAlertCalls.push({subject, body});
|
|
20
|
-
};
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
// Reset the call log before each test, so tests do not depend on execution order.
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
sendAlertCalls = [];
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// Require requestFeature after the mock is in place, so it captures the mocked sendAlert.
|
|
29
|
-
const {response} = require('./requestFeature');
|
|
30
|
-
|
|
31
|
-
after(() => {
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
// TESTS
|
|
35
|
-
|
|
36
|
-
test('requestFeature rejects an empty feature request', async () => {
|
|
37
|
-
const body = await response(['']);
|
|
38
|
-
const details = body['response content']['details about your request'];
|
|
39
|
-
assert.ok(details.error);
|
|
40
|
-
assert.equal(sendAlertCalls.length, 0);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('requestFeature accepts a non-empty feature request and notifies the manager', async () => {
|
|
44
|
-
const body = await response(['Add a dark mode toggle']);
|
|
45
|
-
const details = body['response content']['details about your request'];
|
|
46
|
-
assert.equal(details.error, undefined);
|
|
47
|
-
assert.ok(details['date and time received']);
|
|
48
|
-
assert.equal(details.disposition, 'received and logged; manager notified');
|
|
49
|
-
assert.equal(sendAlertCalls.length, 1);
|
|
50
|
-
assert.equal(sendAlertCalls[0].subject, 'MCP feature request received');
|
|
51
|
-
assert.equal(sendAlertCalls[0].body, 'Add a dark mode toggle');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('requestFeature includes tool name and metadata', async () => {
|
|
55
|
-
const body = await response(['Some feature']);
|
|
56
|
-
assert.equal(body['tool name'], 'requestFeature');
|
|
57
|
-
assert.ok(body['response metadata'].identifier);
|
|
58
|
-
});
|
package/api/version.js
DELETED
package/indexFallback.test.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
indexFallback.test.js
|
|
3
|
-
Tests that index.js loads successfully when PROTOCOL and TESTARO_WORKERS
|
|
4
|
-
are not set, exercising the || fallback branches at module load time.
|
|
5
|
-
This must be a separate file because index.js reads these env vars
|
|
6
|
-
at module load time, and each test file runs in its own process.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ENVIRONMENT (must be set before requiring index.js)
|
|
10
|
-
|
|
11
|
-
const path = require('node:path');
|
|
12
|
-
const fixtureDBDir = path.join(__dirname, 'test', 'fixtures', 'db');
|
|
13
|
-
|
|
14
|
-
process.env.DB_DIR = fixtureDBDir;
|
|
15
|
-
process.env.AUTH_CODE = 'test-auth-code';
|
|
16
|
-
// Set PROTOCOL and TESTARO_WORKERS to empty strings (falsy) so that
|
|
17
|
-
// the || fallbacks in index.js are exercised. dotenv does not override
|
|
18
|
-
// existing env vars, so .env will not replace these.
|
|
19
|
-
process.env.PROTOCOL = '';
|
|
20
|
-
process.env.TESTARO_WORKERS = '';
|
|
21
|
-
|
|
22
|
-
// IMPORTS
|
|
23
|
-
|
|
24
|
-
const {test} = require('node:test');
|
|
25
|
-
const assert = require('node:assert/strict');
|
|
26
|
-
const {requestHandler} = require('./index');
|
|
27
|
-
|
|
28
|
-
// TESTS
|
|
29
|
-
|
|
30
|
-
test('index.js loads with empty PROTOCOL and TESTARO_WORKERS using fallbacks', () => {
|
|
31
|
-
// If index.js loaded successfully, requestHandler is a function.
|
|
32
|
-
assert.equal(typeof requestHandler, 'function');
|
|
33
|
-
});
|
package/indexWorkers.test.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
indexWorkers.test.js
|
|
3
|
-
Tests for index.js when TESTARO_WORKERS is invalid JSON.
|
|
4
|
-
This must be a separate file because index.js reads TESTARO_WORKERS
|
|
5
|
-
at module load time.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// ENVIRONMENT (must be set before requiring index.js)
|
|
9
|
-
|
|
10
|
-
const path = require('node:path');
|
|
11
|
-
const fixtureDBDir = path.join(__dirname, 'test', 'fixtures', 'db');
|
|
12
|
-
|
|
13
|
-
process.env.DB_DIR = fixtureDBDir;
|
|
14
|
-
process.env.AUTH_CODE = 'test-auth-code';
|
|
15
|
-
process.env.TESTARO_WORKERS = 'not valid json';
|
|
16
|
-
|
|
17
|
-
// IMPORTS
|
|
18
|
-
|
|
19
|
-
const {test} = require('node:test');
|
|
20
|
-
const assert = require('node:assert/strict');
|
|
21
|
-
const http = require('node:http');
|
|
22
|
-
const {requestHandler} = require('./index');
|
|
23
|
-
|
|
24
|
-
// CONSTANTS
|
|
25
|
-
|
|
26
|
-
const port = 3993;
|
|
27
|
-
|
|
28
|
-
// HELPERS
|
|
29
|
-
|
|
30
|
-
const request = (method, requestPath, body = null, headers = {}) => new Promise((resolve, reject) => {
|
|
31
|
-
const options = {method, host: 'localhost', port, path: requestPath, headers: {...headers}};
|
|
32
|
-
let bodyData = '';
|
|
33
|
-
if (body) {
|
|
34
|
-
bodyData = JSON.stringify(body);
|
|
35
|
-
options.headers['content-type'] = 'application/json';
|
|
36
|
-
options.headers['content-length'] = Buffer.byteLength(bodyData);
|
|
37
|
-
}
|
|
38
|
-
const req = http.request(options, response => {
|
|
39
|
-
const chunks = [];
|
|
40
|
-
response.on('data', chunk => chunks.push(chunk));
|
|
41
|
-
response.on('end', () => {
|
|
42
|
-
resolve({statusCode: response.statusCode, headers: response.headers, body: Buffer.concat(chunks).toString()});
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
req.on('error', reject);
|
|
46
|
-
req.end(bodyData || '');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// TESTS
|
|
50
|
-
|
|
51
|
-
test('index.js loads with invalid TESTARO_WORKERS and treats workers as empty', () => {
|
|
52
|
-
// If index.js loaded successfully, requestHandler is a function.
|
|
53
|
-
assert.equal(typeof requestHandler, 'function');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('POST /worker/job with invalid TESTARO_WORKERS returns 401 for any credentials', async () => {
|
|
57
|
-
const server = http.createServer(requestHandler);
|
|
58
|
-
await new Promise(resolve => server.listen(port, () => resolve()));
|
|
59
|
-
try {
|
|
60
|
-
const auth = Buffer.from('worker1:secret1').toString('base64');
|
|
61
|
-
const res = await request('POST', '/worker/job', {}, {
|
|
62
|
-
authorization: `Basic ${auth}`
|
|
63
|
-
});
|
|
64
|
-
assert.equal(res.statusCode, 401);
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
server.closeAllConnections?.();
|
|
68
|
-
await new Promise(resolve => {
|
|
69
|
-
const timer = setTimeout(() => {
|
|
70
|
-
server.closeAllConnections?.();
|
|
71
|
-
resolve();
|
|
72
|
-
}, 1000);
|
|
73
|
-
server.close(() => {
|
|
74
|
-
clearTimeout(timer);
|
|
75
|
-
resolve();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
});
|
package/jsconfig.json
DELETED
package/pm2.config.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
apps: [
|
|
3
|
-
{
|
|
4
|
-
name: 'kilotest',
|
|
5
|
-
script: 'index.js',
|
|
6
|
-
instances: 1,
|
|
7
|
-
autorestart: true,
|
|
8
|
-
watch: false,
|
|
9
|
-
max_memory_restart: '500M',
|
|
10
|
-
time: true,
|
|
11
|
-
env: {
|
|
12
|
-
NODE_ENV: 'production',
|
|
13
|
-
BASE_PATH: '/',
|
|
14
|
-
DEMO_SSE_DELAY_MS: '100'
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
]
|
|
18
|
-
};
|
package/smokeTest.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
smokeTest.js
|
|
3
|
-
Sends requests to all valid GET and POST paths on the deployed server and verifies that Caddy forwards them to Kilotest (i.e., the response is not a bare Caddy 404).
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// IMPORTS
|
|
7
|
-
|
|
8
|
-
const https = require('https');
|
|
9
|
-
const {routes} = require('./index');
|
|
10
|
-
|
|
11
|
-
// CONSTANTS
|
|
12
|
-
|
|
13
|
-
const host = process.env.SMOKE_HOST || 'kilotest.com';
|
|
14
|
-
// Concrete paths matching each wildcard pattern in the routes table, for smoke testing.
|
|
15
|
-
const concretePaths = {
|
|
16
|
-
GET: {
|
|
17
|
-
'*.html*': '/listReports.html',
|
|
18
|
-
'/': '/',
|
|
19
|
-
'/api-docs': '/api-docs',
|
|
20
|
-
'/api/*': '/api/listReports',
|
|
21
|
-
'/capability.md': '/capability.md',
|
|
22
|
-
'/favicon.*': '/favicon.ico',
|
|
23
|
-
'/fullReport.json/*': '/fullReport.json/260101T0000/mix',
|
|
24
|
-
'/index.html': '/index.html',
|
|
25
|
-
'/llms-full.txt': '/llms-full.txt',
|
|
26
|
-
'/llms.txt': '/llms.txt',
|
|
27
|
-
'/mcp': '/mcp',
|
|
28
|
-
'/openapi.json': '/openapi.json',
|
|
29
|
-
'/openapi.yaml': '/openapi.yaml',
|
|
30
|
-
'/robots.txt': '/robots.txt',
|
|
31
|
-
'/sitemap.xml': '/sitemap.xml',
|
|
32
|
-
'/style.css': '/style.css',
|
|
33
|
-
'/swagger.json': '/swagger.json',
|
|
34
|
-
'/swagger.yaml': '/swagger.yaml',
|
|
35
|
-
'/tutorial/images/*': '/tutorial/images/diagram.png'
|
|
36
|
-
},
|
|
37
|
-
POST: {
|
|
38
|
-
'/api/*': '/api/requestFeature',
|
|
39
|
-
'/mcp': '/mcp',
|
|
40
|
-
'/reannotate.html': '/reannotate.html',
|
|
41
|
-
'/recAction.html': '/recAction.html',
|
|
42
|
-
'/renewWCAG.html': '/renewWCAG.html',
|
|
43
|
-
'/requestRetest.html/*': '/requestRetest.html/260101T0001/ct',
|
|
44
|
-
'/requestTest.html': '/requestTest.html',
|
|
45
|
-
'/tutorialComment.html': '/tutorialComment.html',
|
|
46
|
-
'/worker/job': '/worker/job',
|
|
47
|
-
'/worker/report': '/worker/report'
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
// Minimal POST bodies for paths that require them.
|
|
51
|
-
const postBodies = {
|
|
52
|
-
'/api/requestFeature': {feature: 'smoke test'},
|
|
53
|
-
'/mcp': {},
|
|
54
|
-
'/reannotate.html': {authCode: 'invalid'},
|
|
55
|
-
'/recAction.html': {
|
|
56
|
-
target: 'https://smoketest.example.com\tSmoke Test Page', authCode: 'invalid'
|
|
57
|
-
},
|
|
58
|
-
'/renewWCAG.html': {authCode: 'invalid'},
|
|
59
|
-
'/requestRetest.html/260101T0001/ct': {why: 'smoke test'},
|
|
60
|
-
'/requestTest.html': {what: 'Smoke Test Page', url: 'https://smoketest.example.com', why: 'smoke test'},
|
|
61
|
-
'/tutorialComment.html': {content: 'smoke test'},
|
|
62
|
-
'/worker/job': {},
|
|
63
|
-
'/worker/report': {}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// FUNCTIONS
|
|
67
|
-
|
|
68
|
-
// Sends an HTTPS request and returns the status code and body length.
|
|
69
|
-
const sendRequest = (method, requestPath) => new Promise((resolve, reject) => {
|
|
70
|
-
const body = method === 'POST' ? JSON.stringify(postBodies[requestPath] || {}) : null;
|
|
71
|
-
const headers = {'x-kilotest-smoke': '1'};
|
|
72
|
-
if (body) {
|
|
73
|
-
headers['content-type'] = 'application/json; charset=utf-8';
|
|
74
|
-
headers['content-length'] = Buffer.byteLength(body);
|
|
75
|
-
}
|
|
76
|
-
const options = {
|
|
77
|
-
method,
|
|
78
|
-
host,
|
|
79
|
-
path: requestPath,
|
|
80
|
-
headers
|
|
81
|
-
};
|
|
82
|
-
const req = https.request(options, response => {
|
|
83
|
-
const chunks = [];
|
|
84
|
-
response.on('data', chunk => chunks.push(chunk));
|
|
85
|
-
response.on('end', () => {
|
|
86
|
-
const responseBody = chunks.join('');
|
|
87
|
-
resolve({statusCode: response.statusCode, bodyLength: responseBody.length});
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
req.on('error', reject);
|
|
91
|
-
req.end(body || '');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Returns whether a response is a bare Caddy 404 (the failure this test detects).
|
|
95
|
-
const isCaddy404 = result => result.statusCode === 404 && result.bodyLength === 0;
|
|
96
|
-
|
|
97
|
-
// EXECUTION
|
|
98
|
-
|
|
99
|
-
(async () => {
|
|
100
|
-
let failures = 0;
|
|
101
|
-
for (const method of ['GET', 'POST']) {
|
|
102
|
-
console.log(`\n=== ${method} paths ===`);
|
|
103
|
-
for (const pattern of routes[method]) {
|
|
104
|
-
const requestPath = concretePaths[method][pattern];
|
|
105
|
-
if (!requestPath) {
|
|
106
|
-
console.log(`FAIL: no concrete path defined for pattern ${pattern}`);
|
|
107
|
-
failures++;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const result = await sendRequest(method, requestPath);
|
|
112
|
-
if (isCaddy404(result)) {
|
|
113
|
-
console.log(`FAIL: ${method} ${requestPath} -> Caddy 404 (path not forwarded)`);
|
|
114
|
-
failures++;
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
console.log(`PASS: ${method} ${requestPath} -> ${result.statusCode} (body ${result.bodyLength} bytes)`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
console.log(`FAIL: ${method} ${requestPath} -> error: ${error.message}`);
|
|
122
|
-
failures++;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
console.log(`\n=== ${failures === 0 ? 'All paths forwarded' : `${failures} failure(s)`} ===`);
|
|
127
|
-
process.exit(failures === 0 ? 0 : 1);
|
|
128
|
-
})();
|