@jrpool/kilotest 46.0.4 → 46.0.6
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.js +62 -0
- package/api/listDiagnoses.test.js +88 -0
- package/api/listIssues.test.js +98 -0
- package/api/listReports.test.js +104 -0
- package/api/listViolators.test.js +73 -0
- package/api/requestFeature.test.js +54 -0
- package/api/requestRetest.js +4 -8
- package/api/requestRetest.test.js +76 -0
- package/api/requestTest.test.js +81 -0
- package/index.js +0 -3
- package/package.json +1 -1
- package/test/fixtures/buildFixtures.js +1 -1
- package/util.js +26 -77
- package/util.test.js +2 -4
- package/web/expungeReportsForm/index.js +1 -3
- package/web/hideReportForm/index.js +1 -3
- package/web/pruneReportsForm/index.js +1 -3
- package/web/unhideReportForm/index.js +1 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
getReport.test.js
|
|
3
|
+
Tests for api/getReport.js using the fixture corpus.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {test, before, after} = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {response} = require('./getReport');
|
|
12
|
+
|
|
13
|
+
// SETUP AND TEARDOWN
|
|
14
|
+
|
|
15
|
+
const savedDBDir = process.env.DB_DIR;
|
|
16
|
+
|
|
17
|
+
before(() => {
|
|
18
|
+
process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
after(() => {
|
|
22
|
+
if (savedDBDir !== undefined) {
|
|
23
|
+
process.env.DB_DIR = savedDBDir;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
delete process.env.DB_DIR;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// TESTS
|
|
31
|
+
|
|
32
|
+
test('getReport returns the full report for a valid report', async () => {
|
|
33
|
+
const body = await response(['260101T0000', 'mix']);
|
|
34
|
+
const content = body['response content'];
|
|
35
|
+
assert.equal(typeof content['size of the report in bytes'], 'number');
|
|
36
|
+
assert.ok(content['size of the report in bytes'] > 0);
|
|
37
|
+
const report = content['full report'];
|
|
38
|
+
assert.equal(report.id, '260101T0000-mix');
|
|
39
|
+
assert.equal(report.target.what, 'Mixed Outcomes Page');
|
|
40
|
+
assert.equal(report.target.url, 'https://example.com/mixed');
|
|
41
|
+
assert.equal(report.acts.length, 2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('getReport returns an error for a nonexistent report', async () => {
|
|
45
|
+
const body = await response(['999999T9999', 'xyz']);
|
|
46
|
+
const content = body['response content'];
|
|
47
|
+
assert.equal(content['full report'], null);
|
|
48
|
+
assert.equal(typeof content['size of the report in bytes'], 'string');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('getReport returns an error for the hidden report', async () => {
|
|
52
|
+
const body = await response(['260101T0007', 'hid']);
|
|
53
|
+
const content = body['response content'];
|
|
54
|
+
assert.equal(content['full report'], null);
|
|
55
|
+
assert.equal(typeof content['size of the report in bytes'], 'string');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('getReport includes tool name and metadata', async () => {
|
|
59
|
+
const body = await response(['260101T0000', 'mix']);
|
|
60
|
+
assert.equal(body['tool name'], 'getReport');
|
|
61
|
+
assert.ok(body['response metadata'].identifier);
|
|
62
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
listDiagnoses.test.js
|
|
3
|
+
Tests for api/listDiagnoses.js using the fixture corpus, with emphasis on outcome handling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {test, before, after} = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {response} = require('./listDiagnoses');
|
|
12
|
+
|
|
13
|
+
// SETUP AND TEARDOWN
|
|
14
|
+
|
|
15
|
+
const savedDBDir = process.env.DB_DIR;
|
|
16
|
+
|
|
17
|
+
before(() => {
|
|
18
|
+
process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
after(() => {
|
|
22
|
+
if (savedDBDir !== undefined) {
|
|
23
|
+
process.env.DB_DIR = savedDBDir;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
delete process.env.DB_DIR;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// TESTS
|
|
31
|
+
|
|
32
|
+
test('listDiagnoses returns 2 diagnoses for catalogIndex 0, linkNoText, in the mixed report (axe and alfa, excluding cantTell)', async () => {
|
|
33
|
+
const body = await response(['0', 'linkNoText', '260101T0000', 'mix']);
|
|
34
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
35
|
+
assert.equal(diagnoses.length, 2);
|
|
36
|
+
const rules = diagnoses.map(d => d['identifier of the violated rule']).sort();
|
|
37
|
+
assert.deepEqual(rules, ['r11', 'r11']);
|
|
38
|
+
const descriptions = diagnoses.map(d => d['description of the violation']);
|
|
39
|
+
assert.ok(descriptions.every(d => d === 'The link does not have an accessible name'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('listDiagnoses returns 0 diagnoses when all instances are cantTell', async () => {
|
|
43
|
+
const body = await response(['0', 'focusIndicationBad', '260101T0001', 'ct']);
|
|
44
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
45
|
+
assert.equal(diagnoses.length, 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('listDiagnoses treats missing outcome as a violation', async () => {
|
|
49
|
+
const body = await response(['0', 'linkNoText', '260101T0002', 'no']);
|
|
50
|
+
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'];
|
|
51
|
+
assert.equal(diagnoses.length, 1);
|
|
52
|
+
assert.equal(diagnoses[0]['description of the violation'], 'Button has no accessible name');
|
|
53
|
+
assert.equal(diagnoses[0]['severity of the violation on a 0-to-3 scale'], 3);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('listDiagnoses returns element basics from the catalog', async () => {
|
|
57
|
+
const body = await response(['0', 'linkNoText', '260101T0000', 'mix']);
|
|
58
|
+
const elementBasics = body['response content']['basics about the element'];
|
|
59
|
+
assert.equal(elementBasics.identifier, '0');
|
|
60
|
+
assert.equal(elementBasics['tag name'], 'A');
|
|
61
|
+
assert.equal(elementBasics['inner text'], 'About Us');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('listDiagnoses returns element details from the catalog', async () => {
|
|
65
|
+
const body = await response(['0', 'linkNoText', '260101T0000', 'mix']);
|
|
66
|
+
const elementDetails = body['response content']['details about the element'];
|
|
67
|
+
assert.equal(elementDetails['start tag'], '<a>');
|
|
68
|
+
assert.equal(elementDetails['XPath'], '/html/body/a[1]');
|
|
69
|
+
assert.equal(elementDetails['x, y, width, and height of bounding box'], '10:20:80:30');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('listDiagnoses returns an error for an unknown issue ID', async () => {
|
|
73
|
+
const body = await response(['0', 'nonexistentIssue', '260101T0000', 'mix']);
|
|
74
|
+
const basics = body['response content']['basics about the issue'];
|
|
75
|
+
assert.ok(basics.error);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('listDiagnoses returns an error for a nonexistent catalog index', async () => {
|
|
79
|
+
const body = await response(['999', 'linkNoText', '260101T0000', 'mix']);
|
|
80
|
+
const basics = body['response content']['basics about the element'];
|
|
81
|
+
assert.ok(basics.error);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('listDiagnoses returns an error for a nonexistent report', async () => {
|
|
85
|
+
const body = await response(['0', 'linkNoText', '999999T9999', 'xyz']);
|
|
86
|
+
const basics = body['response content']['basics about the report'];
|
|
87
|
+
assert.ok(basics.error);
|
|
88
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
listIssues.test.js
|
|
3
|
+
Tests for api/listIssues.js using the fixture corpus, with emphasis on outcome handling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {test, before, after} = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {response} = require('./listIssues');
|
|
12
|
+
|
|
13
|
+
// SETUP AND TEARDOWN
|
|
14
|
+
|
|
15
|
+
const savedDBDir = process.env.DB_DIR;
|
|
16
|
+
|
|
17
|
+
before(() => {
|
|
18
|
+
process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
after(() => {
|
|
22
|
+
if (savedDBDir !== undefined) {
|
|
23
|
+
process.env.DB_DIR = savedDBDir;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
delete process.env.DB_DIR;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// TESTS
|
|
31
|
+
|
|
32
|
+
test('listIssues returns 2 issues for the mixed-outcomes report, excluding cantTell', async () => {
|
|
33
|
+
const body = await response(['260101T0000', 'mix']);
|
|
34
|
+
const issues = body['response content']['basics about all issues reported in the report'];
|
|
35
|
+
assert.equal(issues.length, 2);
|
|
36
|
+
const summaries = issues.map(i => i.summary).sort();
|
|
37
|
+
assert.deepEqual(summaries, ['all-capital text', 'link not named']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('listIssues returns 0 issues for the all-cantTell report', async () => {
|
|
41
|
+
const body = await response(['260101T0001', 'ct']);
|
|
42
|
+
const issues = body['response content']['basics about all issues reported in the report'];
|
|
43
|
+
assert.equal(issues.length, 0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('listIssues treats missing outcome as a violation, not cantTell', async () => {
|
|
47
|
+
const body = await response(['260101T0002', 'no']);
|
|
48
|
+
const issues = body['response content']['basics about all issues reported in the report'];
|
|
49
|
+
assert.equal(issues.length, 1);
|
|
50
|
+
assert.equal(issues[0].identifier, 'linkNoText');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('listIssues returns 0 issues for the empty report', async () => {
|
|
54
|
+
const body = await response(['260101T0005', 'emp']);
|
|
55
|
+
const issues = body['response content']['basics about all issues reported in the report'];
|
|
56
|
+
assert.equal(issues.length, 0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('listIssues reports prevented rule engines for the prevented report', async () => {
|
|
60
|
+
const body = await response(['260101T0006', 'prv']);
|
|
61
|
+
const details = body['response content']['details about the report'];
|
|
62
|
+
const preventions = details['test results']['rule engines that could not test the page'];
|
|
63
|
+
assert.equal(preventions.length, 1);
|
|
64
|
+
assert.equal(preventions[0].name, 'Alfa');
|
|
65
|
+
assert.equal(preventions[0]['reason for failure'], 'page timed out');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('listIssues includes reporter names for each issue in the mixed report', async () => {
|
|
69
|
+
const body = await response(['260101T0000', 'mix']);
|
|
70
|
+
const issues = body['response content']['basics about all issues reported in the report'];
|
|
71
|
+
const linkIssue = issues.find(i => i.identifier === 'linkNoText');
|
|
72
|
+
assert.deepEqual(linkIssue['rule engines with any violations belonging to the issue'], ['Alfa', 'Axe']);
|
|
73
|
+
const allCapsIssue = issues.find(i => i.identifier === 'allCaps');
|
|
74
|
+
assert.deepEqual(allCapsIssue['rule engines with any violations belonging to the issue'], ['Alfa']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('listIssues counts issues by priority correctly for the mixed report', async () => {
|
|
78
|
+
const body = await response(['260101T0000', 'mix']);
|
|
79
|
+
const details = body['response content']['details about the report'];
|
|
80
|
+
const counts = details['test results']['counts of issues by priority'];
|
|
81
|
+
// linkNoText weight 4 (highest), allCaps weight 1 (lowest).
|
|
82
|
+
assert.equal(counts.highest, 1);
|
|
83
|
+
assert.equal(counts.high, 0);
|
|
84
|
+
assert.equal(counts.low, 0);
|
|
85
|
+
assert.equal(counts.lowest, 1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('listIssues reports the superseded status for the mixed report', async () => {
|
|
89
|
+
const body = await response(['260101T0000', 'mix']);
|
|
90
|
+
const basics = body['response content']['basics about the report'];
|
|
91
|
+
assert.equal(basics['whether a later report about the same page exists'], true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('listIssues returns an error for a nonexistent report', async () => {
|
|
95
|
+
const body = await response(['999999T9999', 'xyz']);
|
|
96
|
+
const basics = body['response content']['basics about the report'];
|
|
97
|
+
assert.ok(basics.error);
|
|
98
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/*
|
|
2
|
+
listReports.test.js
|
|
3
|
+
Tests for api/listReports.js using the fixture corpus.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {test, before, after} = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {response} = require('./listReports');
|
|
12
|
+
|
|
13
|
+
// CONSTANTS
|
|
14
|
+
|
|
15
|
+
// Fixture report identifiers in the reports/ directory (excluding the hidden one).
|
|
16
|
+
const fixtureIds = [
|
|
17
|
+
['260101T0000', 'mix'],
|
|
18
|
+
['260101T0001', 'ct'],
|
|
19
|
+
['260101T0002', 'no'],
|
|
20
|
+
['260101T0005', 'emp'],
|
|
21
|
+
['260101T0006', 'prv'],
|
|
22
|
+
['260202T0000', 'new']
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// SETUP AND TEARDOWN
|
|
26
|
+
|
|
27
|
+
const savedDBDir = process.env.DB_DIR;
|
|
28
|
+
|
|
29
|
+
before(() => {
|
|
30
|
+
process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
after(() => {
|
|
34
|
+
if (savedDBDir !== undefined) {
|
|
35
|
+
process.env.DB_DIR = savedDBDir;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
delete process.env.DB_DIR;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// TESTS
|
|
43
|
+
|
|
44
|
+
test('listReports returns basics about all 6 non-hidden reports', async () => {
|
|
45
|
+
const body = await response();
|
|
46
|
+
const reportsBasics = body['response content']['basics about all available reports'];
|
|
47
|
+
assert.equal(reportsBasics.length, 6);
|
|
48
|
+
const ids = reportsBasics.map(b => b.identifier).sort();
|
|
49
|
+
const expectedIds = fixtureIds.map(([ts, jid]) => `${ts}-${jid}`).sort();
|
|
50
|
+
assert.deepEqual(ids, expectedIds);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('listReports does not include the hidden report', async () => {
|
|
54
|
+
const body = await response();
|
|
55
|
+
const reportsBasics = body['response content']['basics about all available reports'];
|
|
56
|
+
const ids = reportsBasics.map(b => b.identifier);
|
|
57
|
+
assert.ok(!ids.includes('260101T0007-hid'), 'hidden report must not appear in listReports');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('listReports sorts reports by page description and then by completion time', async () => {
|
|
61
|
+
const body = await response();
|
|
62
|
+
const reportsBasics = body['response content']['basics about all available reports'];
|
|
63
|
+
const descriptions = reportsBasics.map(b => b['tested web page'].description);
|
|
64
|
+
// All CantTell Page, Empty Results Page, Mixed Outcomes Page, Mixed Outcomes Page, No Outcomes Page, Prevented Page.
|
|
65
|
+
assert.deepEqual(
|
|
66
|
+
descriptions,
|
|
67
|
+
[
|
|
68
|
+
'All CantTell Page',
|
|
69
|
+
'Empty Results Page',
|
|
70
|
+
'Mixed Outcomes Page',
|
|
71
|
+
'Mixed Outcomes Page',
|
|
72
|
+
'No Outcomes Page',
|
|
73
|
+
'Prevented Page'
|
|
74
|
+
]
|
|
75
|
+
);
|
|
76
|
+
// The two Mixed Outcomes Page reports should be sorted by completion time (older first).
|
|
77
|
+
const mixedReports = reportsBasics.filter(b => b['tested web page'].description === 'Mixed Outcomes Page');
|
|
78
|
+
assert.equal(mixedReports[0].identifier, '260101T0000-mix');
|
|
79
|
+
assert.equal(mixedReports[1].identifier, '260202T0000-new');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('listReports marks the superseded report correctly', async () => {
|
|
83
|
+
const body = await response();
|
|
84
|
+
const reportsBasics = body['response content']['basics about all available reports'];
|
|
85
|
+
const mix = reportsBasics.find(b => b.identifier === '260101T0000-mix');
|
|
86
|
+
const newer = reportsBasics.find(b => b.identifier === '260202T0000-new');
|
|
87
|
+
assert.equal(mix['whether a later report about the same page exists'], true);
|
|
88
|
+
assert.equal(newer['whether a later report about the same page exists'], false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('listReports includes request-test instructions', async () => {
|
|
92
|
+
const body = await response();
|
|
93
|
+
const content = body['response content'];
|
|
94
|
+
assert.ok(content['how to request that a page with no report be tested']);
|
|
95
|
+
assert.ok(content['how a web user can request that the page be tested']);
|
|
96
|
+
assert.equal(content['how to request that a page with no report be tested'].method, 'POST');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('listReports includes tool name and metadata', async () => {
|
|
100
|
+
const body = await response();
|
|
101
|
+
assert.equal(body['tool name'], 'listReports');
|
|
102
|
+
assert.ok(body['response metadata'].identifier);
|
|
103
|
+
assert.ok(body['response metadata']['date and time']);
|
|
104
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
listViolators.test.js
|
|
3
|
+
Tests for api/listViolators.js using the fixture corpus, with emphasis on outcome handling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {test, before, after} = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const {response} = require('./listViolators');
|
|
12
|
+
|
|
13
|
+
// SETUP AND TEARDOWN
|
|
14
|
+
|
|
15
|
+
const savedDBDir = process.env.DB_DIR;
|
|
16
|
+
|
|
17
|
+
before(() => {
|
|
18
|
+
process.env.DB_DIR = path.join(__dirname, '..', 'test', 'fixtures', 'db');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
after(() => {
|
|
22
|
+
if (savedDBDir !== undefined) {
|
|
23
|
+
process.env.DB_DIR = savedDBDir;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
delete process.env.DB_DIR;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// TESTS
|
|
31
|
+
|
|
32
|
+
test('listViolators returns 1 violator for linkNoText in the mixed report, excluding cantTell', async () => {
|
|
33
|
+
const body = await response(['linkNoText', '260101T0000', 'mix']);
|
|
34
|
+
const violators = body['response content']['basics about all elements exhibiting the issue'];
|
|
35
|
+
assert.equal(violators.length, 1);
|
|
36
|
+
assert.equal(violators[0].identifier, '0');
|
|
37
|
+
assert.equal(violators[0]['tag name'], 'A');
|
|
38
|
+
assert.equal(violators[0]['inner text'], 'About Us');
|
|
39
|
+
assert.equal(violators[0]['count of rule engines reporting that the element exhibited the issue'], 2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('listViolators returns 0 violators for an issue when all instances are cantTell', async () => {
|
|
43
|
+
const body = await response(['focusIndicationBad', '260101T0001', 'ct']);
|
|
44
|
+
const violators = body['response content']['basics about all elements exhibiting the issue'];
|
|
45
|
+
assert.equal(violators.length, 0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('listViolators treats missing outcome as a violation', async () => {
|
|
49
|
+
const body = await response(['linkNoText', '260101T0002', 'no']);
|
|
50
|
+
const violators = body['response content']['basics about all elements exhibiting the issue'];
|
|
51
|
+
assert.equal(violators.length, 1);
|
|
52
|
+
assert.equal(violators[0].identifier, '0');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('listViolators returns an error for an unknown issue ID', async () => {
|
|
56
|
+
const body = await response(['nonexistentIssue', '260101T0000', 'mix']);
|
|
57
|
+
const basics = body['response content']['basics about the issue'];
|
|
58
|
+
assert.ok(basics.error);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('listViolators returns an error for a nonexistent report', async () => {
|
|
62
|
+
const body = await response(['linkNoText', '999999T9999', 'xyz']);
|
|
63
|
+
const basics = body['response content']['basics about the report'];
|
|
64
|
+
assert.ok(basics.error);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('listViolators includes reporter facts for the issue', async () => {
|
|
68
|
+
const body = await response(['linkNoText', '260101T0000', 'mix']);
|
|
69
|
+
const details = body['response content']['details about the issue'];
|
|
70
|
+
const reporters = details['rule engines reporting violations belonging to the issue'];
|
|
71
|
+
const names = reporters.map(r => r.name).sort();
|
|
72
|
+
assert.deepEqual(names, ['Alfa', 'Axe']);
|
|
73
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
requestFeature.test.js
|
|
3
|
+
Tests for api/requestFeature.js with mocked side effects.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {test, before, 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
|
+
sendAlertCalls = [];
|
|
18
|
+
// @ts-expect-error: Replacing the real function with a mock for testing.
|
|
19
|
+
alerts.sendAlert = async (subject, body) => {
|
|
20
|
+
sendAlertCalls.push({subject, body});
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Require requestFeature after the mock is in place, so it captures the mocked sendAlert.
|
|
25
|
+
const {response} = require('./requestFeature');
|
|
26
|
+
|
|
27
|
+
after(() => {
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// TESTS
|
|
31
|
+
|
|
32
|
+
test('requestFeature rejects an empty feature request', async () => {
|
|
33
|
+
const body = await response(['']);
|
|
34
|
+
const details = body['response content']['details about your request'];
|
|
35
|
+
assert.ok(details.error);
|
|
36
|
+
assert.equal(sendAlertCalls.length, 0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('requestFeature accepts a non-empty feature request and notifies the manager', async () => {
|
|
40
|
+
const body = await response(['Add a dark mode toggle']);
|
|
41
|
+
const details = body['response content']['details about your request'];
|
|
42
|
+
assert.equal(details.error, undefined);
|
|
43
|
+
assert.ok(details['date and time received']);
|
|
44
|
+
assert.equal(details.disposition, 'received and logged; manager notified');
|
|
45
|
+
assert.equal(sendAlertCalls.length, 1);
|
|
46
|
+
assert.equal(sendAlertCalls[0].subject, 'MCP feature request received');
|
|
47
|
+
assert.equal(sendAlertCalls[0].body, 'Add a dark mode toggle');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('requestFeature includes tool name and metadata', async () => {
|
|
51
|
+
const body = await response(['Some feature']);
|
|
52
|
+
assert.equal(body['tool name'], 'requestFeature');
|
|
53
|
+
assert.ok(body['response metadata'].identifier);
|
|
54
|
+
});
|
package/api/requestRetest.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {getResponseMetadata, getToolsFacts, processTestRequest, thisHost} = require('./util');
|
|
9
|
-
const {getLatestReportExtracts,
|
|
9
|
+
const {getLatestReportExtracts, getReportExtract} = require('../util');
|
|
10
10
|
|
|
11
11
|
// FUNCTIONS
|
|
12
12
|
|
|
@@ -19,15 +19,11 @@ exports.response = async args => {
|
|
|
19
19
|
'disposition of your request': null
|
|
20
20
|
};
|
|
21
21
|
const reasonLength = reason.length;
|
|
22
|
-
// Get data on the available reports.
|
|
23
|
-
const reportsExtract = await getReportsExtract();
|
|
24
22
|
// Get data on the report.
|
|
25
|
-
const reportExtract =
|
|
26
|
-
.
|
|
27
|
-
.find(extract => extract.timeStamp === timeStamp && extract.jobID === jobID);
|
|
28
|
-
const {what, url} = reportExtract || {};
|
|
23
|
+
const reportExtract = await getReportExtract(timeStamp, jobID);
|
|
24
|
+
const {what, url} = reportExtract.error ? {} : reportExtract;
|
|
29
25
|
// If this failed:
|
|
30
|
-
if (
|
|
26
|
+
if (reportExtract.error) {
|
|
31
27
|
// Add this to the response content.
|
|
32
28
|
responseContent['details about your request'] = {
|
|
33
29
|
error: 'request invalid: the specified existing report is not an available report'
|
|
@@ -0,0 +1,76 @@
|
|
|
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, 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
|
+
processTestRequestCalls = [];
|
|
21
|
+
// @ts-expect-error: Replacing the real function with a mock for testing.
|
|
22
|
+
apiUtil.processTestRequest = async (testType, what, url, reason) => {
|
|
23
|
+
processTestRequestCalls.push({testType, what, url, reason});
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Require requestRetest after the mock is in place, so it captures the mocked processTestRequest.
|
|
28
|
+
const {response} = require('./requestRetest');
|
|
29
|
+
|
|
30
|
+
after(() => {
|
|
31
|
+
if (savedDBDir !== undefined) {
|
|
32
|
+
process.env.DB_DIR = savedDBDir;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
delete process.env.DB_DIR;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// TESTS
|
|
40
|
+
|
|
41
|
+
test('requestRetest rejects a nonexistent report', async () => {
|
|
42
|
+
const body = await response(['999999T9999', 'xyz', 'A reason that is long enough.']);
|
|
43
|
+
assert.ok(body['response content']['details about your request'].error);
|
|
44
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('requestRetest rejects a superseded report', async () => {
|
|
48
|
+
const body = await response(['260101T0000', 'mix', 'A reason that is long enough.']);
|
|
49
|
+
const details = body['response content']['details about your request'];
|
|
50
|
+
assert.ok(details.error.includes('later report'));
|
|
51
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('requestRetest rejects a reason shorter than 20 characters', async () => {
|
|
55
|
+
const body = await response(['260101T0001', 'ct', 'short']);
|
|
56
|
+
const details = body['response content']['details about your request'];
|
|
57
|
+
assert.ok(details.error.includes('reason'));
|
|
58
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('requestRetest rejects a reason longer than 100 characters', async () => {
|
|
62
|
+
const longReason = 'x'.repeat(101);
|
|
63
|
+
const body = await response(['260101T0001', 'ct', longReason]);
|
|
64
|
+
const details = body['response content']['details about your request'];
|
|
65
|
+
assert.ok(details.error.includes('reason'));
|
|
66
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('requestRetest accepts a valid retest request for the latest report of a page', async () => {
|
|
70
|
+
const body = await response(['260202T0000', 'new', 'A reason that is long enough.']);
|
|
71
|
+
const details = body['response content']['details about your request'];
|
|
72
|
+
assert.equal(details.error, undefined);
|
|
73
|
+
assert.equal(details['page to be retested'].description, 'Mixed Outcomes Page');
|
|
74
|
+
assert.equal(processTestRequestCalls.length, 1);
|
|
75
|
+
assert.equal(processTestRequestCalls[0].testType, 'retest');
|
|
76
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
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, 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
|
+
processTestRequestCalls = [];
|
|
21
|
+
// @ts-expect-error: Replacing the real function with a mock for testing.
|
|
22
|
+
apiUtil.processTestRequest = async (testType, what, url, reason) => {
|
|
23
|
+
processTestRequestCalls.push({testType, what, url, reason});
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Require requestTest after the mock is in place, so it captures the mocked processTestRequest.
|
|
28
|
+
const {response} = require('./requestTest');
|
|
29
|
+
|
|
30
|
+
after(() => {
|
|
31
|
+
if (savedDBDir !== undefined) {
|
|
32
|
+
process.env.DB_DIR = savedDBDir;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
delete process.env.DB_DIR;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// TESTS
|
|
40
|
+
|
|
41
|
+
test('requestTest rejects an empty description', async () => {
|
|
42
|
+
const body = await response(['', 'https://example.com/test', 'A reason that is long enough.']);
|
|
43
|
+
assert.ok(body['response content']['details about your request'].error);
|
|
44
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('requestTest rejects a description longer than 100 characters', async () => {
|
|
48
|
+
const longWhat = 'x'.repeat(101);
|
|
49
|
+
const body = await response([longWhat, 'https://example.com/test', 'A reason that is long enough.']);
|
|
50
|
+
assert.ok(body['response content']['details about your request'].error);
|
|
51
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('requestTest rejects a URL shorter than 12 characters', async () => {
|
|
55
|
+
const body = await response(['Test Page', 'short', 'A reason that is long enough.']);
|
|
56
|
+
assert.ok(body['response content']['details about your request'].error);
|
|
57
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('requestTest rejects an already-tested page', async () => {
|
|
61
|
+
const body = await response(['Mixed Outcomes Page', 'https://example.com/mixed', 'A reason that is long enough.']);
|
|
62
|
+
const details = body['response content']['details about your request'];
|
|
63
|
+
assert.ok(details.error.includes('already been tested'));
|
|
64
|
+
assert.equal(processTestRequestCalls.length, 0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('requestTest accepts a valid new page request', async () => {
|
|
68
|
+
const body = await response(['Brand New Page', 'https://example.com/brandnew', 'A reason that is long enough.']);
|
|
69
|
+
const details = body['response content']['details about your request'];
|
|
70
|
+
assert.equal(details.error, undefined);
|
|
71
|
+
assert.equal(details['page to be tested'].description, 'Brand New Page');
|
|
72
|
+
assert.equal(processTestRequestCalls.length, 1);
|
|
73
|
+
assert.equal(processTestRequestCalls[0].testType, 'test');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('requestTest includes disposition information for a valid request', async () => {
|
|
77
|
+
const body = await response(['Brand New Page', 'https://example.com/brandnew', 'A reason that is long enough.']);
|
|
78
|
+
const disposition = body['response content']['disposition of your request'];
|
|
79
|
+
assert.ok(disposition);
|
|
80
|
+
assert.ok(disposition['what happens next']);
|
|
81
|
+
});
|
package/index.js
CHANGED
|
@@ -24,7 +24,6 @@ const {
|
|
|
24
24
|
isTimeStamp,
|
|
25
25
|
isJobID,
|
|
26
26
|
jobsPath,
|
|
27
|
-
makeReportsExtract,
|
|
28
27
|
recsLock,
|
|
29
28
|
reportsPath
|
|
30
29
|
} = require('./util');
|
|
@@ -747,8 +746,6 @@ const requestHandler = async (request, response) => {
|
|
|
747
746
|
report.sources = {...report.sources, worker: workerName};
|
|
748
747
|
// Save the report.
|
|
749
748
|
await fs.writeFile(getReportPath(timeStamp, jobID), getJSON(report));
|
|
750
|
-
// Update the data on the available reports.
|
|
751
|
-
await makeReportsExtract();
|
|
752
749
|
// Annotate the report.
|
|
753
750
|
await annotateReport(timeStamp, jobID);
|
|
754
751
|
console.log(`Testaro report ${id} was annotated, saved, and indexed`);
|
package/package.json
CHANGED
|
@@ -216,7 +216,7 @@ const main = async () => {
|
|
|
216
216
|
preventedReport
|
|
217
217
|
);
|
|
218
218
|
|
|
219
|
-
// Fixture 7: hidden. A report placed in hiddenReports/ instead of reports/, so it must not appear in listReports, listIssues, listViolators, listDiagnoses, or getReport API responses.
|
|
219
|
+
// Fixture 7: hidden. A report placed in hiddenReports/ instead of reports/, so it must not appear in listReports, listIssues, listViolators, listDiagnoses, or getReport API responses.
|
|
220
220
|
const hiddenCatalog = {
|
|
221
221
|
'0': catalogItem('A', 'Secret link', '/html/body/a[1]', '5:10:60:20')
|
|
222
222
|
};
|
package/util.js
CHANGED
|
@@ -24,8 +24,6 @@ const recsPath = exports.recsPath = () => path.join(jobsPath(), 'recs.json');
|
|
|
24
24
|
const reportsPath = exports.reportsPath = () => path.join(dbPath(), 'reports');
|
|
25
25
|
// Path of the hidden-reports directory.
|
|
26
26
|
const hiddenReportsPath = exports.hiddenReportsPath = () => path.join(dbPath(), 'hiddenReports');
|
|
27
|
-
// Path of the reports data file.
|
|
28
|
-
const reportsExtractPath = exports.reportsExtractPath = () => path.join(dbPath(), 'reportsExtract.json');
|
|
29
27
|
// IDs, names, and sponsors of Testaro rule engines.
|
|
30
28
|
const ruleEngines = exports.ruleEngines = {
|
|
31
29
|
alfa: ['Alfa', 'Siteimprove'],
|
|
@@ -643,11 +641,11 @@ exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
|
|
|
643
641
|
};
|
|
644
642
|
// Returns the creation time and size of a report.
|
|
645
643
|
const getReportStats = exports.getReportStats = async (timeStamp, jobID) => {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
{
|
|
649
|
-
|
|
650
|
-
|
|
644
|
+
let reportStat;
|
|
645
|
+
try {
|
|
646
|
+
reportStat = await fs.stat(path.join(reportsPath(), `${timeStamp}-${jobID}.json`));
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
651
649
|
return null;
|
|
652
650
|
}
|
|
653
651
|
const reportTime = reportStat.birthtime;
|
|
@@ -662,68 +660,43 @@ exports.isHidden = async (timeStamp, jobID) => {
|
|
|
662
660
|
// Return whether the report is among them.
|
|
663
661
|
return hiddenReportFileNames.includes(`${timeStamp}-${jobID}.json`);
|
|
664
662
|
};
|
|
665
|
-
//
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
// For each of them:
|
|
672
|
-
for (const reportFileName of reportFileNames) {
|
|
673
|
-
// Get its content.
|
|
674
|
-
const reportJSON = await fs.readFile(path.join(reportsPath(), reportFileName), 'utf8');
|
|
675
|
-
// Get the object that it represents.
|
|
663
|
+
// Returns an extract of an available report, or an error object if it cannot be read or parsed.
|
|
664
|
+
const getReportExtract = exports.getReportExtract = async (timeStamp, jobID) => {
|
|
665
|
+
try {
|
|
666
|
+
const reportJSON = await fs.readFile(
|
|
667
|
+
path.join(reportsPath(), `${timeStamp}-${jobID}.json`), 'utf8'
|
|
668
|
+
);
|
|
676
669
|
const report = JSON.parse(reportJSON);
|
|
677
670
|
const {target, jobData} = report;
|
|
678
|
-
const jobName = reportFileName.slice(0, -5);
|
|
679
|
-
const [timeStamp, jobID] = jobName.split('-');
|
|
680
671
|
const {what, url} = target;
|
|
681
|
-
|
|
682
|
-
const reportExtract = {
|
|
672
|
+
return {
|
|
683
673
|
timeStamp,
|
|
684
674
|
jobID,
|
|
685
675
|
what,
|
|
686
676
|
url,
|
|
687
677
|
reportTime: new Date(`20${jobData.endTime}Z`).toISOString()
|
|
688
678
|
};
|
|
689
|
-
// Add the report extract to the reports extract.
|
|
690
|
-
reportsExtract[jobName] = reportExtract;
|
|
691
|
-
}
|
|
692
|
-
// Ensure the data directory exists.
|
|
693
|
-
await fs.mkdir(dbPath(), {recursive: true});
|
|
694
|
-
// Save the extract of the reports.
|
|
695
|
-
await fs.writeFile(reportsExtractPath(), getJSON(reportsExtract));
|
|
696
|
-
// Return it.
|
|
697
|
-
return reportsExtract;
|
|
698
|
-
};
|
|
699
|
-
// Gets an extract of all available reports.
|
|
700
|
-
const getReportsExtract = exports.getReportsExtract = async () => {
|
|
701
|
-
let reportsExtract;
|
|
702
|
-
try {
|
|
703
|
-
// Get the reports extract file.
|
|
704
|
-
const reportsExtractJSON = await fs.readFile(reportsExtractPath(), 'utf8');
|
|
705
|
-
// Get its data.
|
|
706
|
-
reportsExtract = JSON.parse(reportsExtractJSON);
|
|
707
|
-
// If it is empty:
|
|
708
|
-
if (!Object.keys(reportsExtract).length) {
|
|
709
|
-
// Throw an error.
|
|
710
|
-
throw new Error('Reports extract file is empty');
|
|
711
|
-
}
|
|
712
679
|
}
|
|
713
|
-
// If this fails:
|
|
714
680
|
catch {
|
|
715
|
-
|
|
716
|
-
|
|
681
|
+
return {
|
|
682
|
+
error: `No report ${timeStamp}-${jobID} is available`
|
|
683
|
+
};
|
|
717
684
|
}
|
|
718
|
-
// Return the extract.
|
|
719
|
-
return reportsExtract;
|
|
720
685
|
};
|
|
721
686
|
// Returns extracts of all available reports.
|
|
722
687
|
const getReportExtracts = exports.getReportExtracts = async () => {
|
|
723
|
-
const
|
|
724
|
-
|
|
688
|
+
const reportFileNames = await fs.readdir(reportsPath());
|
|
689
|
+
const extracts = [];
|
|
690
|
+
for (const reportFileName of reportFileNames) {
|
|
691
|
+
const [timeStamp, jobID] = reportFileName.slice(0, -5).split('-');
|
|
692
|
+
const extract = await getReportExtract(timeStamp, jobID);
|
|
693
|
+
if (!extract.error) {
|
|
694
|
+
extracts.push(extract);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return extracts;
|
|
725
698
|
};
|
|
726
|
-
// Returns whether a report
|
|
699
|
+
// Returns whether a report with a description or URL is available.
|
|
727
700
|
exports.isReportAvailable = async (what, url) => {
|
|
728
701
|
const reportExtracts = await getReportExtracts();
|
|
729
702
|
const whats = reportExtracts.map(reportExtract => reportExtract.what);
|
|
@@ -732,43 +705,19 @@ exports.isReportAvailable = async (what, url) => {
|
|
|
732
705
|
};
|
|
733
706
|
// Gets extracts of the latest available reports for all page descriptions.
|
|
734
707
|
exports.getLatestReportExtracts = async () => {
|
|
735
|
-
// Get extracts of all available reports.
|
|
736
708
|
const reportExtracts = await getReportExtracts();
|
|
737
|
-
// Sort them primarily by page description and secondarily by completion time.
|
|
738
709
|
objectSort(reportExtracts, 'reportTime', 'alpha');
|
|
739
710
|
objectSort(reportExtracts, 'what', 'alpha');
|
|
740
|
-
// Get the latest ones for all page descriptions.
|
|
741
711
|
const latestReportExtracts = reportExtracts
|
|
742
712
|
.filter((extract, index) => extract.what !== reportExtracts[index + 1]?.what);
|
|
743
|
-
// Return them.
|
|
744
713
|
return latestReportExtracts;
|
|
745
714
|
};
|
|
746
|
-
// Returns an extract of an available report.
|
|
747
|
-
exports.getReportExtract = async (timeStamp, jobID) => {
|
|
748
|
-
// Get the extract of all available reports.
|
|
749
|
-
const reportsExtract = await getReportsExtract();
|
|
750
|
-
// Get the extract of the report from it.
|
|
751
|
-
const reportExtract = reportsExtract[`${timeStamp}-${jobID}`];
|
|
752
|
-
// If it exists:
|
|
753
|
-
if (reportExtract) {
|
|
754
|
-
// Return it.
|
|
755
|
-
return reportExtract;
|
|
756
|
-
}
|
|
757
|
-
// Otherwise, i.e. if it does not exist, return this.
|
|
758
|
-
return {
|
|
759
|
-
error: `No report ${timeStamp}-${jobID} is available`
|
|
760
|
-
};
|
|
761
|
-
};
|
|
762
715
|
// Gets the descriptions of multi-report pages.
|
|
763
716
|
exports.getMultiReportWhats = async () => {
|
|
764
|
-
// Get extracts of all available reports.
|
|
765
717
|
const reportExtracts = await getReportExtracts();
|
|
766
|
-
// Get their sorted page descriptions.
|
|
767
718
|
const sortedWhats = reportExtracts.map(extract => extract.what).sort();
|
|
768
|
-
// Get those that at least 2 reports have.
|
|
769
719
|
const multiReportWhats = sortedWhats.filter(
|
|
770
720
|
(what, index) => what !== sortedWhats[index - 1] && what === sortedWhats[index + 1]
|
|
771
721
|
);
|
|
772
|
-
// Return an array of descriptions of multi-report pages.
|
|
773
722
|
return multiReportWhats;
|
|
774
723
|
};
|
package/util.test.js
CHANGED
|
@@ -13,8 +13,7 @@ const {
|
|
|
13
13
|
jobsPath,
|
|
14
14
|
recsPath,
|
|
15
15
|
reportsPath,
|
|
16
|
-
hiddenReportsPath
|
|
17
|
-
reportsExtractPath
|
|
16
|
+
hiddenReportsPath
|
|
18
17
|
} = require('./util');
|
|
19
18
|
|
|
20
19
|
// TESTS
|
|
@@ -48,7 +47,7 @@ test('dbPath honors DB_DIR when it is set', () => {
|
|
|
48
47
|
}
|
|
49
48
|
});
|
|
50
49
|
|
|
51
|
-
test('jobsPath, recsPath, reportsPath,
|
|
50
|
+
test('jobsPath, recsPath, reportsPath, and hiddenReportsPath derive from DB_DIR', () => {
|
|
52
51
|
const saved = process.env.DB_DIR;
|
|
53
52
|
process.env.DB_DIR = '/tmp/kilotest-fixtures';
|
|
54
53
|
try {
|
|
@@ -56,7 +55,6 @@ test('jobsPath, recsPath, reportsPath, hiddenReportsPath, and reportsExtractPath
|
|
|
56
55
|
assert.equal(recsPath(), path.join('/tmp/kilotest-fixtures', 'jobs', 'recs.json'));
|
|
57
56
|
assert.equal(reportsPath(), path.join('/tmp/kilotest-fixtures', 'reports'));
|
|
58
57
|
assert.equal(hiddenReportsPath(), path.join('/tmp/kilotest-fixtures', 'hiddenReports'));
|
|
59
|
-
assert.equal(reportsExtractPath(), path.join('/tmp/kilotest-fixtures', 'reportsExtract.json'));
|
|
60
58
|
}
|
|
61
59
|
finally {
|
|
62
60
|
if (saved !== undefined) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {getReportData,
|
|
8
|
+
const {getReportData, objectSort, reportsPath} = require('../../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -26,8 +26,6 @@ exports.answer = async (_, search) => {
|
|
|
26
26
|
// Delete it.
|
|
27
27
|
await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
|
|
28
28
|
}
|
|
29
|
-
// Update the extract of the available reports.
|
|
30
|
-
await makeReportsExtract();
|
|
31
29
|
}
|
|
32
30
|
// If this failed:
|
|
33
31
|
catch (error) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {getReportExtracts, hiddenReportsPath,
|
|
8
|
+
const {getReportExtracts, hiddenReportsPath, reportsPath} = require('../../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -24,8 +24,6 @@ exports.answer = async (_, search) => {
|
|
|
24
24
|
try {
|
|
25
25
|
// Move the specified report to the directory of hidden reports.
|
|
26
26
|
await fs.rename(path.join(reportsPath(), fileName), path.join(hiddenReportsPath(), fileName));
|
|
27
|
-
// Update the data on the available reports.
|
|
28
|
-
await makeReportsExtract();
|
|
29
27
|
}
|
|
30
28
|
// If this failed:
|
|
31
29
|
catch (error) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {getReportData,
|
|
8
|
+
const {getReportData, reportsPath} = require('../../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -26,8 +26,6 @@ exports.answer = async (_, search) => {
|
|
|
26
26
|
// Delete it.
|
|
27
27
|
await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
|
|
28
28
|
}
|
|
29
|
-
// Update the extract of the available reports.
|
|
30
|
-
await makeReportsExtract();
|
|
31
29
|
}
|
|
32
30
|
// If this failed:
|
|
33
31
|
catch (error) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {hiddenReportsPath,
|
|
8
|
+
const {hiddenReportsPath, reportsPath} = require('../../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -24,8 +24,6 @@ exports.answer = async (_, search) => {
|
|
|
24
24
|
try {
|
|
25
25
|
// Move the report to the reports directory.
|
|
26
26
|
await fs.rename(path.join(hiddenReportsPath(), fileName), path.join(reportsPath(), fileName));
|
|
27
|
-
// Update the data on the available reports.
|
|
28
|
-
await makeReportsExtract();
|
|
29
27
|
}
|
|
30
28
|
catch (error) {
|
|
31
29
|
// Return why.
|