@jrpool/kilotest 60.0.10 → 60.1.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/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 unrecognized value
74
- // meaning 'duplicate'); callers pass their own extra branch for a requestResult value
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
@@ -4423,5 +4423,5 @@
4423
4423
  }
4424
4424
  }
4425
4425
  ],
4426
- "version": "60.0.10"
4426
+ "version": "60.1.1"
4427
4427
  }
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.10
5
+ version: 60.1.1
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.10
5
+ version: 60.1.1
6
6
  license:
7
7
  name: MIT
8
8
  identifier: MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "60.0.10",
3
+ "version": "60.1.1",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/jrpool/kilotest",
7
7
  "source": "github"
8
8
  },
9
- "version": "60.0.10",
9
+ "version": "60.1.1",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
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' | 'ok';
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.
@@ -60,9 +60,9 @@ const populateQuery = async (
60
60
  // If the violator has a linkable text item, add a take-me-there link.
61
61
  if (catalogIndex && catalogItem?.textLinkable) {
62
62
  const href = getTextFragmentHref(catalogItem.text as string, url);
63
- const label = `Take me to element ${catalogIndex} on the page (in a new tab)`;
64
- const link = `<a href="${href}" target="_blank" aria-label="${label}">Take me there</a>`;
65
- query.takeMeThere = `${margin} <p>${link}</p>`;
63
+ const label = `Take me there (to element ${catalogIndex})`;
64
+ const link = `<a href="${href}" aria-label="${label}">Take me there</a>`;
65
+ query.takeMeThere = `${margin} <li>${link}</li>`;
66
66
  }
67
67
  else {
68
68
  query.takeMeThere = '';
@@ -124,7 +124,7 @@ const populateQuery = async (
124
124
  // If any lines contain text-fragment links:
125
125
  if (takeMeAdviceNeeded) {
126
126
  // Include advice about them in the answer.
127
- query.takeMeThere = '<p><q>Take me there</q> links will open the page in a new tab and try to scroll to the element and highlight it. This does not always succeed. You can return here by closing the new tab.</p>';
127
+ query.takeMeThere = '<p><q>Take me there</q> links will open the page and try to scroll to the element and highlight it. This does not always succeed.</p>';
128
128
  }
129
129
  };
130
130
  // Returns a page answering the violators question.
@@ -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="80" name="url" pattern="https://.+\..+" required>
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>