@jrpool/kilotest 48.0.0 → 50.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.
- package/.claude/scheduled_tasks.lock +1 -0
- package/api/getReport.ts +3 -1
- package/api/requestFeature.test.ts +23 -3
- package/api/requestFeature.ts +19 -11
- package/api/requestRetest.test.ts +39 -13
- package/api/requestRetest.ts +11 -5
- package/api/requestTest.ts +6 -6
- package/api/schemas.ts +2 -2
- package/docs/BACKLOG.md +232 -12
- package/docs/SERVICE.md +84 -3
- package/index.test.ts +443 -20
- package/index.ts +246 -27
- package/linkValidation.test.ts +1 -0
- package/mcp.test.ts +31 -1
- package/mcp.ts +9 -0
- package/package.json +1 -1
- package/smokeTest.ts +16 -0
- package/test/dbFixture.ts +8 -0
- package/util.test.ts +295 -0
- package/util.ts +121 -0
- package/web/ai0BalanceForm/ai0BalanceForm.test.ts +16 -10
- package/web/ai0BalanceForm/index.html +1 -1
- package/web/ai0BalanceForm/index.ts +12 -8
- package/web/enqueue/index.ts +33 -39
- package/web/expungeReportsForm/expungeReportsForm.test.ts +19 -11
- package/web/expungeReportsForm/index.html +1 -1
- package/web/expungeReportsForm/index.ts +1 -1
- package/web/hideReportForm/hideReportForm.test.ts +24 -9
- package/web/hideReportForm/index.html +1 -1
- package/web/hideReportForm/index.ts +13 -8
- package/web/listTopIssues/index.ts +1 -1
- package/web/manage/index.html +2 -1
- package/web/metrics/index.html +19 -0
- package/web/metrics/index.ts +127 -0
- package/web/metrics/metrics.test.ts +211 -0
- package/web/pruneReportsForm/index.html +1 -1
- package/web/pruneReportsForm/index.ts +1 -1
- package/web/pruneReportsForm/pruneReportsForm.test.ts +18 -10
- package/web/reannotate/index.ts +7 -3
- package/web/reannotate/reannotate.test.ts +1 -1
- package/web/renewWCAG/index.ts +6 -3
- package/web/renewWCAG/renewWCAG.test.ts +1 -1
- package/web/reportDeletion.ts +13 -7
- package/web/rewindReportsForm/index.html +1 -1
- package/web/rewindReportsForm/index.ts +1 -1
- package/web/rewindReportsForm/rewindReportsForm.test.ts +18 -10
- package/web/showHiddenReportsForm/index.html +24 -0
- package/web/showHiddenReportsForm/index.ts +45 -0
- package/web/showHiddenReportsForm/showHiddenReportsForm.test.ts +103 -0
- package/web/tutorialAI/index.ts +1 -1
- package/web/tutorialWeb/index.ts +1 -1
- package/web/unhideReportForm/index.html +1 -1
- package/web/unhideReportForm/index.ts +13 -8
- package/web/unhideReportForm/unhideReportForm.test.ts +19 -11
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
showHiddenReportsForm.test.ts
|
|
3
|
+
Unit tests for web/showHiddenReportsForm/index.ts.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
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
|
+
|
|
16
|
+
// CONSTANTS
|
|
17
|
+
|
|
18
|
+
const hiddenFile = '260101T0007-hid.json';
|
|
19
|
+
|
|
20
|
+
// SETUP AND TEARDOWN
|
|
21
|
+
|
|
22
|
+
const savedAuthCode = process.env.AUTH_CODE;
|
|
23
|
+
const savedDBDir = process.env.DB_DIR;
|
|
24
|
+
import {fixtureDBDir} from '../../test/dbFixture.ts';
|
|
25
|
+
|
|
26
|
+
const getPaths = () => ({
|
|
27
|
+
hidden: path.join(hiddenReportsPath(), hiddenFile),
|
|
28
|
+
report: path.join(reportsPath(), hiddenFile)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
before(() => {
|
|
32
|
+
process.env.AUTH_CODE = 'test-auth-code';
|
|
33
|
+
process.env.DB_DIR = fixtureDBDir;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
// Ensure the hidden report is in the hidden directory before each test.
|
|
38
|
+
const {hidden, report} = getPaths();
|
|
39
|
+
try {
|
|
40
|
+
await fs.rename(report, hidden);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Report may already be hidden.
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
after(async () => {
|
|
48
|
+
// Restore the hidden report if it was moved.
|
|
49
|
+
const {hidden, report} = getPaths();
|
|
50
|
+
try {
|
|
51
|
+
await fs.rename(report, hidden);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Report may already be hidden.
|
|
55
|
+
}
|
|
56
|
+
if (savedAuthCode !== undefined) {
|
|
57
|
+
process.env.AUTH_CODE = savedAuthCode;
|
|
58
|
+
}
|
|
59
|
+
if (savedDBDir !== undefined) {
|
|
60
|
+
process.env.DB_DIR = savedDBDir;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// TESTS
|
|
65
|
+
|
|
66
|
+
test('showHiddenReportsForm displays a bare authCode form on a GET request, disclosing nothing', async () => {
|
|
67
|
+
const result: any = await answer(null, '', 'GET');
|
|
68
|
+
assert.equal(result.status, 'ok');
|
|
69
|
+
const html = parse(result.answerPage);
|
|
70
|
+
assert.ok(html.querySelector('input[name="authCode"]'));
|
|
71
|
+
assert.equal(html.querySelectorAll('input[type="radio"]').length, 0);
|
|
72
|
+
assert.ok(!result.answerPage.includes('260101T0007-hid'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('showHiddenReportsForm submits its form as a POST', async () => {
|
|
76
|
+
const result: any = await answer(null, '', 'GET');
|
|
77
|
+
const html = parse(result.answerPage);
|
|
78
|
+
const form = html.querySelector('form');
|
|
79
|
+
assert.equal(form?.getAttribute('method'), 'post');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('showHiddenReportsForm ignores a valid authCode query string on a GET request', async () => {
|
|
83
|
+
const result: any = await answer(null, 'authCode=test-auth-code', 'GET');
|
|
84
|
+
assert.equal(result.status, 'ok');
|
|
85
|
+
const html = parse(result.answerPage);
|
|
86
|
+
assert.ok(html.querySelector('input[name="authCode"]'));
|
|
87
|
+
assert.equal(html.querySelectorAll('input[type="radio"]').length, 0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('showHiddenReportsForm returns an error for an invalid auth code on a POST request', async () => {
|
|
91
|
+
const result: any = await answer(null, 'authCode=wrong', 'POST');
|
|
92
|
+
assert.equal(result.status, 'error');
|
|
93
|
+
assert.equal(result.message, 'Invalid request');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('showHiddenReportsForm serves the hidden reports list on a POST request with a valid auth code', async () => {
|
|
97
|
+
const result: any = await answer(null, 'authCode=test-auth-code', 'POST');
|
|
98
|
+
assert.equal(result.status, 'ok');
|
|
99
|
+
const html = parse(result.answerPage);
|
|
100
|
+
const radios = html.querySelectorAll('input[type="radio"]');
|
|
101
|
+
assert.ok(radios.length > 0);
|
|
102
|
+
assert.ok(result.answerPage.includes('260101T0007-hid'));
|
|
103
|
+
});
|
package/web/tutorialAI/index.ts
CHANGED
|
@@ -75,6 +75,6 @@ export const handleComment = async (content: unknown) => {
|
|
|
75
75
|
// Save the revised comments.
|
|
76
76
|
await fs.writeFile(commentsPath, getJSON(comments));
|
|
77
77
|
// Send an alert to the manager.
|
|
78
|
-
await sendAlert('New AI tutorial comment received', 'A new AI tutorial comment has been received.');
|
|
78
|
+
await sendAlert('Kilotest: New AI tutorial comment received', 'A new AI tutorial comment has been received.');
|
|
79
79
|
return {status: 'ok'};
|
|
80
80
|
};
|
package/web/tutorialWeb/index.ts
CHANGED
|
@@ -73,6 +73,6 @@ export const handleComment = async (content: unknown) => {
|
|
|
73
73
|
// Save the revised comments.
|
|
74
74
|
await fs.writeFile(commentsPath, getJSON(comments));
|
|
75
75
|
// Send an alert to the manager.
|
|
76
|
-
await sendAlert('New web tutorial comment received', 'A new web tutorial comment has been received.');
|
|
76
|
+
await sendAlert('Kilotest: New web tutorial comment received', 'A new web tutorial comment has been received.');
|
|
77
77
|
return {status: 'ok'};
|
|
78
78
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<main>
|
|
15
15
|
<h1><a href="/">Kilotest</a>: Declassify report as experimental</h1>
|
|
16
16
|
<p>Choose a report to declassify as experimental.</p>
|
|
17
|
-
<form action="/unhideReportForm.html">
|
|
17
|
+
<form action="/unhideReportForm.html" method="post">
|
|
18
18
|
<fieldset>
|
|
19
19
|
<legend>Reports</legend>
|
|
20
20
|
__reports__
|
|
@@ -5,21 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
hiddenReportsPath, isValidAuthCode, populateTemplate, readdirOrCreate, reportsPath
|
|
10
|
+
} from '../../util.ts';
|
|
9
11
|
import fs from 'node:fs/promises';
|
|
10
12
|
import path from 'node:path';
|
|
11
13
|
|
|
12
14
|
// FUNCTIONS
|
|
13
15
|
|
|
14
|
-
// Returns a form for unhiding a report.
|
|
15
|
-
|
|
16
|
+
// Returns a form for unhiding a report. A GET request never processes a submission,
|
|
17
|
+
// regardless of its query string; only a POST request (the form's own submission) does.
|
|
18
|
+
export const answer = async (_: any, search: string, method: string) => {
|
|
16
19
|
const searchParams = new URLSearchParams(search);
|
|
17
20
|
const authCode = searchParams?.get('authCode');
|
|
18
21
|
const jobName = searchParams?.get('report');
|
|
19
|
-
// If the form has been
|
|
20
|
-
if (jobName) {
|
|
22
|
+
// If the form has been submitted and a report is to be unhidden:
|
|
23
|
+
if (method === 'POST' && jobName) {
|
|
21
24
|
// If the authorization code is valid:
|
|
22
|
-
if (authCode
|
|
25
|
+
if (isValidAuthCode(authCode)) {
|
|
23
26
|
const fileName = `${jobName}.json`;
|
|
24
27
|
try {
|
|
25
28
|
// Move the report to the reports directory.
|
|
@@ -35,10 +38,12 @@ export const answer = async (_: any, search: string) => {
|
|
|
35
38
|
}
|
|
36
39
|
// Otherwise, i.e. if the authorization code is invalid:
|
|
37
40
|
else {
|
|
38
|
-
// Report the error
|
|
41
|
+
// Report the error, deliberately vague so as not to confirm to an attacker that the
|
|
42
|
+
// authorization code specifically (as opposed to some other part of the request) is
|
|
43
|
+
// what was wrong.
|
|
39
44
|
return {
|
|
40
45
|
status: 'error',
|
|
41
|
-
message: 'Invalid
|
|
46
|
+
message: 'Invalid request'
|
|
42
47
|
}
|
|
43
48
|
}
|
|
44
49
|
}
|
|
@@ -63,8 +63,8 @@ after(async () => {
|
|
|
63
63
|
|
|
64
64
|
// TESTS
|
|
65
65
|
|
|
66
|
-
test('unhideReportForm displays a list of hidden reports
|
|
67
|
-
const result: any = await answer(null, '');
|
|
66
|
+
test('unhideReportForm displays a list of hidden reports on a GET request', async () => {
|
|
67
|
+
const result: any = await answer(null, '', 'GET');
|
|
68
68
|
assert.equal(result.status, 'ok');
|
|
69
69
|
assert.ok(result.answerPage);
|
|
70
70
|
const html = parse(result.answerPage);
|
|
@@ -72,22 +72,30 @@ test('unhideReportForm displays a list of hidden reports when no submission', as
|
|
|
72
72
|
assert.ok(radios.length > 0);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
test('unhideReportForm
|
|
76
|
-
const
|
|
75
|
+
test('unhideReportForm ignores a report query string on a GET request', async () => {
|
|
76
|
+
const {report} = getPaths();
|
|
77
|
+
const result: any = await answer(null, 'authCode=test-auth-code&report=260101T0007-hid', 'GET');
|
|
78
|
+
assert.equal(result.status, 'ok');
|
|
79
|
+
// The report should NOT have been unhidden by a mere GET request.
|
|
80
|
+
await assert.rejects(fs.access(report));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('unhideReportForm returns an error for an invalid auth code on a POST request', async () => {
|
|
84
|
+
const result: any = await answer(null, 'authCode=wrong&report=260101T0007-hid', 'POST');
|
|
77
85
|
assert.equal(result.status, 'error');
|
|
78
|
-
assert.equal(result.message, 'Invalid
|
|
86
|
+
assert.equal(result.message, 'Invalid request');
|
|
79
87
|
});
|
|
80
88
|
|
|
81
|
-
test('unhideReportForm unhides a report with valid auth code', async () => {
|
|
89
|
+
test('unhideReportForm unhides a report with valid auth code on a POST request', async () => {
|
|
82
90
|
const {report} = getPaths();
|
|
83
|
-
const result: any = await answer(null, 'authCode=test-auth-code&report=260101T0007-hid');
|
|
91
|
+
const result: any = await answer(null, 'authCode=test-auth-code&report=260101T0007-hid', 'POST');
|
|
84
92
|
assert.equal(result.status, 'ok');
|
|
85
93
|
// The report should now be in the reports directory.
|
|
86
94
|
await fs.access(report);
|
|
87
95
|
});
|
|
88
96
|
|
|
89
|
-
test('unhideReportForm returns an error when unhiding a nonexistent report', async () => {
|
|
90
|
-
const result: any = await answer(null, 'authCode=test-auth-code&report=999999T9999-nope');
|
|
97
|
+
test('unhideReportForm returns an error when unhiding a nonexistent report on a POST request', async () => {
|
|
98
|
+
const result: any = await answer(null, 'authCode=test-auth-code&report=999999T9999-nope', 'POST');
|
|
91
99
|
assert.equal(result.status, 'error');
|
|
92
100
|
assert.ok(result.message.includes('Unhiding report'));
|
|
93
101
|
});
|
|
@@ -102,7 +110,7 @@ test('unhideReportForm sorts hidden reports with the same page name by timeStamp
|
|
|
102
110
|
const copyPath = path.join(hiddenReportsPath(), '260101T0010-hid2.json');
|
|
103
111
|
await fs.writeFile(copyPath, JSON.stringify(copy));
|
|
104
112
|
try {
|
|
105
|
-
const result: any = await answer(null, '');
|
|
113
|
+
const result: any = await answer(null, '', 'GET');
|
|
106
114
|
assert.equal(result.status, 'ok');
|
|
107
115
|
// Both reports should appear in the form.
|
|
108
116
|
assert.ok(result.answerPage.includes('260101T0007-hid'));
|
|
@@ -123,7 +131,7 @@ test('unhideReportForm sorts hidden reports with different page names alphabetic
|
|
|
123
131
|
const copyPath = path.join(hiddenReportsPath(), '260101T0011-hid3.json');
|
|
124
132
|
await fs.writeFile(copyPath, JSON.stringify(copy));
|
|
125
133
|
try {
|
|
126
|
-
const result: any = await answer(null, '');
|
|
134
|
+
const result: any = await answer(null, '', 'GET');
|
|
127
135
|
assert.equal(result.status, 'ok');
|
|
128
136
|
// Both reports should appear in the form.
|
|
129
137
|
assert.ok(result.answerPage.includes('260101T0007-hid'));
|