@jrpool/kilotest 46.0.0 → 46.0.2

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.
@@ -0,0 +1,42 @@
1
+ /*
2
+ util.test.js
3
+ Tests for api/util.js.
4
+ */
5
+
6
+ // IMPORTS
7
+
8
+ const {test} = require('node:test');
9
+ const assert = require('node:assert/strict');
10
+ const {getRuleEngineFacts, getRuleEnginesFacts} = require('./util');
11
+
12
+ // TESTS
13
+
14
+ test('getRuleEngineFacts returns the name and sponsor of a known rule engine', () => {
15
+ const facts = getRuleEngineFacts('axe');
16
+ assert.deepEqual(facts, {
17
+ identifier: 'axe',
18
+ name: 'Axe',
19
+ sponsor: 'Deque'
20
+ });
21
+ });
22
+
23
+ test('getRuleEngineFacts returns nulls for name and sponsor of an unknown rule engine', () => {
24
+ const facts = getRuleEngineFacts('nonexistentEngine');
25
+ assert.deepEqual(facts, {
26
+ identifier: 'nonexistentEngine',
27
+ name: null,
28
+ sponsor: null
29
+ });
30
+ });
31
+
32
+ test('getRuleEnginesFacts returns facts sorted alphabetically by name', () => {
33
+ // Names: ibm -> "Accessibility Checker", axe -> "Axe", wave -> "WAVE".
34
+ const facts = getRuleEnginesFacts(new Set(['wave', 'axe', 'ibm']));
35
+ assert.deepEqual(facts.map(fact => fact.identifier), ['ibm', 'axe', 'wave']);
36
+ assert.deepEqual(facts.map(fact => fact.name), ['Accessibility Checker', 'Axe', 'WAVE']);
37
+ });
38
+
39
+ test('getRuleEnginesFacts returns an empty array for an empty set', () => {
40
+ const facts = getRuleEnginesFacts(new Set());
41
+ assert.deepEqual(facts, []);
42
+ });
package/env.example CHANGED
@@ -12,6 +12,9 @@ THIS_KILOTEST_HOST=__placeholder__
12
12
  LOCAL_KILOTEST_HOST=http://localhost:3000
13
13
  # URL of the deployed host.
14
14
  DEPLOYED_KILOTEST_HOST=https://kilotest.com
15
+ # Path of the data directory (defaults to the db subdirectory of the project root). Tests set
16
+ # this to a fixture directory so they never read or write the real db.
17
+ DB_DIR=__placeholder__
15
18
 
16
19
  # Alert configuration:
17
20
 
