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: '