import { test } from 'node:test';
import assert from 'node:assert/strict';
import { normalizeUrl, pageId, registrableDomain, isThirdParty } from '../../src/lib/urls.js';
import { isoWeekOf, previousWeekOf } from '../../src/lib/week.js';
import { parseRobots } from '../../src/lib/robots.js';
import { discoverFromSitemaps } from '../../src/lib/sitemap.js';
import { addPage, pickBatch } from '../../src/lib/state.js';
import { resolveWcag, severityFor, classifyFinding } from '../../src/lib/wcag.js';
import { buildUrlFilter } from '../../src/lib/urls.js';
import { buildBugReports, bugReportToMarkdown } from '../../src/lib/bug-report.js';
import { splitSentences, estimateSyllables } from '../../src/engines/plain-language.js';
import { checkLink } from '../../src/lib/links.js';
import { normalizeRate, shouldRun } from '../../src/lib/sampling.js';
import { updateFindings } from '../../src/lib/findings.js';
import { findMisspellings } from '../../src/lib/spell.js';
import { impactFor, estimateExcluded, pct } from '../../src/lib/fpc.js';
import { toCsv, ruleSlug } from '../../src/lib/csv.js';
import { writeLighthouseCsv, writeLighthouseJson } from '../../src/lib/csv.js';
import { updateResourceLedger } from '../../src/lib/resource-ledger.js';
import { buildAcrData, buildAcrYaml } from '../../src/lib/acr.js';
import { headersToWappalyzer } from '../../src/engines/tech.js';
import { buildCooccurrence, lift, rankAssociations, mergeFleet, rankFleetAssociations } from '../../src/lib/tech-findings.js';
import { rollupThirdParty } from '../../src/lib/third-party-rollup.js';
import { buildLineManifest } from '../../src/lib/paracharts.js';
import { extractAudits } from '../../src/engines/lighthouse.js';
import { renderLighthousePage } from '../../src/report-html.js';
import { assessAltText, isAltProblem, ALT_VERDICTS } from '../../src/lib/alt-text.js';
import { loadPriorityUrls } from '../../src/lib/top-tasks.js';
import { prioritizeAccessibilityBugs } from '../../src/lib/accessibility-priority.js';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
test('normalizeUrl: identity is stable and tracking-free', () => {
const base = 'https://example.gov/';
assert.equal(
normalizeUrl('/a/b/?utm_source=x&z=1&a=2#frag', base, 'example.gov'),
'https://example.gov/a/b?a=2&z=1'
);
assert.equal(normalizeUrl('https://EXAMPLE.gov:443/path/', base, 'example.gov'), 'https://example.gov/path');
assert.equal(normalizeUrl('https://other.gov/x', base, 'example.gov'), null, 'off-host rejected');
assert.equal(normalizeUrl('/file.pdf', base, 'example.gov'), null, 'binary rejected');
assert.equal(normalizeUrl('mailto:x@y.z', base, 'example.gov'), null);
assert.equal(normalizeUrl('https://example.gov/', base, 'example.gov'), 'https://example.gov/', 'root keeps slash');
});
test('normalizeUrl: apex and www are the same host, other subdomains are not', () => {
// Target the apex: www links of the same registrable domain are accepted...
assert.equal(
normalizeUrl('https://www.cdc.gov/about', 'https://cdc.gov/', 'cdc.gov'),
'https://www.cdc.gov/about',
'www variant accepted, actual host preserved'
);
// ...and vice versa: target www, apex link accepted.
assert.equal(
normalizeUrl('https://cdc.gov/about', 'https://www.cdc.gov/', 'www.cdc.gov'),
'https://cdc.gov/about',
'apex variant accepted from a www target'
);
// Any other subdomain is a different site and is rejected.
assert.equal(
normalizeUrl('https://data.cms.gov/x', 'https://www.cms.gov/', 'www.cms.gov'),
null,
'non-www subdomain rejected'
);
assert.equal(
normalizeUrl('https://www.cms.gov/x', 'https://data.cms.gov/', 'data.cms.gov'),
null,
'www of base domain rejected when target is a different subdomain'
);
});
test('pageId: deterministic', () => {
assert.equal(pageId('https://example.gov/a'), pageId('https://example.gov/a'));
assert.notEqual(pageId('https://example.gov/a'), pageId('https://example.gov/b'));
});
test('isoWeek: known dates', () => {
assert.equal(isoWeekOf(new Date(Date.UTC(2026, 0, 1))), '2026-W01');
assert.equal(isoWeekOf(new Date(Date.UTC(2026, 5, 12))), '2026-W24');
assert.equal(isoWeekOf(new Date(Date.UTC(2027, 0, 1))), '2026-W53'); // Jan 1 2027 is a Friday in ISO week 53 of 2026
assert.equal(previousWeekOf('2026-W24', ['2026-W22', '2026-W24', '2026-W20']), '2026-W22');
});
test('robots: disallow, allow, wildcards, crawl-delay', () => {
const r = parseRobots(
`User-agent: *\nDisallow: /private/\nDisallow: /*.cgi$\nAllow: /private/ok\nCrawl-delay: 2\n`,
'vital-scans/0.1'
);
assert.equal(r.isAllowed('/public/page'), true);
assert.equal(r.isAllowed('/private/secret'), false);
assert.equal(r.isAllowed('/private/ok/page'), true, 'longer Allow wins');
assert.equal(r.isAllowed('/script.cgi'), false, '$ anchor');
assert.equal(r.isAllowed('/script.cgi.html'), true);
assert.equal(r.crawlDelay, 2);
});
test('robots: empty file allows everything', () => {
const r = parseRobots('', 'vital-scans/0.1');
assert.equal(r.isAllowed('/anything'), true);
assert.equal(r.crawlDelay, null);
});
test('sitemap: traverses multiple sibling index branches one level deep', async () => {
const realFetch = globalThis.fetch;
try {
const xml = {
'https://example.gov/sitemap.xml':
'' +
'https://example.gov/sitemap-a.xml' +
'https://example.gov/sitemap-b.xml' +
'',
'https://example.gov/sitemap-a.xml':
'' +
'https://example.gov/a' +
'',
'https://example.gov/sitemap-b.xml':
'' +
'https://example.gov/b' +
'',
};
globalThis.fetch = async (input) => {
const key = String(input);
const body = xml[key];
if (!body) return { ok: false, text: async () => '' };
return { ok: true, text: async () => body };
};
const found = await discoverFromSitemaps('https://example.gov', 'example.gov', 'vital-scans/0.1');
assert.deepEqual(found.sort(), ['https://example.gov/a', 'https://example.gov/b']);
} finally {
globalThis.fetch = realFetch;
}
});
test('pickBatch: never-scanned first, weekly cap respected, no rescan same week', () => {
const state = { domain: 'x', pages: {} };
addPage(state, 'a', 'https://x/a', 0);
addPage(state, 'b', 'https://x/b', 1);
addPage(state, 'c', 'https://x/c', 2);
state.pages.a.lastScannedWeek = '2026-W24';
state.pages.b.lastScannedWeek = '2026-W23';
const { batch } = pickBatch(state, '2026-W24', 10, 100);
// a excluded (already scanned this week); c (never scanned) before b (stale).
assert.equal(batch.find((x) => x.id === 'a'), undefined, 'a excluded — already scanned this week');
assert.deepEqual(batch.map((b) => b.id), ['c', 'b'], 'never-scanned (c) before previously-scanned (b)');
// Weekly cap: 1 already scanned this week, cap 2 -> only 1 more allowed.
const { batch: capped } = pickBatch(state, '2026-W24', 10, 2);
assert.equal(capped.length, 1);
// Failing pages excluded after 3 failures.
state.pages.c.failCount = 3;
const { batch: noFail } = pickBatch(state, '2026-W24', 10, 100);
assert.deepEqual(noFail.map((b) => b.id), ['b']);
});
test('pickBatch: priority URLs scanned first, no rescan within a week', () => {
const state = { domain: 'x', pages: {} };
// 5 normal pages, 1 priority page added later (so not first by insertion).
for (let i = 0; i < 5; i++) addPage(state, 'n' + i, `https://x/n${i}`, 1);
addPage(state, 'top', 'https://x/top', 0, { priority: true });
const { batch } = pickBatch(state, '2026-W24', 3, 100);
assert.equal(batch[0].id, 'top', 'priority page comes first regardless of insertion order');
assert.equal(batch[0].priority, true);
// Simulate scanning the batch this week; none reappear in the same week.
for (const b of batch) state.pages[b.id].lastScannedWeek = '2026-W24';
const { batch: next } = pickBatch(state, '2026-W24', 10, 100);
assert.ok(!next.some((b) => batch.some((p) => p.id === b.id)), 'already-scanned pages not repeated this week');
});
test('pickBatch: non-priority order is stable per week but varies across weeks', () => {
const state = { domain: 'x', pages: {} };
for (let i = 0; i < 50; i++) addPage(state, 'p' + i, `https://x/p${i}`, 1);
const w24a = pickBatch(state, '2026-W24', 50, 100).batch.map((b) => b.id);
const w24b = pickBatch(state, '2026-W24', 50, 100).batch.map((b) => b.id);
const w25 = pickBatch(state, '2026-W25', 50, 100).batch.map((b) => b.id);
assert.deepEqual(w24a, w24b, 'same week -> identical order (deterministic, replayable)');
assert.notDeepEqual(w24a, w25, 'different week -> different random spread');
// Same set, just reordered.
assert.deepEqual([...w24a].sort(), [...w25].sort(), 'same pages, different order');
});
test('pickBatch: failed pages can retry in-week until fail threshold', () => {
const state = { domain: 'x', pages: {} };
addPage(state, 'done', 'https://x/done', 1);
addPage(state, 'retry', 'https://x/retry', 1);
addPage(state, 'blocked', 'https://x/blocked', 1);
// done: completed this week -> excluded.
state.pages.done.lastScannedWeek = '2026-W24';
// retry: no completed outcome yet this week and below fail threshold -> eligible.
state.pages.retry.failCount = 2;
// blocked: reached fail threshold -> excluded.
state.pages.blocked.failCount = 3;
const { batch } = pickBatch(state, '2026-W24', 10, 100);
assert.deepEqual(batch.map((b) => b.id), ['retry']);
});
test('addPage: priority promotes an existing page without duplicating', () => {
const state = { domain: 'x', pages: {} };
assert.equal(addPage(state, 'a', 'https://x/a', 1), true, 'first add');
assert.equal(addPage(state, 'a', 'https://x/a', 1), false, 'duplicate add is a no-op');
assert.equal(state.pages.a.priority, false);
assert.equal(addPage(state, 'a', 'https://x/a', 0, { priority: true }), true, 'promotion counts as a change');
assert.equal(state.pages.a.priority, true, 'existing page promoted to priority');
});
test('resolveWcag: axe tags and alfa rule ids map to criteria', () => {
assert.deepEqual(resolveWcag('axe-core', { tags: ['cat.color', 'wcag2aa', 'wcag143'] }), {
sc: '1.4.3', name: 'Contrast (Minimum)', level: 'AA', wcag_version: '2.0',
});
assert.deepEqual(resolveWcag('axe-core', { tags: ['wcag412'] }), {
sc: '4.1.2', name: 'Name, Role, Value', level: 'A', wcag_version: '2.0',
});
assert.equal(resolveWcag('axe-core', { tags: ['best-practice', 'wcag2a'] }), null, 'level-only tags have no SC');
assert.deepEqual(resolveWcag('alfa', { ruleId: 'sia-r12' }), {
sc: '4.1.2', name: 'Name, Role, Value', level: 'A', wcag_version: '2.0',
});
assert.deepEqual(resolveWcag('alfa', { ruleId: '90' }), {
sc: '4.1.2', name: 'Name, Role, Value', level: 'A', wcag_version: '2.0',
}, 'numeric SI id is normalized to sia-rN and mapped');
assert.deepEqual(resolveWcag('alfa', { ruleId: 'sia-r67' }), {
sc: '1.1.1', name: 'Non-text Content', level: 'A', wcag_version: '2.0',
}, 'alfa map from data file resolves additional rules');
assert.equal(resolveWcag('alfa', { ruleId: 'sia-r9999' }), null, 'unknown alfa rule undetermined');
});
test('severityFor: axe impact maps, frequency amplifies', () => {
assert.equal(severityFor('critical', 1, 50), 'Critical');
assert.equal(severityFor('minor', 1, 50), 'Low', 'rare minor stays low');
assert.equal(severityFor('minor', 30, 50), 'Medium', 'site-wide minor escalates one level');
assert.equal(severityFor('serious', 40, 50), 'Critical', 'site-wide serious escalates to critical');
assert.equal(severityFor(null, 1, 50), 'Medium', 'no impact (alfa) defaults medium');
});
test('buildBugReports: shape, ids stable, sorted, placeholders present', () => {
const target = { domain: 'example.gov', key: 'example.gov' };
const summary = {
domain: 'example.gov',
week: '2026-W24',
generatedAt: '2026-06-13T00:00:00.000Z',
pagesScanned: 10,
axe: { rules: {
'color-contrast': { count: 8, pages: 6, impact: 'serious', help: 'Elements must have sufficient color contrast',
helpUrl: 'https://dequeuniversity.com/rules/axe/4.9/color-contrast', tags: ['wcag143', 'wcag2aa'],
examplePages: ['https://example.gov/a'],
instances: [{ url: 'https://example.gov/a', target: '.btn', html: 'Go' }] },
} },
alfa: { rules: {
'sia-r12': { count: 2, pages: 1, ruleUrl: 'https://act-rules.github.io/rules/97a4e1',
examplePages: ['https://example.gov/b'],
instances: [{ url: 'https://example.gov/b', target: '