@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.
package/util.js CHANGED
@@ -37,6 +37,7 @@ const tools = exports.tools = {
37
37
  wave: ['WAVE', 'Utah State University'],
38
38
  wax: ['WallyAX', 'Wally']
39
39
  };
40
+ exports.ruleEngines = tools;
40
41
  exports.researchAgents = {
41
42
  'research-agent': 'Internal Research Agent'
42
43
  }
@@ -83,7 +84,7 @@ const getRecordPath = exports.getRecordPath = (recordType, timeStamp, jobID) =>
83
84
  return null;
84
85
  }
85
86
  };
86
- // Returns a log or report or an error message.
87
+ // Returns a log or report.
87
88
  const getRecord = exports.getRecord = async (recordType, timeStamp, jobID) => {
88
89
  const recordPath = getRecordPath(recordType, timeStamp, jobID);
89
90
  let recordJSON, record;
@@ -92,18 +93,18 @@ const getRecord = exports.getRecord = async (recordType, timeStamp, jobID) => {
92
93
  }
93
94
  catch(error) {
94
95
  console.log(error.message);
95
- return `ERROR: Requested ${recordType} ${timeStamp}-${jobID} not found`;
96
+ recordJSON = JSON.stringify({error: `Requested ${recordType} ${timeStamp}-${jobID} not found`});
96
97
  }
97
98
  try {
98
99
  record = JSON.parse(recordJSON);
99
100
  }
100
101
  catch (error) {
101
102
  console.log(error.message);
102
- return `ERROR: Requested ${recordType} ${timeStamp}-${jobID} not JSON`;
103
+ record = {error: `Requested ${recordType} ${timeStamp}-${jobID} not JSON`};
103
104
  }
104
105
  return record;
105
106
  };
106
- // Returns a report or an error message.
107
+ // Returns a report.
107
108
  const getReport = exports.getReport = async (timeStamp, jobID) => await getRecord(
108
109
  'report', timeStamp, jobID
109
110
  );
