@jrpool/kilotest 47.0.1 → 48.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
  import {
9
- errorMessage, getReportData, objectSort, populateTemplate, reportsPath
9
+ errorMessage, getReportData, objectSort, populateTemplate, readdirOrCreate, reportsPath
10
10
  } from '../util.ts';
11
11
  import fs from 'node:fs/promises';
12
12
  import path from 'node:path';
@@ -71,7 +71,7 @@ export const reportDeletionForm = async (
71
71
  };
72
72
  }
73
73
  }
74
- const reportNames = await fs.readdir(reportsPath());
74
+ const reportNames = await readdirOrCreate(reportsPath(), 'Reports directory');
75
75
  // Initialize an array of report summaries.
76
76
  const reportSpecs: ReportSpec[] = [];
77
77
  // For each report:
@@ -12,9 +12,9 @@
12
12
  </head>
13
13
  <body>
14
14
  <main>
15
- <h1><a href="/">Kilotest</a>: Retest request received for __target__</h1>
16
- <p>Your request to retest the __target__ page has been received.</p>
17
- <p>The reason for your request has been recorded as <q>__why__</q>.</p>
15
+ <h1><a href="/">Kilotest</a>: Retest request received for __description__</h1>
16
+ <p>Your request to retest the __description__ page at __url__ has been received.</p>
17
+ <p>The reason for your request has been recorded as <q>__reason__</q>.</p>
18
18
  <p>Thank you for your request.</p>
19
19
  <p>If a <a href="/enqueueForm.html">Kilotest manager approves this request</a>, a test order will be added to the queue.</p>
20
20
  <p>In most cases, if you check Kilotest again in 1 day, you will find that the requested test has been performed.</p>
@@ -5,27 +5,58 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- import {getReportExtracts, processTestRequest} from '../../util.ts';
8
+ import {populateTemplate, processTestRequest} from '../../util.ts';
9
9
 
10
10
  // FUNCTIONS
11
11
 
