@jrpool/kilotest 46.1.2 → 48.0.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.
Files changed (153) hide show
  1. package/.claude/settings.local.json +13 -1
  2. package/.devin/config.local.json +5 -1
  3. package/.github/workflows/ci.yml +0 -3
  4. package/.github/workflows/periodic-smoke-tests.yml +30 -0
  5. package/.github/workflows/publish-mcp.yml +4 -4
  6. package/{alerts.test.js → alerts.test.ts} +17 -29
  7. package/{alerts.js → alerts.ts} +10 -13
  8. package/api/{getReport.test.js → getReport.test.ts} +9 -10
  9. package/api/{getReport.js → getReport.ts} +23 -24
  10. package/api/{listDiagnoses.test.js → listDiagnoses.test.ts} +26 -27
  11. package/api/{listDiagnoses.js → listDiagnoses.ts} +46 -70
  12. package/api/{listIssues.test.js → listIssues.test.ts} +21 -14
  13. package/api/{listIssues.js → listIssues.ts} +75 -74
  14. package/api/{listReports.test.js → listReports.test.ts} +17 -16
  15. package/api/listReports.ts +87 -0
  16. package/api/{listViolators.test.js → listViolators.test.ts} +19 -20
  17. package/api/{listViolators.js → listViolators.ts} +45 -70
  18. package/api/requestFeature.test.ts +54 -0
  19. package/api/{requestFeature.js → requestFeature.ts} +18 -15
  20. package/api/requestRetest.test.ts +144 -0
  21. package/api/requestRetest.ts +119 -0
  22. package/api/requestTest.test.ts +153 -0
  23. package/api/requestTest.ts +123 -0
  24. package/api/{routes.test.js → routes.test.ts} +5 -5
  25. package/api/{routes.js → routes.ts} +6 -6
  26. package/api/{schemas.js → schemas.ts} +69 -50
  27. package/api/{util.test.js → util.test.ts} +35 -14
  28. package/api/{util.js → util.ts} +54 -51
  29. package/api/version.ts +8 -0
  30. package/{apiIntegration.test.js → apiIntegration.test.ts} +12 -13
  31. package/balances.ts +87 -0
  32. package/{checkinfo.js → checkinfo.ts} +24 -20
  33. package/docs/AI-TOOLS.md +4 -4
  34. package/docs/BACKLOG.md +360 -0
  35. package/docs/SERVICE.md +170 -17
  36. package/docs/decisions/002-coverage-tool.md +40 -0
  37. package/docs/decisions/003-adr-threshold.md +34 -0
  38. package/docs/decisions/004-docs-directory.md +23 -0
  39. package/docs/decisions/005-observability.md +28 -0
  40. package/docs/decisions/006-git-workflow.md +28 -0
  41. package/docs/decisions/007-internal-testing.md +34 -0
  42. package/docs/decisions/008-dependencies.md +28 -0
  43. package/docs/decisions/009-qai-integration.md +40 -0
  44. package/docs/decisions/QAI-HISTORICAL-CONTEXT.md +42 -0
  45. package/eslint.config.mjs +27 -0
  46. package/index.html +3 -3
  47. package/{index.test.js → index.test.ts} +397 -152
  48. package/{index.js → index.ts} +386 -285
  49. package/{indexBalances.test.js → indexBalances.test.ts} +21 -17
  50. package/{indexServe.test.js → indexServe.test.ts} +9 -9
  51. package/linkValidation.test.ts +190 -0
  52. package/{mcp.test.js → mcp.test.ts} +53 -26
  53. package/{mcp.js → mcp.ts} +29 -20
  54. package/memory/MEMORY.md +7 -0
  55. package/memory/user_maintainer_context.md +24 -0
  56. package/openapi.yaml +115 -68
  57. package/package.json +35 -15
  58. package/pm2.config.cjs +29 -0
  59. package/{routes.test.js → routes.test.ts} +8 -7
  60. package/{routing.test.js → routing.test.ts} +11 -12
  61. package/scripts/{generate-openapi.js → generate-openapi.ts} +13 -13
  62. package/serve.ts +15 -0
  63. package/smokeTest.ts +234 -0
  64. package/test/dbFixture.ts +21 -0
  65. package/test/fixtures/{buildFixtures.js → buildFixtures.ts} +14 -13
  66. package/test/{setup.cjs → setup.ts} +2 -2
  67. package/tsconfig.json +16 -0
  68. package/util.test.ts +1129 -0
  69. package/util.ts +1017 -0
  70. package/web/ai0BalanceForm/{ai0BalanceForm.test.js → ai0BalanceForm.test.ts} +15 -15
  71. package/web/ai0BalanceForm/{index.js → index.ts} +10 -18
  72. package/web/enqueue/index.ts +56 -0
  73. package/web/enqueueForm/index.html +9 -9
  74. package/web/enqueueForm/index.ts +43 -0
  75. package/web/expungeReportsForm/{expungeReportsForm.test.js → expungeReportsForm.test.ts} +18 -20
  76. package/web/expungeReportsForm/index.ts +18 -0
  77. package/web/hideReportForm/{hideReportForm.test.js → hideReportForm.test.ts} +14 -14
  78. package/web/hideReportForm/{index.js → index.ts} +17 -21
  79. package/web/listDiagnoses/{index.js → index.ts} +48 -58
  80. package/web/listDiagnoses/{listDiagnoses.test.js → listDiagnoses.test.ts} +39 -34
  81. package/web/listIssues/{index.js → index.ts} +67 -92
  82. package/web/listIssues/{listIssues.test.js → listIssues.test.ts} +45 -51
  83. package/web/listReports/index.html +3 -3
  84. package/web/listReports/{index.js → index.ts} +74 -58
  85. package/web/listReports/{listReports.test.js → listReports.test.ts} +41 -43
  86. package/web/listRules/{index.js → index.ts} +18 -23
  87. package/web/listRules/{listRules.test.js → listRules.test.ts} +26 -19
  88. package/web/listTopIssues/{index.js → index.ts} +36 -47
  89. package/web/listTopIssues/{listTopIssues.test.js → listTopIssues.test.ts} +26 -17
  90. package/web/listViolators/{index.js → index.ts} +51 -71
  91. package/web/listViolators/{listViolators.test.js → listViolators.test.ts} +43 -36
  92. package/web/manage/index.ts +22 -0
  93. package/web/pruneReportsForm/index.ts +18 -0
  94. package/web/pruneReportsForm/{pruneReportsForm.test.js → pruneReportsForm.test.ts} +11 -11
  95. package/web/reannotate/index.ts +71 -0
  96. package/web/reannotate/{reannotate.test.js → reannotate.test.ts} +19 -22
  97. package/web/reannotateForm/{index.js → index.ts} +41 -50
  98. package/web/reannotateForm/{reannotateForm.test.js → reannotateForm.test.ts} +13 -16
  99. package/web/renewWCAG/{index.js → index.ts} +9 -9
  100. package/web/renewWCAG/{renewWCAG.test.js → renewWCAG.test.ts} +17 -20
  101. package/web/renewWCAGForm/index.ts +22 -0
  102. package/web/{expungeReportsForm/index.js → reportDeletion.ts} +57 -39
  103. package/web/requestRetest/index.html +7 -7
  104. package/web/requestRetest/index.ts +62 -0
  105. package/web/requestRetest/requestRetest.test.ts +130 -0
  106. package/web/requestRetestForm/{index.js → index.ts} +10 -16
  107. package/web/requestRetestForm/{requestRetestForm.test.js → requestRetestForm.test.ts} +11 -12
  108. package/web/requestTest/index.html +7 -7
  109. package/web/requestTest/index.ts +52 -0
  110. package/web/requestTest/{requestTest.test.js → requestTest.test.ts} +39 -24
  111. package/web/requestTestForm/index.html +4 -4
  112. package/web/requestTestForm/index.ts +21 -0
  113. package/web/requestTestForm/{requestTestForm.test.js → requestTestForm.test.ts} +9 -9
  114. package/web/rewindReportsForm/index.ts +18 -0
  115. package/web/rewindReportsForm/{rewindReportsForm.test.js → rewindReportsForm.test.ts} +11 -11
  116. package/web/tutorialAI/index.html +184 -0
  117. package/web/tutorialAI/index.ts +80 -0
  118. package/web/tutorialAI/tutorialAI.test.ts +192 -0
  119. package/web/{tutorial → tutorialWeb}/index.html +9 -8
  120. package/web/tutorialWeb/index.ts +78 -0
  121. package/web/tutorialWeb/tutorialWeb.test.ts +195 -0
  122. package/web/unhideReportForm/{index.js → index.ts} +18 -22
  123. package/web/unhideReportForm/{unhideReportForm.test.js → unhideReportForm.test.ts} +16 -16
  124. package/aceconfig.js +0 -28
  125. package/api/listReports.js +0 -89
  126. package/api/requestFeature.test.js +0 -58
  127. package/api/requestRetest.js +0 -96
  128. package/api/requestRetest.test.js +0 -80
  129. package/api/requestTest.js +0 -106
  130. package/api/requestTest.test.js +0 -92
  131. package/api/version.js +0 -8
  132. package/indexFallback.test.js +0 -33
  133. package/indexWorkers.test.js +0 -79
  134. package/jsconfig.json +0 -8
  135. package/pm2.config.js +0 -18
  136. package/smokeTest.js +0 -128
  137. package/util.js +0 -744
  138. package/util.test.js +0 -968
  139. package/web/enqueue/index.js +0 -67
  140. package/web/enqueueForm/index.js +0 -49
  141. package/web/manage/index.js +0 -22
  142. package/web/pruneReportsForm/index.js +0 -123
  143. package/web/reannotate/index.js +0 -58
  144. package/web/renewWCAGForm/index.js +0 -22
  145. package/web/requestRetest/index.js +0 -31
  146. package/web/requestRetest/requestRetest.test.js +0 -119
  147. package/web/requestTest/index.js +0 -24
  148. package/web/requestTestForm/index.js +0 -21
  149. package/web/rewindReportsForm/index.js +0 -121
  150. package/web/tutorial/index.js +0 -63
  151. package/web/tutorial/tutorial.test.js +0 -97
  152. /package/test/fixtures/db/jobs/{recs.json → testRequests.json} +0 -0
  153. /package/web/{tutorial → tutorialWeb}/images/newsletter-form.png +0 -0
