@jrpool/kilotest 51.1.0 → 55.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/.devin/config.local.json +4 -1
- package/.github/workflows/ci.yml +5 -2
- package/.github/workflows/periodic-smoke-tests.yml +2 -2
- package/.github/workflows/publish-mcp.yml +1 -1
- package/README.md +5 -5
- package/api/requestFeature.test.ts +36 -0
- package/api/requestFeature.ts +26 -4
- package/api/requestRetest.test.ts +18 -0
- package/api/requestRetest.ts +4 -0
- package/api/requestTest.test.ts +50 -0
- package/api/requestTest.ts +25 -1
- package/capability.md +2 -2
- package/docs/AI-TOOLS.md +3 -3
- package/docs/SERVICE.md +23 -2
- package/env.example +32 -0
- package/index.test.ts +126 -0
- package/index.ts +74 -23
- package/llms-full.txt +1 -1
- package/llms.txt +1 -1
- package/mcp.test.ts +41 -4
- package/mcp.ts +67 -17
- package/openapi.yaml +5 -5
- package/package.json +2 -1
- package/scripts/generate-openapi.ts +1 -1
- package/test/setup.ts +5 -0
- package/util.test.ts +429 -0
- package/util.ts +182 -3
- package/web/requestRetest/index.ts +9 -0
- package/web/requestRetest/requestRetest.test.ts +14 -0
- package/web/requestTest/index.ts +9 -0
- package/web/requestTest/requestTest.test.ts +16 -0
- package/web/tutorialAI/index.ts +25 -3
- package/web/tutorialAI/tutorialAI.test.ts +32 -0
- package/web/tutorialWeb/index.ts +25 -3
- package/web/tutorialWeb/tutorialWeb.test.ts +32 -0
package/test/setup.ts
CHANGED
|
@@ -12,3 +12,8 @@
|
|
|
12
12
|
for (const key of ['MANAGER_EMAIL', 'ALERT_API_HOST', 'ALERT_API_PATH', 'ALERT_API_KEY', 'ALERT_FROM']) {
|
|
13
13
|
process.env[key] = '';
|
|
14
14
|
}
|
|
15
|
+
// Allow internal targets by default, so that tests submitting an ordinary
|
|
16
|
+
// https://example.com/... URL for testing do not depend on real DNS/network access
|
|
17
|
+
// to pass the resolution check that isAllowedTarget performs. A test of that check
|
|
18
|
+
// itself sets this env var back to unset (or false) around the specific call being tested.
|
|
19
|
+
process.env.ALLOW_INTERNAL_TARGETS = 'true';
|
package/util.test.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {test, before, after} from 'node:test';
|
|
9
9
|
import assert from 'node:assert/strict';
|
|
10
|
+
import net from 'node:net';
|
|
10
11
|
import path from 'node:path';
|
|
11
12
|
import fs from 'node:fs/promises';
|
|
12
13
|
|
|
@@ -25,12 +26,14 @@ import {
|
|
|
25
26
|
checkLength,
|
|
26
27
|
createLock,
|
|
27
28
|
dbPath,
|
|
29
|
+
describeMax,
|
|
28
30
|
getAgoDays,
|
|
29
31
|
getAgoString,
|
|
30
32
|
getCountString,
|
|
31
33
|
getDateString,
|
|
32
34
|
getDateTime,
|
|
33
35
|
getEngineNamesString,
|
|
36
|
+
getEnvMax,
|
|
34
37
|
getIssue,
|
|
35
38
|
errorMessage,
|
|
36
39
|
getJSON,
|
|
@@ -51,6 +54,9 @@ import {
|
|
|
51
54
|
getWeightName,
|
|
52
55
|
hiddenReportsPath,
|
|
53
56
|
htmlSafe,
|
|
57
|
+
isAllowedRedirectTarget,
|
|
58
|
+
isAllowedReport,
|
|
59
|
+
isAllowedTarget,
|
|
54
60
|
isJobID,
|
|
55
61
|
isReportAvailable,
|
|
56
62
|
isTimeStamp,
|
|
@@ -265,6 +271,46 @@ test('getDateTime returns null for an invalid time stamp', () => {
|
|
|
265
271
|
assert.equal(getDateTime('invalid'), null);
|
|
266
272
|
});
|
|
267
273
|
|
|
274
|
+
test('getEnvMax returns the default when the variable is unset', () => {
|
|
275
|
+
delete process.env.TEST_ENV_MAX_VAR;
|
|
276
|
+
assert.equal(getEnvMax('TEST_ENV_MAX_VAR', 20), 20);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('getEnvMax returns the default when the variable is empty', () => {
|
|
280
|
+
process.env.TEST_ENV_MAX_VAR = '';
|
|
281
|
+
assert.equal(getEnvMax('TEST_ENV_MAX_VAR', 20), 20);
|
|
282
|
+
delete process.env.TEST_ENV_MAX_VAR;
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('getEnvMax returns the configured value when it is a positive integer', () => {
|
|
286
|
+
process.env.TEST_ENV_MAX_VAR = '50';
|
|
287
|
+
assert.equal(getEnvMax('TEST_ENV_MAX_VAR', 20), 50);
|
|
288
|
+
delete process.env.TEST_ENV_MAX_VAR;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test('getEnvMax returns the default when the variable is not an integer', () => {
|
|
292
|
+
process.env.TEST_ENV_MAX_VAR = 'not-a-number';
|
|
293
|
+
assert.equal(getEnvMax('TEST_ENV_MAX_VAR', 20), 20);
|
|
294
|
+
delete process.env.TEST_ENV_MAX_VAR;
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('getEnvMax returns 0 when the variable is explicitly 0, meaning no limit', () => {
|
|
298
|
+
process.env.TEST_ENV_MAX_VAR = '0';
|
|
299
|
+
assert.equal(getEnvMax('TEST_ENV_MAX_VAR', 20), 0);
|
|
300
|
+
delete process.env.TEST_ENV_MAX_VAR;
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('getEnvMax returns the default when the variable is negative', () => {
|
|
304
|
+
process.env.TEST_ENV_MAX_VAR = '-5';
|
|
305
|
+
assert.equal(getEnvMax('TEST_ENV_MAX_VAR', 20), 20);
|
|
306
|
+
delete process.env.TEST_ENV_MAX_VAR;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('describeMax returns "no limit" for a cap of 0 and the number otherwise', () => {
|
|
310
|
+
assert.equal(describeMax(0), 'no limit');
|
|
311
|
+
assert.equal(describeMax(20), '20');
|
|
312
|
+
});
|
|
313
|
+
|
|
268
314
|
test('getIssue returns an issue ID for a known engine and rule', () => {
|
|
269
315
|
const issueID = getIssue('axe', 'link-name');
|
|
270
316
|
assert.ok(typeof issueID === 'string' || issueID === null);
|
|
@@ -366,6 +412,332 @@ test('isURL returns false for an invalid URL', () => {
|
|
|
366
412
|
assert.equal(isURL('not-a-url'), false);
|
|
367
413
|
});
|
|
368
414
|
|
|
415
|
+
// A fake resolver, so isAllowedTarget tests need no real DNS/network access.
|
|
416
|
+
const fakeLookup = (address: string) => async () => [{address, family: net.isIP(address)}];
|
|
417
|
+
|
|
418
|
+
// isAllowedTarget and isAllowedReport branch on ALLOW_INTERNAL_TARGETS, which other test
|
|
419
|
+
// files (sharing this process) set to "true" for their own unrelated tests, so every test
|
|
420
|
+
// below sets the env var itself, rather than relying on it being unset by default, and
|
|
421
|
+
// restores whatever value preceded it.
|
|
422
|
+
let savedAllowInternalTargets: string | undefined;
|
|
423
|
+
|
|
424
|
+
before(() => {
|
|
425
|
+
savedAllowInternalTargets = process.env.ALLOW_INTERNAL_TARGETS;
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
after(() => {
|
|
429
|
+
if (savedAllowInternalTargets === undefined) {
|
|
430
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
process.env.ALLOW_INTERNAL_TARGETS = savedAllowInternalTargets;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('isAllowedTarget returns true for a URL resolving to a public address', async () => {
|
|
438
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
439
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('93.184.216.34')), true);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('isAllowedTarget returns false for a URL resolving to a loopback address', async () => {
|
|
443
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
444
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('127.0.0.1')), false);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('isAllowedTarget returns false for a URL resolving to a private address', async () => {
|
|
448
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
449
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('10.0.0.5')), false);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test('isAllowedTarget returns false for a URL resolving to a 172.16/12 private address', async () => {
|
|
453
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
454
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('172.20.0.5')), false);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('isAllowedTarget returns false for a URL resolving to a carrier-grade NAT address', async () => {
|
|
458
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
459
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('100.70.0.5')), false);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('isAllowedTarget returns false for a URL resolving to a link-local/cloud-metadata address', async () => {
|
|
463
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
464
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('169.254.169.254')), false);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test('isAllowedTarget returns false for a URL resolving to an IPv6 loopback address', async () => {
|
|
468
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
469
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('::1')), false);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test('isAllowedTarget returns false for a URL resolving to an IPv6 unspecified address', async () => {
|
|
473
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
474
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('::')), false);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test('isAllowedTarget returns false for a URL resolving to an IPv4-mapped private IPv6 address', async () => {
|
|
478
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
479
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('::ffff:10.0.0.5')), false);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('isAllowedTarget returns false for a URL resolving to an IPv6 unique-local address', async () => {
|
|
483
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
484
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('fd12:3456:789a:1::1')), false);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
test('isAllowedTarget returns false for a URL resolving to an IPv6 link-local address', async () => {
|
|
488
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
489
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('fe80::1')), false);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test('isAllowedTarget returns true for a URL resolving to a public IPv6 address', async () => {
|
|
493
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
494
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('2606:2800:220:1:248:1893:25c8:1946')), true);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('isAllowedTarget returns false for a URL resolving to a TEST-NET-1 address', async () => {
|
|
498
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
499
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('192.0.2.1')), false);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
test('isAllowedTarget returns false for a URL resolving to a TEST-NET-2 address', async () => {
|
|
503
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
504
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('198.51.100.1')), false);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('isAllowedTarget returns false for a URL resolving to a TEST-NET-3 address', async () => {
|
|
508
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
509
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('203.0.113.1')), false);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
test('isAllowedTarget returns false for a URL resolving to a benchmarking (198.18/15) address', async () => {
|
|
513
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
514
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('198.18.0.1')), false);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('isAllowedTarget returns false for a URL resolving to a reserved 240/4 address', async () => {
|
|
518
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
519
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('240.0.0.1')), false);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test('isAllowedTarget returns false for a URL resolving to the broadcast address', async () => {
|
|
523
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
524
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('255.255.255.255')), false);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test('isAllowedTarget returns false for a URL resolving to a multicast address', async () => {
|
|
528
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
529
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('224.0.0.1')), false);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test('isAllowedTarget returns false for a URL resolving to a deprecated IPv6 site-local address', async () => {
|
|
533
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
534
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('fec0::1')), false);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
test('isAllowedTarget returns false for a URL resolving to an IPv6 multicast address', async () => {
|
|
538
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
539
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('ff02::1')), false);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test('isAllowedTarget returns false for a URL resolving to a 6to4 (2002::/16) address', async () => {
|
|
543
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
544
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('2002::1')), false);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('isAllowedTarget returns false for a URL resolving to a Teredo (2001::/32) address', async () => {
|
|
548
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
549
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('2001::1')), false);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('isAllowedTarget returns false for a URL resolving to an NAT64 (64:ff9b::/96) address', async () => {
|
|
553
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
554
|
+
assert.equal(await isAllowedTarget('https://example.com/page', fakeLookup('64:ff9b::10.0.0.5')), false);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test('isAllowedTarget returns false for a URL resolving to an address family net.isIP does not recognize', async () => {
|
|
558
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
559
|
+
assert.equal(await isAllowedTarget('https://example.com/page', async () => [{address: 'not-an-ip', family: 0}]), false);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test('isAllowedTarget returns false when the DNS lookup rejects', async () => {
|
|
563
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
564
|
+
const lookup = async () => { throw new Error('lookup failed'); };
|
|
565
|
+
assert.equal(await isAllowedTarget('https://nonexistent.invalid/page', lookup), false);
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
test('isAllowedTarget returns false for a URL whose literal host is a private IP', async () => {
|
|
569
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
570
|
+
assert.equal(await isAllowedTarget('https://192.168.1.1/page'), false);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('isAllowedTarget returns true for a URL whose literal host is a public IP', async () => {
|
|
574
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
575
|
+
assert.equal(await isAllowedTarget('https://93.184.216.34/page'), true);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
test('isAllowedTarget returns false for a syntactically invalid URL', async () => {
|
|
579
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
580
|
+
assert.equal(await isAllowedTarget('not-a-url'), false);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test('isAllowedTarget returns true for a private-address target when ALLOW_INTERNAL_TARGETS is true', async () => {
|
|
584
|
+
process.env.ALLOW_INTERNAL_TARGETS = 'true';
|
|
585
|
+
assert.equal(await isAllowedTarget('https://192.168.1.1/page'), true);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// A fake fetch that returns a response whose url reflects any redirect,
|
|
589
|
+
// so isAllowedRedirectTarget tests need no real network access.
|
|
590
|
+
const fakeFetch = (finalURL: string) => async () => ({url: finalURL}) as Response;
|
|
591
|
+
|
|
592
|
+
test('isAllowedRedirectTarget returns true when the final URL resolves to a public address', async () => {
|
|
593
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
594
|
+
assert.equal(
|
|
595
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://example.com/page'), fakeLookup('93.184.216.34')),
|
|
596
|
+
true
|
|
597
|
+
);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a private address', async () => {
|
|
601
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
602
|
+
assert.equal(
|
|
603
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://10.0.0.5/page'), fakeLookup('93.184.216.34')),
|
|
604
|
+
false
|
|
605
|
+
);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
test('isAllowedRedirectTarget returns false when the fetch throws', async () => {
|
|
609
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
610
|
+
const fetchImpl = async () => { throw new Error('fetch failed'); };
|
|
611
|
+
assert.equal(await isAllowedRedirectTarget('https://example.com/page', fetchImpl), false);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test('isAllowedRedirectTarget returns true for any URL when ALLOW_INTERNAL_TARGETS is true, without fetching', async () => {
|
|
615
|
+
process.env.ALLOW_INTERNAL_TARGETS = 'true';
|
|
616
|
+
const fetchImpl = async () => { throw new Error('fetch should not be called'); };
|
|
617
|
+
assert.equal(await isAllowedRedirectTarget('https://192.168.1.1/page', fetchImpl), true);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a TEST-NET-1 address', async () => {
|
|
621
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
622
|
+
assert.equal(
|
|
623
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://192.0.2.1/page'), fakeLookup('93.184.216.34')),
|
|
624
|
+
false
|
|
625
|
+
);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a TEST-NET-2 address', async () => {
|
|
629
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
630
|
+
assert.equal(
|
|
631
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://198.51.100.1/page'), fakeLookup('93.184.216.34')),
|
|
632
|
+
false
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a TEST-NET-3 address', async () => {
|
|
637
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
638
|
+
assert.equal(
|
|
639
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://203.0.113.1/page'), fakeLookup('93.184.216.34')),
|
|
640
|
+
false
|
|
641
|
+
);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a benchmarking (198.18/15) address', async () => {
|
|
645
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
646
|
+
assert.equal(
|
|
647
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://198.18.0.1/page'), fakeLookup('93.184.216.34')),
|
|
648
|
+
false
|
|
649
|
+
);
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a reserved 240/4 address', async () => {
|
|
653
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
654
|
+
assert.equal(
|
|
655
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://240.0.0.1/page'), fakeLookup('93.184.216.34')),
|
|
656
|
+
false
|
|
657
|
+
);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to the broadcast address', async () => {
|
|
661
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
662
|
+
assert.equal(
|
|
663
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://255.255.255.255/page'), fakeLookup('93.184.216.34')),
|
|
664
|
+
false
|
|
665
|
+
);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a multicast address', async () => {
|
|
669
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
670
|
+
assert.equal(
|
|
671
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://224.0.0.1/page'), fakeLookup('93.184.216.34')),
|
|
672
|
+
false
|
|
673
|
+
);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a deprecated IPv6 site-local address', async () => {
|
|
677
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
678
|
+
assert.equal(
|
|
679
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://[fec0::1]/page'), fakeLookup('93.184.216.34')),
|
|
680
|
+
false
|
|
681
|
+
);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a 6to4 (2002::/16) address', async () => {
|
|
685
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
686
|
+
assert.equal(
|
|
687
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://[2002::1]/page'), fakeLookup('93.184.216.34')),
|
|
688
|
+
false
|
|
689
|
+
);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test('isAllowedRedirectTarget returns false when a redirect leads to a Teredo (2001::/32) address', async () => {
|
|
693
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
694
|
+
assert.equal(
|
|
695
|
+
await isAllowedRedirectTarget('https://example.com/page', fakeFetch('https://[2001::1]/page'), fakeLookup('93.184.216.34')),
|
|
696
|
+
false
|
|
697
|
+
);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
test('isAllowedReport returns true when every act has a public actualURL', async () => {
|
|
701
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
702
|
+
const report = {
|
|
703
|
+
target: {what: 'Example', url: 'https://example.com/page'},
|
|
704
|
+
acts: [
|
|
705
|
+
{type: 'test', actualURL: 'https://example.com/page'},
|
|
706
|
+
{type: 'test', actualURL: 'https://example.com/page2'}
|
|
707
|
+
]
|
|
708
|
+
};
|
|
709
|
+
assert.equal(await isAllowedReport(report, fakeLookup('93.184.216.34')), true);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
test('isAllowedReport returns false when any act has a disallowed actualURL', async () => {
|
|
713
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
714
|
+
const report = {
|
|
715
|
+
target: {what: 'Example', url: 'https://example.com/page'},
|
|
716
|
+
acts: [
|
|
717
|
+
{type: 'test', actualURL: 'https://example.com/page'},
|
|
718
|
+
{type: 'test', actualURL: 'https://internal.example.net/page'}
|
|
719
|
+
]
|
|
720
|
+
};
|
|
721
|
+
let callCount = 0;
|
|
722
|
+
const lookup = async (hostname: string) => {
|
|
723
|
+
callCount++;
|
|
724
|
+
return hostname === 'internal.example.net'
|
|
725
|
+
? [{address: '10.0.0.5', family: 4}]
|
|
726
|
+
: [{address: '93.184.216.34', family: 4}];
|
|
727
|
+
};
|
|
728
|
+
assert.equal(await isAllowedReport(report, lookup), false);
|
|
729
|
+
assert.ok(callCount > 0);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
test('isAllowedReport falls back to the report target URL when an act has no actualURL', async () => {
|
|
733
|
+
delete process.env.ALLOW_INTERNAL_TARGETS;
|
|
734
|
+
const report = {
|
|
735
|
+
target: {what: 'Example', url: 'https://example.com/page'},
|
|
736
|
+
acts: [{type: 'test'}]
|
|
737
|
+
};
|
|
738
|
+
assert.equal(await isAllowedReport(report, fakeLookup('93.184.216.34')), true);
|
|
739
|
+
});
|
|
740
|
+
|
|
369
741
|
test('isValidAuthCode returns true when the code matches AUTH_CODE', () => {
|
|
370
742
|
const saved = process.env.AUTH_CODE;
|
|
371
743
|
process.env.AUTH_CODE = 'secret-code';
|
|
@@ -524,6 +896,63 @@ test('processTestRequest returns a retest error when a report already exists for
|
|
|
524
896
|
assert.equal(result, 'retest');
|
|
525
897
|
});
|
|
526
898
|
|
|
899
|
+
test('processTestRequest returns a queueFull error once the pending-request queue is full', {timeout: 500}, async () => {
|
|
900
|
+
// Fill testRequests.json with 20 pending requests, all for the same URL, so the total
|
|
901
|
+
// pending count (summed across all URLs' request arrays) reaches the cap directly,
|
|
902
|
+
// without needing 20 distinct URLs.
|
|
903
|
+
const filler = Array.from({length: 20}, (_, i) => ({
|
|
904
|
+
timeStamp: getNowStamp(), description: `Filler Page ${i}`, reason: 'because filler'
|
|
905
|
+
}));
|
|
906
|
+
await fs.writeFile(testRequestsPath(), getJSON({'https://example.com/filler': filler}));
|
|
907
|
+
// A further, distinct request is rejected as queueFull.
|
|
908
|
+
const {result} = await processTestRequest(
|
|
909
|
+
'because accessibility', {description: 'One Too Many Page', url: 'https://example.com/one-too-many'}
|
|
910
|
+
);
|
|
911
|
+
assert.equal(result, 'queueFull');
|
|
912
|
+
// Clean up testRequests.json.
|
|
913
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test('processTestRequest honors TEST_REQUEST_QUEUE_MAX when the cap is reached below the default', {timeout: 500}, async () => {
|
|
917
|
+
// With the cap lowered to 2, a single pending request already fills the queue.
|
|
918
|
+
process.env.TEST_REQUEST_QUEUE_MAX = '2';
|
|
919
|
+
try {
|
|
920
|
+
await fs.writeFile(testRequestsPath(), getJSON({
|
|
921
|
+
'https://example.com/filler': [
|
|
922
|
+
{timeStamp: getNowStamp(), description: 'Filler Page A', reason: 'because filler'},
|
|
923
|
+
{timeStamp: getNowStamp(), description: 'Filler Page B', reason: 'because filler'}
|
|
924
|
+
]
|
|
925
|
+
}));
|
|
926
|
+
const {result} = await processTestRequest(
|
|
927
|
+
'because accessibility', {description: 'One Too Many Page', url: 'https://example.com/one-too-many'}
|
|
928
|
+
);
|
|
929
|
+
assert.equal(result, 'queueFull');
|
|
930
|
+
}
|
|
931
|
+
finally {
|
|
932
|
+
delete process.env.TEST_REQUEST_QUEUE_MAX;
|
|
933
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
test('processTestRequest never returns queueFull when TEST_REQUEST_QUEUE_MAX is 0 (no limit)', {timeout: 500}, async () => {
|
|
938
|
+
// With the cap set to 0, a queue already past the default size is still not full.
|
|
939
|
+
process.env.TEST_REQUEST_QUEUE_MAX = '0';
|
|
940
|
+
try {
|
|
941
|
+
const filler = Array.from({length: 25}, (_, i) => ({
|
|
942
|
+
timeStamp: getNowStamp(), description: `Filler Page ${i}`, reason: 'because filler'
|
|
943
|
+
}));
|
|
944
|
+
await fs.writeFile(testRequestsPath(), getJSON({'https://example.com/filler': filler}));
|
|
945
|
+
const {result} = await processTestRequest(
|
|
946
|
+
'because accessibility', {description: 'One More Page', url: 'https://example.com/one-more'}
|
|
947
|
+
);
|
|
948
|
+
assert.equal(result, 'ok');
|
|
949
|
+
}
|
|
950
|
+
finally {
|
|
951
|
+
delete process.env.TEST_REQUEST_QUEUE_MAX;
|
|
952
|
+
await fs.writeFile(testRequestsPath(), '{}\n');
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
|
|
527
956
|
test('annotateReportObject annotates a report object in place without reading or writing a file', async () => {
|
|
528
957
|
// A report object that was never written to disk (e.g. one just received from a worker,
|
|
529
958
|
// as in index.ts's worker/report handler, which annotates before the first write).
|