@jrpool/kilotest 58.2.4 → 59.0.2
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/api/getReport.test.ts +1 -1
- package/api/listIssues.test.ts +32 -0
- package/api/listIssues.ts +38 -8
- package/api/listPages.test.ts +92 -0
- package/api/listPages.ts +93 -0
- package/api/orderNewTest.test.ts +2 -2
- package/api/orderNewTest.ts +4 -4
- package/api/orderRetest.test.ts +1 -1
- package/api/requestNewTest.test.ts +1 -1
- package/api/requestNewTest.ts +6 -6
- package/api/requestRetest.test.ts +3 -3
- package/api/requestRetest.ts +2 -2
- package/api/routes.test.ts +1 -1
- package/api/routes.ts +5 -5
- package/api/schemas.ts +24 -7
- package/api/util.ts +26 -45
- package/apiIntegration.test.ts +5 -5
- package/docs/AI-TOOLS.md +1 -1
- package/docs/SERVICE.md +16 -5
- package/index.test.ts +31 -19
- package/index.ts +25 -13
- package/lhm.plugin.json +1433 -141
- package/linkValidation.test.ts +2 -2
- package/llms-full.txt +9 -9
- package/llms.txt +4 -4
- package/mcp.test.ts +9 -9
- package/mcp.ts +14 -14
- package/openapi.yaml +84 -30
- package/package.json +1 -1
- package/routing.test.ts +5 -5
- package/server.json +1 -1
- package/sitemap.xml +1 -1
- package/smokeTest.ts +2 -2
- package/style.css +8 -8
- package/test/fixtures/buildFixtures.ts +3 -3
- package/test/fixtures/db/reports/260101T0000-mix.json +2 -2
- package/test/fixtures/db/reports/260202T0000-new.json +2 -2
- package/util.test.ts +31 -22
- package/util.ts +16 -5
- package/web/enqueueForm/index.html +1 -1
- package/web/hideReportForm/hideReportForm.test.ts +5 -0
- package/web/hideReportForm/index.ts +9 -6
- package/web/home/home.test.ts +19 -0
- package/{index.html → web/home/index.html} +2 -2
- package/web/home/index.ts +20 -0
- package/web/listDiagnoses/listDiagnoses.test.ts +1 -1
- package/web/listIssues/index.html +9 -10
- package/web/listIssues/index.ts +45 -3
- package/web/listIssues/listIssues.test.ts +82 -6
- package/web/{listReports → listPages}/index.html +1 -2
- package/web/listPages/index.ts +99 -0
- package/web/listPages/listPages.test.ts +196 -0
- package/web/listViolators/listViolators.test.ts +1 -1
- package/web/metrics/metrics.test.ts +2 -2
- package/web/requestNewTest/requestNewTest.test.ts +1 -1
- package/web/requestRetest/requestRetest.test.ts +1 -2
- package/web/requestRetestForm/index.ts +4 -2
- package/web/requestRetestForm/requestRetestForm.test.ts +12 -1
- package/web/unhideReportForm/index.ts +9 -6
- package/web/unhideReportForm/unhideReportForm.test.ts +8 -5
- package/web/util.test.ts +87 -23
- package/web/util.ts +27 -31
- package/api/listReports.test.ts +0 -109
- package/api/listReports.ts +0 -87
- package/web/listReports/index.ts +0 -182
- package/web/listReports/listReports.test.ts +0 -300
package/api/util.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
objectSort,
|
|
16
16
|
ruleEngines
|
|
17
17
|
} from '../util.ts';
|
|
18
|
-
import type {
|
|
18
|
+
import type {TestRequestResult} from '../util.ts';
|
|
19
19
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
20
20
|
|
|
21
21
|
// TYPES
|
|
@@ -51,9 +51,9 @@ export const getResponseMetadata = () => ({
|
|
|
51
51
|
export const getToolsFacts = () => ({
|
|
52
52
|
'name': 'Kilotest',
|
|
53
53
|
'description': {
|
|
54
|
-
'what Kilotest does': 'Kilotest tools generate and make available findings about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. A Kilotest job generates findings by using Testaro to test a page against about 1300 rules defined by an ensemble of twelve rule engines. Testaro produces a report of the job. The report describes violations of the rules. Kilotest uses Testilo to enhance the report with a classification of the rule violations into about 380 issues. Kilotest makes facts about the issues and the violations retrievable at four levels of granularity.',
|
|
54
|
+
'what Kilotest does': 'Kilotest tools generate and make available findings about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. A Kilotest job generates findings by using Testaro to test a page against about 1300 rules defined by an ensemble of twelve rule engines. Testaro produces a report of the job. The report describes violations of the rules. Kilotest uses Testilo to enhance the report with a classification of the rule violations into about 380 issues. Kilotest makes facts about the tested pages, the issues, and the violations retrievable at four levels of granularity.',
|
|
55
55
|
'how to retrieve findings': {
|
|
56
|
-
'level 1': 'Use the
|
|
56
|
+
'level 1': 'Use the listPages tool to get a list of tested pages, each with its latest report. The listIssues output about any report lists all reports about the same page.',
|
|
57
57
|
'level 2': 'Use the listIssues tool to get a list of issues in one report.',
|
|
58
58
|
'level 3': 'Use the listViolators tool to get a list of elements on one page that were reported in one report as exhibiting one issue.',
|
|
59
59
|
'level 4': 'Use the listDiagnoses tool to get a list of diagnoses of how one element on one page exhibited one issue in one report.',
|
|
@@ -62,7 +62,7 @@ export const getToolsFacts = () => ({
|
|
|
62
62
|
'new testing': 'If no report is available yet about a page, use the requestNewTest tool to request that it be tested.',
|
|
63
63
|
'retesting': 'If the listIssues tool shows that the latest report about a page is obsolete, because the page has been revised or for another reason, use the requestRetest tool to request that the page be retested.',
|
|
64
64
|
'latency': 'Requests for testing and retesting are usually approved and fulfilled within one day.',
|
|
65
|
-
'confirmation': 'Use the
|
|
65
|
+
'confirmation': 'Use the listPages tool to determine whether a requested new report exists. There is currently no process for notification of the outcome of requests.'
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
'URL': `${getThisHost()}/mcp`,
|
|
@@ -131,49 +131,30 @@ export const getRuleEnginesFacts = (ruleEngineIDSet: Iterable<string>) => {
|
|
|
131
131
|
objectSort(ruleEnginesFacts, 'name', 'alpha');
|
|
132
132
|
return ruleEnginesFacts;
|
|
133
133
|
};
|
|
134
|
-
// Returns the basics about a report.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Get the creation time of the report.
|
|
154
|
-
const reportStats = await getReportStats(timeStamp, jobID);
|
|
155
|
-
// If the report does not exist:
|
|
156
|
-
if (!reportStats) {
|
|
157
|
-
// Log and return this.
|
|
158
|
-
console.error(`Report ${timeStamp}-${jobID} does not exist.`);
|
|
159
|
-
return {
|
|
160
|
-
error: `Report ${timeStamp}-${jobID} could not be retrieved.`
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
// Otherwise, i.e. if it exists, get an extract of the report.
|
|
164
|
-
const fetchedExtract = await getReportExtract(timeStamp, jobID);
|
|
165
|
-
// If this failed, return why.
|
|
166
|
-
if ('error' in fetchedExtract) {
|
|
167
|
-
return fetchedExtract;
|
|
168
|
-
}
|
|
169
|
-
extract = fetchedExtract;
|
|
134
|
+
// Returns the basics about a report, or an error object if the report cannot be read.
|
|
135
|
+
export const getReportBasics = async (
|
|
136
|
+
timeStamp: string, jobID: string
|
|
137
|
+
): Promise<ReportBasics | {error: string}> => {
|
|
138
|
+
// Verify that the report exists.
|
|
139
|
+
const reportStats = await getReportStats(timeStamp, jobID);
|
|
140
|
+
// If it does not exist:
|
|
141
|
+
if (!reportStats) {
|
|
142
|
+
// Log and return this.
|
|
143
|
+
console.error(`Report ${timeStamp}-${jobID} does not exist.`);
|
|
144
|
+
return {
|
|
145
|
+
error: `Report ${timeStamp}-${jobID} could not be retrieved.`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// Otherwise, i.e. if it exists, get an extract of the report.
|
|
149
|
+
const extract = await getReportExtract(timeStamp, jobID);
|
|
150
|
+
// If this failed, return why.
|
|
151
|
+
if ('error' in extract) {
|
|
152
|
+
return extract;
|
|
170
153
|
}
|
|
171
154
|
const {url, description, reportTime} = extract;
|
|
172
155
|
// Get whether this report has been superseded.
|
|
173
|
-
const isSuperseded =
|
|
174
|
-
|
|
175
|
-
: (await getReportExtracts(true))
|
|
176
|
-
.every(ex => ex.timeStamp !== timeStamp || ex.jobID !== jobID);
|
|
156
|
+
const isSuperseded = (await getReportExtracts(true))
|
|
157
|
+
.every(ex => ex.timeStamp !== timeStamp || ex.jobID !== jobID);
|
|
177
158
|
// Get the basics about the report.
|
|
178
159
|
const basics = {
|
|
179
160
|
identifier: `${timeStamp}-${jobID}`,
|
|
@@ -187,7 +168,7 @@ export async function getReportBasics(
|
|
|
187
168
|
};
|
|
188
169
|
// Return them.
|
|
189
170
|
return basics;
|
|
190
|
-
}
|
|
171
|
+
};
|
|
191
172
|
// Returns the specification of an issue.
|
|
192
173
|
export const getIssueSpec = (issueID: string) => {
|
|
193
174
|
// Get the issue specification.
|
package/apiIntegration.test.ts
CHANGED
|
@@ -93,11 +93,11 @@ const request = (method: string, requestPath: string, body: any = null): Promise
|
|
|
93
93
|
|
|
94
94
|
// TESTS
|
|
95
95
|
|
|
96
|
-
test('GET /api/
|
|
97
|
-
const {body} = await request('GET', '/api/
|
|
98
|
-
assert.equal(body['tool name'], '
|
|
99
|
-
const
|
|
100
|
-
assert.equal(
|
|
96
|
+
test('GET /api/listPages returns valid JSON through the HTTP routing layer', {timeout: 500}, async () => {
|
|
97
|
+
const {body} = await request('GET', '/api/listPages');
|
|
98
|
+
assert.equal(body['tool name'], 'listPages');
|
|
99
|
+
const pages = body['response content']['basics about all tested pages'];
|
|
100
|
+
assert.equal(pages.length, 7);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test('POST /api/requestFeature parses a JSON body and returns a JSON response through the HTTP routing layer', async () => {
|
package/docs/AI-TOOLS.md
CHANGED
|
@@ -21,7 +21,7 @@ The internal features that make Kilotest a collection of tools for language mode
|
|
|
21
21
|
|
|
22
22
|
- An API, with specific functional and utility modules in the `api` directory.
|
|
23
23
|
- Tests of the functionalities of the API.
|
|
24
|
-
- A [`JSON-LD`](https://json-ld.org/) script in the `index.html` file, providing structured data about the Kilotest API.
|
|
24
|
+
- A [`JSON-LD`](https://json-ld.org/) script in the `web/home/index.html` file, providing structured data about the Kilotest API.
|
|
25
25
|
- Environment variables in the `env.example` file.
|
|
26
26
|
- An `llms.txt` file and an `llms-full.txt` file, documenting the use of Kilotest by language models, conforming to the [llms-txt](https://llmstxt.org/) specification.
|
|
27
27
|
- A generated `openapi.yaml` file, documenting the Kilotest API, conforming to the [OpenAPI specification](https://spec.openapis.org/oas/v3.1.0).
|
package/docs/SERVICE.md
CHANGED
|
@@ -323,7 +323,19 @@ Both logs are rotated; see “PM2 log rotation” above for the application log
|
|
|
323
323
|
|
|
324
324
|
## Version management
|
|
325
325
|
|
|
326
|
-
When
|
|
326
|
+
When the Kilotest web, API, or MCP interface is revised in a feature branch:
|
|
327
|
+
|
|
328
|
+
1. Update the version in `package.json`
|
|
329
|
+
1. Update the version in `server.json`
|
|
330
|
+
1. Update the version and any changes in the API in `lhm.plugin.json`
|
|
331
|
+
1. If the API has changed, run the `generate:openapi` script
|
|
332
|
+
1. Execute the command `npx @lobehub/market-cli plugin update --dir .`
|
|
333
|
+
1. Commit and push the changes to the feature branch
|
|
334
|
+
1. Create a pull request to merge the feature branch into `main`
|
|
335
|
+
1. Once the checks succeed, squash and merge the branch into `main`
|
|
336
|
+
1. Execute `npm publish`
|
|
337
|
+
|
|
338
|
+
When a new version of the `kilotest` package has been published, update the service as follows:
|
|
327
339
|
|
|
328
340
|
1. Connect to the server: `ssh linuxuser@kilotest.com`
|
|
329
341
|
1. Navigate to the package root: `cd /opt/jpdev/kilotest`
|
|
@@ -331,7 +343,6 @@ When a new version of the `kilotest` package has been published, the service can
|
|
|
331
343
|
1. Delete the record of that discard: `git stash drop`
|
|
332
344
|
1. Fetch and merge the new version: `git pull`
|
|
333
345
|
1. Update the dependencies: `npm update`
|
|
334
|
-
1. Update the Playwright browsers: `npx playwright install`
|
|
335
346
|
1. Restart the service: `pm2 restart kilotest`
|
|
336
347
|
|
|
337
348
|
## Branch protection
|
|
@@ -498,9 +509,9 @@ Kilotest records basic counts of how it is used, so the maintainer can answer qu
|
|
|
498
509
|
|
|
499
510
|
`recordMetric(category, name)`, in `util.ts`, increments a count for a `(category, name)` pair and writes the result to `db/metrics.json`. It is called from three places:
|
|
500
511
|
|
|
501
|
-
- The generic `.html` GET dispatch in `index.ts`, once per successfully served web page, recorded under the `pageViews` category by page name (for example `tutorialWeb`, `
|
|
512
|
+
- The generic `.html` GET dispatch in `index.ts`, once per successfully served web page, recorded under the `pageViews` category by page name (for example `tutorialWeb`, `listPages`), unless the page is a manager-only page (see "Manager page activity" below), in which case it is recorded there instead.
|
|
502
513
|
- The home page (`/` and `/index.html`), served by its own branch in `index.ts` rather than through the generic `.html` dispatch, recorded under `pageViews` as `index`.
|
|
503
|
-
- Each of the 9 MCP tool handlers in `mcp.ts`, once per successful tool call, recorded under the `mcpToolCalls` category by tool name (for example `
|
|
514
|
+
- Each of the 9 MCP tool handlers in `mcp.ts`, once per successful tool call, recorded under the `mcpToolCalls` category by tool name (for example `listPages`, `requestNewTest`).
|
|
504
515
|
- The `/api/*` GET and POST service branches in `index.ts`, once per call, recorded under the `apiOperations` category by operation name.
|
|
505
516
|
|
|
506
517
|
`db/metrics.json` also stores a `since` time stamp, set when the file is first created, so the counts can be read as "since this date" rather than assumed to cover Kilotest's entire history. `getMetrics()` backfills any category absent from an existing `db/metrics.json` (for example one written before a category such as `managerActivity` existed), so an older file remains readable rather than causing every subsequent request to error.
|
|
@@ -513,7 +524,7 @@ This exclusion exists because manager pages reflect the maintainer operating Kil
|
|
|
513
524
|
|
|
514
525
|
#### Excluding the maintainer's own manual testing
|
|
515
526
|
|
|
516
|
-
The manager-page exclusion above only keeps the maintainer's use of manager-only pages out of `pageViews`; it does not, by itself, keep the maintainer's manual testing of *ordinary* pages (for example clicking through `tutorialWeb.html` or `
|
|
527
|
+
The manager-page exclusion above only keeps the maintainer's use of manager-only pages out of `pageViews`; it does not, by itself, keep the maintainer's manual testing of *ordinary* pages (for example clicking through `tutorialWeb.html` or `listPages.html` after a deployment) from being counted as real usage. A separate mechanism addresses that: submitting a valid `authCode` on `/metrics.html` sets a cookie (`kilotestExclude`), valid for 30 days, whose value is a SHA-256 hash of `AUTH_CODE` rather than the code itself, so the secret is never placed in a long-lived browser cookie. Every subsequent request from that browser that carries a matching cookie is excluded from `pageViews` and `apiOperations` (not from `managerActivity`, which should keep counting regardless, since it exists to surface suspected abuse, including from the maintainer's own browser if its cookie were ever compromised).
|
|
517
528
|
|
|
518
529
|
A fixed or guessable cookie value was deliberately avoided: Kilotest's source is public, so anyone could otherwise read it and set the same cookie in their own browser to exclude themselves from metrics at no cost, defeating the feature. Hashing `AUTH_CODE` means only someone who has already proven they know the real code can derive the correct cookie value.
|
|
519
530
|
|
package/index.test.ts
CHANGED
|
@@ -198,7 +198,7 @@ test('routes has GET and POST arrays', () => {
|
|
|
198
198
|
// TESTS: smoke-test short-circuit
|
|
199
199
|
|
|
200
200
|
test('GET with x-kilotest-smoke header returns a perfunctory 200', async () => {
|
|
201
|
-
const res = await request('GET', '/api/
|
|
201
|
+
const res = await request('GET', '/api/listPages', null, {'x-kilotest-smoke': '1'});
|
|
202
202
|
assert.equal(res.statusCode, 200);
|
|
203
203
|
assert.equal(res.body, '{}');
|
|
204
204
|
});
|
|
@@ -210,7 +210,7 @@ test('POST with x-kilotest-smoke header returns a perfunctory 200', async () =>
|
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
test('GET without x-kilotest-smoke header executes the handler', async () => {
|
|
213
|
-
const res = await request('GET', '/api/
|
|
213
|
+
const res = await request('GET', '/api/listPages');
|
|
214
214
|
assert.equal(res.statusCode, 200);
|
|
215
215
|
assert.notEqual(res.body, '{}');
|
|
216
216
|
});
|
|
@@ -332,13 +332,13 @@ test('GET /favicon.ico serves the favicon as an icon', async () => {
|
|
|
332
332
|
assert.ok(res.headers['content-type'].includes('image/x-icon'));
|
|
333
333
|
});
|
|
334
334
|
|
|
335
|
-
test('GET /api/
|
|
336
|
-
const countBefore = await getMetricCount('apiOperations', '
|
|
337
|
-
const res = await request('GET', '/api/
|
|
335
|
+
test('GET /api/listPages returns JSON with report data and records an API-operation metric', async () => {
|
|
336
|
+
const countBefore = await getMetricCount('apiOperations', 'listPages');
|
|
337
|
+
const res = await request('GET', '/api/listPages');
|
|
338
338
|
assert.equal(res.statusCode, 200);
|
|
339
339
|
const body = jsonBody(res);
|
|
340
|
-
assert.equal(body['tool name'], '
|
|
341
|
-
assert.equal(await getMetricCount('apiOperations', '
|
|
340
|
+
assert.equal(body['tool name'], 'listPages');
|
|
341
|
+
assert.equal(await getMetricCount('apiOperations', 'listPages'), countBefore + 1);
|
|
342
342
|
});
|
|
343
343
|
|
|
344
344
|
test('GET /api/listIssues/260101T0000/mix returns JSON with issue data', async () => {
|
|
@@ -471,7 +471,7 @@ test('POST /requestNewTest.html with valid data returns an HTML page', async ()
|
|
|
471
471
|
|
|
472
472
|
test('POST /requestNewTest.html with an already-tested URL returns an error', async () => {
|
|
473
473
|
const res = await formRequest('POST', '/requestNewTest.html', {
|
|
474
|
-
description: 'Mixed Outcomes
|
|
474
|
+
description: 'Mixed Outcomes',
|
|
475
475
|
url: 'https://example.com/mixed',
|
|
476
476
|
why: 'Because accessibility matters'
|
|
477
477
|
});
|
|
@@ -688,14 +688,14 @@ test('PUT / returns an invalid method error', async () => {
|
|
|
688
688
|
|
|
689
689
|
// TESTS: requestHandler catch boundary
|
|
690
690
|
|
|
691
|
-
test('GET /
|
|
691
|
+
test('GET /listPages.html returns a 500 error when an unexpected internal error occurs', {timeout: 500}, async () => {
|
|
692
692
|
// Corrupt testRequests.json so that getTestRequests throws, an error that should never occur in normal
|
|
693
693
|
// operation and so is not handled by handleRequest itself, only by requestHandler's
|
|
694
694
|
// outer catch boundary.
|
|
695
695
|
const backup = await fs.readFile(testRequestsPath, 'utf8');
|
|
696
696
|
await fs.writeFile(testRequestsPath, 'not valid json');
|
|
697
697
|
try {
|
|
698
|
-
const res = await request('GET', '/
|
|
698
|
+
const res = await request('GET', '/listPages.html');
|
|
699
699
|
assert.equal(res.statusCode, 500);
|
|
700
700
|
assert.ok(res.headers['content-type'].includes('application/json'));
|
|
701
701
|
const body = jsonBody(res);
|
|
@@ -932,10 +932,22 @@ test('GET /rules.html/<segment> redirects to the equivalent /listRules.html/<seg
|
|
|
932
932
|
assert.equal(res.headers['location'], '/listRules.html/allCaps');
|
|
933
933
|
});
|
|
934
934
|
|
|
935
|
-
test('GET /targets.html redirects to /
|
|
935
|
+
test('GET /targets.html redirects to /listPages.html', async () => {
|
|
936
936
|
const res = await request('GET', '/targets.html', {}, {followRedirects: false});
|
|
937
937
|
assert.equal(res.statusCode, 301);
|
|
938
|
-
assert.equal(res.headers['location'], '/
|
|
938
|
+
assert.equal(res.headers['location'], '/listPages.html');
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test('GET /listReports.html redirects to /listPages.html', async () => {
|
|
942
|
+
const res = await request('GET', '/listReports.html', {}, {followRedirects: false});
|
|
943
|
+
assert.equal(res.statusCode, 301);
|
|
944
|
+
assert.equal(res.headers['location'], '/listPages.html');
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
test('GET /api/listReports redirects to /api/listPages', async () => {
|
|
948
|
+
const res = await request('GET', '/api/listReports', {}, {followRedirects: false});
|
|
949
|
+
assert.equal(res.statusCode, 301);
|
|
950
|
+
assert.equal(res.headers['location'], '/api/listPages');
|
|
939
951
|
});
|
|
940
952
|
|
|
941
953
|
test('GET /requestTest.html redirects to /requestNewTest.html', async () => {
|
|
@@ -1463,7 +1475,7 @@ test('GET /requestRetest.html/260202T0000/new is rejected as an invalid GET requ
|
|
|
1463
1475
|
// tested by the unit tests in web/*/*.test.cjs.
|
|
1464
1476
|
|
|
1465
1477
|
const htmlPagePaths = [
|
|
1466
|
-
'/
|
|
1478
|
+
'/listPages.html',
|
|
1467
1479
|
'/listViolators.html/linkNoText/260101T0000/mix',
|
|
1468
1480
|
'/listDiagnoses.html/linkNoText/260101T0000/mix/0',
|
|
1469
1481
|
'/enqueueForm.html',
|
|
@@ -1520,11 +1532,11 @@ test('a request carrying the metrics-exclusion cookie does not record a pageView
|
|
|
1520
1532
|
const {getExclusionCookieValue} = await import('./util.ts');
|
|
1521
1533
|
const cookieHeader = `kilotestExclude=${getExclusionCookieValue()}`;
|
|
1522
1534
|
const pageViewCountBefore = await getMetricCount('pageViews', 'tutorialWeb');
|
|
1523
|
-
const apiOperationCountBefore = await getMetricCount('apiOperations', '
|
|
1535
|
+
const apiOperationCountBefore = await getMetricCount('apiOperations', 'listPages');
|
|
1524
1536
|
await request('GET', '/tutorialWeb.html', null, {cookie: cookieHeader});
|
|
1525
|
-
await request('GET', '/api/
|
|
1537
|
+
await request('GET', '/api/listPages', null, {cookie: cookieHeader});
|
|
1526
1538
|
assert.equal(await getMetricCount('pageViews', 'tutorialWeb'), pageViewCountBefore);
|
|
1527
|
-
assert.equal(await getMetricCount('apiOperations', '
|
|
1539
|
+
assert.equal(await getMetricCount('apiOperations', 'listPages'), apiOperationCountBefore);
|
|
1528
1540
|
});
|
|
1529
1541
|
|
|
1530
1542
|
test('a request carrying an invalid exclusion cookie still records metrics normally', async () => {
|
|
@@ -1945,7 +1957,7 @@ test('a request logs a single-line JSON record with the expected fields', async
|
|
|
1945
1957
|
const originalLog = console.log;
|
|
1946
1958
|
console.log = (line: string) => logged.push(line);
|
|
1947
1959
|
try {
|
|
1948
|
-
await request('GET', '/
|
|
1960
|
+
await request('GET', '/listPages.html');
|
|
1949
1961
|
}
|
|
1950
1962
|
finally {
|
|
1951
1963
|
console.log = originalLog;
|
|
@@ -1961,7 +1973,7 @@ test('a request logs a single-line JSON record with the expected fields', async
|
|
|
1961
1973
|
assert.equal(requestLines.length, 1);
|
|
1962
1974
|
const record = JSON.parse(requestLines[0]!);
|
|
1963
1975
|
assert.equal(record.method, 'GET');
|
|
1964
|
-
assert.equal(record.path, '/
|
|
1976
|
+
assert.equal(record.path, '/listPages.html');
|
|
1965
1977
|
assert.equal(record.status, 200);
|
|
1966
1978
|
assert.ok(record.ip);
|
|
1967
1979
|
assert.ok(record.userAgent);
|
|
@@ -1991,7 +2003,7 @@ test('a smoke-test-flagged request is not logged as a general request', async ()
|
|
|
1991
2003
|
const originalLog = console.log;
|
|
1992
2004
|
console.log = (line: string) => logged.push(line);
|
|
1993
2005
|
try {
|
|
1994
|
-
await request('GET', '/
|
|
2006
|
+
await request('GET', '/listPages.html', null, {'x-kilotest-smoke': '1'});
|
|
1995
2007
|
}
|
|
1996
2008
|
finally {
|
|
1997
2009
|
console.log = originalLog;
|
package/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ import {handleMCP, mcpPath} from './mcp.ts';
|
|
|
40
40
|
import fs from 'node:fs/promises';
|
|
41
41
|
import {handleComment as handleTutorialWebComment} from './web/tutorialWeb/index.ts';
|
|
42
42
|
import {answer as tutorialWeb} from './web/tutorialWeb/index.ts';
|
|
43
|
+
import {answer as home} from './web/home/index.ts';
|
|
43
44
|
import {answer as privacy} from './web/privacy/index.ts';
|
|
44
45
|
import {answer as tutorialAI, handleComment as handleTutorialAIComment} from './web/tutorialAI/index.ts';
|
|
45
46
|
import http, {type IncomingMessage, type ServerResponse} from 'node:http';
|
|
@@ -54,7 +55,7 @@ import {answer as expungeReportsForm} from './web/expungeReportsForm/index.ts';
|
|
|
54
55
|
import {answer as hideReportForm} from './web/hideReportForm/index.ts';
|
|
55
56
|
import {answer as listDiagnosesPage} from './web/listDiagnoses/index.ts';
|
|
56
57
|
import {answer as listIssuesPage} from './web/listIssues/index.ts';
|
|
57
|
-
import {answer as
|
|
58
|
+
import {answer as listPagesPage} from './web/listPages/index.ts';
|
|
58
59
|
import {answer as listRules} from './web/listRules/index.ts';
|
|
59
60
|
import {answer as listTopIssues} from './web/listTopIssues/index.ts';
|
|
60
61
|
import {answer as listViolatorsPage} from './web/listViolators/index.ts';
|
|
@@ -75,7 +76,7 @@ import {answer as unhideReportForm} from './web/unhideReportForm/index.ts';
|
|
|
75
76
|
import {response as getReportAPI} from './api/getReport.ts';
|
|
76
77
|
import {response as listDiagnosesAPI} from './api/listDiagnoses.ts';
|
|
77
78
|
import {response as listIssuesAPI} from './api/listIssues.ts';
|
|
78
|
-
import {response as
|
|
79
|
+
import {response as listPagesAPI} from './api/listPages.ts';
|
|
79
80
|
import {response as listViolatorsAPI} from './api/listViolators.ts';
|
|
80
81
|
import {response as requestFeatureAPI} from './api/requestFeature.ts';
|
|
81
82
|
import {response as requestRetestAPI} from './api/requestRetest.ts';
|
|
@@ -112,7 +113,7 @@ const answer: {
|
|
|
112
113
|
hideReportForm: PageHandler;
|
|
113
114
|
listDiagnoses: PageHandler;
|
|
114
115
|
listIssues: PageHandler;
|
|
115
|
-
|
|
116
|
+
listPages: PageHandler;
|
|
116
117
|
listRules: PageHandler;
|
|
117
118
|
listTopIssues: PageHandler;
|
|
118
119
|
listViolators: PageHandler;
|
|
@@ -143,7 +144,7 @@ const answer: {
|
|
|
143
144
|
hideReportForm,
|
|
144
145
|
listDiagnoses: listDiagnosesPage,
|
|
145
146
|
listIssues: listIssuesPage,
|
|
146
|
-
|
|
147
|
+
listPages: listPagesPage,
|
|
147
148
|
listRules,
|
|
148
149
|
listTopIssues,
|
|
149
150
|
listViolators: listViolatorsPage,
|
|
@@ -171,7 +172,7 @@ const apiRespond: {
|
|
|
171
172
|
getReport: ApiResponder;
|
|
172
173
|
listDiagnoses: ApiResponder;
|
|
173
174
|
listIssues: ApiResponder;
|
|
174
|
-
|
|
175
|
+
listPages: ApiResponder;
|
|
175
176
|
listViolators: ApiResponder;
|
|
176
177
|
requestFeature: ApiResponder;
|
|
177
178
|
requestRetest: ApiResponder;
|
|
@@ -183,7 +184,7 @@ const apiRespond: {
|
|
|
183
184
|
getReport: getReportAPI,
|
|
184
185
|
listDiagnoses: listDiagnosesAPI,
|
|
185
186
|
listIssues: listIssuesAPI,
|
|
186
|
-
|
|
187
|
+
listPages: listPagesAPI,
|
|
187
188
|
listViolators: listViolatorsAPI,
|
|
188
189
|
requestFeature: requestFeatureAPI,
|
|
189
190
|
requestRetest: requestRetestAPI,
|
|
@@ -291,12 +292,16 @@ const noDirectGetPages = new Set(['unhideReportForm']);
|
|
|
291
292
|
// on 2026-09-28 (see the newTest/retest naming-ambiguity fix), to distinguish the
|
|
292
293
|
// new-test-only page from bare "test" language used elsewhere.
|
|
293
294
|
// TEMPORARY: retire this redirect after 2027-03-28 once crawlers have re-indexed.
|
|
295
|
+
// listReports was renamed to listPages on 2026-10-09, since it lists tested pages, each
|
|
296
|
+
// linked to its latest report.
|
|
297
|
+
// TEMPORARY: retire this redirect after 2027-04-09 once crawlers have re-indexed.
|
|
294
298
|
const renamedPagePrefixes: Record<string, string> = {
|
|
295
299
|
diagnoses: 'listDiagnoses',
|
|
296
300
|
reportIssue: 'listViolators',
|
|
297
301
|
reportIssues: 'listIssues',
|
|
298
302
|
rules: 'listRules',
|
|
299
|
-
targets: '
|
|
303
|
+
targets: 'listPages',
|
|
304
|
+
listReports: 'listPages',
|
|
300
305
|
requestTest: 'requestNewTest',
|
|
301
306
|
requestTestForm: 'requestNewTestForm'
|
|
302
307
|
};
|
|
@@ -638,10 +643,10 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
|
|
|
638
643
|
else if (['/', '/index.html'].includes(pathname)) {
|
|
639
644
|
await recordPageMetric('pageViews', 'index');
|
|
640
645
|
// Get the home page.
|
|
641
|
-
const
|
|
646
|
+
const {answerPage} = await home();
|
|
642
647
|
// Serve it.
|
|
643
648
|
setHeaders('text/html', '/index.html', 'medium');
|
|
644
|
-
response.end(
|
|
649
|
+
response.end(answerPage);
|
|
645
650
|
}
|
|
646
651
|
// Otherwise, if it is for the crawler specification:
|
|
647
652
|
else if (pageName === 'robots.txt') {
|
|
@@ -800,15 +805,22 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
|
|
|
800
805
|
// Otherwise, if it is for an API service:
|
|
801
806
|
else if (pageName === 'api') {
|
|
802
807
|
const [service, ...specs] = pathTail.split('/');
|
|
803
|
-
// If the service lists the
|
|
804
|
-
if (service === '
|
|
808
|
+
// If the service lists the tested pages:
|
|
809
|
+
if (service === 'listPages') {
|
|
805
810
|
// Get the response body.
|
|
806
|
-
const responseBody = await apiRespond.
|
|
807
|
-
await recordPageMetric('apiOperations', '
|
|
811
|
+
const responseBody = await apiRespond.listPages([]);
|
|
812
|
+
await recordPageMetric('apiOperations', 'listPages');
|
|
808
813
|
// Send it.
|
|
809
814
|
setHeaders('application/json', null, 'ultra');
|
|
810
815
|
response.end(JSON.stringify(responseBody));
|
|
811
816
|
}
|
|
817
|
+
// Otherwise, if the service has the name that listPages had before 2026-10-09:
|
|
818
|
+
// TEMPORARY: retire this redirect after 2027-04-09.
|
|
819
|
+
else if (service === 'listReports') {
|
|
820
|
+
// Redirect the request to listPages.
|
|
821
|
+
response.writeHead(301, {Location: '/api/listPages'});
|
|
822
|
+
response.end();
|
|
823
|
+
}
|
|
812
824
|
// Otherwise, if the service lists the issues in a report:
|
|
813
825
|
else if (service === 'listIssues') {
|
|
814
826
|
// Get the response body.
|