import assert from "node:assert/strict"; import { createHash, webcrypto } from "node:crypto"; import { readFileSync, readdirSync, statSync } from "node:fs"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; import { DataContractError, REQUIRED_ASSETS, SCHEMA_VERSION, loadDashboardData, validateAssetSet, } from "../js/data.js"; const DASHBOARD_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const PUBLIC_DATA = path.join(DASHBOARD_ROOT, "public", "data"); function json(filename) { return JSON.parse(readFileSync(path.join(PUBLIC_DATA, filename), "utf8")); } function assetSet() { return Object.fromEntries( Object.entries(REQUIRED_ASSETS).map(([name, filename]) => [name, json(filename)]), ); } function publicDataFetch(overrides = new Map()) { return async (requestUrl) => { const filename = path.basename(new URL(String(requestUrl)).pathname); const source = path.join(PUBLIC_DATA, filename); let bytes; try { bytes = overrides.has(filename) ? overrides.get(filename) : readFileSync(source); } catch { return { ok: false, async arrayBuffer() { return new ArrayBuffer(0); } }; } return { ok: true, async arrayBuffer() { return Uint8Array.from(bytes).buffer; }, }; }; } function filesRecursively(directory) { return readdirSync(directory).flatMap((name) => { const target = path.join(directory, name); return statSync(target).isDirectory() ? filesRecursively(target) : [target]; }); } test("static shell exposes four semantic navigable views", () => { const html = readFileSync(path.join(DASHBOARD_ROOT, "index.html"), "utf8"); assert.match(html, /]*aria-label="Dashboard views"/); assert.match(html, /One-time holdout<\/strong>Already evaluated<\/small>/); assert.doesNotMatch(html, /Locked test<\/strong>|Final evaluation<\/small>/); }); test("estimator remains disabled and contains no identifying input", () => { const html = readFileSync(path.join(DASHBOARD_ROOT, "index.html"), "utf8"); const estimator = html.match(/
/)?.[0]; assert.ok(estimator, "estimator section is present"); assert.match(estimator, /
/); const controlAttributes = [...estimator.matchAll(/<(?:input|select|textarea)\b([^>]*)>/g)].map( (match) => match[1], ); for (const attributes of controlAttributes) { assert.doesNotMatch( attributes, /(?:name|id)\s*=\s*["'][^"']*(?:vin|plate|address|station|free.?text|zip)[^"']*["']/i, ); } }); test("all required generated assets satisfy the browser contract", () => { const validated = validateAssetSet(assetSet()); assert.equal(validated.manifest.schema_version, SCHEMA_VERSION); assert.equal(validated.manifest.development_preview, true); assert.equal(validated.manifest.population_estimate_allowed, false); assert.equal(validated.manifest.definitions.locked_test_metrics_published, false); assert.match(validated.manifest.release_id, /^[0-9a-f]{64}$/); assert.ok(validated.manifest.model_versions.length > 0); assert.deepEqual( validated.filters.partitions, ["train", "tune", "calibrate"], ); assert.ok(validated.overview.rows.length > 0); }); test("browser loader verifies every raw asset digest before rendering", async () => { const validated = await loadDashboardData({ basePath: "http://dashboard.test/public/data/", fetchImplementation: publicDataFetch(), cryptoImplementation: webcrypto, }); assert.equal(validated.manifest.schema_version, SCHEMA_VERSION); const changedOverview = Buffer.concat([ readFileSync(path.join(PUBLIC_DATA, REQUIRED_ASSETS.overview)), Buffer.from("\n"), ]); await assert.rejects( loadDashboardData({ basePath: "http://dashboard.test/public/data/", fetchImplementation: publicDataFetch( new Map([[REQUIRED_ASSETS.overview, changedOverview]]), ), cryptoImplementation: webcrypto, }), (error) => error instanceof DataContractError && /Integrity verification failed for overview_period_county\.json/.test(error.message), ); }); test("sha256 manifest covers and matches every approved data asset", () => { const manifest = json(REQUIRED_ASSETS.shaManifest); const expectedNames = Object.entries(REQUIRED_ASSETS) .filter(([name]) => name !== "shaManifest") .map(([, filename]) => filename) .sort(); assert.deepEqual( manifest.files.map((file) => file.name).sort(), expectedNames, ); for (const entry of manifest.files) { const digest = createHash("sha256") .update(readFileSync(path.join(PUBLIC_DATA, entry.name))) .digest("hex"); assert.equal(digest, entry.sha256, `${entry.name} digest`); } }); test("contract fails closed for missing, inconsistent, or sensitive data", () => { const missing = assetSet(); delete missing.overview; assert.throws(() => validateAssetSet(missing), DataContractError); const inconsistent = assetSet(); inconsistent.ageRisk.population_estimate_allowed = true; assert.throws(() => validateAssetSet(inconsistent), DataContractError); const sensitive = assetSet(); sensitive.scorecards.rows[0].vehicle_token = "not-public"; assert.throws(() => validateAssetSet(sensitive), DataContractError); const extraRowField = assetSet(); extraRowField.overview.rows[0].note = "unapproved"; assert.throws(() => validateAssetSet(extraRowField), DataContractError); const lockedMetrics = assetSet(); lockedMetrics.manifest.definitions.locked_test_metrics_published = true; assert.throws(() => validateAssetSet(lockedMetrics), DataContractError); const extraPartition = assetSet(); extraPartition.filters.partitions.push("locked_test"); assert.throws(() => validateAssetSet(extraPartition), DataContractError); const unsortedVersions = assetSet(); unsortedVersions.manifest.model_versions = ["z_v1", "a_v1"]; assert.throws(() => validateAssetSet(unsortedVersions), DataContractError); const belowSuppression = assetSet(); belowSuppression.overview.rows[0].support_rounded = belowSuppression.manifest.definitions.suppression_min_support - 1; assert.throws(() => validateAssetSet(belowSuppression), DataContractError); const incorrectlyRounded = assetSet(); incorrectlyRounded.coverage.rows[0].support_rounded += 1; assert.throws(() => validateAssetSet(incorrectlyRounded), DataContractError); }); test("dashboard source has no external runtime dependency or credential marker", () => { const sourceFiles = filesRecursively(DASHBOARD_ROOT).filter( (filename) => !filename.includes(`${path.sep}public${path.sep}data${path.sep}`) && !filename.includes(`${path.sep}tests${path.sep}`) && /\.(?:html|css|js|mjs)$/.test(filename), ); for (const filename of sourceFiles) { const source = readFileSync(filename, "utf8"); assert.doesNotMatch(source, /(?:postgres(?:ql)?:\/\/|PGPASSWORD|PGHOST|countydata\.)/i); assert.doesNotMatch(source, /]+src=["']https?:|@import\s+url\(["']?https?:/i); } });