@jrpool/kilotest 60.0.10 → 60.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/util.test.ts +10 -1
- package/api/util.ts +6 -3
- package/index.test.ts +24 -0
- package/lhm.plugin.json +1 -1
- package/openapi-3.0.yaml +1 -1
- package/openapi.yaml +1 -1
- package/package.json +1 -1
- package/server.json +1 -1
- package/util.test.ts +17 -0
- package/util.ts +8 -1
- package/web/requestNewTest/index.ts +8 -0
- package/web/requestNewTest/requestNewTest.test.ts +21 -0
- package/web/requestNewTestForm/index.html +1 -1
package/api/util.test.ts
CHANGED
|
@@ -16,7 +16,7 @@ for (const key of ['MANAGER_EMAIL', 'ALERT_API_HOST', 'ALERT_API_PATH', 'ALERT_A
|
|
|
16
16
|
process.env[key] = '';
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
import {getReportBasics, getResponseMetadata, getRuleEngineFacts, getRuleEnginesFacts, getIssueSpec, getToolsFacts} from './util.ts';
|
|
19
|
+
import {getReportBasics, getResponseMetadata, getRuleEngineFacts, getRuleEnginesFacts, getIssueSpec, getToolsFacts, getRequestFailureReason} from './util.ts';
|
|
20
20
|
import {processTestRequest} from '../util.ts';
|
|
21
21
|
|
|
22
22
|
// SETUP
|
|
@@ -136,3 +136,12 @@ test('getToolsFacts returns facts about the Kilotest tool collection', () => {
|
|
|
136
136
|
assert.ok(facts.description['how to retrieve findings']);
|
|
137
137
|
assert.ok(facts.description['how to generate more findings']);
|
|
138
138
|
});
|
|
139
|
+
|
|
140
|
+
test('getRequestFailureReason returns the URL-length reason for a urlLength result', () => {
|
|
141
|
+
// processTestRequest can return urlLength for a new-test request whose URL is
|
|
142
|
+
// outside the 12-to-300-character bound. No current caller reaches this
|
|
143
|
+
// branch, because each checks length itself first, but the mapping must be
|
|
144
|
+
// correct for any caller that relies on the shared check instead.
|
|
145
|
+
const reason = getRequestFailureReason('urlLength');
|
|
146
|
+
assert.equal(reason, 'the URL is not between 12 and 300 characters long.');
|
|
147
|
+
});
|
package/api/util.ts
CHANGED
|
@@ -70,9 +70,9 @@ export const getToolsFacts = () => ({
|
|
|
70
70
|
});
|
|
71
71
|
// Returns the human-readable reason a test/retest/instant-test request was not
|
|
72
72
|
// processed, given the result processTestRequest returned. requestResult is always a
|
|
73
|
-
// rejection reason here ('description', 'url', 'queueFull', or an
|
|
74
|
-
// meaning 'duplicate'); callers pass their own extra branch for a
|
|
75
|
-
// specific to their own request type ('reportExists' for requestNewTest, 'superseded' for
|
|
73
|
+
// rejection reason here ('description', 'url', 'urlLength', 'queueFull', or an
|
|
74
|
+
// unrecognized value meaning 'duplicate'); callers pass their own extra branch for a
|
|
75
|
+
// requestResult value specific to their own request type ('reportExists' for requestNewTest, 'superseded' for
|
|
76
76
|
// requestRetest), since that is the one branch not shared between them.
|
|
77
77
|
export const getRequestFailureReason = (
|
|
78
78
|
requestResult: Exclude<TestRequestResult, 'ok'>,
|
|
@@ -84,6 +84,9 @@ export const getRequestFailureReason = (
|
|
|
84
84
|
if (requestResult === 'url') {
|
|
85
85
|
return 'a request to test a page with the same URL is already approved.';
|
|
86
86
|
}
|
|
87
|
+
if (requestResult === 'urlLength') {
|
|
88
|
+
return 'the URL is not between 12 and 300 characters long.';
|
|
89
|
+
}
|
|
87
90
|
if (extraBranch && requestResult === extraBranch.result) {
|
|
88
91
|
return extraBranch.reason;
|
|
89
92
|
}
|
package/index.test.ts
CHANGED
|
@@ -489,6 +489,30 @@ test('POST /requestNewTest.html with invalid data returns an error', async () =>
|
|
|
489
489
|
assert.ok(res.body.includes('Invalid test request'));
|
|
490
490
|
});
|
|
491
491
|
|
|
492
|
+
test('POST /requestNewTest.html with a URL shorter than 12 characters returns an error', async () => {
|
|
493
|
+
// processTestRequest enforces the 12-to-300-character bound on new-test
|
|
494
|
+
// request URLs for every surface, so this 11-character URL is rejected even
|
|
495
|
+
// though it is a syntactically valid https URL.
|
|
496
|
+
const res = await formRequest('POST', '/requestNewTest.html', {
|
|
497
|
+
description: `Short URL Page ${uniqueStamp}`,
|
|
498
|
+
url: 'https://a.b',
|
|
499
|
+
why: 'Because accessibility matters'
|
|
500
|
+
});
|
|
501
|
+
assert.equal(res.statusCode, 400);
|
|
502
|
+
assert.ok(res.body.includes('The URL must be between 12 and 300 characters long'));
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('POST /requestNewTest.html with a URL longer than 300 characters returns an error', async () => {
|
|
506
|
+
// The same bound rejects this 320-character URL.
|
|
507
|
+
const res = await formRequest('POST', '/requestNewTest.html', {
|
|
508
|
+
description: `Long URL Page ${uniqueStamp}`,
|
|
509
|
+
url: `https://example.com/${'a'.repeat(300)}`,
|
|
510
|
+
why: 'Because accessibility matters'
|
|
511
|
+
});
|
|
512
|
+
assert.equal(res.statusCode, 400);
|
|
513
|
+
assert.ok(res.body.includes('The URL must be between 12 and 300 characters long'));
|
|
514
|
+
});
|
|
515
|
+
|
|
492
516
|
test('POST /requestNewTest.html with an unreadable body returns an error', async () => {
|
|
493
517
|
const res = await request('POST', '/requestNewTest.html', 'rawbody', {'content-type': 'text/plain'});
|
|
494
518
|
assert.equal(res.statusCode, 400);
|
package/lhm.plugin.json
CHANGED
package/openapi-3.0.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.0.3
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 60.0
|
|
5
|
+
version: 60.1.0
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
url: https://github.com/jrpool/kilotest/blob/main/LICENSE
|
package/openapi.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.1.0
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 60.0
|
|
5
|
+
version: 60.1.0
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
identifier: MIT
|
package/package.json
CHANGED
package/server.json
CHANGED
package/util.test.ts
CHANGED
|
@@ -757,6 +757,23 @@ test('processTestRequest succeeds for a valid new-test request', {timeout: 500},
|
|
|
757
757
|
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
758
758
|
});
|
|
759
759
|
|
|
760
|
+
test('processTestRequest returns a urlLength error for a URL shorter than 12 characters', {timeout: 500}, async () => {
|
|
761
|
+
// New-test request URLs must be 12 to 300 characters long; the bound lives
|
|
762
|
+
// here, not in callers, so it applies on every submission surface.
|
|
763
|
+
const {result} = await processTestRequest(
|
|
764
|
+
'because accessibility', {description: 'Short URL Page', url: 'https://a.b'}
|
|
765
|
+
);
|
|
766
|
+
assert.equal(result, 'urlLength');
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
test('processTestRequest returns a urlLength error for a URL longer than 300 characters', {timeout: 500}, async () => {
|
|
770
|
+
const {result} = await processTestRequest(
|
|
771
|
+
'because accessibility',
|
|
772
|
+
{description: 'Long URL Page', url: `https://example.com/${'a'.repeat(300)}`}
|
|
773
|
+
);
|
|
774
|
+
assert.equal(result, 'urlLength');
|
|
775
|
+
});
|
|
776
|
+
|
|
760
777
|
test('processTestRequest returns a duplicate error for a repeated request', {timeout: 500}, async () => {
|
|
761
778
|
// Reset testRequests.json to empty, then make a successful request.
|
|
762
779
|
await fs.writeFile(testRequestsPath(), '{}\n');
|
package/util.ts
CHANGED
|
@@ -86,7 +86,8 @@ export type TestRequests = Record<string, TestRequest[]>;
|
|
|
86
86
|
// retest a page that already has a report (a request kind); the two must not be
|
|
87
87
|
// confused with one another.
|
|
88
88
|
export type TestRequestResult
|
|
89
|
-
= 'url' | 'description' | 'reportExists' | 'duplicate' | 'superseded' | 'nonreport' | 'queueFull'
|
|
89
|
+
= 'url' | 'description' | 'reportExists' | 'duplicate' | 'superseded' | 'nonreport' | 'queueFull'
|
|
90
|
+
| 'urlLength' | 'ok';
|
|
90
91
|
|
|
91
92
|
// Target of a new-test or retest request: a description and URL for a new-test request,
|
|
92
93
|
// or the identifier of the cited report for a retest request.
|
|
@@ -1019,6 +1020,12 @@ export const processTestRequest = (
|
|
|
1019
1020
|
else {
|
|
1020
1021
|
requestType = 'newTest';
|
|
1021
1022
|
({description, url} = target);
|
|
1023
|
+
// If the URL violates the 12-to-300-character bound on new-test requests:
|
|
1024
|
+
if (url.length < 12 || url.length > 300) {
|
|
1025
|
+
// Reject it here, where every new-test request passes, rather than in each
|
|
1026
|
+
// caller, so the bound applies on every submission surface.
|
|
1027
|
+
return {result: 'urlLength', description, url};
|
|
1028
|
+
}
|
|
1022
1029
|
}
|
|
1023
1030
|
try {
|
|
1024
1031
|
// Get any property the page shares with a claimed job.
|
|
@@ -50,6 +50,14 @@ export const answer = async (description: string, url: string, reason: string) =
|
|
|
50
50
|
'or post your request at https://github.com/jrpool/kilotest/issues or email info@kilotest.com.'
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
// Otherwise, if the URL is shorter than 12 or longer than 300 characters:
|
|
54
|
+
else if (result === 'urlLength') {
|
|
55
|
+
// Report this.
|
|
56
|
+
return {
|
|
57
|
+
status: 'error',
|
|
58
|
+
message: 'The URL must be between 12 and 300 characters long'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
53
61
|
// Otherwise, i.e. if a request to test a page with the same description or URL is approved:
|
|
54
62
|
else {
|
|
55
63
|
// Report this.
|
|
@@ -144,3 +144,24 @@ test('answer returns an error with a fallback channel when the request queue is
|
|
|
144
144
|
assert.ok(result.message.includes('https://github.com/jrpool/kilotest/issues'));
|
|
145
145
|
assert.ok(result.message.includes('info@kilotest.com'));
|
|
146
146
|
});
|
|
147
|
+
|
|
148
|
+
test('answer returns an error for a URL shorter than 12 characters', {timeout: 500}, async () => {
|
|
149
|
+
const {answer} = await import('./index.ts');
|
|
150
|
+
// processTestRequest enforces the 12-to-300-character bound stated by the
|
|
151
|
+
// request form’s minlength/maxlength, so this 11-character URL is rejected.
|
|
152
|
+
const result: any = await answer(
|
|
153
|
+
'Short URL Page', 'https://a.b', 'Because accessibility'
|
|
154
|
+
);
|
|
155
|
+
assert.equal(result.status, 'error');
|
|
156
|
+
assert.equal(result.message, 'The URL must be between 12 and 300 characters long');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('answer returns an error for a URL longer than 300 characters', {timeout: 500}, async () => {
|
|
160
|
+
const {answer} = await import('./index.ts');
|
|
161
|
+
// The same bound rejects this 320-character URL.
|
|
162
|
+
const result: any = await answer(
|
|
163
|
+
'Long URL Page', `https://example.com/${'a'.repeat(300)}`, 'Because accessibility'
|
|
164
|
+
);
|
|
165
|
+
assert.equal(result.status, 'error');
|
|
166
|
+
assert.equal(result.message, 'The URL must be between 12 and 300 characters long');
|
|
167
|
+
});
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
</label></p>
|
|
22
22
|
<p><label>
|
|
23
23
|
What is the URL (starting with <code>https://</code>)?<br>
|
|
24
|
-
<input type="url" size="60" maxlength="
|
|
24
|
+
<input type="url" size="60" minlength="12" maxlength="300" name="url" pattern="https://.+\..+" required>
|
|
25
25
|
</label></p>
|
|
26
26
|
<p><label>
|
|
27
27
|
Why test the page?<br>
|