@jrpool/kilotest 37.0.0 → 38.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/alerts.js +1 -1
- package/diagnoses/index.js +39 -5
- package/eslint.config.mjs +4 -1
- package/index.js +27 -26
- package/issues/index.js +31 -10
- package/llms-full.txt +15 -15
- package/llms.txt +2 -2
- package/mcp.js +24 -18
- package/openapi.yaml +125 -70
- package/package.json +1 -1
- package/reannotate/index.js +10 -2
- package/reannotateForm/index.js +19 -4
- package/reportHideForm/index.js +20 -1
- package/reportIssue/api.js +28 -9
- package/reportIssue/index.js +39 -7
- package/reportIssues/index.js +26 -14
- package/reportIssues/util.js +27 -8
- package/reportList/api.js +36 -28
- package/reportSummary/api.js +181 -0
- package/reportUnhideForm/index.js +20 -2
- package/reportsExpungeForm/index.js +9 -1
- package/reportsPruneForm/index.js +11 -1
- package/reportsRewindForm/index.js +9 -1
- package/researchAgent.js +38 -29
- package/retestRec/index.js +11 -2
- package/retestRecForm/index.js +5 -0
- package/target/api.js +46 -35
- package/targets/index.js +20 -1
- package/testRecForm/api.js +2 -2
- package/tutorial/index.js +15 -5
- package/util.js +63 -48
- package/reportIssues/api.js +0 -129
package/reportIssues/util.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
|
-
getPageData, getReport, getToolsData, getToolList, isValidReport, objectSort
|
|
9
|
+
getPageData, getReport, getToolsData, getToolList, isHidden, isValidReport, objectSort
|
|
10
10
|
} = require('../util');
|
|
11
11
|
const issuesClassification = require('testilo/procs/score/tic').issues;
|
|
12
12
|
|
|
@@ -16,7 +16,12 @@ const issuesClassification = require('testilo/procs/score/tic').issues;
|
|
|
16
16
|
const getIssuesData = async (timeStamp, jobID) => {
|
|
17
17
|
// Get the report.
|
|
18
18
|
const report = await getReport(timeStamp, jobID);
|
|
19
|
-
|
|
19
|
+
const reportIsHidden = await isHidden(timeStamp, jobID);
|
|
20
|
+
// If it exists and is hidden:
|
|
21
|
+
if (reportIsHidden) {
|
|
22
|
+
return {error: 'Report is not available'}
|
|
23
|
+
}
|
|
24
|
+
// Otherwise, if it exists and is valid:
|
|
20
25
|
if (isValidReport(report)) {
|
|
21
26
|
// Initialize the temporary data.
|
|
22
27
|
const temp = {
|
|
@@ -110,10 +115,24 @@ const getIssuesData = async (timeStamp, jobID) => {
|
|
|
110
115
|
// Return the data.
|
|
111
116
|
return final;
|
|
112
117
|
}
|
|
113
|
-
// Otherwise, i.e. if it is invalid, return this.
|
|
114
|
-
return
|
|
118
|
+
// Otherwise, i.e. if it is invalid or does not exist, return this.
|
|
119
|
+
return {error: 'Report missing or invalid.'};
|
|
120
|
+
};
|
|
121
|
+
// Get page and issues data from a report.
|
|
122
|
+
exports.getData = async (timeStamp, jobID) => {
|
|
123
|
+
const pageData = await getPageData(timeStamp, jobID);
|
|
124
|
+
const issuesData = await getIssuesData(timeStamp, jobID);
|
|
125
|
+
const pageError = pageData.error || '';
|
|
126
|
+
const issuesError = issuesData.error || '';
|
|
127
|
+
const errors = [pageError, issuesError].filter(Boolean).join('; ');
|
|
128
|
+
// If the data of either type are missing or invalid:
|
|
129
|
+
if (errors) {
|
|
130
|
+
// Return this.
|
|
131
|
+
return {error: errors};
|
|
132
|
+
}
|
|
133
|
+
// Otherwise, return the data.
|
|
134
|
+
return {
|
|
135
|
+
pageData,
|
|
136
|
+
issuesData
|
|
137
|
+
};
|
|
115
138
|
};
|
|
116
|
-
exports.getData = async (timeStamp, jobID) => ({
|
|
117
|
-
pageData: await getPageData(timeStamp, jobID),
|
|
118
|
-
issuesData: await getIssuesData(timeStamp, jobID)
|
|
119
|
-
});
|
package/reportList/api.js
CHANGED
|
@@ -22,54 +22,62 @@ const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
|
22
22
|
|
|
23
23
|
// Returns a response to an API request for reports.
|
|
24
24
|
exports.response = async () => {
|
|
25
|
+
// Initialize an array of facts about reports.
|
|
25
26
|
const reportsFacts = [];
|
|
26
27
|
// Get the available logs, with added job names.
|
|
27
28
|
const availableLogs = await getLogs();
|
|
28
|
-
// For each
|
|
29
|
+
// For each log:
|
|
29
30
|
for (const availableLog of availableLogs) {
|
|
30
31
|
const {jobName} = availableLog;
|
|
31
32
|
const [timeStamp, jobID] = jobName.split('-');
|
|
32
33
|
const reportSize = await getReportSize(timeStamp, jobID);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
34
|
+
// If its report exists:
|
|
35
|
+
if (reportSize) {
|
|
36
|
+
const {superseded, url, what} = availableLog;
|
|
37
|
+
// Add facts about the report to the array.
|
|
38
|
+
reportsFacts.push({
|
|
39
|
+
identifier: jobName,
|
|
40
|
+
'creation date and time': getDateTime(timeStamp),
|
|
41
|
+
'days since the creation date': getAgoDays(timeStamp),
|
|
42
|
+
'tested web page': {
|
|
43
|
+
description: what,
|
|
44
|
+
URL: url
|
|
45
|
+
},
|
|
46
|
+
'whether a later report about the same page exists': !! superseded,
|
|
47
|
+
'URLs for more details': {
|
|
48
|
+
'for you': `${thisHost}/api/reportSummary/${timeStamp}/${jobID}`,
|
|
49
|
+
'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
50
|
+
},
|
|
51
|
+
'size of the report in bytes': reportSize,
|
|
52
|
+
'URL to get the entire report as machine-oriented JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// Otherwise, i.e. if the report does not exist:
|
|
56
|
+
else {
|
|
57
|
+
// Report this.
|
|
58
|
+
console.error(`Log ${jobName} exists but its report does not.`);
|
|
59
|
+
}
|
|
50
60
|
}
|
|
51
|
-
//
|
|
61
|
+
// Create a response body.
|
|
52
62
|
const content = {
|
|
53
63
|
summary: `This document fulfills a request made by a language model to a Kilotest tool. The model asked which reports about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages are available from Kilotest. Each report describes the results of a job that tested one web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs more than a thousand tests on a public web page, using a combination of rule- and machine-learning-based methods. Kilotest exposes API endpoints and web UI URLs to recommend web pages for testing and to obtain information from Kilotest reports. You and your users can learn more about Kilotest and the advantages of testing with an ensemble of rule engines at the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}).`,
|
|
54
64
|
'tool collection name': 'Kilotest',
|
|
55
|
-
'tool name': '
|
|
65
|
+
'tool name': 'listAllAvailableReports',
|
|
56
66
|
request: {
|
|
57
|
-
'
|
|
58
|
-
identifier: 'reportList',
|
|
59
|
-
description: 'Get a list of all available reports. Each report describes the results of a job that tested a web page for front-end quality (i.e. accessibility, usability, and standards conformity). For each report, the list identifies which page was tested and when and provides URLs to begin incremental retrieval of the test results.'
|
|
60
|
-
},
|
|
67
|
+
description: 'List all available reports. For each report, the list should identify which page was tested and when and provide URLs to begin incremental retrieval of the test results from the report.',
|
|
61
68
|
method: 'GET',
|
|
62
69
|
URLs: {
|
|
63
|
-
'
|
|
64
|
-
'
|
|
70
|
+
'for you': `${thisHost}/api/reportList`,
|
|
71
|
+
'for humans': `${thisHost}/targets.html`
|
|
65
72
|
},
|
|
66
73
|
'closest ancestor request': null
|
|
67
74
|
},
|
|
68
75
|
'response metadata': {
|
|
69
76
|
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
70
|
-
'date and time': new Date().toISOString()
|
|
77
|
+
'date and time': new Date().toISOString()
|
|
71
78
|
},
|
|
72
|
-
'
|
|
79
|
+
'response content': reportsFacts
|
|
73
80
|
};
|
|
81
|
+
// Return it.
|
|
74
82
|
return content;
|
|
75
83
|
};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/*
|
|
2
|
+
api.js
|
|
3
|
+
Responds to the reportSummary API request.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getNowStamp,
|
|
10
|
+
getRandomString,
|
|
11
|
+
getReport,
|
|
12
|
+
isHidden,
|
|
13
|
+
objectSort,
|
|
14
|
+
ruleEngines
|
|
15
|
+
} = require('../util');
|
|
16
|
+
const issuesClassification = require('testilo/procs/score/tic').issues;
|
|
17
|
+
|
|
18
|
+
// CONSTANTS
|
|
19
|
+
|
|
20
|
+
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
21
|
+
|
|
22
|
+
// FUNCTIONS
|
|
23
|
+
|
|
24
|
+
// Returns facts about a report.
|
|
25
|
+
const getReportFacts = async (report) => {
|
|
26
|
+
// Initialize the facts.
|
|
27
|
+
const facts = {
|
|
28
|
+
ruleEngineIDs: new Set(),
|
|
29
|
+
reporterIDs: new Set(),
|
|
30
|
+
issueIDs: new Set(),
|
|
31
|
+
violators: new Set()
|
|
32
|
+
};
|
|
33
|
+
// For each act in the report:
|
|
34
|
+
report.acts.forEach(act => {
|
|
35
|
+
// If it is a test act:
|
|
36
|
+
if (act.type === 'test') {
|
|
37
|
+
const {result, which} = act;
|
|
38
|
+
// Ensure its rule engine is in the facts.
|
|
39
|
+
facts.ruleEngineIDs.add(which);
|
|
40
|
+
const instances = result?.standardResult?.instances ?? [];
|
|
41
|
+
// For each of its standard instances:
|
|
42
|
+
instances.forEach(instance => {
|
|
43
|
+
const {catalogIndex, issueID} = instance;
|
|
44
|
+
// If the instance identifies a non-ignorable issue:
|
|
45
|
+
if (issueID && issueID !== 'ignorable') {
|
|
46
|
+
const issueClassification = issuesClassification[issueID];
|
|
47
|
+
// If the issue is classified and has a valid weight:
|
|
48
|
+
if (issueClassification && [1, 2, 3, 4].includes(issueClassification.weight)) {
|
|
49
|
+
// Ensure the issue ID is in the facts.
|
|
50
|
+
facts.issueIDs.add(issueID);
|
|
51
|
+
// Ensure its rule engine is among the reporters in the facts.
|
|
52
|
+
facts.reporterIDs.add(which);
|
|
53
|
+
// If the instance has a catalog index:
|
|
54
|
+
if (catalogIndex) {
|
|
55
|
+
// Ensure the violator is in the facts.
|
|
56
|
+
facts.violators.add(catalogIndex);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Return the facts.
|
|
64
|
+
return facts;
|
|
65
|
+
};
|
|
66
|
+
// Returns facts about a rule engine.
|
|
67
|
+
const getRuleEngineFacts = ruleEngineID => {
|
|
68
|
+
const ruleEngineData = ruleEngines[ruleEngineID];
|
|
69
|
+
return {
|
|
70
|
+
identifier: ruleEngineID,
|
|
71
|
+
name: ruleEngineData[0],
|
|
72
|
+
sponsor: ruleEngineData[1]
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
// Returns facts about rule engines that were prevented from testing the page.
|
|
76
|
+
const getPreventionFacts = report => {
|
|
77
|
+
return objectSort(Object.entries(report.jobData.preventions).map(([ruleEngineID, reason]) => ({
|
|
78
|
+
'name': getRuleEngineFacts(ruleEngineID).name,
|
|
79
|
+
'reason for failure': reason
|
|
80
|
+
})), 'name', 'alpha');
|
|
81
|
+
};
|
|
82
|
+
// Returns rule engine IDs sorted by name.
|
|
83
|
+
const getSortedRuleEngineIDs = ruleEngineIDSet => {
|
|
84
|
+
return Array.from(ruleEngineIDSet).sort((a, b) => {
|
|
85
|
+
const aData = ruleEngines[a][0];
|
|
86
|
+
const bData = ruleEngines[b][0];
|
|
87
|
+
return aData.localeCompare(bData, 'en', {sensitivity: 'base'});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
// Returns issue IDs sorted by priority and summary.
|
|
91
|
+
const getSortedIssueIDs = issueIDSet => {
|
|
92
|
+
return Array.from(issueIDSet).sort((a, b) => {
|
|
93
|
+
const aData = issuesClassification[a];
|
|
94
|
+
const bData = issuesClassification[b];
|
|
95
|
+
if (aData.weight !== bData.weight) {
|
|
96
|
+
return bData.weight - aData.weight;
|
|
97
|
+
}
|
|
98
|
+
return aData.summary.localeCompare(bData.summary, 'en', {sensitivity: 'base'});
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
// Get facts about an issue.
|
|
102
|
+
const getIssueFacts = (issueID, timeStamp, jobID) => {
|
|
103
|
+
const issueData = issuesClassification[issueID];
|
|
104
|
+
const {summary, weight, why} = issueData;
|
|
105
|
+
return {
|
|
106
|
+
identifier: issueID,
|
|
107
|
+
summary,
|
|
108
|
+
'impact on a user': why,
|
|
109
|
+
'priority': ['lowest', 'low', 'high', 'highest'][weight - 1],
|
|
110
|
+
'URLs for more details': {
|
|
111
|
+
'for you': `${thisHost}/api/reportIssue/${issueID}/${timeStamp}/${jobID}`,
|
|
112
|
+
'for humans': `${thisHost}/reportIssue.html/${issueID}/${timeStamp}/${jobID}`
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
// Returns a response to an API request for a summary of one report.
|
|
117
|
+
exports.response = async (args) => {
|
|
118
|
+
const [timeStamp, jobID] = args;
|
|
119
|
+
const reportIsHidden = await isHidden(timeStamp, jobID);
|
|
120
|
+
// If the report exists and is hidden:
|
|
121
|
+
if (reportIsHidden) {
|
|
122
|
+
// Return this.
|
|
123
|
+
return {
|
|
124
|
+
status: 'error',
|
|
125
|
+
message: 'Report not available',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// Otherwise, i.e. if the report is not hidden, get it.
|
|
129
|
+
const report = await getReport(timeStamp, jobID);
|
|
130
|
+
const {error} = report;
|
|
131
|
+
// If this failed:
|
|
132
|
+
if (error) {
|
|
133
|
+
// Return why.
|
|
134
|
+
return {
|
|
135
|
+
status: 'error',
|
|
136
|
+
message: error
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Otherwise, i.e. if it succeeded, get facts about it.
|
|
140
|
+
const reportFacts = await getReportFacts(report);
|
|
141
|
+
// Create a response body.
|
|
142
|
+
const content = {
|
|
143
|
+
summary: 'This document fulfills a request made by a language model to a Kilotest tool. The model asked for a summary of one Kilotest report. The model had previously used the listAllAvailableReports tool and had acquired from that tool basic facts about Kilotest, the ensemble testing that Kilotest performs, and the reports available from Kilotest. This summary does not repeat those facts.',
|
|
144
|
+
'tool collection name': 'Kilotest',
|
|
145
|
+
'tool name': 'summarizeOneReport',
|
|
146
|
+
request: {
|
|
147
|
+
description: 'Summarize one report. The summary should briefly describe the testing job and the results, including the rule engines that tested the web page and the issues that were revealed by the reported rule violations, and provide URLs for getting details about issues. The timeStamp and jobID parameters identify the report and were obtained from the response to a listAllAvailableReports operation.',
|
|
148
|
+
method: 'GET',
|
|
149
|
+
URLs: {
|
|
150
|
+
'for you': `${thisHost}/api/reportSummary/${timeStamp}/${jobID}`,
|
|
151
|
+
'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
152
|
+
},
|
|
153
|
+
'closest ancestor request': {
|
|
154
|
+
'tool name': 'listAllAvailableReports',
|
|
155
|
+
description: 'List all available reports.',
|
|
156
|
+
URLs: {
|
|
157
|
+
'for you': `${thisHost}/api/reportList`,
|
|
158
|
+
'for humans': `${thisHost}/targets.html`
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
'response metadata': {
|
|
163
|
+
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
164
|
+
'date and time': new Date().toISOString()
|
|
165
|
+
},
|
|
166
|
+
'response content': {
|
|
167
|
+
'rule engines that tried to test the page': getSortedRuleEngineIDs(reportFacts.ruleEngineIDs)
|
|
168
|
+
.map(id => getRuleEngineFacts(id)),
|
|
169
|
+
'rule engines that could not test the page': getPreventionFacts(report),
|
|
170
|
+
'names of rule engines that reported rule violations': Array
|
|
171
|
+
.from(reportFacts.reporterIDs)
|
|
172
|
+
.map(id => getRuleEngineFacts(id).name)
|
|
173
|
+
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'})),
|
|
174
|
+
'number of elements reported as violators': reportFacts.violators.size,
|
|
175
|
+
'issues revealed': getSortedIssueIDs(reportFacts.issueIDs)
|
|
176
|
+
.map(id => getIssueFacts(id, timeStamp, jobID))
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
// Return it.
|
|
180
|
+
return content;
|
|
181
|
+
};
|
|
@@ -22,7 +22,15 @@ exports.answer = async (_, search) => {
|
|
|
22
22
|
if (authCode === process.env.AUTH_CODE) {
|
|
23
23
|
// Get the log of the report.
|
|
24
24
|
const log = await getLog(... jobName.split('-'));
|
|
25
|
-
//
|
|
25
|
+
// If this failed:
|
|
26
|
+
if (log.error) {
|
|
27
|
+
// Return why.
|
|
28
|
+
return {
|
|
29
|
+
status: 'error',
|
|
30
|
+
message: log.error
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Otherwise, i.e. if it succeeded, reverse the hiddenness property of the log.
|
|
26
34
|
log.hidden = false;
|
|
27
35
|
// Save the updated log.
|
|
28
36
|
await fs.writeFile(path.join(logsPath, `${jobName}.json`), getJSON(log));
|
|
@@ -37,13 +45,23 @@ exports.answer = async (_, search) => {
|
|
|
37
45
|
}
|
|
38
46
|
}
|
|
39
47
|
const reportFileNames = await fs.readdir(reportsPath);
|
|
48
|
+
// Initialize an array of report specifications.
|
|
40
49
|
const reportSpecs = [];
|
|
41
50
|
// For each report:
|
|
42
51
|
for (const reportFileName of reportFileNames) {
|
|
43
52
|
const [timeStamp, jobID] = reportFileName.slice(0, -5).split('-');
|
|
44
53
|
// Get its log.
|
|
45
54
|
const log = await getLog(timeStamp, jobID);
|
|
46
|
-
const {what, hidden} = log;
|
|
55
|
+
const {error, what, hidden} = log;
|
|
56
|
+
// If this failed:
|
|
57
|
+
if (error) {
|
|
58
|
+
// Return why.
|
|
59
|
+
return {
|
|
60
|
+
status: 'error',
|
|
61
|
+
message: error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Otherwise, i.e. if it succeeded, add the report to the array.
|
|
47
65
|
reportSpecs.push({
|
|
48
66
|
what,
|
|
49
67
|
timeStamp,
|
|
@@ -44,7 +44,15 @@ exports.answer = async (_, search) => {
|
|
|
44
44
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
45
45
|
// Get a summary of it.
|
|
46
46
|
const reportSummary = await getReportData(timeStamp, jobID);
|
|
47
|
-
const {issueCount, preventedToolCount, url} = reportSummary;
|
|
47
|
+
const {error, issueCount, preventedToolCount, url} = reportSummary;
|
|
48
|
+
// If this failed:
|
|
49
|
+
if (error) {
|
|
50
|
+
// Return why.
|
|
51
|
+
return {
|
|
52
|
+
status: 'error',
|
|
53
|
+
message: error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
48
56
|
reportSpecs.push({
|
|
49
57
|
timeStamp,
|
|
50
58
|
jobID,
|
|
@@ -38,13 +38,23 @@ exports.answer = async (_, search) => {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
const reportNames = await fs.readdir(reportsPath);
|
|
41
|
+
// Initialize an array of report summaries.
|
|
41
42
|
const reportSpecs = [];
|
|
42
43
|
// For each report:
|
|
43
44
|
for (const reportName of reportNames) {
|
|
44
45
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
45
46
|
// Get a summary of it.
|
|
46
47
|
const reportSummary = await getReportData(timeStamp, jobID);
|
|
47
|
-
const {issueCount, preventedToolCount, url} = reportSummary;
|
|
48
|
+
const {error, issueCount, preventedToolCount, url} = reportSummary;
|
|
49
|
+
// If this failed:
|
|
50
|
+
if (error) {
|
|
51
|
+
// Return why.
|
|
52
|
+
return {
|
|
53
|
+
status: 'error',
|
|
54
|
+
message: error
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Otherwise, i.e. if it succeeded, add the summary to the array.
|
|
48
58
|
reportSpecs.push({
|
|
49
59
|
timeStamp,
|
|
50
60
|
jobID,
|
|
@@ -44,7 +44,15 @@ exports.answer = async (_, search) => {
|
|
|
44
44
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
45
45
|
// Get a summary of it.
|
|
46
46
|
const reportSummary = await getReportData(timeStamp, jobID);
|
|
47
|
-
const {issueCount, preventedToolCount, url} = reportSummary;
|
|
47
|
+
const {error, issueCount, preventedToolCount, url} = reportSummary;
|
|
48
|
+
// If this failed:
|
|
49
|
+
if (error) {
|
|
50
|
+
// Return why.
|
|
51
|
+
return {
|
|
52
|
+
status: 'error',
|
|
53
|
+
message: error
|
|
54
|
+
}
|
|
55
|
+
}
|
|
48
56
|
reportSpecs.push({
|
|
49
57
|
timeStamp,
|
|
50
58
|
jobID,
|
package/researchAgent.js
CHANGED
|
@@ -80,11 +80,10 @@ const getRequestOptions = (path, method = 'GET') => ({
|
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
82
|
// Submits a request, returns the response content, and increments the results.
|
|
83
|
-
const submitRequest = async (
|
|
83
|
+
const submitRequest = async (path, method, body = null) => new Promise(resolve => {
|
|
84
84
|
console.log(`Making ${scheme} ${method} request on port ${port} to ${host}${path}`);
|
|
85
85
|
client.request(getRequestOptions(path, method), async response => {
|
|
86
86
|
const responseContent = await getContent(response);
|
|
87
|
-
results.push(responseContent.error ? 'bad' : 'good');
|
|
88
87
|
resolve(responseContent);
|
|
89
88
|
})
|
|
90
89
|
.on('error', error => {
|
|
@@ -104,26 +103,25 @@ const requestService = async () => {
|
|
|
104
103
|
let jobID;
|
|
105
104
|
let description;
|
|
106
105
|
let url;
|
|
107
|
-
|
|
108
|
-
console.log('======================\nRequest 1: Summarize nonexistent reports');
|
|
106
|
+
console.log('======================\nRequest: Summarize nonexistent reports');
|
|
109
107
|
method = 'POST';
|
|
110
108
|
path = '/api/target';
|
|
111
|
-
content = await submitRequest(
|
|
112
|
-
|
|
109
|
+
content = await submitRequest(path, method, {
|
|
110
|
+
description: 'oesntuhaesouht',
|
|
113
111
|
hostname: 'osentuhaoesuht.aoesntuh'
|
|
114
112
|
});
|
|
115
113
|
if (content.error) {
|
|
116
114
|
return;
|
|
117
115
|
}
|
|
118
|
-
console.log('======================\nRequest
|
|
116
|
+
console.log('======================\nRequest: List all available reports');
|
|
119
117
|
method = 'GET';
|
|
120
118
|
path = '/api/reportList';
|
|
121
|
-
content = await submitRequest(
|
|
122
|
-
reports = content?.['
|
|
119
|
+
content = await submitRequest(path, method);
|
|
120
|
+
reports = content?.['response content'] ?? [];
|
|
123
121
|
if (content.error || ! Array.isArray(reports) || ! reports.length) {
|
|
124
122
|
return;
|
|
125
123
|
}
|
|
126
|
-
console.log('======================\nRequest
|
|
124
|
+
console.log('======================\nRequest: Summarize matching reports');
|
|
127
125
|
// Choose one available report at random.
|
|
128
126
|
report = reports[Math.floor(Math.random() * reports.length)];
|
|
129
127
|
({description, URL: url} = report?.['tested web page'] ?? ['', '']);
|
|
@@ -132,61 +130,72 @@ const requestService = async () => {
|
|
|
132
130
|
}
|
|
133
131
|
method = 'POST';
|
|
134
132
|
path = '/api/target';
|
|
135
|
-
content = await submitRequest(
|
|
136
|
-
|
|
133
|
+
content = await submitRequest(path, method, {
|
|
134
|
+
description,
|
|
137
135
|
hostname: new URL(url).hostname ?? ''
|
|
138
136
|
});
|
|
139
137
|
if (content.error) {
|
|
140
138
|
return;
|
|
141
139
|
}
|
|
142
|
-
console.log('======================\nRequest
|
|
140
|
+
console.log('======================\nRequest: Summarize one nonexistent report');
|
|
141
|
+
[timeStamp, jobID] = ['111111T1111', 'abc'];
|
|
142
|
+
method = 'GET';
|
|
143
|
+
path = `/api/reportSummary/${timeStamp}/${jobID}`;
|
|
144
|
+
content = await submitRequest(path, method);
|
|
145
|
+
if (! content.message) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
console.log('======================\nRequest: Summarize one report');
|
|
143
149
|
[timeStamp, jobID] = report.identifier?.split('-') ?? ['', ''];
|
|
144
150
|
if (! (timeStamp && jobID)) {
|
|
145
151
|
return;
|
|
146
152
|
}
|
|
147
153
|
method = 'GET';
|
|
148
|
-
path = `/api/
|
|
149
|
-
content = await submitRequest(
|
|
150
|
-
if (content.
|
|
154
|
+
path = `/api/reportSummary/${timeStamp}/${jobID}`;
|
|
155
|
+
content = await submitRequest(path, method);
|
|
156
|
+
if (content.message || ! content.summary) {
|
|
151
157
|
return;
|
|
152
158
|
}
|
|
153
|
-
console.log('======================\nRequest
|
|
154
|
-
if (content['number of
|
|
159
|
+
console.log('======================\nRequest: Describe one issue from one report');
|
|
160
|
+
if (content['response content']['number of elements reported as violators'] === 0) {
|
|
155
161
|
console.log('reportIssue request cannot be submitted, because no issues were reported');
|
|
156
162
|
}
|
|
157
163
|
else {
|
|
158
164
|
// Get the issue IDs.
|
|
159
165
|
const issueIDs = Object
|
|
160
|
-
.values(content['issues
|
|
161
|
-
.map(
|
|
162
|
-
.flat();
|
|
166
|
+
.values(content['response content']['issues revealed'])
|
|
167
|
+
.map(issue => issue.identifier);
|
|
163
168
|
// Choose one at random.
|
|
164
169
|
const issueID = issueIDs[Math.floor(Math.random() * issueIDs.length)];
|
|
165
170
|
method = 'GET';
|
|
166
171
|
path = `/api/reportIssue/${issueID}/${timeStamp}/${jobID}`;
|
|
167
|
-
content = await submitRequest(
|
|
168
|
-
if (content.
|
|
172
|
+
content = await submitRequest(path, method);
|
|
173
|
+
if (content.message) {
|
|
169
174
|
return;
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
|
-
console.log('======================\nRequest
|
|
177
|
+
console.log('======================\nRequest: Make a permitted test recommendation');
|
|
173
178
|
method = 'POST';
|
|
174
179
|
path = '/api/testRecForm';
|
|
175
|
-
content = await submitRequest(
|
|
180
|
+
content = await submitRequest(path, method, {
|
|
176
181
|
'description of the web page': 'aoseeou',
|
|
177
182
|
'URL of the web page': 'https://oaaestuh.osneth',
|
|
178
183
|
'reason for testing the web page': 'Just testing'
|
|
179
184
|
});
|
|
180
|
-
if (content.
|
|
185
|
+
if (content.message) {
|
|
181
186
|
return;
|
|
182
187
|
}
|
|
183
|
-
console.log('======================\nRequest
|
|
184
|
-
await submitRequest(
|
|
188
|
+
console.log('======================\nRequest: Make an illicit test recommendation');
|
|
189
|
+
content = await submitRequest(path, method, {
|
|
185
190
|
'description of the web page': description,
|
|
186
191
|
'URL of the web page': url,
|
|
187
192
|
'reason for testing the web page': 'Just testing'
|
|
188
193
|
});
|
|
189
|
-
|
|
194
|
+
if (! content.message) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
console.log('======================\nRequest: Results');
|
|
198
|
+
console.log('All tests succeeded');
|
|
190
199
|
};
|
|
191
200
|
|
|
192
201
|
// EXECUTION
|
package/retestRec/index.js
CHANGED
|
@@ -12,8 +12,17 @@ const {getLog, processRec} = require('../util');
|
|
|
12
12
|
// Records a retest recommendation and returns an acknowledgement page.
|
|
13
13
|
exports.answer = async (pageArgs, why) => {
|
|
14
14
|
const [timeStamp, jobID] = pageArgs.split('/');
|
|
15
|
+
// Get the log of the specified report.
|
|
15
16
|
const log = await getLog(timeStamp, jobID);
|
|
16
|
-
const {url, what} = log;
|
|
17
|
-
//
|
|
17
|
+
const {error, url, what} = log;
|
|
18
|
+
// If this failed:
|
|
19
|
+
if (error) {
|
|
20
|
+
// Return why.
|
|
21
|
+
return {
|
|
22
|
+
status: 'error',
|
|
23
|
+
message: error
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// Otherwise, i.e. if it succeeded, process the recommendation.
|
|
18
27
|
return await processRec('retest', __dirname, what, url, why);
|
|
19
28
|
};
|
package/retestRecForm/index.js
CHANGED
|
@@ -15,6 +15,11 @@ const path = require('path');
|
|
|
15
15
|
exports.answer = async pageArgs => {
|
|
16
16
|
const [timeStamp, jobID] = pageArgs.split('/');
|
|
17
17
|
const log = await getLog(timeStamp, jobID);
|
|
18
|
+
// If this failed:
|
|
19
|
+
if (log.error) {
|
|
20
|
+
// Make the form report the failure.
|
|
21
|
+
log.what = 'The specified page is not available for retesting';
|
|
22
|
+
}
|
|
18
23
|
const query = {
|
|
19
24
|
target: log.what,
|
|
20
25
|
timeStamp,
|