@jrpool/kilotest 37.1.0 → 38.1.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.
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  index.js
3
- Answers the diagnoses question.
3
+ Answers the reportIssue question.
4
4
  */
5
5
 
6
6
  // IMPORTS
@@ -25,10 +25,26 @@ const path = require('path');
25
25
 
26
26
  // Adds parameters to a query for the answer page.
27
27
  const populateQuery = async (issueID, timeStamp, jobID, query) => {
28
- // Add facts about the issue to the query.
29
- query.issue = issues[issueID].summary;
28
+ // Get descriptions of the page facts.
30
29
  const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
31
- const {what, url, urlLink, testInfo} = pageDataStrings;
30
+ // If this failed:
31
+ if (pageDataStrings.error) {
32
+ // Populate the query with the reason.
33
+ query.error = pageDataStrings.error;
34
+ // Stop populating the query.
35
+ return;
36
+ }
37
+ // Otherwise, i.e. if it succeeded, add the issue summary to the query.
38
+ query.issue = issues[issueID]?.summary;
39
+ // If adding the issue summary failed:
40
+ if (! query.issue) {
41
+ // Populate the query with the reason.
42
+ query.error = 'Issue not found';
43
+ // Stop populating the query.
44
+ return;
45
+ }
46
+ const {testInfo, url, urlLink, what} = pageDataStrings;
47
+ // Add page facts to the query.
32
48
  query.target = what;
33
49
  query.urlLink = urlLink;
34
50
  query.testInfo = testInfo;
@@ -44,6 +60,14 @@ const populateQuery = async (issueID, timeStamp, jobID, query) => {
44
60
  // Get the report.
45
61
  const report = await getReport(timeStamp, jobID);
46
62
  const {acts, catalog} = report;
63
+ // If this failed:
64
+ if (report.error) {
65
+ // Populate the query with the reason.
66
+ query.error = report.error;
67
+ // Stop populating the query.
68
+ return;
69
+ }
70
+ // Otherwise, i.e. if it succeeded, get the test acts of the report.
47
71
  const testActs = acts.filter(act => act.type === 'test');
48
72
  // For each test act:
49
73
  testActs.forEach(act => {
@@ -159,7 +183,15 @@ exports.answer = async pageArgs => {
159
183
  const query = {};
160
184
  // Create a query to replace the placeholders.
161
185
  await populateQuery(issueID, timeStamp, jobID, query);
162
- // If the test specifications are valid:
186
+ // If this failed:
187
+ if (query.error) {
188
+ // Return the error.
189
+ return {
190
+ status: 'error',
191
+ message: query.error
192
+ };
193
+ }
194
+ // Otherwise, if it succeeded and the report facts were obtained:
163
195
  if (query.testInfo) {
164
196
  // Get the template.
165
197
  let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
@@ -173,9 +205,9 @@ exports.answer = async pageArgs => {
173
205
  answerPage
174
206
  };
175
207
  }
176
- // Otherwise, report this.
208
+ // Otherwise, i.e. if the report facts were not obtained, report this.
177
209
  return {
178
210
  status: 'error',
179
- message: 'Invalid report specification'
211
+ message: 'Report facts not obtained'
180
212
  };
181
213
  };
@@ -24,20 +24,24 @@ const populateQuery = async (timeStamp, jobID, query) => {
24
24
  // Get data on the target and its issues according to the report.
25
25
  const data = await getData(timeStamp, jobID);
26
26
  const {pageData, issuesData} = data;
27
- // If the page data are invalid:
28
- if (typeof pageData === 'string') {
29
- // Return this.
30
- return pageData;
27
+ // If this failed:
28
+ if (data.error) {
29
+ // Populate the query with the reason.
30
+ query.error = data.error;
31
+ // Stop populating the query.
32
+ return;
31
33
  }
32
- // Otherwise, if the issues data are invalid:
33
- if (typeof issuesData === 'string') {
34
- // Return this.
35
- return issuesData;
36
- }
37
- // Otherwise, get fact descriptions for the target.
34
+ // Otherwise, i.e. if it succeeded, get fact descriptions for the target.
38
35
  const pageInfo = await getPageDataStrings(timeStamp, jobID, pageData);
39
- const {what, urlLink, testInfo} = pageInfo;
40
- // Add target data to the query.
36
+ const {testInfo, urlLink, what} = pageInfo;
37
+ // If this failed:
38
+ if (pageInfo.error) {
39
+ // Populate the query with the reason.
40
+ query.error = pageInfo.error;
41
+ // Stop populating the query.
42
+ return;
43
+ }
44
+ // Otherwise, i.e. if it succeeded, add target data to the query.
41
45
  query.target = what;
42
46
  query.urlLink = urlLink;
43
47
  query.testInfo = testInfo;
@@ -154,7 +158,15 @@ exports.answer = async pageArgs => {
154
158
  const query = {};
155
159
  // Create a query to replace the placeholders.
156
160
  await populateQuery(timeStamp, jobID, query);
157
- // If the report facts were obtained:
161
+ // If this failed:
162
+ if (query.error) {
163
+ // Return the error.
164
+ return {
165
+ status: 'error',
166
+ message: query.error
167
+ };
168
+ }
169
+ // Otherwise, if it succeeded and the report facts were obtained:
158
170
  if (query.testInfo) {
159
171
  // Get the template.
160
172
  let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
@@ -171,6 +183,6 @@ exports.answer = async pageArgs => {
171
183
  // Otherwise, i.e. if they were not obtained, report this.
172
184
  return {
173
185
  status: 'error',
174
- message: 'Report processing failed'
186
+ message: 'Report facts not obtained'
175
187
  };
176
188
  };
@@ -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
- });
@@ -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,
package/target/api.js CHANGED
@@ -21,9 +21,10 @@ const thisHost = process.env.THIS_KILOTEST_HOST;
21
21
 
22
22
  // Returns a response to a target request.
23
23
  exports.response = async args => {
24
- const [what = '', hostname = ''] = args;
24
+ const [description = '', hostname = ''] = args;
25
25
  const hostLC = hostname.toLowerCase();
26
- const whatLC = what.toLowerCase();
26
+ const whatLC = description.toLowerCase();
27
+ // Initialize an array of summaries of matching reports.
27
28
  const matchingReports = [];
28
29
  // Get the non-hidden logs.
29
30
  const targetLogs = await getLogs();
@@ -42,6 +43,7 @@ exports.response = async args => {
42
43
  // Get data about its report.
43
44
  const data = await getReportData(timeStamp, jobID);
44
45
  const {
46
+ error,
45
47
  creationDate,
46
48
  daysAgo,
47
49
  issueCount,
@@ -53,35 +55,44 @@ exports.response = async args => {
53
55
  preventedToolNames,
54
56
  preventedToolCount
55
57
  } = data;
56
- matchingReports.push({
57
- identifier: jobName,
58
- 'creation date': creationDate,
59
- 'days since the creation date': daysAgo,
60
- 'tested web page': {
61
- description: reportWhat,
62
- URL: reportURL
63
- },
64
- 'whether a later report about the same page exists': !! superseded,
65
- 'rule engines that tried to test the page': {
66
- number: toolCount,
67
- names: toolNames
68
- },
69
- 'rule engines that were unable to test the page': {
70
- number: preventedToolCount,
71
- names: preventedToolNames
72
- },
73
- 'rule engines that reported issues': {
74
- number: reporterCount,
75
- names: reporterNames
76
- },
77
- 'number of issues reported': issueCount,
78
- 'number of HTML elements reported as exhibiting issues': violatorCount,
79
- 'URLs for getting data about the reported issues': {
80
- 'for you': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
81
- 'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
82
- },
83
- 'URL for getting the full technical report as JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
84
- });
58
+ // If this failed:
59
+ if (error) {
60
+ // Report why.
61
+ console.error(error);
62
+ }
63
+ // Otherwise, i.e. if it succeeded:
64
+ else {
65
+ // Add a report summary to the array.
66
+ matchingReports.push({
67
+ identifier: jobName,
68
+ 'creation date': creationDate,
69
+ 'days since the creation date': daysAgo,
70
+ 'tested web page': {
71
+ description: reportWhat,
72
+ URL: reportURL
73
+ },
74
+ 'whether a later report about the same page exists': !! superseded,
75
+ 'rule engines that tried to test the page': {
76
+ number: toolCount,
77
+ names: toolNames
78
+ },
79
+ 'rule engines that were unable to test the page': {
80
+ number: preventedToolCount,
81
+ names: preventedToolNames
82
+ },
83
+ 'rule engines that reported issues': {
84
+ number: reporterCount,
85
+ names: reporterNames
86
+ },
87
+ 'number of issues reported': issueCount,
88
+ 'number of HTML elements reported as exhibiting issues': violatorCount,
89
+ 'URLs for getting data about the reported issues': {
90
+ 'for you': `${thisHost}/api/reportSummary/${timeStamp}/${jobID}`,
91
+ 'for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
92
+ },
93
+ 'URL for getting the full technical report as JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
94
+ });
95
+ }
85
96
  }
86
97
  }
87
98
  // Get a response.
@@ -96,12 +107,12 @@ exports.response = async args => {
96
107
  },
97
108
  method: 'POST',
98
109
  payload: {
99
- 'all or part of a description of the web page': what,
100
- 'all or part of the hostname of the URL of the web page': hostname
110
+ description,
111
+ hostname
101
112
  },
102
113
  URLs: {
103
- 'URL of your request': `${thisHost}/api/target`,
104
- 'equivalent URL for humans': 'none'
114
+ 'for you': `${thisHost}/api/target`,
115
+ 'for humans': 'none'
105
116
  },
106
117
  'closest ancestor request': null
107
118
  },
package/targets/index.js CHANGED
@@ -80,8 +80,10 @@ const populateQuery = async query => {
80
80
  for (const targetLog of targetLogs) {
81
81
  const {jobName, url, what} = targetLog;
82
82
  const [timeStamp, jobID] = jobName.split('-');
83
+ // Get data about its report.
83
84
  const reportData = await getReportData(timeStamp, jobID);
84
85
  const {
86
+ error,
85
87
  issueCount,
86
88
  preventedToolCount,
87
89
  preventedToolNames,
@@ -89,6 +91,15 @@ const populateQuery = async query => {
89
91
  reporterCount,
90
92
  violatorCount
91
93
  } = reportData;
94
+ // If this failed:
95
+ if (error) {
96
+ console.error(error);
97
+ // Populate the query with the reason.
98
+ query.error = error;
99
+ // Stop populating the query.
100
+ return;
101
+ }
102
+ // Otherwise, i.e. if it succeeded, add lines about the report.
92
103
  lines.tested.push(`${margin}<details>`);
93
104
  const daysAgo = getAgoDays(timeStamp);
94
105
  const pageDataStrings = await getPageDataStrings(timeStamp, jobID, {what, url, daysAgo});
@@ -155,7 +166,15 @@ exports.answer = async () => {
155
166
  const query = {};
156
167
  // Create a query to replace placeholders.
157
168
  await populateQuery(query);
158
- // Get the template.
169
+ // If the query reports an error:
170
+ if (query.error) {
171
+ // Return it.
172
+ return {
173
+ status: 'error',
174
+ message: query.error
175
+ };
176
+ }
177
+ // Otherwise, i.e. if it does not report an error, get the template.
159
178
  let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
160
179
  // Replace its placeholders.
161
180
  Object.keys(query).forEach(param => {
@@ -41,8 +41,8 @@ exports.response = async (what, url, why) => {
41
41
  'reason for testing the web page': why
42
42
  },
43
43
  URLs: {
44
- 'URL of your request': `${thisHost}/api/testRecForm`,
45
- 'equivalent URL for humans': `${thisHost}/testRecForm.html`
44
+ 'for you': `${thisHost}/api/testRecForm`,
45
+ 'for humans': `${thisHost}/testRecForm.html`
46
46
  },
47
47
  'closest ancestor request': null
48
48
  },