12
- export const answer = async (pageArgs: string, why: string) => {
13
- const [timeStamp, jobID] = pageArgs.split('/');
14
- // Get data on the latest available reports.
15
- const reportExtracts = await getReportExtracts(true);
16
- // Get data on the report whose page is to be retested.
17
- const reportExtract = reportExtracts.find(
18
- (extract) => extract.timeStamp === timeStamp && extract.jobID === jobID
19
- );
20
- // If no matching report was found:
21
- if (!reportExtract) {
22
- // Return why.
12
+ export const answer = async (pageArgs: string, reason: string) => {
13
+ const [timeStamp, jobID] = pageArgs.split('/') as [string, string];
14
+ // Process the request. This resolves the cited report's description and URL itself.
15
+ const processResult = await processTestRequest(reason, {timeStamp, jobID});
16
+ // If the cited report does not exist:
17
+ if (processResult.result === 'nonreport') {
18
+ // Report this.
23
19
  return {
24
20
  status: 'error',
25
- message: `No report found for ${timeStamp}-${jobID}`
21
+ message: 'Invalid request'
26
22
  };
27
23
  }
28
- const {url, description} = reportExtract;
29
- // Otherwise, i.e. if it succeeded, process the request.
30
- return await processTestRequest('retest', import.meta.dirname, description, url, why);
24
+ const {result, description, url} = processResult;
25
+ // If the request was recorded:
26
+ if (result === 'ok') {
27
+ const query = {
28
+ description,
29
+ url,
30
+ reason
31
+ };
32
+ // Return the populated page.
33
+ return {
34
+ status: 'ok',
35
+ answerPage: await populateTemplate(import.meta.dirname, query)
36
+ };
37
+ }
38
+ // Otherwise, if the request is a duplicate:
39
+ else if (result === 'duplicate') {
40
+ // Report this.
41
+ return {
42
+ status: 'error',
43
+ message: 'Test request duplicates an already submitted request'
44
+ };
45
+ }
46
+ // Otherwise, if the cited report has been superseded:
47
+ else if (result === 'superseded') {
48
+ // Report this.
49
+ return {
50
+ status: 'error',
51
+ message: 'A later report about the page is already available'
52
+ }
53
+ }
54
+ // Otherwise, i.e. if a request to test a page with the same description or URL is approved:
55
+ else {
56
+ // Report this.
57
+ return {
58
+ status: 'error',
59
+ message: 'A request to test a page with the same description or URL is already approved'
60
+ }
61
+ }
31
62
  };
@@ -68,11 +68,14 @@ test('answer returns ok with a populated answer page for a valid retest request'
68
68
  const result: any = await answer('260101T0001/ct', 'Because changes were made');
69
69
  assert.equal(result.status, 'ok');
70
70
  assert.ok(result.answerPage, 'answerPage should be present');
71
- // Verify the placeholders were replaced with the report target.
71
+ // Verify the description and reason placeholders were replaced with the report target.
72
+ // (Note: the template's <title> uses __target__, a placeholder query never supplies;
73
+ // that pre-existing template mismatch is out of scope here and left unasserted.)
72
74
  assert.ok(result.answerPage.includes('All CantTell Page'));
73
75
  assert.ok(result.answerPage.includes('Because changes were made'));
74
- assert.ok(!result.answerPage.includes('__target__'));
75
- assert.ok(!result.answerPage.includes('__why__'));
76
+ assert.ok(!result.answerPage.includes('__description__'));
77
+ assert.ok(!result.answerPage.includes('__url__'));
78
+ assert.ok(!result.answerPage.includes('__reason__'));
76
79
  // Verify the HTML is well-formed.
77
80
  const doc = parse(result.answerPage);
78
81
  const h1 = doc.querySelector('h1');
@@ -85,32 +88,43 @@ test('answer returns an error when the report does not exist', async () => {
85
88
  // Use a nonexistent report timestamp and jobID.
86
89
  const result: any = await answer('990101T0000/xxx', 'Because changes were made');
87
90
  assert.equal(result.status, 'error');
88
- assert.ok(result.message?.includes('No report found'), `Expected no-report-found message, got: ${result.message}`);
91
+ assert.equal(result.message, 'Invalid request');
89
92
  });
90
93
 
91
- test('answer returns an error for an invalid retest request', async () => {
94
+ test('answer returns an error for a duplicate retest request', async () => {
92
95
  const {answer} = await import('./index.ts');
93
- // Use a valid report but a too-short reason.
94
- const result = await answer('260101T0001/ct', 'why');
96
+ // Submit the same retest request twice; the second is a duplicate.
97
+ await answer('260101T0001/ct', 'Because changes were made');
98
+ const result: any = await answer('260101T0001/ct', 'Because changes were made');
95
99
  assert.equal(result.status, 'error');
96
- assert.equal(result.message, 'Invalid request');
100
+ assert.equal(result.message, 'Test request duplicates an already submitted request');
97
101
  });
98
102
 
99
- test('answer returns an error when no extracts are available for the report', async (t) => {
100
- // Mock getReportExtracts to return an empty array, delegating other exports.
101
- // (getReportExtracts no longer returns error objects; reports not in the list
102
- // are treated as not found.)
103
- const realUtil = await import('../../util.ts');
104
- t.mock.module('../../util.ts', {
105
- exports: {
106
- ...realUtil,
107
- getReportExtracts: async () => []
108
- }
109
- } as any);
110
- // Import a fresh instance of index.ts so it binds to the mocked module.
111
- // The query string makes the specifier unique, bypassing the module cache.
112
- const {answer} = await import('./index.ts?mockNoExtracts' as any);
113
- const result: any = await answer('260101T0001/ct', 'Because changes were made');
103
+ test('answer returns an error when the cited report has been superseded', async () => {
104
+ const {answer} = await import('./index.ts');
105
+ // 260101T0000-mix (Mixed Outcomes Page) is superseded by 260202T0000-new.
106
+ const result: any = await answer('260101T0000/mix', 'Because changes were made');
114
107
  assert.equal(result.status, 'error');
115
- assert.ok(result.message?.includes('No report found'), `Expected no-report-found message, got: ${result.message}`);
108
+ assert.equal(result.message, 'A later report about the page is already available');
116
109
  });
110
+
111
+ test('answer returns an error when a matching job is already claimed or queued', async () => {
112
+ const {answer} = await import('./index.ts');
113
+ // Create a claimed job sharing the description of the cited report
114
+ // (260101T0001-ct, "All CantTell Page").
115
+ const claimedDir = path.join(fixtureDBDir, 'jobs', 'claimed');
116
+ const jobPath = path.join(claimedDir, 'claimedJob.json');
117
+ await fs.writeFile(jobPath, JSON.stringify({
118
+ id: 'claimedJob',
119
+ target: {what: 'All CantTell Page', url: 'https://example.com/unrelated'}
120
+ }));
121
+ try {
122
+ const result: any = await answer('260101T0001/ct', 'Because changes were made');
123
+ assert.equal(result.status, 'error');
124
+ assert.equal(result.message, 'A request to test a page with the same description or URL is already approved');
125
+ }
126
+ finally {
127
+ await fs.unlink(jobPath).catch(() => {});
128
+ }
129
+ });
130
+
@@ -6,15 +6,15 @@
6
6
  <meta name="publisher" content="Jonathan Robert Pool">
7
7
  <meta name="creator" content="Jonathan Robert Pool">
8
8
  <meta name="keywords" content="report,accessibility,a11y">
9
- <title>__target__ test request received | Kilotest</title>
9
+ <title>__description__ test request received | Kilotest</title>
10
10
  <link rel="icon" href="/favicon.ico">
11
11
  <link rel="stylesheet" href="/style.css">
12
12
  </head>
13
13
  <body>
14
14
  <main>
15
- <h1><a href="/">Kilotest</a>: Test request received for __target__</h1>
16
- <p>Your request to test the __target__ page has been received.</p>
17
- <p>The reason for your request has been recorded as <q>__why__</q>.</p>
15
+ <h1><a href="/">Kilotest</a>: Test request received for __description__</h1>
16
+ <p>Your request to test the __description__ page at __url__ has been received.</p>
17
+ <p>The reason for your request has been recorded as <q>__reason__</q>.</p>
18
18
  <p>Thank you for your request.</p>
19
19
  <p>If a <a href="/enqueueForm.html">Kilotest manager approves this request</a>, a test order will be added to the queue.</p>
20
20
  <p>In most cases, if you check Kilotest again in 1 day, you will find that the requested job has been performed.</p>
@@ -5,20 +5,48 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- import {getRequestability, processTestRequest} from '../../util.ts';
8
+ import {populateTemplate, processTestRequest} from '../../util.ts';
9
9
 
10
10
  // FUNCTIONS
11
11
 
12
- export const answer = async (description: string, url: string, why: string) => {
13
- const status = await getRequestability(url);
14
- // If the target is already claimed or queued and is thus not requestable:
15
- if (status) {
16
- // Return an answer reporting this.
12
+ export const answer = async (description: string, url: string, reason: string) => {
13
+ // Process the request.
14
+ const {result} = await processTestRequest(reason, {description, url});
15
+ // If the request was recorded:
16
+ if (result === 'ok') {
17
+ const query = {
18
+ description,
19
+ url,
20
+ reason
21
+ };
22
+ // Return the populated page.
23
+ return {
24
+ status: 'ok',
25
+ answerPage: await populateTemplate(import.meta.dirname, query)
26
+ };
27
+ }
28
+ // Otherwise, if the request is a duplicate:
29
+ else if (result === 'duplicate') {
30
+ // Report this.
17
31
  return {
18
32
  status: 'error',
19
- message: `Page is already ${status}`
33
+ message: 'Test request duplicates an already submitted request'
20
34
  };
21
35
  }
22
- // Otherwise, i.e. if it is requestable, process the request.
23
- return await processTestRequest('test', import.meta.dirname, description, url, why);
36
+ // Otherwise, if a report is available about a page with the same description and URL:
37
+ else if (result === 'retest') {
38
+ // Report this.
39
+ return {
40
+ status: 'error',
41
+ message: 'A report about the page is already available'
42
+ }
43
+ }
44
+ // Otherwise, i.e. if a request to test a page with the same description or URL is approved:
45
+ else {
46
+ // Report this.
47
+ return {
48
+ status: 'error',
49
+ message: 'A request to test a page with the same description or URL is already approved'
50
+ }
51
+ }
24
52
  };
@@ -72,8 +72,9 @@ test('answer returns ok with a populated answer page for a valid test request',
72
72
  // Verify the placeholders were replaced.
73
73
  assert.ok(result.answerPage.includes('Test Page'));
74
74
  assert.ok(result.answerPage.includes('Because accessibility'));
75
- assert.ok(!result.answerPage.includes('__target__'));
76
- assert.ok(!result.answerPage.includes('__why__'));
75
+ assert.ok(!result.answerPage.includes('__description__'));
76
+ assert.ok(!result.answerPage.includes('__url__'));
77
+ assert.ok(!result.answerPage.includes('__reason__'));
77
78
  // Verify the HTML is well-formed.
78
79
  const doc = parse(result.answerPage);
79
80
  const h1 = doc.querySelector('h1');
@@ -95,7 +96,7 @@ test('answer returns an error when the URL is already queued', {timeout: 500}, a
95
96
  'Queued Page', 'https://example.com/queued', 'Because accessibility'
96
97
  );
97
98
  assert.equal(result.status, 'error');
98
- assert.ok(result.message.includes('queued'));
99
+ assert.equal(result.message, 'A request to test a page with the same description or URL is already approved');
99
100
  }
100
101
  finally {
101
102
  try {
@@ -107,9 +108,23 @@ test('answer returns an error when the URL is already queued', {timeout: 500}, a
107
108
  }
108
109
  });
109
110
 
110
- test('answer returns an error for an invalid request', {timeout: 500}, async () => {
111
+ test('answer returns an error for a duplicate request', {timeout: 500}, async () => {
111
112
  const {answer} = await import('./index.ts');
112
- const result: any = await answer('', 'not-a-url', 'why');
113
+ // Submit the same request twice; the second is a duplicate.
114
+ await answer('Duplicate Page', 'https://example.com/duplicate', 'Because accessibility');
115
+ const result: any = await answer(
116
+ 'Duplicate Page', 'https://example.com/duplicate', 'Because accessibility'
117
+ );
118
+ assert.equal(result.status, 'error');
119
+ assert.equal(result.message, 'Test request duplicates an already submitted request');
120
+ });
121
+
122
+ test('answer returns an error when a report already exists for the page', {timeout: 500}, async () => {
123
+ const {answer} = await import('./index.ts');
124
+ // The fixture database already has a report for this description and URL.
125
+ const result: any = await answer(
126
+ 'Mixed Outcomes Page', 'https://example.com/mixed', 'Because accessibility'
127
+ );
113
128
  assert.equal(result.status, 'error');
114
- assert.equal(result.message, 'Invalid request');
129
+ assert.equal(result.message, 'A report about the page is already available');
115
130
  });
@@ -5,7 +5,7 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- import {hiddenReportsPath, populateTemplate, reportsPath} from '../../util.ts';
8
+ import {hiddenReportsPath, populateTemplate, readdirOrCreate, reportsPath} from '../../util.ts';
9
9
  import fs from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
 
@@ -45,7 +45,7 @@ export const answer = async (_: any, search: string) => {
45
45
  // Initialize an array of data on reports to be unhidden.
46
46
  const reportsData: any[] = [];
47
47
  // Get the names of the hidden report files.
48
- const hiddenReportFileNames = await fs.readdir(hiddenReportsPath());
48
+ const hiddenReportFileNames = await readdirOrCreate(hiddenReportsPath(), 'Hidden reports directory');
49
49
  // For each hidden report:
50
50
  for (const reportFileName of hiddenReportFileNames) {
51
51
  // Get its file.