@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.
package/index.test.ts CHANGED
@@ -1036,16 +1036,20 @@ test('GET /style.css with a read error returns an error page', async () => {
1036
1036
  }
1037
1037
  });
1038
1038
 
1039
- test('GET /requestTest.html returns an error page when called without arguments', async () => {
1039
+ // requestTest.html and requestRetest.html are POST-only routes (see the
1040
+ // `routes.POST` list above). They also match the generic '*.html*' GET pattern,
1041
+ // but the generic `pageName.endsWith('.html')` branch of the GET dispatcher
1042
+ // excludes any path that is POST-only, since such a path's answer handler
1043
+ // expects POST's argument list and performs no GET-appropriate rendering.
1044
+
1045
+ test('GET /requestTest.html without arguments is rejected as an invalid GET request', async () => {
1040
1046
  const res = await request('GET', '/requestTest.html');
1041
1047
  assert.equal(res.statusCode, 400);
1042
- assert.ok(res.headers['content-type'].includes('text/html'));
1043
1048
  });
1044
1049
 
1045
- test('GET /requestRetest.html/260202T0000/new returns an error page when called as GET', async () => {
1050
+ test('GET /requestRetest.html/260202T0000/new is rejected as an invalid GET request', async () => {
1046
1051
  const res = await request('GET', '/requestRetest.html/260202T0000/new');
1047
1052
  assert.equal(res.statusCode, 400);
1048
- assert.ok(res.headers['content-type'].includes('text/html'));
1049
1053
  });
1050
1054
 
1051
1055
  // TESTS: GET HTML page routing
@@ -1186,34 +1190,38 @@ test('POST /mcp with a valid initialize request returns server info via SSE', as
1186
1190
 
1187
1191
  test('POST /requestTest.html with valid format but duplicate URL returns an answer error', {timeout: 500}, async () => {
1188
1192
  await fs.writeFile(testRequestsPath, '{}\n');
1189
- // First request to create the request.
1193
+ // First request to create the request. processTestRequest treats a request as a
1194
+ // duplicate only when description, URL, AND reason all match an existing pending
1195
+ // request, so the reason must be identical between the two requests here.
1190
1196
  await formRequest('POST', '/requestTest.html', {
1191
1197
  description: `Dup Test Page ${uniqueStamp}`,
1192
1198
  url: `https://example.com/dup-${uniqueStamp}`,
1193
1199
  why: 'Because accessibility matters'
1194
1200
  });
1195
- // Second request with the same URL should get a duplicate error.
1201
+ // Second, identical request should get a duplicate error.
1196
1202
  const res = await formRequest('POST', '/requestTest.html', {
1197
1203
  description: `Dup Test Page ${uniqueStamp}`,
1198
1204
  url: `https://example.com/dup-${uniqueStamp}`,
1199
- why: 'Because accessibility matters again'
1205
+ why: 'Because accessibility matters'
1200
1206
  });
1201
1207
  assert.equal(res.statusCode, 400);
1202
- assert.ok(res.body.includes('Duplicate request'));
1208
+ assert.ok(res.body.includes('Test request duplicates an already submitted request'));
1203
1209
  });
1204
1210
 
1205
1211
  test('POST /requestRetest.html with valid format but duplicate retest returns an answer error', {timeout: 500}, async () => {
1206
1212
  await fs.writeFile(testRequestsPath, '{}\n');
1207
- // First retest to create the recommendation.
1213
+ // First retest to create the recommendation. As above, the reason must match
1214
+ // exactly between the two requests for processTestRequest to treat the second as
1215
+ // a duplicate rather than a second, independent (and thus also 'ok') request.
1208
1216
  await formRequest('POST', '/requestRetest.html/260202T0000/new', {
1209
1217
  why: 'Because the report is obsolete and needs refreshing'
1210
1218
  });
1211
- // Second retest with the same report should get a duplicate error.
1219
+ // Second, identical retest of the same report should get a duplicate error.
1212
1220
  const res = await formRequest('POST', '/requestRetest.html/260202T0000/new', {
1213
- why: 'Because the report is obsolete and needs refreshing again'
1221
+ why: 'Because the report is obsolete and needs refreshing'
1214
1222
  });
1215
1223
  assert.equal(res.statusCode, 400);
1216
- assert.ok(res.body.includes('Duplicate request'));
1224
+ assert.ok(res.body.includes('Test request duplicates an already submitted request'));
1217
1225
  });
1218
1226
 
1219
1227
  test('POST /requestAction.html with valid auth code and approval of a duplicate returns an error', {timeout: 500}, async () => {
package/index.ts CHANGED
@@ -533,8 +533,10 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
533
533
  // Otherwise, if it is for an HTML page other than the home page:
534
534
  else if (pageName.endsWith('.html')) {
535
535
  const topic = pageName.slice(0, -5);
536
- // If the page can be generated:
537
- if (answer[topic]) {
536
+ // If the page can be generated and is not POST-only (a POST-only path also
537
+ // matches the '*.html*' GET pattern, but its handler expects POST's argument
538
+ // list and performs no GET-appropriate rendering):
539
+ if (answer[topic] && !isPathAllowed('POST', pathname)) {
538
540
  setHeaders('text/html', pathname, 'ultra');
539
541
  // Get the answer data.
540
542
  const answerData = await answer[topic](pathTail, search);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "47.0.1",
3
+ "version": "48.0.0",
4
4
  "description": "Ensemble testing for front-end web quality",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/util.test.ts CHANGED
@@ -52,8 +52,7 @@ import {
52
52
  processTestRequest,
53
53
  testRequestsLock,
54
54
  testRequestsPath,
55
- reportsPath,
56
- addTestRequest
55
+ reportsPath
57
56
  } from './util.ts';
58
57
 
59
58
  // TESTS
@@ -366,9 +365,8 @@ test('objectSort sorts objects numerically descending', () => {
366
365
 
367
366
  // TESTS FOR processTestRequest AND isReportAvailable
368
367
 
369
- // These tests use the fixture database directory and the real web/requestTest template.
368
+ // These tests use the fixture database directory.
370
369
  import {fixtureDBDir as fixtureDbDir} from './test/dbFixture.ts';
371
- const requestTestDir = path.join(import.meta.dirname, 'web', 'requestTest');
372
370
  const savedDbDir = process.env.DB_DIR;
373
371
 
374
372
  before(() => {
@@ -428,44 +426,15 @@ test('getPageDataStrings uses provided pageData instead of reading the report',
428
426
  assert.ok(strings.testInfo.includes('1 day ago'));
429
427
  });
430
428
 
431
- test('processTestRequest returns an error for an invalid test type', async () => {
432
- const result: any = await processTestRequest('invalid', requestTestDir, 'Page', 'https://example.com', 'because');
433
- assert.equal(result.status, 'error');
434
- assert.equal(result.message, 'Invalid request');
435
- });
436
-
437
- test('processTestRequest returns an error for an invalid URL', async () => {
438
- const result: any = await processTestRequest('test', requestTestDir, 'Page', 'not-a-url', 'because');
439
- assert.equal(result.status, 'error');
440
- assert.equal(result.message, 'Invalid request');
441
- });
442
-
443
- test('processTestRequest returns an error for a short reason', async () => {
444
- const result: any = await processTestRequest('test', requestTestDir, 'Page', 'https://example.com', 'abc');
445
- assert.equal(result.status, 'error');
446
- assert.equal(result.message, 'Invalid request');
447
- });
448
-
449
- test('processTestRequest returns an error for a missing description', async () => {
450
- const result: any = await processTestRequest('test', requestTestDir, '', 'https://example.com', 'because');
451
- assert.equal(result.status, 'error');
452
- assert.equal(result.message, 'Invalid request');
453
- });
454
-
455
- test('processTestRequest returns an error for a mismatched directory name', async () => {
456
- const result: any = await processTestRequest('test', '/tmp/wrongDir', 'Page', 'https://example.com', 'because');
457
- assert.equal(result.status, 'error');
458
- assert.equal(result.message, 'Invalid request');
459
- });
460
-
461
-
462
- test('processTestRequest succeeds and populates the template for a valid request', {timeout: 500}, async () => {
429
+ test('processTestRequest succeeds for a valid new-test request', {timeout: 500}, async () => {
463
430
  // Reset testRequests.json to empty before the test.
464
431
  await fs.writeFile(testRequestsPath(), '{}\n');
465
- const result: any = await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'because accessibility');
466
- assert.equal(result.status, 'ok');
467
- assert.ok(result.answerPage.includes('Example Page'));
468
- assert.ok(result.answerPage.includes('because accessibility'));
432
+ const {result} = await processTestRequest(
433
+ 'because accessibility', {description: 'Example Page', url: 'https://example.com'}
434
+ );
435
+ assert.equal(result, 'ok');
436
+ const testRequests = JSON.parse(await fs.readFile(testRequestsPath(), 'utf8'));
437
+ assert.equal(testRequests['https://example.com'][0].description, 'Example Page');
469
438
  // Clean up testRequests.json.
470
439
  await fs.writeFile(testRequestsPath(), '{}\n');
471
440
  });
@@ -473,15 +442,23 @@ test('processTestRequest succeeds and populates the template for a valid request
473
442
  test('processTestRequest returns a duplicate error for a repeated request', {timeout: 500}, async () => {
474
443
  // Reset testRequests.json to empty, then make a successful request.
475
444
  await fs.writeFile(testRequestsPath(), '{}\n');
476
- await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'because accessibility');
477
- // Repeat the same request.
478
- const result: any = await processTestRequest('test', requestTestDir, 'Example Page', 'https://example.com', 'another reason');
479
- assert.equal(result.status, 'error');
480
- assert.equal(result.message, 'Duplicate request');
445
+ const target = {description: 'Example Page', url: 'https://example.com'};
446
+ await processTestRequest('because accessibility', target);
447
+ // Repeat the same request (same description, URL, and reason).
448
+ const {result} = await processTestRequest('because accessibility', target);
449
+ assert.equal(result, 'duplicate');
481
450
  // Clean up testRequests.json.
482
451
  await fs.writeFile(testRequestsPath(), '{}\n');
483
452
  });
484
453
 
454
+ test('processTestRequest returns a retest error when a report already exists for the page', async () => {
455
+ // The fixture database already has a report for this description and URL.
456
+ const {result} = await processTestRequest(
457
+ 'because accessibility', {description: 'Mixed Outcomes Page', url: 'https://example.com/mixed'}
458
+ );
459
+ assert.equal(result, 'retest');
460
+ });
461
+
485
462
  test('annotateReportObject annotates a report object in place without reading or writing a file', async () => {
486
463
  // A report object that was never written to disk (e.g. one just received from a worker,
487
464
  // as in index.ts's worker/report handler, which annotates before the first write).
@@ -558,6 +535,92 @@ test('isReportAvailable returns false for an unknown page and URL', async () =>
558
535
  assert.equal(result, false);
559
536
  });
560
537
 
538
+ // TESTS FOR processTestRequest's claimed/queued-job detection
539
+
540
+ test('processTestRequest returns "description" for a page matching a claimed job by description', async () => {
541
+ const claimedPath = path.join(jobsPath(), 'claimed', 'clm.json');
542
+ await fs.writeFile(claimedPath, getJSON({
543
+ target: {what: 'Claimed Page', url: 'https://example.com/claimed-job'}
544
+ }));
545
+ try {
546
+ const {result} = await processTestRequest(
547
+ 'because', {description: 'Claimed Page', url: 'https://example.com/some-other-url'}
548
+ );
549
+ assert.equal(result, 'description');
550
+ }
551
+ finally {
552
+ await fs.unlink(claimedPath);
553
+ }
554
+ });
555
+
556
+ test('processTestRequest returns "url" for a page matching a claimed job by URL', async () => {
557
+ const claimedPath = path.join(jobsPath(), 'claimed', 'clm.json');
558
+ await fs.writeFile(claimedPath, getJSON({
559
+ target: {what: 'Some Claimed Page', url: 'https://example.com/claimed-url'}
560
+ }));
561
+ try {
562
+ const {result} = await processTestRequest(
563
+ 'because', {description: 'A Different Page', url: 'https://example.com/claimed-url'}
564
+ );
565
+ assert.equal(result, 'url');
566
+ }
567
+ finally {
568
+ await fs.unlink(claimedPath);
569
+ }
570
+ });
571
+
572
+ test('processTestRequest returns "description" for a page matching a queued job by description', async () => {
573
+ const queuedPath = path.join(jobsPath(), 'queue', 'que.json');
574
+ await fs.writeFile(queuedPath, getJSON({
575
+ target: {what: 'Queued Page', url: 'https://example.com/queued-job'}
576
+ }));
577
+ try {
578
+ const {result} = await processTestRequest(
579
+ 'because', {description: 'Queued Page', url: 'https://example.com/some-other-url'}
580
+ );
581
+ assert.equal(result, 'description');
582
+ }
583
+ finally {
584
+ await fs.unlink(queuedPath);
585
+ }
586
+ });
587
+
588
+ test('processTestRequest returns "url" for a page matching a queued job by URL', async () => {
589
+ const queuedPath = path.join(jobsPath(), 'queue', 'que.json');
590
+ await fs.writeFile(queuedPath, getJSON({
591
+ target: {what: 'Some Queued Page', url: 'https://example.com/queued-url'}
592
+ }));
593
+ try {
594
+ const {result} = await processTestRequest(
595
+ 'because', {description: 'A Different Page', url: 'https://example.com/queued-url'}
596
+ );
597
+ assert.equal(result, 'url');
598
+ }
599
+ finally {
600
+ await fs.unlink(queuedPath);
601
+ }
602
+ });
603
+
604
+ test('processTestRequest returns "nonreport" when the cited report does not exist', async () => {
605
+ // Cite a timeStamp/jobID that does not correspond to any available report.
606
+ const {result} = await processTestRequest('because', {timeStamp: '999999T9999', jobID: 'xxx'});
607
+ assert.equal(result, 'nonreport');
608
+ });
609
+
610
+ test('processTestRequest throws when a job file in a category directory is defective', async () => {
611
+ const claimedPath = path.join(jobsPath(), 'claimed', 'bad.json');
612
+ await fs.writeFile(claimedPath, 'not valid json');
613
+ try {
614
+ await assert.rejects(
615
+ processTestRequest('because', {description: 'Any Page', url: 'https://example.com/any'}),
616
+ /Failed to process test request/
617
+ );
618
+ }
619
+ finally {
620
+ await fs.unlink(claimedPath);
621
+ }
622
+ });
623
+
561
624
  // TESTS FOR REMAINING BRANCH COVERAGE
562
625
 
563
626
  test('getJobNames creates missing job directories and returns empty arrays', async () => {
@@ -605,6 +668,93 @@ test('getJobNames throws when a job directory is a file, not a directory', async
605
668
  }
606
669
  });
607
670
 
671
+ test('getJobsData returns an empty array, instead of throwing ENOENT, when its category directory does not yet exist', async () => {
672
+ // Regression test: on a fresh deployment, jobs/claimed and jobs/queue may never
673
+ // have been created yet (no job has ever been claimed or queued there). getJobsData
674
+ // must tolerate that the same way getJobNames does, rather than reading the
675
+ // category directory directly and throwing ENOENT.
676
+ const os = await import('node:os');
677
+ const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kilotest-jobsdata-'));
678
+ const savedDbDir = process.env.DB_DIR;
679
+ process.env.DB_DIR = path.join(tmpRoot, 'db');
680
+ try {
681
+ const {getJobsData} = await import('./util.ts');
682
+ const result = await getJobsData('claimed');
683
+ assert.deepEqual(result, []);
684
+ }
685
+ finally {
686
+ process.env.DB_DIR = savedDbDir;
687
+ await fs.rm(tmpRoot, {recursive: true}).catch(() => {});
688
+ }
689
+ });
690
+
691
+ test('getJobsData skips a job file that is removed between the directory listing and its read', async (t) => {
692
+ // Regression test: on the deployed server, a worker can complete or reclaim a
693
+ // claimed job (moving or deleting its file) between getJobsData listing the
694
+ // claimed directory and reading a specific file it found there. That race must
695
+ // result in the vanished job simply being skipped, not an uncaught ENOENT.
696
+ const claimedPath = path.join(jobsPath(), 'claimed', 'raced.json');
697
+ await fs.writeFile(claimedPath, getJSON({
698
+ target: {what: 'Raced Page', url: 'https://example.com/raced'}
699
+ }));
700
+ const originalReadFile = fs.readFile;
701
+ t.mock.method(fs, 'readFile', async (filePath: any, ...rest: any[]) => {
702
+ // Simulate the file having been removed by a worker just before this read.
703
+ if (String(filePath) === claimedPath) {
704
+ await fs.unlink(claimedPath);
705
+ const error: any = new Error('ENOENT: no such file or directory');
706
+ error.code = 'ENOENT';
707
+ throw error;
708
+ }
709
+ return (originalReadFile as any)(filePath, ...rest);
710
+ });
711
+ try {
712
+ const {getJobsData} = await import('./util.ts');
713
+ const result = await getJobsData('claimed');
714
+ assert.ok(!result.some(job => job.description === 'Raced Page'));
715
+ }
716
+ finally {
717
+ t.mock.reset();
718
+ await fs.unlink(claimedPath).catch(() => {});
719
+ }
720
+ });
721
+
722
+ test('getJobsData throws when a job file cannot be read for a reason other than its removal', async () => {
723
+ // A directory where a job file is expected causes fs.readFile to fail with
724
+ // EISDIR, not ENOENT, so getJobsData must still treat it as defective (unlike
725
+ // the ENOENT case above, which is skipped as a normal race with a worker).
726
+ const claimedDirAsFile = path.join(jobsPath(), 'claimed', 'notAFile.json');
727
+ await fs.mkdir(claimedDirAsFile);
728
+ try {
729
+ const {getJobsData} = await import('./util.ts');
730
+ await assert.rejects(getJobsData('claimed'), /defective/);
731
+ }
732
+ finally {
733
+ await fs.rm(claimedDirAsFile, {recursive: true}).catch(() => {});
734
+ }
735
+ });
736
+
737
+ test('processTestRequest succeeds on a fresh deployment with no job directories yet', async () => {
738
+ // Regression test for the same ENOENT scenario, exercised through the actual
739
+ // public interface: a brand-new DB_DIR with no jobs/claimed or jobs/queue
740
+ // directories must not prevent a new-test request from being approved.
741
+ const os = await import('node:os');
742
+ const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'kilotest-freshdeploy-'));
743
+ const savedDbDir = process.env.DB_DIR;
744
+ process.env.DB_DIR = path.join(tmpRoot, 'db');
745
+ try {
746
+ const {processTestRequest: freshProcessTestRequest} = await import('./util.ts');
747
+ const {result} = await freshProcessTestRequest(
748
+ 'because accessibility', {description: 'Fresh Deploy Page', url: 'https://example.com/fresh'}
749
+ );
750
+ assert.equal(result, 'ok');
751
+ }
752
+ finally {
753
+ process.env.DB_DIR = savedDbDir;
754
+ await fs.rm(tmpRoot, {recursive: true}).catch(() => {});
755
+ }
756
+ });
757
+
608
758
  test('getObject throws for a file that is not valid JSON', async () => {
609
759
  const tmpFile = path.join((await import('node:os')).tmpdir(), 'kilotest-test-invalid.json');
610
760
  (await import('node:fs')).writeFileSync(tmpFile, 'not json');
@@ -683,69 +833,6 @@ test('getPOSTData resolves with parsed query for form-urlencoded requests', asyn
683
833
  assert.equal(result.why, 'Because');
684
834
  });
685
835
 
686
- test('getRequestability returns "claimed" for a URL in a claimed job', async () => {
687
- const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-requestability-test';
688
- const fsSync = await import('node:fs');
689
- fsSync.mkdirSync(tmpDir + '/jobs/claimed', {recursive: true});
690
- fsSync.mkdirSync(tmpDir + '/jobs/queue', {recursive: true});
691
- fsSync.mkdirSync(tmpDir + '/jobs/failed', {recursive: true});
692
- fsSync.writeFileSync(tmpDir + '/jobs/claimed/job1.json', JSON.stringify({
693
- target: {url: 'https://example.com/test', what: 'Test Page'}
694
- }));
695
- const savedDbDir = process.env.DB_DIR;
696
- process.env.DB_DIR = tmpDir;
697
- try {
698
- const {getRequestability} = await import('./util.ts');
699
- const result = await getRequestability('https://example.com/test');
700
- assert.equal(result, 'claimed');
701
- }
702
- finally {
703
- process.env.DB_DIR = savedDbDir;
704
- fsSync.rmSync(tmpDir, {recursive: true});
705
- }
706
- });
707
-
708
- test('getRequestability returns "queued" for a URL in a queued job', async () => {
709
- const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-requestability-test';
710
- const fsSync = await import('node:fs');
711
- fsSync.mkdirSync(tmpDir + '/jobs/claimed', {recursive: true});
712
- fsSync.mkdirSync(tmpDir + '/jobs/queue', {recursive: true});
713
- fsSync.mkdirSync(tmpDir + '/jobs/failed', {recursive: true});
714
- fsSync.writeFileSync(tmpDir + '/jobs/queue/job1.json', JSON.stringify({
715
- target: {url: 'https://example.com/test', what: 'Test Page'}
716
- }));
717
- const savedDbDir = process.env.DB_DIR;
718
- process.env.DB_DIR = tmpDir;
719
- try {
720
- const {getRequestability} = await import('./util.ts');
721
- const result = await getRequestability('https://example.com/test');
722
- assert.equal(result, 'queued');
723
- }
724
- finally {
725
- process.env.DB_DIR = savedDbDir;
726
- fsSync.rmSync(tmpDir, {recursive: true});
727
- }
728
- });
729
-
730
- test('getRequestability returns empty string for a URL with no matching jobs', async () => {
731
- const tmpDir = (await import('node:os')).tmpdir() + '/kilotest-requestability-test';
732
- const fsSync = await import('node:fs');
733
- fsSync.mkdirSync(tmpDir + '/jobs/claimed', {recursive: true});
734
- fsSync.mkdirSync(tmpDir + '/jobs/queue', {recursive: true});
735
- fsSync.mkdirSync(tmpDir + '/jobs/failed', {recursive: true});
736
- const savedDbDir = process.env.DB_DIR;
737
- process.env.DB_DIR = tmpDir;
738
- try {
739
- const {getRequestability} = await import('./util.ts');
740
- const result = await getRequestability('https://example.com/no-match');
741
- assert.equal(result, '');
742
- }
743
- finally {
744
- process.env.DB_DIR = savedDbDir;
745
- fsSync.rmSync(tmpDir, {recursive: true});
746
- }
747
- });
748
-
749
836
  test('isURL returns false for a malformed URL', () => {
750
837
  assert.equal(isURL('https://[invalid'), false);
751
838
  });
@@ -988,25 +1075,6 @@ test('testRequestsLock is a function (the lock returned by createLock)', () => {
988
1075
  assert.equal(typeof testRequestsLock, 'function');
989
1076
  });
990
1077
 
991
- test('addTestRequest adds a request and returns success', async () => {
992
- await fs.writeFile(testRequestsPath(), '{}\n');
993
- const result = await addTestRequest('Test Page', 'https://example.com/test', 'because');
994
- assert.equal(result.error, undefined);
995
- const testRequests = JSON.parse(await fs.readFile(testRequestsPath(), 'utf8'));
996
- assert.ok(testRequests['https://example.com/test']);
997
- assert.equal(testRequests['https://example.com/test'].length, 1);
998
- assert.equal(testRequests['https://example.com/test'][0].description, 'Test Page');
999
- await fs.writeFile(testRequestsPath(), '{}\n');
1000
- });
1001
-
1002
- test('addTestRequest returns a duplicate error for a repeated request', async () => {
1003
- await fs.writeFile(testRequestsPath(), '{}\n');
1004
- await addTestRequest('Test Page', 'https://example.com/test', 'because');
1005
- const result = await addTestRequest('Test Page', 'https://example.com/test', 'another reason');
1006
- assert.equal(result.error, 'duplicate');
1007
- await fs.writeFile(testRequestsPath(), '{}\n');
1008
- });
1009
-
1010
1078
  test('getReportPath returns the path of a report file', () => {
1011
1079
  const result = getReportPath('260101T0000', 'mix');
1012
1080
  assert.ok(result.endsWith('260101T0000-mix.json'));