package/eslint.config.mjs CHANGED
@@ -9,7 +9,8 @@ export default defineConfig([
9
9
  {
10
10
  ignores: [
11
11
  "docs/IDEAS.md",
12
- "package-lock.json"
12
+ "package-lock.json",
13
+ "coverage/**"
13
14
  ]
14
15
  },
15
16
  {
package/index.js CHANGED
@@ -65,9 +65,9 @@ const answer = {
65
65
  // CONSTANTS
66
66
 
67
67
  const protocol = process.env.PROTOCOL || 'http';
68
- const queuePath = path.join(jobsPath, 'queue');
69
- const claimedPath = path.join(jobsPath, 'claimed');
70
- const failedPath = path.join(jobsPath, 'failed');
68
+ const queuePath = () => path.join(jobsPath(), 'queue');
69
+ const claimedPath = () => path.join(jobsPath(), 'claimed');
70
+ const failedPath = () => path.join(jobsPath(), 'failed');
71
71
  // Credentials of the Testaro workers, by worker ID, from a JSON-object environment variable.
72
72
  // Each worker ID maps to a secret (used only to authenticate the worker, never published) and a
73
73
  // name (a non-secret label safe to publish, e.g. in report data and logs).
@@ -238,7 +238,7 @@ const processJobRequest = async (request, response, workerName) => jobLock(async
238
238
  const claimedJobNames = jobNames.claimed;
239
239
  // For each claimed job:
240
240
  for (const jobName of claimedJobNames) {
241
- const job = await getObject(path.join(jobsPath, 'claimed', jobName));
241
+ const job = await getObject(path.join(jobsPath(), 'claimed', jobName));
242
242
  const {id, sources} = job;
243
243
  const {worker} = sources;
244
244
  // If its assignee is the worker:
@@ -248,7 +248,7 @@ const processJobRequest = async (request, response, workerName) => jobLock(async
248
248
  await serveError({message: `${messageStart}${messageEnd}`}, response, false);
249
249
  // Reclassify the job as failed.
250
250
  await fs.rename(
251
- path.join(claimedPath, jobName), path.join(failedPath, jobName)
251
+ path.join(claimedPath(), jobName), path.join(failedPath(), jobName)
252
252
  );
253
253
  clean = false;
254
254
  // Stop checking claimed jobs.
@@ -262,7 +262,7 @@ const processJobRequest = async (request, response, workerName) => jobLock(async
262
262
  if (queuedJobNames.length) {
263
263
  const oldestJobName = queuedJobNames[0];
264
264
  // Get the first one.
265
- const firstJob = await getObject(path.join(queuePath, oldestJobName));
265
+ const firstJob = await getObject(path.join(queuePath(), oldestJobName));
266
266
  // Add the public worker name to the job, in a property Testaro does not read or alter.
267
267
  firstJob.sources.worker = workerName;
268
268
  console.log(
@@ -278,10 +278,10 @@ const processJobRequest = async (request, response, workerName) => jobLock(async
278
278
  console.log(`${messageStart}${messageEnd}`);
279
279
  // Save the job in the claimed-jobs directory.
280
280
  await fs.writeFile(
281
- path.join(claimedPath, oldestJobName), getJSON(firstJob)
281
+ path.join(claimedPath(), oldestJobName), getJSON(firstJob)
282
282
  );
283
283
  // Delete it from the queue.
284
- await fs.unlink(path.join(queuePath, oldestJobName));
284
+ await fs.unlink(path.join(queuePath(), oldestJobName));
285
285
  }
286
286
  // Otherwise, i.e. if no jobs are queued:
287
287
  else {
@@ -661,7 +661,7 @@ const requestHandler = async (request, response) => {
661
661
  // Delete the rejected URL.
662
662
  delete recs[url];
663
663
  // Save the revised recommendations.
664
- await fs.writeFile(path.join(jobsPath, 'recs.json'), getJSON(recs));
664
+ await fs.writeFile(path.join(jobsPath(), 'recs.json'), getJSON(recs));
665
665
  });
666
666
  // Set a location header for a response.
667
667
  response.setHeader('content-location', '/enqueueForm.html');
@@ -739,7 +739,7 @@ const requestHandler = async (request, response) => {
739
739
  if (id && isTimeStamp(timeStamp) && isJobID(jobID) && what && url) {
740
740
  const [timeStamp, jobID] = id.split('-');
741
741
  // Get the job the report is from.
742
- const claimedJob = await getObject(path.join(claimedPath, `${id}.json`));
742
+ const claimedJob = await getObject(path.join(claimedPath(), `${id}.json`));
743
743
  // If the job was actually assigned to this worker:
744
744
  if (typeof claimedJob === 'object' && claimedJob.sources?.worker === workerName) {
745
745
  console.log(`Testaro report ${id} was received from worker ${workerName}`);
@@ -755,7 +755,7 @@ const requestHandler = async (request, response) => {
755
755
  // Check the monetary balances and send alerts if nearing exhaustion.
756
756
  await checkBalancesForAlerts(report);
757
757
  // Delete the job.
758
- await fs.unlink(path.join(claimedPath, `${id}.json`));
758
+ await fs.unlink(path.join(claimedPath(), `${id}.json`));
759
759
  console.log(`Completed job ${id} deleted`);
760
760
  // Acknowledge receipt.
761
761
  response.setHeader('content-type', 'application/json; charset=utf-8');
@@ -868,7 +868,7 @@ const requestHandler = async (request, response) => {
868
868
 
869
869
  const serve = async (protocolModule, options) => {
870
870
  // Create any missing directories.
871
- for (const path of [queuePath, claimedPath, failedPath, hiddenReportsPath, reportsPath]) {
871
+ for (const path of [queuePath(), claimedPath(), failedPath(), hiddenReportsPath(), reportsPath()]) {
872
872
  await fs.mkdir(path, {recursive: true});
873
873
  }
874
874
  const server = protocolModule === 'https'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "46.0.0",
3
+ "version": "46.0.2",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "start": "node index",
11
11
  "lint": "npx eslint && npx markdownlint-cli2",
12
- "test": "echo \"Error: no test specified\" && exit 1",
12
+ "test": "c8 node --test",
13
13
  "test:api": "node api/testSuite",
14
14
  "generate:openapi": "node scripts/generate-openapi"
15
15
  },
@@ -44,6 +44,7 @@
44
44
  "@eslint/json": "*",
45
45
  "@eslint/markdown": "*",
46
46
  "@redocly/cli": "*",
47
+ "c8": "*",
47
48
  "eslint": "*",
48
49
  "globals": "*",
49
50
  "js-yaml": "*",
@@ -52,5 +53,28 @@
52
53
  },
53
54
  "allowScripts": {
54
55
  "fsevents@2.3.2": true
56
+ },
57
+ "c8": {
58
+ "all": true,
59
+ "reporter": [
60
+ "text",
61
+ "html"
62
+ ],
63
+ "include": [
64
+ "index.js",
65
+ "mcp.js",
66
+ "alerts.js",
67
+ "util.js",
68
+ "api/**/*.js"
69
+ ],
70
+ "exclude": [
71
+ "coverage/**",
72
+ "test{,s}/**",
73
+ "test{,-*}.{js,cjs,mjs}",
74
+ "**/*{.,-}test.{js,cjs,mjs}",
75
+ "**/__tests__/**",
76
+ "web/**",
77
+ "scripts/**"
78
+ ]
55
79
  }
56
80
  }
package/util.js CHANGED
@@ -14,18 +14,19 @@ const wcagMap = require('./wcagMap.json');
14
14
 
15
15
  // CONSTANTS
16
16
 
17
- // Path of the data directory.
18
- const dbPath = exports.dbPath = path.join(__dirname, 'db');
17
+ // Path of the data directory. Read afresh on every call (rather than cached at module load) so
18
+ // that tests can point Kilotest at a fixture directory via the DB_DIR environment variable.
19
+ const dbPath = exports.dbPath = () => process.env.DB_DIR || path.join(__dirname, 'db');
19
20
  // Path of the jobs directory.
20
- const jobsPath = exports.jobsPath = path.join(dbPath, 'jobs');
21
+ const jobsPath = exports.jobsPath = () => path.join(dbPath(), 'jobs');
21
22
  // Path of the recommendations file.
22
- const recsPath = exports.recsPath = path.join(jobsPath, 'recs.json');
23
+ const recsPath = exports.recsPath = () => path.join(jobsPath(), 'recs.json');
23
24
  // Path of the reports directory.
24
- const reportsPath = exports.reportsPath = path.join(dbPath, 'reports');
25
+ const reportsPath = exports.reportsPath = () => path.join(dbPath(), 'reports');
25
26
  // Path of the hidden-reports directory.
26
- const hiddenReportsPath = exports.hiddenReportsPath = path.join(dbPath, 'hiddenReports');
27
+ const hiddenReportsPath = exports.hiddenReportsPath = () => path.join(dbPath(), 'hiddenReports');
27
28
  // Path of the reports data file.
28
- const reportsExtractPath = exports.reportsExtractPath = path.join(dbPath, 'reportsExtract.json');
29
+ const reportsExtractPath = exports.reportsExtractPath = () => path.join(dbPath(), 'reportsExtract.json');
29
30
  // IDs, names, and sponsors of Testaro rule engines.
30
31
  const ruleEngines = exports.ruleEngines = {
31
32
  alfa: ['Alfa', 'Siteimprove'],
@@ -145,7 +146,7 @@ const getJobNames = exports.getJobNames = async () => {
145
146
  let fileNames;
146
147
  for (const category of ['queue', 'claimed', 'failed']) {
147
148
  try {
148
- fileNames = await fs.readdir(path.join(jobsPath, category));
149
+ fileNames = await fs.readdir(path.join(jobsPath(), category));
149
150
  }
150
151
  catch(error) {
151
152
  return `ERROR: Job directory ${category} not readable (${error.message})`;
@@ -229,10 +230,10 @@ const getRecs = exports.getRecs = async () => {
229
230
  let recs;
230
231
  let recsJSON;
231
232
  try {
232
- recsJSON = await fs.readFile(recsPath, 'utf8');
233
+ recsJSON = await fs.readFile(recsPath(), 'utf8');
233
234
  }
234
235
  catch(error) {
235
- await fs.writeFile(recsPath, '{}\n');
236
+ await fs.writeFile(recsPath(), '{}\n');
236
237
  return `ERROR: recommendations file not readable, so created an empty one (${error.message})`;
237
238
  }
238
239
  try {
@@ -285,7 +286,7 @@ exports.isRecommendable = async url => {
285
286
  const jobNames = await getJobNames();
286
287
  // For each claimed job:
287
288
  for (const fileName of jobNames.claimed) {
288
- const job = await getObject(path.join(jobsPath, 'claimed', fileName));
289
+ const job = await getObject(path.join(jobsPath(), 'claimed', fileName));
289
290
  // If its URL is that of the recommended target:
290
291
  if (job.target.url === url) {
291
292
  // Return this.
@@ -294,7 +295,7 @@ exports.isRecommendable = async url => {
294
295
  }
295
296
  // If no claimed job has the URL of the target, for each queued job:
296
297
  for (const fileName of jobNames.queue) {
297
- const job = await getObject(path.join(jobsPath, 'queue', fileName));
298
+ const job = await getObject(path.join(jobsPath(), 'queue', fileName));
298
299
  // If its URL is that of the recommended target:
299
300
  if (job.target.url === url) {
300
301
  // Return this.
@@ -415,7 +416,7 @@ const updateRecs = exports.updateRecs = (what, url, why) => recsLock(async () =>
415
416
  why
416
417
  });
417
418
  // Save the revised recommendations.
418
- await fs.writeFile(recsPath, getJSON(recs));
419
+ await fs.writeFile(recsPath(), getJSON(recs));
419
420
  // Return success.
420
421
  return {};
421
422
  });
@@ -435,7 +436,7 @@ exports.getPathID = (catalog, catalogIndex, pathID) => {
435
436
  };
436
437
  // Returns the path of an available report file.
437
438
  const getReportPath = exports.getReportPath = (timeStamp, jobID) => path
438
- .join(reportsPath, `${timeStamp}-${jobID}.json`);
439
+ .join(reportsPath(), `${timeStamp}-${jobID}.json`);
439
440
  // Returns whether a report is valid.
440
441
  const isValidReport = exports.isValidReport = report => {
441
442
  // Return whether it has the type and properties required by Kilotest:
@@ -644,7 +645,7 @@ exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
644
645
  // Returns the creation time and size of a report.
645
646
  const getReportStats = exports.getReportStats = async (timeStamp, jobID) => {
646
647
  const reportStat = await fs.stat(
647
- path.join(reportsPath, `${timeStamp}-${jobID}.json`),
648
+ path.join(reportsPath(), `${timeStamp}-${jobID}.json`),
648
649
  {throwIfNoEntry: false}
649
650
  );
650
651
  if (!reportStat) {
@@ -656,9 +657,9 @@ const getReportStats = exports.getReportStats = async (timeStamp, jobID) => {
656
657
  };
657
658
  // Returns whether a report is hidden.
658
659
  exports.isHidden = async (timeStamp, jobID) => {
659
- await fs.mkdir(hiddenReportsPath, {recursive: true});
660
+ await fs.mkdir(hiddenReportsPath(), {recursive: true});
660
661
  // Get the names of the hidden report files.
661
- const hiddenReportFileNames = await fs.readdir(hiddenReportsPath);
662
+ const hiddenReportFileNames = await fs.readdir(hiddenReportsPath());
662
663
  // Return whether the report is among them.
663
664
  return hiddenReportFileNames.includes(`${timeStamp}-${jobID}.json`);
664
665
  };
@@ -667,11 +668,11 @@ const makeReportsExtract = exports.makeReportsExtract = async () => {
667
668
  // Initialize the reports data.
668
669
  const reportsExtract = {};
669
670
  // Get the names of all report files.
670
- const reportFileNames = await fs.readdir(reportsPath);
671
+ const reportFileNames = await fs.readdir(reportsPath());
671
672
  // For each of them:
672
673
  for (const reportFileName of reportFileNames) {
673
674
  // Get its content.
674
- const reportJSON = await fs.readFile(path.join(reportsPath, reportFileName), 'utf8');
675
+ const reportJSON = await fs.readFile(path.join(reportsPath(), reportFileName), 'utf8');
675
676
  // Get the object that it represents.
676
677
  const report = JSON.parse(reportJSON);
677
678
  const {target, jobData} = report;
@@ -690,9 +691,9 @@ const makeReportsExtract = exports.makeReportsExtract = async () => {
690
691
  reportsExtract[jobName] = reportExtract;
691
692
  }
692
693
  // Ensure the data directory exists.
693
- await fs.mkdir(dbPath, {recursive: true});
694
+ await fs.mkdir(dbPath(), {recursive: true});
694
695
  // Save the extract of the reports.
695
- await fs.writeFile(reportsExtractPath, getJSON(reportsExtract));
696
+ await fs.writeFile(reportsExtractPath(), getJSON(reportsExtract));
696
697
  // Return it.
697
698
  return reportsExtract;
698
699
  };
@@ -701,7 +702,7 @@ const getReportsExtract = exports.getReportsExtract = async () => {
701
702
  let reportsExtract;
702
703
  try {
703
704
  // Get the reports extract file.
704
- const reportsExtractJSON = await fs.readFile(reportsExtractPath, 'utf8');
705
+ const reportsExtractJSON = await fs.readFile(reportsExtractPath(), 'utf8');
705
706
  // Get its data.
706
707
  reportsExtract = JSON.parse(reportsExtractJSON);
707
708
  // If it is empty:
package/util.test.js ADDED
@@ -0,0 +1,87 @@
1
+ /*
2
+ util.test.js
3
+ Tests for util.js data-path injection (Phase 1).
4
+ */
5
+
6
+ // IMPORTS
7
+
8
+ const {test} = require('node:test');
9
+ const assert = require('node:assert/strict');
10
+ const path = require('node:path');
11
+ const {
12
+ dbPath,
13
+ jobsPath,
14
+ recsPath,
15
+ reportsPath,
16
+ hiddenReportsPath,
17
+ reportsExtractPath
18
+ } = require('./util');
19
+
20
+ // TESTS
21
+
22
+ test('dbPath defaults to the project db directory when DB_DIR is unset', () => {
23
+ const saved = process.env.DB_DIR;
24
+ delete process.env.DB_DIR;
25
+ try {
26
+ assert.equal(dbPath(), path.join(__dirname, 'db'));
27
+ }
28
+ finally {
29
+ if (saved !== undefined) {
30
+ process.env.DB_DIR = saved;
31
+ }
32
+ }
33
+ });
34
+
35
+ test('dbPath honors DB_DIR when it is set', () => {
36
+ const saved = process.env.DB_DIR;
37
+ process.env.DB_DIR = '/tmp/kilotest-fixtures';
38
+ try {
39
+ assert.equal(dbPath(), '/tmp/kilotest-fixtures');
40
+ }
41
+ finally {
42
+ if (saved !== undefined) {
43
+ process.env.DB_DIR = saved;
44
+ }
45
+ else {
46
+ delete process.env.DB_DIR;
47
+ }
48
+ }
49
+ });
50
+
51
+ test('jobsPath, recsPath, reportsPath, hiddenReportsPath, and reportsExtractPath derive from DB_DIR', () => {
52
+ const saved = process.env.DB_DIR;
53
+ process.env.DB_DIR = '/tmp/kilotest-fixtures';
54
+ try {
55
+ assert.equal(jobsPath(), path.join('/tmp/kilotest-fixtures', 'jobs'));
56
+ assert.equal(recsPath(), path.join('/tmp/kilotest-fixtures', 'jobs', 'recs.json'));
57
+ assert.equal(reportsPath(), path.join('/tmp/kilotest-fixtures', 'reports'));
58
+ assert.equal(hiddenReportsPath(), path.join('/tmp/kilotest-fixtures', 'hiddenReports'));
59
+ assert.equal(reportsExtractPath(), path.join('/tmp/kilotest-fixtures', 'reportsExtract.json'));
60
+ }
61
+ finally {
62
+ if (saved !== undefined) {
63
+ process.env.DB_DIR = saved;
64
+ }
65
+ else {
66
+ delete process.env.DB_DIR;
67
+ }
68
+ }
69
+ });
70
+
71
+ test('changing DB_DIR between calls is reflected by the path functions', () => {
72
+ const saved = process.env.DB_DIR;
73
+ try {
74
+ process.env.DB_DIR = '/tmp/a';
75
+ assert.equal(reportsPath(), path.join('/tmp/a', 'reports'));
76
+ process.env.DB_DIR = '/tmp/b';
77
+ assert.equal(reportsPath(), path.join('/tmp/b', 'reports'));
78
+ }
79
+ finally {
80
+ if (saved !== undefined) {
81
+ process.env.DB_DIR = saved;
82
+ }
83
+ else {
84
+ delete process.env.DB_DIR;
85
+ }
86
+ }
87
+ });
@@ -35,7 +35,7 @@ exports.answer = async (url, what, authCode) => {
35
35
  };
36
36
  // Save the job in the queue.
37
37
  await fs.writeFile(
38
- path.join(jobsPath, 'queue', `${jobName}.json`), getJSON(job)
38
+ path.join(jobsPath(), 'queue', `${jobName}.json`), getJSON(job)
39
39
  );
40
40
  console.log(`Retest queued for ${what} as job ${jobName}`);
41
41
  // Isolate this revision.
@@ -45,7 +45,7 @@ exports.answer = async (url, what, authCode) => {
45
45
  // Delete the recommendations to test the target.
46
46
  delete recs[url];
47
47
  // Save the revised recommendations.
48
- await fs.writeFile(path.join(jobsPath, 'recs.json'), getJSON(recs));
48
+ await fs.writeFile(path.join(jobsPath(), 'recs.json'), getJSON(recs));
49
49
  });
50
50
  // Get the answer template.
51
51
  let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
@@ -24,7 +24,7 @@ exports.answer = async (_, search) => {
24
24
  // For each report to be deleted:
25
25
  for (const jobName of jobNames) {
26
26
  // Delete it.
27
- await fs.unlink(path.join(reportsPath, `${jobName}.json`));
27
+ await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
28
28
  }
29
29
  // Update the extract of the available reports.
30
30
  await makeReportsExtract();
@@ -47,7 +47,7 @@ exports.answer = async (_, search) => {
47
47
  }
48
48
  }
49
49
  }
50
- const reportNames = await fs.readdir(reportsPath);
50
+ const reportNames = await fs.readdir(reportsPath());
51
51
  const reportSpecs = [];
52
52
  // For each report:
53
53
  for (const reportName of reportNames) {
@@ -23,7 +23,7 @@ exports.answer = async (_, search) => {
23
23
  const fileName = `${jobName}.json`;
24
24
  try {
25
25
  // Move the specified report to the directory of hidden reports.
26
- await fs.rename(path.join(reportsPath, fileName), path.join(hiddenReportsPath, fileName));
26
+ await fs.rename(path.join(reportsPath(), fileName), path.join(hiddenReportsPath(), fileName));
27
27
  // Update the data on the available reports.
28
28
  await makeReportsExtract();
29
29
  }
@@ -60,7 +60,7 @@ const populateQuery = async query => {
60
60
  // For each job in the category:
61
61
  for (const fileName of jobFileNames[category]) {
62
62
  // Get the job.
63
- const job = await getObject(path.join(jobsPath, category, fileName));
63
+ const job = await getObject(path.join(jobsPath(), category, fileName));
64
64
  // Add a line.
65
65
  lines[category].push(`${margin}<li><code>${job.target.url}</code> (${job.target.what})</li>`);
66
66
  }
@@ -24,7 +24,7 @@ exports.answer = async (_, search) => {
24
24
  // For each report to be deleted:
25
25
  for (const jobName of jobNames) {
26
26
  // Delete it.
27
- await fs.unlink(path.join(reportsPath, `${jobName}.json`));
27
+ await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
28
28
  }
29
29
  // Update the extract of the available reports.
30
30
  await makeReportsExtract();
@@ -47,7 +47,7 @@ exports.answer = async (_, search) => {
47
47
  }
48
48
  }
49
49
  }
50
- const reportNames = await fs.readdir(reportsPath);
50
+ const reportNames = await fs.readdir(reportsPath());
51
51
  // Initialize an array of report summaries.
52
52
  const reportSpecs = [];
53
53
  // For each report:
@@ -24,7 +24,7 @@ exports.answer = async (_, search) => {
24
24
  // For each report to be deleted:
25
25
  for (const jobName of jobNames) {
26
26
  // Delete it.
27
- await fs.unlink(path.join(reportsPath, `${jobName}.json`));
27
+ await fs.unlink(path.join(reportsPath(), `${jobName}.json`));
28
28
  }
29
29
  }
30
30
  // If this failed:
@@ -45,7 +45,7 @@ exports.answer = async (_, search) => {
45
45
  }
46
46
  }
47
47
  }
48
- const reportNames = await fs.readdir(reportsPath);
48
+ const reportNames = await fs.readdir(reportsPath());
49
49
  const reportSpecs = [];
50
50
  // For each report:
51
51
  for (const reportName of reportNames) {
@@ -23,7 +23,7 @@ exports.answer = async (_, search) => {
23
23
  const fileName = `${jobName}.json`;
24
24
  try {
25
25
  // Move the report to the reports directory.
26
- await fs.rename(path.join(hiddenReportsPath, fileName), path.join(reportsPath, fileName));
26
+ await fs.rename(path.join(hiddenReportsPath(), fileName), path.join(reportsPath(), fileName));
27
27
  // Update the data on the available reports.
28
28
  await makeReportsExtract();
29
29
  }
@@ -47,11 +47,11 @@ exports.answer = async (_, search) => {
47
47
  // Initialize an array of data on reports to be unhidden.
48
48
  const reportsData = [];
49
49
  // Get the names of the hidden report files.
50
- const hiddenReportFileNames = await fs.readdir(hiddenReportsPath);
50
+ const hiddenReportFileNames = await fs.readdir(hiddenReportsPath());
51
51
  // For each hidden report:
52
52
  for (const reportFileName of hiddenReportFileNames) {
53
53
  // Get its file.
54
- const reportJSON = await fs.readFile(path.join(hiddenReportsPath, reportFileName), 'utf8');
54
+ const reportJSON = await fs.readFile(path.join(hiddenReportsPath(), reportFileName), 'utf8');
55
55
  // Get the report.
56
56
  const report = JSON.parse(reportJSON);
57
57
  const {id, target} = report;