@@ -246,13 +247,13 @@ const getIssue = exports.getIssue = (ruleIDs, toolID, ruleID) => {
246
247
  // Adds issue IDs to the standard instances of a report.
247
248
  const annotateReport = exports.annotateReport = async (ruleIDs, timeStamp, jobID) => {
248
249
  // Get a copy of the report.
249
- const reportOrError = await getReport(timeStamp, jobID);
250
+ const report = await getReport(timeStamp, jobID);
250
251
  // If this failed:
251
- if (typeof reportOrError === 'string') {
252
- // Return this.
253
- return reportOrError;
252
+ if (report.error) {
253
+ // Return why.
254
+ return report.error;
254
255
  }
255
- const report = reportOrError;
256
+ // Otherwise, i.e. if it succeeded:
256
257
  const unclassifiableRules = new Set();
257
258
  // For each of its acts:
258
259
  for (const act of report.acts) {
@@ -293,36 +294,35 @@ const annotateReport = exports.annotateReport = async (ruleIDs, timeStamp, jobID
293
294
  // Save the annotated report.
294
295
  await fs.writeFile(getReportPath(timeStamp, jobID), getJSON(report));
295
296
  // Get a copy of the log of the report.
296
- const logOrError = await getLog(timeStamp, jobID, false);
297
+ const log = await getLog(timeStamp, jobID, false);
297
298
  // If this failed:
298
- if (typeof logOrError === 'string') {
299
- // Return this.
300
- return logOrError;
299
+ if (log.error) {
300
+ // Return why.
301
+ return log.error;
301
302
  }
302
303
  // Otherwise, i.e. if it succeeded:
303
304
  else {
304
- const log = logOrError;
305
305
  // Mark the report as annotated in the log.
306
306
  log.annotated = true;
307
307
  // Save the revised log.
308
308
  await fs.writeFile(getLogPath(timeStamp, jobID), getJSON(log));
309
- // Return without an error message.
309
+ // Return success.
310
+ return '';
310
311
  }
311
312
  };
312
- // Returns a report log after conditionally annotating it or an error message.
313
+ // Returns a report log after conditionally annotating it.
313
314
  const getLog = exports.getLog = async (timeStamp, jobID, annotate = false) => {
314
315
  const log = await getRecord('log', timeStamp, jobID);
315
- if (typeof log === 'object' && annotate && ! log.annotated) {
316
- annotateReport(ruleIDs, timeStamp, jobID);
316
+ if (annotate && ! (log.error || log.annotated)) {
317
+ await annotateReport(ruleIDs, timeStamp, jobID);
317
318
  }
318
319
  return log;
319
320
  };
320
- // Returns whether a report is hidden or an error message.
321
+ // Returns whether a report is hidden.
321
322
  exports.isHidden = async (timeStamp, jobID) => {
323
+ // Get the log of the report.
322
324
  const log = await getLog(timeStamp, jobID, false);
323
- if (typeof log === 'string') {
324
- return log;
325
- }
325
+ // Return whether the log exists and its report is hidden.
326
326
  return !! log.hidden;
327
327
  };
328
328
  // Returns summary data on a report.
@@ -330,9 +330,9 @@ exports.getReportData = async (timeStamp, jobID) => {
330
330
  // Validate the report and annotate it if necessary.
331
331
  const log = await getLog(timeStamp, jobID, true);
332
332
  // If this failed:
333
- if (typeof log === 'string') {
334
- // Return this.
335
- return log;
333
+ if (log.error) {
334
+ // Return why.
335
+ return {error: log.error};
336
336
  }
337
337
  // Initialize the data.
338
338
  const data = {
@@ -357,9 +357,9 @@ exports.getReportData = async (timeStamp, jobID) => {
357
357
  // Get the report.
358
358
  const report = await getReport(timeStamp, jobID);
359
359
  // If this failed:
360
- if (typeof report === 'string') {
361
- // Return this.
362
- return report;
360
+ if (report.error) {
361
+ // Return why.
362
+ return {error: report.error};
363
363
  }
364
364
  // For each act of the report:
365
365
  report.acts.forEach(act => {
@@ -526,30 +526,31 @@ const getLogs = exports.getLogs = async () => {
526
526
  try {
527
527
  logFileNames = await fs.readdir(logsPath);
528
528
  }
529
- catch(error) {
530
- return `ERROR: logs directory not readable (${error.message})`;
529
+ catch {
530
+ console.error('Logs directory not readable');
531
+ logFileNames = [];
531
532
  }
532
533
  // For each log:
533
534
  for (const fileName of logFileNames) {
534
535
  const logName = fileName.slice(0, -5);
535
536
  const [timeStamp, jobID] = logName.split('-');
536
537
  // Get it.
537
- const logOrError = await getLog(timeStamp, jobID);
538
+ const log = await getLog(timeStamp, jobID);
538
539
  // If this failed:
539
- if (typeof logOrError === 'string') {
540
- // Return this.
541
- return logOrError;
540
+ if (log.error) {
541
+ // Report why.
542
+ console.error(log.error);
542
543
  }
543
- const log = logOrError;
544
- // If the report is hidden:
545
- if (log.hidden) {
546
- // Disregard it.
547
- continue;
544
+ // Otherwise, i.e. if it succeeded:
545
+ else {
546
+ // If the report is not hidden:
547
+ if (! log.hidden) {
548
+ // Add the job name to the log.
549
+ log.jobName = logName;
550
+ // Add the log to the logs.
551
+ logs.push(log);
552
+ }
548
553
  }
549
- // Add the job name to the log.
550
- log.jobName = logName;
551
- // Add the log to the logs.
552
- logs.push(log);
553
554
  }
554
555
  // Sort the logs by target name and secondarily by test time.
555
556
  logs.sort((a, b) => {
@@ -692,12 +693,12 @@ const getPageData = exports.getPageData = async (timeStamp, jobID) => {
692
693
  // Get the log of the report.
693
694
  const log = await getLog(timeStamp, jobID, false);
694
695
  // If this failed:
695
- if (typeof log === 'string') {
696
- // Return the error.
696
+ if (log.error) {
697
+ // Return why.
697
698
  return log;
698
699
  }
699
700
  const {url, what} = log;
700
- // Get the elapsed time in days since the test.
701
+ // Otherwise, i.e. if it succeeded, get the elapsed time in days since the test.
701
702
  const daysAgo = getAgoDays(timeStamp);
702
703
  // Return the data.
703
704
  return {
@@ -713,7 +714,15 @@ exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
713
714
  // Get them.
714
715
  pageData = await getPageData(timeStamp, jobID);
715
716
  }
716
- const {what, url, daysAgo} = pageData;
717
+ const {daysAgo, error, url, what} = pageData;
718
+ // If the page data are invalid:
719
+ if (error) {
720
+ // Return why.
721
+ return {
722
+ error
723
+ };
724
+ }
725
+ // Otherwise, i.e. if they are valid, get a description of the timestamp.
717
726
  const when = getDateTimeString(timeStamp);
718
727
  // Return the HTML strings.
719
728
  return {
@@ -766,7 +775,13 @@ exports.isReportAvailable = async (what, url) => {
766
775
  };
767
776
  // Returns the size of a report.
768
777
  exports.getReportSize = async (timeStamp, jobID) => {
769
- const reportStat = await fs.stat(path.join(reportsPath, `${timeStamp}-${jobID}.json`));
778
+ const reportStat = await fs.stat(
779
+ path.join(reportsPath, `${timeStamp}-${jobID}.json`),
780
+ {throwIfNoEntry: false}
781
+ );
782
+ if (! reportStat) {
783
+ return 0;
784
+ }
770
785
  const reportSize = reportStat.size;
771
786
  return reportSize;
772
787
  };
@@ -1,129 +0,0 @@
1
- /*
2
- api.js
3
- Responds to the report-issues request.
4
- */
5
-
6
- // IMPORTS
7
-
8
- const {getData} = require('./util');
9
- const {
10
- getDateTime,
11
- getNowStamp,
12
- getRandomString,
13
- getToolsFacts,
14
- isHidden,
15
- tools
16
- } = require('../util');
17
-
18
- // FUNCTIONS
19
-
20
- // Gets facts about an issue.
21
- const getIssueFacts = (thisHost, timeStamp, jobID, issue) => {
22
- const {issueID, reporterCount, reporters, summary, violatorCount, wcag, why} = issue;
23
- const wcagType = wcag.length === 3 ? 'guideline' : 'success criterion';
24
- return {
25
- identifier: issueID,
26
- summary,
27
- 'related WCAG 2.2 standard': {
28
- layer: wcagType,
29
- 'numeric identifier': wcag
30
- },
31
- 'impact on a user': why,
32
- 'rule engines reporting the issue': {
33
- 'number': reporterCount,
34
- 'names': reporters.map(tool => tool.toolName)
35
- },
36
- 'number of HTML elements reported as exhibiting the issue': violatorCount,
37
- 'URLs for details about the issue on the page': {
38
- 'for you': `${thisHost}/api/reportIssue/${issueID}/${timeStamp}/${jobID}`,
39
- 'for humans': `${thisHost}/reportIssue.html/${issueID}/${timeStamp}/${jobID}`
40
- }
41
- };
42
- };
43
- // Returns a response to a report-issues request.
44
- exports.response = async args => {
45
- const [timeStamp, jobID] = args;
46
- const reportIsHidden = await isHidden(timeStamp, jobID);
47
- // If the report is not available:
48
- if (reportIsHidden) {
49
- return {
50
- status: 'error',
51
- message: 'Report not available'
52
- };
53
- }
54
- // Otherwise, i.e. if the report is available, get data on the target and issues.
55
- const data = await getData(timeStamp, jobID);
56
- const {pageData, issuesData} = data;
57
- const {what, url, daysAgo} = pageData;
58
- const {issueCount, issues, preventions, reporterCount, reporters, violatorCount} = issuesData;
59
- const preventedTools = Object.entries(preventions).map(prevention => ({
60
- name: tools[prevention[0]][0],
61
- 'reason for failure': prevention[1]
62
- }));
63
- const thisHost = process.env.THIS_KILOTEST_HOST;
64
- // Get a response.
65
- const content = {
66
- summary: `This document fulfills a request made by a language model to a Kilotest tool. The model requested data from a Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes several API endpoints to recommend web pages for testing and to obtain information from Kilotest reports. To learn more about Kilotest and the advantages of testing with an ensemble of rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
67
- 'tool collection name': 'Kilotest',
68
- 'tool name': 'describeQualityOfOneWebPage',
69
- request: {
70
- 'type of request': {
71
- identifier: 'reportIssues',
72
- description: 'Describe the quality of one web page.'
73
- },
74
- method: 'GET',
75
- URLs: {
76
- 'URL of your request': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
77
- 'equivalent URL for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
78
- },
79
- 'closest ancestor request': {
80
- identifier: 'getListOfAllAvailableReports',
81
- description: 'Get a list of all available reports.',
82
- URLs: {
83
- 'for you': `${thisHost}/api/reportList`,
84
- 'for humans': `${thisHost}/targets.html`
85
- }
86
- }
87
- },
88
- 'response metadata': {
89
- identifier: `${getNowStamp()}-${getRandomString(3)}`,
90
- 'date and time': new Date().toISOString()
91
- },
92
- report: {
93
- identifier: `${timeStamp}-${jobID}`,
94
- 'creation date': getDateTime(timeStamp),
95
- 'days since the creation date': daysAgo
96
- },
97
- 'tested web page': {
98
- description: what,
99
- URL: url
100
- },
101
- 'rule engines that tried to test the page': getToolsFacts(Object.keys(tools)),
102
- 'rule engines that were unable to test the page': preventedTools,
103
- 'rule engines that reported issues': {
104
- number: reporterCount,
105
- names: reporters.map(tool => tool.toolName)
106
- },
107
- 'number of issues reported': {
108
- total: issueCount,
109
- 'by priority': {
110
- 'highest priority': issues[4].length,
111
- 'high priority': issues[3].length,
112
- 'low priority': issues[2].length,
113
- 'lowest priority': issues[1].length
114
- }
115
- },
116
- 'number of HTML elements reported as exhibiting issues': violatorCount,
117
- 'issues reported': {
118
- 'highest priority': issues[4]
119
- .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
120
- 'high priority': issues[3]
121
- .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
122
- 'low priority': issues[2]
123
- .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
124
- 'lowest priority': issues[1]
125
- .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue))
126
- }
127
- };
128
- return content;
129
- };