@@ -1,17 +1,17 @@
1
1
  /*
2
- unhideReportForm.test.js
3
- Unit tests for web/unhideReportForm/index.js.
2
+ unhideReportForm.test.ts
3
+ Unit tests for web/unhideReportForm/index.ts.
4
4
  */
5
5
 
6
6
  // IMPORTS
7
7
 
8
- const {test, before, after, beforeEach} = require('node:test');
9
- const assert = require('node:assert/strict');
10
- const path = require('node:path');
11
- const fs = require('fs/promises');
12
- const {parse} = require('node-html-parser');
13
- const {answer} = require('./index');
14
- const {reportsPath, hiddenReportsPath} = require('../../util');
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
- const fixtureDBDir = path.join(__dirname, '..', '..', 'test', 'fixtures', 'db');
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
- };
@@ -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
- });
@@ -1,96 +0,0 @@
1
- /*
2
- requestRetest.js
3
- Processes a request to retest a page and returns an acknowledgement.
4
- */
5
-
6
- // IMPORTS
7
-
8
- const {getResponseMetadata, getToolsFacts, processTestRequest, thisHost} = require('./util');
9
- const {getLatestReportExtracts, getReportExtract} = require('../util');
10
-
11
- // FUNCTIONS
12
-
13
- // Returns the response body.
14
- exports.response = async args => {
15
- const [timeStamp = '', jobID = '', reason = ''] = args;
16
- // Initialize the response content.
17
- const responseContent = {
18
- 'details about your request': {},
19
- 'disposition of your request': null
20
- };
21
- const reasonLength = reason.length;
22
- // Get data on the report.
23
- const reportExtract = await getReportExtract(timeStamp, jobID);
24
- const {what, url} = reportExtract.error ? {} : reportExtract;
25
- // If this failed:
26
- if (reportExtract.error) {
27
- // Add this to the response content.
28
- responseContent['details about your request'] = {
29
- error: 'request invalid: the specified existing report is not an available report'
30
- };
31
- }
32
- // Otherwise, if the report has been superseded:
33
- else if (
34
- (await getLatestReportExtracts())
35
- .every(extract => extract.timeStamp !== timeStamp || extract.jobID !== jobID)
36
- ) {
37
- // Add this to the response content.
38
- responseContent['details about your request'] = {
39
- error: 'request invalid: a later report about the page exists'
40
- };
41
- }
42
- // Otherwise, if the encoded reason is too short or too long:
43
- else if (reasonLength < 20 || reasonLength > 100) {
44
- // Add this to the response content.
45
- responseContent['details about your request'] = {
46
- error: 'request invalid: your reason is not between 20 and 100 characters long'
47
- }
48
- }
49
- // Otherwise, i.e. if getting the report succeeded and the request is valid:
50
- else {
51
- // Process the request.
52
- await processTestRequest('retest', what, url, reason);
53
- // Add details about the request to the response content.
54
- responseContent['details about your request'] = {
55
- 'date and time received': new Date().toISOString(),
56
- 'page to be retested': {
57
- 'description': what,
58
- 'URL': url
59
- },
60
- 'reason why the page should be retested': reason
61
- };
62
- // Add information about the disposition of the request to the response content.
63
- responseContent['disposition of your request'] = {
64
- 'what happens next': 'Your request is likely to be approved and processed within 1 hour to 1 day.',
65
- 'how you can check for completion': 'You can call the listReports tool to learn whether the page has been retested and a new report is available.',
66
- 'how a web user can check for completion': `A web user can visit ${thisHost}/listReports.html to learn whether the page has been retested and a new report is available.`
67
- };
68
- }
69
- // Create a response body.
70
- const body = {
71
- 'tool collection': getToolsFacts(),
72
- 'tool name': 'requestRetest',
73
- 'this request': {
74
- description: 'Process my request to retest a page. The timeStamp and jobID parameters identify the latest available report about the page. Those parameters were in the response to my earlier listIssues request. The reason property of the request body is the reason why the page should be retested.',
75
- method: 'POST',
76
- URL: `${thisHost}/api/requestRetest/${timeStamp}/${jobID}`,
77
- body: {
78
- reason
79
- },
80
- 'closest ancestor request': {
81
- 'tool name': 'listIssues',
82
- description: 'Provide details about one report, including basics about the issues reported in it.',
83
- method: 'GET',
84
- URL: `${thisHost}/api/listIssues/${timeStamp}/${jobID}`
85
- }
86
- },
87
- 'URLs of similar requests for web users': {
88
- 'this request': `${thisHost}/requestRetestForm.html/${timeStamp}/${jobID}`,
89
- 'closest ancestor request': `${thisHost}/listIssues.html/${timeStamp}/${jobID}`
90
- },
91
- 'response metadata': getResponseMetadata(),
92
- 'response content': responseContent
93
- };
94
- // Return it.
95
- return body;
96
- };
@@ -1,80 +0,0 @@
1
- /*
2
- requestRetest.test.js
3
- Tests for api/requestRetest.js using the fixture corpus, 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 path = require('node:path');
11
- const apiUtil = require('./util');
12
-
13
- // SETUP AND TEARDOWN
14
-
15
- const savedDBDir = process.env.DB_DIR;
16
- let processTestRequestCalls = [];
17
-
18
- before(() => {
19
- process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
20
- // @ts-expect-error: Replacing the real function with a mock for testing.
21
- apiUtil.processTestRequest = async (testType, what, url, reason) => {
22
- processTestRequestCalls.push({testType, what, url, reason});
23
- };
24
- });
25
-
26
- // Reset the call log before each test, so tests do not depend on execution order.
27
- beforeEach(() => {
28
- processTestRequestCalls = [];
29
- });
30
-
31
- // Require requestRetest after the mock is in place, so it captures the mocked processTestRequest.
32
- const {response} = require('./requestRetest');
33
-
34
- after(() => {
35
- if (savedDBDir !== undefined) {
36
- process.env.DB_DIR = savedDBDir;
37
- }
38
- else {
39
- delete process.env.DB_DIR;
40
- }
41
- });
42
-
43
- // TESTS
44
-
45
- test('requestRetest rejects a nonexistent report', async () => {
46
- const body = await response(['999999T9999', 'xyz', 'A reason that is long enough.']);
47
- assert.ok(body['response content']['details about your request'].error);
48
- assert.equal(processTestRequestCalls.length, 0);
49
- });
50
-
51
- test('requestRetest rejects a superseded report', async () => {
52
- const body = await response(['260101T0000', 'mix', 'A reason that is long enough.']);
53
- const details = body['response content']['details about your request'];
54
- assert.ok(details.error.includes('later report'));
55
- assert.equal(processTestRequestCalls.length, 0);
56
- });
57
-
58
- test('requestRetest rejects a reason shorter than 20 characters', async () => {
59
- const body = await response(['260101T0001', 'ct', 'short']);
60
- const details = body['response content']['details about your request'];
61
- assert.ok(details.error.includes('reason'));
62
- assert.equal(processTestRequestCalls.length, 0);
63
- });
64
-
65
- test('requestRetest rejects a reason longer than 100 characters', async () => {
66
- const longReason = 'x'.repeat(101);
67
- const body = await response(['260101T0001', 'ct', longReason]);
68
- const details = body['response content']['details about your request'];
69
- assert.ok(details.error.includes('reason'));
70
- assert.equal(processTestRequestCalls.length, 0);
71
- });
72
-
73
- test('requestRetest accepts a valid retest request for the latest report of a page', async () => {
74
- const body = await response(['260202T0000', 'new', 'A reason that is long enough.']);
75
- const details = body['response content']['details about your request'];
76
- assert.equal(details.error, undefined);
77
- assert.equal(details['page to be retested'].description, 'Mixed Outcomes Page');
78
- assert.equal(processTestRequestCalls.length, 1);
79
- assert.equal(processTestRequestCalls[0].testType, 'retest');
80
- });
@@ -1,106 +0,0 @@
1
- /*
2
- requestTest.js
3
- Processes a request to test an untested page and returns an acknowledgement.
4
- */
5
-
6
- // IMPORTS
7
-
8
- const {getResponseMetadata, getToolsFacts, processTestRequest, thisHost} = require('./util');
9
- const {getReportExtracts, isURL} = require('../util');
10
-
11
- // FUNCTIONS
12
-
13
- // Returns the response body.
14
- exports.response = async args => {
15
- const [what = '', url = '', reason = ''] = args;
16
- // Initialize the response content.
17
- const responseContent = {
18
- 'details about your request': {},
19
- 'disposition of your request': null
20
- };
21
- const whatLength = what.length;
22
- // If the description is empty or too long:
23
- if (!whatLength || whatLength > 100) {
24
- // Add this to the response content.
25
- responseContent['details about your request'] = {
26
- error: 'request invalid: your description of the page is not between 1 and 100 characters long'
27
- };
28
- }
29
- // Otherwise, i.e. if the URL is too short or too long::
30
- else if (url.length < 12 || url.length > 300) {
31
- // Add this to the response content.
32
- responseContent['details about your request'] = {
33
- error: 'request invalid: you specified a URL for the page that is not between 12 and 300 characters long'
34
- };
35
- }
36
- // Otherwise, i.e. if the URL is invalid:
37
- else if (!isURL(url)) {
38
- // Add this to the response content.
39
- responseContent['details about your request'] = {
40
- error: 'request invalid: you specified an invalid URL for the page'
41
- };
42
- }
43
- // Otherwise, i.e. if the description and URL are valid:
44
- else {
45
- // Get an extract of the available reports.
46
- const reportExtracts = await getReportExtracts();
47
- // If any report is on a page with the specified description and URL:
48
- if (
49
- reportExtracts.some(extract => extract.what === what && extract.url === url)
50
- ) {
51
- // Add this to the response content.
52
- responseContent['details about your request'] = {
53
- error: 'request invalid: the page has already been tested and its report is available'
54
- };
55
- }
56
- // Otherwise, i.e. if none is on the page:
57
- else {
58
- // Process the request.
59
- await processTestRequest('test', what, url, reason);
60
- // Add details about the request to the response content.
61
- responseContent['details about your request'] = {
62
- 'date and time received': new Date().toISOString(),
63
- 'page to be tested': {
64
- description: what,
65
- URL: url
66
- },
67
- 'reason why the page should be tested': reason
68
- };
69
- // Add information about the disposition of the request to the response content.
70
- responseContent['disposition of your request'] = {
71
- 'what happens next': 'Your request is likely to be approved and processed within 1 hour to 1 day.',
72
- 'how you can check for completion': 'You can call the listReports tool to learn whether the page has been tested and a report is available.',
73
- 'how a web user can check for completion': `A web user can visit ${thisHost}/listReports.html to learn whether the page has been tested and a report is available.`
74
- };
75
- }
76
- }
77
- // Create a response body.
78
- const body = {
79
- 'tool collection': getToolsFacts(),
80
- 'tool name': 'requestTest',
81
- 'this request': {
82
- description: 'Process my request to test a page about which no report is available yet. I have provided a description and the URL of the page and a reason why it should be tested.',
83
- method: 'POST',
84
- URL: `${thisHost}/api/requestTest`,
85
- body: {
86
- description: what,
87
- URL: url,
88
- reason
89
- },
90
- 'closest ancestor request': {
91
- 'tool name': 'listReports',
92
- description: 'Provide basics about all available reports.',
93
- method: 'GET',
94
- URL: `${thisHost}/api/listReports`
95
- }
96
- },
97
- 'URLs of similar requests for web users': {
98
- 'this request': `${thisHost}/requestTestForm.html`,
99
- 'closest ancestor request': `${thisHost}/listReports.html`
100
- },
101
- 'response metadata': getResponseMetadata(),
102
- 'response content': responseContent
103
- };
104
- // Return it.
105
- return body;
106
- };
@@ -1,92 +0,0 @@
1
- /*
2
- requestTest.test.js
3
- Tests for api/requestTest.js using the fixture corpus, 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 path = require('node:path');
11
- const apiUtil = require('./util');
12
-
13
- // SETUP AND TEARDOWN
14
-
15
- const savedDBDir = process.env.DB_DIR;
16
- let processTestRequestCalls = [];
17
-
18
- before(() => {
19
- process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
20
- // @ts-expect-error: Replacing the real function with a mock for testing.
21
- apiUtil.processTestRequest = async (testType, what, url, reason) => {
22
- processTestRequestCalls.push({testType, what, url, reason});
23
- };
24
- });
25
-
26
- // Reset the call log before each test, so tests do not depend on execution order.
27
- beforeEach(() => {
28
- processTestRequestCalls = [];
29
- });
30
-
31
- // Require requestTest after the mock is in place, so it captures the mocked processTestRequest.
32
- const {response} = require('./requestTest');
33
-
34
- after(() => {
35
- if (savedDBDir !== undefined) {
36
- process.env.DB_DIR = savedDBDir;
37
- }
38
- else {
39
- delete process.env.DB_DIR;
40
- }
41
- });
42
-
43
- // TESTS
44
-
45
- test('requestTest rejects an empty description', async () => {
46
- const body = await response(['', 'https://example.com/test', 'A reason that is long enough.']);
47
- assert.ok(body['response content']['details about your request'].error);
48
- assert.equal(processTestRequestCalls.length, 0);
49
- });
50
-
51
- test('requestTest rejects a description longer than 100 characters', async () => {
52
- const longWhat = 'x'.repeat(101);
53
- const body = await response([longWhat, 'https://example.com/test', 'A reason that is long enough.']);
54
- assert.ok(body['response content']['details about your request'].error);
55
- assert.equal(processTestRequestCalls.length, 0);
56
- });
57
-
58
- test('requestTest rejects a URL shorter than 12 characters', async () => {
59
- const body = await response(['Test Page', 'short', 'A reason that is long enough.']);
60
- assert.ok(body['response content']['details about your request'].error);
61
- assert.equal(processTestRequestCalls.length, 0);
62
- });
63
-
64
- test('requestTest rejects a syntactically invalid URL with the correct length', async () => {
65
- const body = await response(['Test Page', 'not-a-valid-url', 'A reason that is long enough.']);
66
- const details = body['response content']['details about your request'];
67
- assert.ok(details.error.includes('invalid URL'));
68
- assert.equal(processTestRequestCalls.length, 0);
69
- });
70
-
71
- test('requestTest rejects an already-tested page', async () => {
72
- const body = await response(['Mixed Outcomes Page', 'https://example.com/mixed', 'A reason that is long enough.']);
73
- const details = body['response content']['details about your request'];
74
- assert.ok(details.error.includes('already been tested'));
75
- assert.equal(processTestRequestCalls.length, 0);
76
- });
77
-
78
- test('requestTest accepts a valid new page request', async () => {
79
- const body = await response(['Brand New Page', 'https://example.com/brandnew', 'A reason that is long enough.']);
80
- const details = body['response content']['details about your request'];
81
- assert.equal(details.error, undefined);
82
- assert.equal(details['page to be tested'].description, 'Brand New Page');
83
- assert.equal(processTestRequestCalls.length, 1);
84
- assert.equal(processTestRequestCalls[0].testType, 'test');
85
- });
86
-
87
- test('requestTest includes disposition information for a valid request', async () => {
88
- const body = await response(['Brand New Page', 'https://example.com/brandnew', 'A reason that is long enough.']);
89
- const disposition = body['response content']['disposition of your request'];
90
- assert.ok(disposition);
91
- assert.ok(disposition['what happens next']);
92
- });
package/api/version.js DELETED
@@ -1,8 +0,0 @@
1
- /*
2
- version.js
3
- Single source of truth for the version of the Kilotest API and MCP tool contract, consumed by mcp.js and scripts/generate-openapi.js.
4
- */
5
-
6
- // CONSTANTS
7
-
8
- exports.version = '2.1.0';