@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.
@@ -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
- // If it is valid:
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 'ERROR: Report missing or invalid.';
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 of them:
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
- const {superseded, url, what} = availableLog;
34
- reportsFacts.push({
35
- identifier: jobName,
36
- 'creation date and time': getDateTime(timeStamp),
37
- 'days since the creation date': getAgoDays(timeStamp),
38
- 'tested web page': {
39
- description: what,
40
- URL: url
41
- },
42
- 'whether a later report about the same page exists': !! superseded,
43
- 'URLs for incrementally getting facts from the report': {
44
- 'for you': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
45
- 'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
46
- },
47
- 'size of the report in bytes': reportSize,
48
- 'URL to get the entire report as machine-oriented JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
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
- // Get a response.
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': 'getListOfAllAvailableReports',
65
+ 'tool name': 'listAllAvailableReports',
56
66
  request: {
57
- 'type of request': {
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
- 'URL of your request': `${thisHost}/api/reportList`,
64
- 'equivalent URL for humans': `${thisHost}/targets.html`
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
- 'available reports': reportsFacts
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
- // Reverse the hiddenness property of the log.
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 (results, path, method, body = null) => new Promise(resolve => {
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
- const results = [];
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(results, path, method, {
112
- what: 'oesntuhaesouht',
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 2: List all available reports');
116
+ console.log('======================\nRequest: List all available reports');
119
117
  method = 'GET';
120
118
  path = '/api/reportList';
121
- content = await submitRequest(results, path, method);
122
- reports = content?.['available reports'] ?? [];
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 3: Summarize matching reports');
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(results, path, method, {
136
- what: description,
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 4: Summarize the issues in a report');
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/reportIssues/${timeStamp}/${jobID}`;
149
- content = await submitRequest(results, path, method);
150
- if (content.error) {
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 5: Describe one issue from one report');
154
- if (content['number of HTML elements reported as exhibiting issues'] === 0) {
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 reported'])
161
- .map(levelIssues => levelIssues.map(issue => issue.identifier))
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(results, path, method);
168
- if (content.error) {
172
+ content = await submitRequest(path, method);
173
+ if (content.message) {
169
174
  return;
170
175
  }
171
176
  }
172
- console.log('======================\nRequest 6: Make a permitted test recommendation');
177
+ console.log('======================\nRequest: Make a permitted test recommendation');
173
178
  method = 'POST';
174
179
  path = '/api/testRecForm';
175
- content = await submitRequest(results, path, method, {
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.error) {
185
+ if (content.message) {
181
186
  return;
182
187
  }
183
- console.log('======================\nRequest 7: Make an illicit test recommendation');
184
- await submitRequest(results, path, method, {
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
- console.log(`======================\nResults: ${results}\n`);
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
@@ -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
- // Process the recommendation.
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
  };
@@ -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,