@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/getReport.test.ts
CHANGED
|
@@ -35,7 +35,7 @@ test('getReport returns the full report for a valid report', async () => {
|
|
|
35
35
|
assert.ok((content['size of the report in bytes'] as number) > 0);
|
|
36
36
|
const report = content['full report'] as any;
|
|
37
37
|
assert.equal(report.id, '260101T0000-mix');
|
|
38
|
-
assert.equal(report.target.what, 'Mixed Outcomes
|
|
38
|
+
assert.equal(report.target.what, 'Mixed Outcomes');
|
|
39
39
|
assert.equal(report.target.url, 'https://example.com/mixed');
|
|
40
40
|
assert.equal(report.acts.length, 2);
|
|
41
41
|
});
|
package/api/listIssues.test.ts
CHANGED
|
@@ -113,3 +113,35 @@ test('listIssues handles instances with missing issueID and null instances witho
|
|
|
113
113
|
const ids = issues.map(i => i.identifier);
|
|
114
114
|
assert.ok(ids.includes('duplicateID'));
|
|
115
115
|
});
|
|
116
|
+
|
|
117
|
+
test('listIssues lists the history of reports about the page, from oldest to latest', async () => {
|
|
118
|
+
const body = await response(['260101T0000', 'mix']);
|
|
119
|
+
const history = body['response content']['history of reports about the page']!;
|
|
120
|
+
assert.deepEqual(history.map(entry => entry.identifier), ['260101T0000-mix', '260202T0000-new']);
|
|
121
|
+
assert.deepEqual(
|
|
122
|
+
history.map(entry => [
|
|
123
|
+
entry['whether it is the oldest report about the page'],
|
|
124
|
+
entry['whether it is the latest report about the page'],
|
|
125
|
+
entry['whether it is the report described in this response']
|
|
126
|
+
]),
|
|
127
|
+
[[true, false, true], [false, true, false]]
|
|
128
|
+
);
|
|
129
|
+
assert.equal(history[1]!['completion date and time'], '2026-02-02T00:10:00.000Z');
|
|
130
|
+
assert.equal(typeof history[1]!['days since the report was completed'], 'number');
|
|
131
|
+
assert.ok(history[1]!['how to get details about the report'].URL.endsWith('/api/listIssues/260202T0000/new'));
|
|
132
|
+
assert.ok(history[1]!['web users can get details about the report at'].endsWith('/listIssues.html/260202T0000/new'));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('listIssues gives a one-report history for the only report about a page', async () => {
|
|
136
|
+
const body = await response(['260101T0001', 'ct']);
|
|
137
|
+
const history = body['response content']['history of reports about the page']!;
|
|
138
|
+
assert.equal(history.length, 1);
|
|
139
|
+
assert.equal(history[0]!['whether it is the oldest report about the page'], true);
|
|
140
|
+
assert.equal(history[0]!['whether it is the latest report about the page'], true);
|
|
141
|
+
assert.equal(history[0]!['whether it is the report described in this response'], true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('listIssues gives no history for a nonexistent report', async () => {
|
|
145
|
+
const body = await response(['999999T9999', 'xyz']);
|
|
146
|
+
assert.equal(body['response content']['history of reports about the page'], null);
|
|
147
|
+
});
|
package/api/listIssues.ts
CHANGED
|
@@ -16,7 +16,14 @@ import {
|
|
|
16
16
|
getThisHost
|
|
17
17
|
} from './util.ts';
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
getAgoDays,
|
|
20
|
+
getPageReportExtracts,
|
|
21
|
+
getReport,
|
|
22
|
+
getReportStats,
|
|
23
|
+
getTestActInstances,
|
|
24
|
+
getTestActs,
|
|
25
|
+
isReportError,
|
|
26
|
+
objectSort
|
|
20
27
|
} from '../util.ts';
|
|
21
28
|
import {listIssuesResponseSchema} from './schemas.ts';
|
|
22
29
|
|
|
@@ -38,6 +45,7 @@ export const response = async (args: string[]) => {
|
|
|
38
45
|
// Initialize the response content.
|
|
39
46
|
const responseContent: ResponseContent = {
|
|
40
47
|
'basics about the report': reportBasics,
|
|
48
|
+
'history of reports about the page': null,
|
|
41
49
|
'details about the report': null,
|
|
42
50
|
'how to request that the page be retested': null,
|
|
43
51
|
'how a web user can request that the page be retested': null,
|
|
@@ -55,6 +63,28 @@ export const response = async (args: string[]) => {
|
|
|
55
63
|
device = {id: 'default'},
|
|
56
64
|
browserID = null
|
|
57
65
|
} = report;
|
|
66
|
+
// Get extracts of all reports about the page, from oldest to latest.
|
|
67
|
+
const pageExtracts = await getPageReportExtracts(report.target.what);
|
|
68
|
+
const lastIndex = pageExtracts.length - 1;
|
|
69
|
+
// Add the history of reports about the page to the response content.
|
|
70
|
+
responseContent['history of reports about the page'] = pageExtracts.map((extract, index) => {
|
|
71
|
+
const extractTimeStamp = extract.timeStamp;
|
|
72
|
+
const extractJobID = extract.jobID;
|
|
73
|
+
return {
|
|
74
|
+
identifier: `${extractTimeStamp}-${extractJobID}`,
|
|
75
|
+
'completion date and time': extract.reportTime,
|
|
76
|
+
'days since the report was completed': getAgoDays(new Date(extract.reportTime)),
|
|
77
|
+
'whether it is the oldest report about the page': index === 0,
|
|
78
|
+
'whether it is the latest report about the page': index === lastIndex,
|
|
79
|
+
'whether it is the report described in this response':
|
|
80
|
+
extractTimeStamp === timeStamp && extractJobID === jobID,
|
|
81
|
+
'how to get details about the report': {
|
|
82
|
+
method: 'GET' as const,
|
|
83
|
+
URL: `${thisHost}/api/listIssues/${extractTimeStamp}/${extractJobID}`
|
|
84
|
+
},
|
|
85
|
+
'web users can get details about the report at': `${thisHost}/listIssues.html/${extractTimeStamp}/${extractJobID}`
|
|
86
|
+
};
|
|
87
|
+
});
|
|
58
88
|
// Get details about the job definition.
|
|
59
89
|
const jobDefinitionDetails = {
|
|
60
90
|
'whether the job prohibited redirection': strict,
|
|
@@ -159,7 +189,7 @@ export const response = async (args: string[]) => {
|
|
|
159
189
|
if (reportBasics['whether a later report about the same page exists']) {
|
|
160
190
|
// Add information about requesting a retest to the response content.
|
|
161
191
|
responseContent['how to request that the page be retested'] = {
|
|
162
|
-
notice: 'A later report about the same page already exists. The listIssues output about
|
|
192
|
+
notice: 'A later report about the same page already exists (see the history of reports about the page). The listIssues output about the latest report includes instructions for requesting a retest.'
|
|
163
193
|
};
|
|
164
194
|
}
|
|
165
195
|
// Otherwise, i.e. if the report has not been superseded:
|
|
@@ -172,7 +202,7 @@ export const response = async (args: string[]) => {
|
|
|
172
202
|
reason: '20- to 100-character reason why the page should be retested'
|
|
173
203
|
},
|
|
174
204
|
'how to check whether the request has been fulfilled':
|
|
175
|
-
'use the
|
|
205
|
+
'use the listPages tool to determine whether a later report about the page has become available (typical wait time: 1 hour to 1 day)'
|
|
176
206
|
};
|
|
177
207
|
// Add instructions for a web user to request a retest.
|
|
178
208
|
responseContent['how a web user can request that the page be retested'] = {
|
|
@@ -207,19 +237,19 @@ export const response = async (args: string[]) => {
|
|
|
207
237
|
'tool collection': getToolsFacts(),
|
|
208
238
|
'tool name': 'listIssues',
|
|
209
239
|
'this request': {
|
|
210
|
-
description: 'Provide details about one report, including basics about the issues reported in it. The timeStamp and jobID parameters identify the report that I want details about. Those parameters were in the response to my earlier
|
|
240
|
+
description: 'Provide details about one report, including basics about the issues reported in it. The timeStamp and jobID parameters identify the report that I want details about. Those parameters were in the response to my earlier listPages request, or in the history of reports about the page in an earlier listIssues response.',
|
|
211
241
|
method: 'GET',
|
|
212
242
|
URL: `${thisHost}/api/listIssues/${timeStamp}/${jobID}`,
|
|
213
243
|
'closest ancestor request': {
|
|
214
|
-
'tool name': '
|
|
215
|
-
description: 'Provide basics about all
|
|
244
|
+
'tool name': 'listPages',
|
|
245
|
+
description: 'Provide basics about all tested pages.',
|
|
216
246
|
method: 'GET',
|
|
217
|
-
URL: `${thisHost}/api/
|
|
247
|
+
URL: `${thisHost}/api/listPages`,
|
|
218
248
|
}
|
|
219
249
|
},
|
|
220
250
|
'URLs of similar requests for web users': {
|
|
221
251
|
'this request': `${thisHost}/listIssues.html/${timeStamp}/${jobID}`,
|
|
222
|
-
'closest ancestor request': `${thisHost}/
|
|
252
|
+
'closest ancestor request': `${thisHost}/listPages.html`
|
|
223
253
|
},
|
|
224
254
|
'response metadata': getResponseMetadata(),
|
|
225
255
|
'response content': responseContent
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
listPages.test.ts
|
|
3
|
+
Tests for api/listPages.ts using the fixture corpus.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {test, before, after} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import {response} from './listPages.ts';
|
|
11
|
+
|
|
12
|
+
// SETUP AND TEARDOWN
|
|
13
|
+
|
|
14
|
+
const savedDBDir = process.env.DB_DIR;
|
|
15
|
+
|
|
16
|
+
before(async () => {
|
|
17
|
+
process.env.DB_DIR = (await import('../test/dbFixture.ts')).fixtureDBDir;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
after(() => {
|
|
21
|
+
if (savedDBDir !== undefined) {
|
|
22
|
+
process.env.DB_DIR = savedDBDir;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
delete process.env.DB_DIR;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// TESTS
|
|
30
|
+
|
|
31
|
+
test('listPages returns one entry per tested page, sorted by page description', async () => {
|
|
32
|
+
const body = await response();
|
|
33
|
+
const pagesBasics = body['response content']['basics about all tested pages'];
|
|
34
|
+
const descriptions = pagesBasics.map(b => b['tested web page'].description);
|
|
35
|
+
assert.deepEqual(
|
|
36
|
+
descriptions,
|
|
37
|
+
[
|
|
38
|
+
'All CantTell Page',
|
|
39
|
+
'Branch Coverage Page',
|
|
40
|
+
'Empty Results Page',
|
|
41
|
+
'Mixed Outcomes',
|
|
42
|
+
'Multi Violator Page',
|
|
43
|
+
'No Outcomes Page',
|
|
44
|
+
'Prevented Page'
|
|
45
|
+
]
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('listPages does not include the page of the hidden report', async () => {
|
|
50
|
+
const body = await response();
|
|
51
|
+
const pagesBasics = body['response content']['basics about all tested pages'];
|
|
52
|
+
const ids = pagesBasics.map(b => b['basics about the latest report'].identifier);
|
|
53
|
+
assert.ok(!ids.includes('260101T0007-hid'), 'hidden report must not appear in listPages');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('listPages describes a multi-report page by its latest report and its report count', async () => {
|
|
57
|
+
const body = await response();
|
|
58
|
+
const pagesBasics = body['response content']['basics about all tested pages'];
|
|
59
|
+
const mixed = pagesBasics.find(b => b['tested web page'].description === 'Mixed Outcomes')!;
|
|
60
|
+
assert.equal(mixed['number of reports about the page'], 2);
|
|
61
|
+
const latest = mixed['basics about the latest report'];
|
|
62
|
+
assert.equal(latest.identifier, '260202T0000-new');
|
|
63
|
+
assert.equal(latest['completion date and time'], '2026-02-02T00:10:00.000Z');
|
|
64
|
+
assert.equal(typeof latest['days since the report was completed'], 'number');
|
|
65
|
+
assert.equal(mixed['how to get details about the latest report'].method, 'GET');
|
|
66
|
+
assert.ok(mixed['how to get details about the latest report'].URL.endsWith('/api/listIssues/260202T0000/new'));
|
|
67
|
+
assert.ok(mixed['web users can get details about the latest report at'].endsWith('/listIssues.html/260202T0000/new'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('listPages describes a single-report page', async () => {
|
|
71
|
+
const body = await response();
|
|
72
|
+
const pagesBasics = body['response content']['basics about all tested pages'];
|
|
73
|
+
const canttell = pagesBasics.find(b => b['tested web page'].description === 'All CantTell Page')!;
|
|
74
|
+
assert.equal(canttell['number of reports about the page'], 1);
|
|
75
|
+
assert.equal(canttell['tested web page'].URL, 'https://example.com/canttell');
|
|
76
|
+
assert.equal(canttell['basics about the latest report'].identifier, '260101T0001-ct');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('listPages includes request-test instructions', async () => {
|
|
80
|
+
const body = await response();
|
|
81
|
+
const content = body['response content'];
|
|
82
|
+
assert.ok(content['how to request that a page with no report be tested']);
|
|
83
|
+
assert.ok(content['how a web user can request that the page be tested']);
|
|
84
|
+
assert.equal(content['how to request that a page with no report be tested'].method, 'POST');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('listPages includes tool name and metadata', async () => {
|
|
88
|
+
const body = await response();
|
|
89
|
+
assert.equal(body['tool name'], 'listPages');
|
|
90
|
+
assert.ok(body['response metadata'].identifier);
|
|
91
|
+
assert.ok(body['response metadata']['date and time']);
|
|
92
|
+
});
|
package/api/listPages.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
listPages.ts
|
|
3
|
+
Returns basics about all tested pages.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {z} from 'zod';
|
|
9
|
+
import {getResponseMetadata, getThisHost, getToolsFacts} from './util.ts';
|
|
10
|
+
import {getAgoDays, getReportExtracts} from '../util.ts';
|
|
11
|
+
import {listPagesResponseSchema} from './schemas.ts';
|
|
12
|
+
|
|
13
|
+
// TYPES
|
|
14
|
+
|
|
15
|
+
// The response content defined by the response schema.
|
|
16
|
+
type ResponseContent = z.infer<typeof listPagesResponseSchema>['response content'];
|
|
17
|
+
|
|
18
|
+
// FUNCTIONS
|
|
19
|
+
|
|
20
|
+
// Returns the response body.
|
|
21
|
+
export const response = async () => {
|
|
22
|
+
const thisHost = getThisHost();
|
|
23
|
+
// Get extracts of all available reports, sorted by page description and completion time.
|
|
24
|
+
const reportExtracts = await getReportExtracts();
|
|
25
|
+
// Get the number of reports about each page.
|
|
26
|
+
const reportCounts = new Map<string, number>();
|
|
27
|
+
reportExtracts.forEach(({description}) => {
|
|
28
|
+
reportCounts.set(description, (reportCounts.get(description) ?? 0) + 1);
|
|
29
|
+
});
|
|
30
|
+
// Initialize an array of basics about the pages.
|
|
31
|
+
const pagesBasics: ResponseContent['basics about all tested pages'] = [];
|
|
32
|
+
// For each report:
|
|
33
|
+
reportExtracts.forEach(extract => {
|
|
34
|
+
const {description, jobID, reportTime, superseded, timeStamp, url} = extract;
|
|
35
|
+
// If it is the latest report about its page:
|
|
36
|
+
if (!superseded) {
|
|
37
|
+
// Add basics about the page, with instructions for getting details, to the array.
|
|
38
|
+
pagesBasics.push({
|
|
39
|
+
'tested web page': {
|
|
40
|
+
description,
|
|
41
|
+
URL: url
|
|
42
|
+
},
|
|
43
|
+
'number of reports about the page': reportCounts.get(description)!,
|
|
44
|
+
'basics about the latest report': {
|
|
45
|
+
identifier: `${timeStamp}-${jobID}`,
|
|
46
|
+
'completion date and time': reportTime,
|
|
47
|
+
'days since the report was completed': getAgoDays(new Date(reportTime))
|
|
48
|
+
},
|
|
49
|
+
'how to get details about the latest report': {
|
|
50
|
+
method: 'GET',
|
|
51
|
+
URL: `${thisHost}/api/listIssues/${timeStamp}/${jobID}`
|
|
52
|
+
},
|
|
53
|
+
'web users can get details about the latest report at': `${thisHost}/listIssues.html/${timeStamp}/${jobID}`
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Create the response content.
|
|
58
|
+
const responseContent: ResponseContent = {
|
|
59
|
+
'basics about all tested pages': pagesBasics,
|
|
60
|
+
'how to request that a page with no report be tested': {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
URL: `${thisHost}/api/requestNewTest`,
|
|
63
|
+
'request body': {
|
|
64
|
+
description: '10- to 100-character description of the page conforming to the naming convention used in this list of pages',
|
|
65
|
+
URL: '12- to 300-character URL of the page, including the https:// scheme and any query',
|
|
66
|
+
reason: '20- to 100-character reason why the page should be tested'
|
|
67
|
+
},
|
|
68
|
+
'how to check whether the request has been fulfilled': 'use this listPages tool to determine whether the page has been tested (typical wait time: 1 hour to 1 day)'
|
|
69
|
+
},
|
|
70
|
+
'how a web user can request that the page be tested': {
|
|
71
|
+
URL: `${thisHost}/requestNewTestForm.html`
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
// Create a response body.
|
|
75
|
+
const body = {
|
|
76
|
+
'tool collection': getToolsFacts(),
|
|
77
|
+
'tool name': 'listPages',
|
|
78
|
+
'this request': {
|
|
79
|
+
description: 'Provide basics about all tested pages.',
|
|
80
|
+
method: 'GET',
|
|
81
|
+
URL: `${thisHost}/api/listPages`,
|
|
82
|
+
'closest ancestor request': null
|
|
83
|
+
},
|
|
84
|
+
'URLs of similar requests for web users': {
|
|
85
|
+
'this request': `${thisHost}/listPages.html`,
|
|
86
|
+
'closest ancestor request': null
|
|
87
|
+
},
|
|
88
|
+
'response metadata': getResponseMetadata(),
|
|
89
|
+
'response content': responseContent
|
|
90
|
+
};
|
|
91
|
+
// Return it.
|
|
92
|
+
return body;
|
|
93
|
+
};
|
package/api/orderNewTest.test.ts
CHANGED
|
@@ -115,7 +115,7 @@ test('orderNewTest rejects a URL that redirects to a disallowed target, and aler
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
test('orderNewTest rejects an already-tested page', async () => {
|
|
118
|
-
const body = await response(['Mixed Outcomes
|
|
118
|
+
const body = await response(['Mixed Outcomes', 'https://example.com/mixed', 'A reason that is long enough.']);
|
|
119
119
|
const details = body['response content']['details about your order'] as any;
|
|
120
120
|
assert.equal(details.error, undefined);
|
|
121
121
|
const disposition = body['response content']['disposition of your order'] as any;
|
|
@@ -209,5 +209,5 @@ test('orderNewTest response omits a web UI URL for this request but includes the
|
|
|
209
209
|
const body = await response(['Brand New Ordered Page 2', 'https://example.com/brandnewordered2', 'A reason that is long enough.']);
|
|
210
210
|
const similarWeb = body['URLs of similar requests for web users'] as any;
|
|
211
211
|
assert.equal(similarWeb['this request'], null);
|
|
212
|
-
assert.ok(similarWeb['closest ancestor request'].includes('
|
|
212
|
+
assert.ok(similarWeb['closest ancestor request'].includes('listPages.html'));
|
|
213
213
|
});
|
package/api/orderNewTest.ts
CHANGED
|
@@ -127,15 +127,15 @@ export const response = async (args: string[]) => {
|
|
|
127
127
|
reason
|
|
128
128
|
},
|
|
129
129
|
'closest ancestor request': {
|
|
130
|
-
'tool name': '
|
|
131
|
-
description: 'Provide basics about all
|
|
130
|
+
'tool name': 'listPages',
|
|
131
|
+
description: 'Provide basics about all tested pages.',
|
|
132
132
|
method: 'GET',
|
|
133
|
-
URL: `${thisHost}/api/
|
|
133
|
+
URL: `${thisHost}/api/listPages`
|
|
134
134
|
}
|
|
135
135
|
},
|
|
136
136
|
'URLs of similar requests for web users': {
|
|
137
137
|
'this request': null,
|
|
138
|
-
'closest ancestor request': `${thisHost}/
|
|
138
|
+
'closest ancestor request': `${thisHost}/listPages.html`
|
|
139
139
|
},
|
|
140
140
|
'response metadata': getResponseMetadata(),
|
|
141
141
|
'response content': responseContent
|
package/api/orderRetest.test.ts
CHANGED
|
@@ -87,7 +87,7 @@ test('orderRetest rejects a reason longer than 100 characters', async () => {
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test('orderRetest rejects a superseded report', async () => {
|
|
90
|
-
// 260101T0000-mix is an earlier report of "Mixed Outcomes
|
|
90
|
+
// 260101T0000-mix is an earlier report of "Mixed Outcomes" than 260202T0000-new,
|
|
91
91
|
// so it is superseded.
|
|
92
92
|
const body = await response(['260101T0000', 'mix', 'A reason that is long enough.']);
|
|
93
93
|
const details = body['response content']['details about your order'] as any;
|
|
@@ -110,7 +110,7 @@ test('requestNewTest rejects a URL that redirects to a disallowed target, and al
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
test('requestNewTest rejects an already-tested page', async () => {
|
|
113
|
-
const body = await response(['Mixed Outcomes
|
|
113
|
+
const body = await response(['Mixed Outcomes', 'https://example.com/mixed', 'A reason that is long enough.']);
|
|
114
114
|
const details = body['response content']['details about your request'] as any;
|
|
115
115
|
// A report for this description and URL already exists, so processTestRequest
|
|
116
116
|
// returns 'reportExists' and the failure is reported via the disposition, not details.error.
|
package/api/requestNewTest.ts
CHANGED
|
@@ -81,8 +81,8 @@ export const response = async (args: string[]) => {
|
|
|
81
81
|
requestResult,
|
|
82
82
|
{
|
|
83
83
|
whatHappensNext: 'Your request is likely to be approved and processed within 1 hour to 1 day.',
|
|
84
|
-
howToCheck: 'You can call the
|
|
85
|
-
howWebUserChecks: `A web user can visit ${thisHost}/
|
|
84
|
+
howToCheck: 'You can call the listPages tool to learn whether the page has been tested.',
|
|
85
|
+
howWebUserChecks: `A web user can visit ${thisHost}/listPages.html to learn whether the page has been tested.`
|
|
86
86
|
},
|
|
87
87
|
{result: 'reportExists', reason: 'a report about a page with the same description or URL is available.'}
|
|
88
88
|
);
|
|
@@ -106,15 +106,15 @@ export const response = async (args: string[]) => {
|
|
|
106
106
|
reason
|
|
107
107
|
},
|
|
108
108
|
'closest ancestor request': {
|
|
109
|
-
'tool name': '
|
|
110
|
-
description: 'Provide basics about all
|
|
109
|
+
'tool name': 'listPages',
|
|
110
|
+
description: 'Provide basics about all tested pages.',
|
|
111
111
|
method: 'GET',
|
|
112
|
-
URL: `${thisHost}/api/
|
|
112
|
+
URL: `${thisHost}/api/listPages`
|
|
113
113
|
}
|
|
114
114
|
},
|
|
115
115
|
'URLs of similar requests for web users': {
|
|
116
116
|
'this request': `${thisHost}/requestNewTestForm.html`,
|
|
117
|
-
'closest ancestor request': `${thisHost}/
|
|
117
|
+
'closest ancestor request': `${thisHost}/listPages.html`
|
|
118
118
|
},
|
|
119
119
|
'response metadata': getResponseMetadata(),
|
|
120
120
|
'response content': responseContent
|
|
@@ -85,7 +85,7 @@ test('requestRetest rejects a malformed timestamp or job identifier', async () =
|
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
test('requestRetest rejects a superseded report', async () => {
|
|
88
|
-
// 260101T0000-mix is an earlier report of "Mixed Outcomes
|
|
88
|
+
// 260101T0000-mix is an earlier report of "Mixed Outcomes" than 260202T0000-new,
|
|
89
89
|
// so it is superseded and processTestRequest returns 'superseded', reported via the
|
|
90
90
|
// disposition rather than details.error.
|
|
91
91
|
const body = await response(['260101T0000', 'mix', 'A reason that is long enough.']);
|
|
@@ -168,10 +168,10 @@ test('requestRetest accepts a valid retest request for the latest report of a pa
|
|
|
168
168
|
const body = await response(['260202T0000', 'new', 'A reason that is long enough.']);
|
|
169
169
|
const details = body['response content']['details about your request'] as any;
|
|
170
170
|
assert.equal(details.error, undefined);
|
|
171
|
-
assert.equal(details['page to be retested'].description, 'Mixed Outcomes
|
|
171
|
+
assert.equal(details['page to be retested'].description, 'Mixed Outcomes');
|
|
172
172
|
assert.ok(logged.some(line =>
|
|
173
173
|
line.startsWith('WARNING (Kilotest: request for a retest awaits approval)')
|
|
174
|
-
&& line.includes('Mixed Outcomes
|
|
174
|
+
&& line.includes('Mixed Outcomes')
|
|
175
175
|
&& line.includes('https://example.com/mixed')
|
|
176
176
|
// The alert reports the resulting queue size, so a maintainer who has been away can
|
|
177
177
|
// see at a glance how close the queue is to full.
|
package/api/requestRetest.ts
CHANGED
|
@@ -64,8 +64,8 @@ export const response = async (args: string[]) => {
|
|
|
64
64
|
requestResult,
|
|
65
65
|
{
|
|
66
66
|
whatHappensNext: 'Your request is likely to be approved and processed within 1 hour to 1 day.',
|
|
67
|
-
howToCheck: 'You can call the
|
|
68
|
-
howWebUserChecks: `A web user can visit ${thisHost}/
|
|
67
|
+
howToCheck: 'You can call the listPages tool, or the listIssues tool on the current report, to learn whether the page has been retested and a new report is available.',
|
|
68
|
+
howWebUserChecks: `A web user can visit ${thisHost}/listPages.html to learn whether the page has been retested and a new report is available.`
|
|
69
69
|
},
|
|
70
70
|
{result: 'superseded', reason: 'a later report about a page with the same description is available.'}
|
|
71
71
|
);
|
package/api/routes.test.ts
CHANGED
package/api/routes.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
orderNewTestSchema,
|
|
18
18
|
orderRetestSchema,
|
|
19
19
|
awaitTestSchema,
|
|
20
|
-
|
|
20
|
+
listPagesResponseSchema,
|
|
21
21
|
listIssuesResponseSchema,
|
|
22
22
|
listViolatorsResponseSchema,
|
|
23
23
|
listDiagnosesResponseSchema,
|
|
@@ -34,11 +34,11 @@ import {
|
|
|
34
34
|
|
|
35
35
|
export const routes = [
|
|
36
36
|
{
|
|
37
|
-
operationId: '
|
|
37
|
+
operationId: 'listPages',
|
|
38
38
|
method: 'get',
|
|
39
|
-
path: '/api/
|
|
40
|
-
summary: 'List all
|
|
41
|
-
responseSchema:
|
|
39
|
+
path: '/api/listPages',
|
|
40
|
+
summary: 'List all tested pages',
|
|
41
|
+
responseSchema: listPagesResponseSchema
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
operationId: 'listIssues',
|
package/api/schemas.ts
CHANGED
|
@@ -43,7 +43,7 @@ export const getReportSchema = {
|
|
|
43
43
|
|
|
44
44
|
// requestNewTest: POST /api/requestNewTest
|
|
45
45
|
export const requestNewTestSchema = {
|
|
46
|
-
description: z.string().describe('1- to 100-character description of the page conforming to the naming convention used in the
|
|
46
|
+
description: z.string().describe('1- to 100-character description of the page conforming to the naming convention used in the listPages output'),
|
|
47
47
|
URL: z.string().describe('12- to 300-character URL of the page, including the https:// scheme and any query'),
|
|
48
48
|
reason: z.string().describe('20- to 100-character reason why the page should be tested')
|
|
49
49
|
};
|
|
@@ -62,7 +62,7 @@ export const requestFeatureSchema = {
|
|
|
62
62
|
|
|
63
63
|
// orderNewTest: POST /api/orderNewTest
|
|
64
64
|
export const orderNewTestSchema = {
|
|
65
|
-
description: z.string().describe('1- to 100-character description of the page conforming to the naming convention used in the
|
|
65
|
+
description: z.string().describe('1- to 100-character description of the page conforming to the naming convention used in the listPages output'),
|
|
66
66
|
URL: z.string().describe('12- to 300-character URL of the page, including the https:// scheme and any query'),
|
|
67
67
|
reason: z.string().describe('20- to 100-character reason why the page should be tested')
|
|
68
68
|
};
|
|
@@ -80,7 +80,7 @@ export const awaitTestSchema = {
|
|
|
80
80
|
jobID: z.string().describe('Job identifier of the ordered job, from the report identifier returned by orderNewTest (example: x9z)')
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// listPages takes no input; omitted (mcp.ts already uses inputSchema: {}).
|
|
84
84
|
|
|
85
85
|
// RESPONSE SCHEMAS
|
|
86
86
|
|
|
@@ -194,10 +194,20 @@ const issueBasicsOrErrorSchema = z.union([
|
|
|
194
194
|
z.object({error: z.string()})
|
|
195
195
|
]).meta({id: 'IssueBasicsOrError'});
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
// Facts about a report among those about one page.
|
|
198
|
+
const reportTimeSchema = z.object({
|
|
199
|
+
identifier: z.string().describe('timeStamp-jobID identifier of the report.'),
|
|
200
|
+
'completion date and time': z.string(),
|
|
201
|
+
'days since the report was completed': z.number().nullable()
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
export const listPagesResponseSchema = envelope('GET', z.object({
|
|
205
|
+
'basics about all tested pages': z.array(z.object({
|
|
206
|
+
'tested web page': z.object({description: z.string(), URL: z.string()}),
|
|
207
|
+
'number of reports about the page': z.number(),
|
|
208
|
+
'basics about the latest report': reportTimeSchema,
|
|
209
|
+
'how to get details about the latest report': z.object({method: z.literal('GET'), URL: z.string()}),
|
|
210
|
+
'web users can get details about the latest report at': z.string()
|
|
201
211
|
})),
|
|
202
212
|
'how to request that a page with no report be tested': z.object({
|
|
203
213
|
method: z.literal('POST'),
|
|
@@ -210,6 +220,13 @@ export const listReportsResponseSchema = envelope('GET', z.object({
|
|
|
210
220
|
|
|
211
221
|
export const listIssuesResponseSchema = envelope('GET', z.object({
|
|
212
222
|
'basics about the report': reportBasicsOrErrorSchema,
|
|
223
|
+
'history of reports about the page': z.array(reportTimeSchema.extend({
|
|
224
|
+
'whether it is the oldest report about the page': z.boolean(),
|
|
225
|
+
'whether it is the latest report about the page': z.boolean(),
|
|
226
|
+
'whether it is the report described in this response': z.boolean(),
|
|
227
|
+
'how to get details about the report': z.object({method: z.literal('GET'), URL: z.string()}),
|
|
228
|
+
'web users can get details about the report at': z.string()
|
|
229
|
+
})).nullable().describe('All reports about the same page, from oldest to latest, or null if the report is unavailable.'),
|
|
213
230
|
'details about the report': z.object({
|
|
214
231
|
'job definition': z.object({
|
|
215
232
|
'whether the job prohibited redirection': z.unknown().describe('Copied from the report (strict); type not guaranteed.'),
|