← Source tree
web_demo/test_core.cjs
"use strict";
// Node-only logic/static checks. Not a DOM, rendering, or browser-download test.
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const { createHash } = require("node:crypto");
const { Session, sha256 } = require("./core.js");
require("./fixture.js");
const fixture = globalThis.SYNTHETIC_REPORT;
delete globalThis.SYNTHETIC_REPORT;
const clone = x => JSON.parse(JSON.stringify(x));
const digest = x => createHash("sha256").update(x).digest("hex");
const pending = () => { let resolve; const promise = new Promise(r => { resolve = r; }); return { promise, resolve }; };
test("Web Crypto SHA-256 matches Node for ASCII and Unicode", async () => {
for (const text of ["abc", "Ўзбекча — 00001"]) assert.equal(await sha256(text), digest(text));
});
test("fixture is exactly the stored Python model output", () => {
assert.deepEqual(fixture, JSON.parse(fs.readFileSync(path.join(__dirname, "../sample_output.json"), "utf8")));
});
test("original input byte hash is retained", () => {
assert.equal(fixture.source_sha256, digest(fs.readFileSync(path.join(__dirname, "../sample_input.json"))));
});
test("invalid row counts and review counts are rejected", () => {
for (const field of ["input_rows", "output_rows", "review_rows"]) {
const bad = clone(fixture); bad[field] += 1;
assert.throws(() => new Session(bad), /Invalid demonstration report/);
}
});
test("malformed source records, flags and row numbers are rejected", () => {
for (const change of [r => { r.original = []; }, r => { r.flags = "flag"; },
r => { r.row_number = 7; }, r => { r.needs_review = "false"; }]) {
const bad = clone(fixture); change(bad.rows[0]);
assert.throws(() => new Session(bad), /Invalid demonstration report/);
}
});
test("export and confirmation are refused before review", () => {
const s = new Session(fixture);
assert.throws(() => s.export(), /confirmation required/);
assert.throws(() => s.confirm("fake"), /exact current digest/);
});
test("review retains every row, duplicate ID, ambiguous amount and abstention", async () => {
const viewed = await new Session(fixture).review();
assert.deepEqual(viewed.document.review, fixture);
assert.equal(viewed.document.review.rows.length, 6);
assert.deepEqual(viewed.document.review.rows.map(r => r.original.record_id), ["00001", "00002", "00003", "00003", "00004", "00005"]);
assert.equal(viewed.document.review.rows[4].original.amount, "1,234.50");
assert.equal(viewed.document.review.rows[5].ml.status, "not_scored");
});
test("digest binds exact complete exported UTF-8 document", async () => {
const s = new Session(fixture), v = await s.review();
assert.equal(v.state, "awaiting_demo_confirmation");
assert.equal(s.confirm(v.digest), "demo_confirmed");
assert.equal(digest(s.export()), v.digest);
assert.deepEqual(JSON.parse(s.export()).review, fixture);
});
test("wrong digest cannot unlock export", async () => {
const s = new Session(fixture); await s.review();
assert.throws(() => s.confirm("0".repeat(64)), /exact current digest/);
assert.throws(() => s.export(), /confirmation required/);
});
test("mutation of returned review cannot change export", async () => {
const s = new Session(fixture), v = await s.review();
v.document.review.rows.pop(); v.document.review.rows[0].original.record_id = "changed";
s.confirm(v.digest); assert.deepEqual(JSON.parse(s.export()).review, fixture);
});
test("mutation of constructor input cannot change snapshot", async () => {
const input = clone(fixture), s = new Session(input);
input.rows.length = 0;
assert.deepEqual((await s.review()).document.review, fixture);
});
test("another review invalidates earlier confirmation", async () => {
const s = new Session(fixture), first = await s.review(); s.confirm(first.digest);
await s.review(); assert.throws(() => s.export(), /confirmation required/);
});
test("reset invalidates digest and export permission", async () => {
const s = new Session(fixture), v = await s.review(); s.confirm(v.digest);
assert.equal(s.reset(), "unreviewed");
assert.throws(() => s.export(), /confirmation required/);
assert.throws(() => s.confirm(v.digest), /exact current digest/);
});
test("reset during hashing cannot resurrect a stale review", async () => {
const p = pending(), s = new Session(fixture, () => p.promise);
const review = s.review(); s.reset(); p.resolve("stale");
await assert.rejects(review, /Review cancelled/);
assert.throws(() => s.confirm("stale"), /exact current digest/);
assert.throws(() => s.export(), /confirmation required/);
});
test("overlapping reviews cannot replace the newest digest", async () => {
const a = pending(), b = pending(); let call = 0;
const s = new Session(fixture, () => (++call === 1 ? a.promise : b.promise));
const first = s.review(), second = s.review();
b.resolve("newest"); await second; a.resolve("older");
await assert.rejects(first, /Review cancelled/);
assert.throws(() => s.confirm("older"), /exact current digest/);
s.confirm("newest"); assert.deepEqual(JSON.parse(s.export()).review, fixture);
});
test("hash failure fails closed", async () => {
const s = new Session(fixture, async () => { throw new Error("Web Crypto unavailable"); });
await assert.rejects(s.review(), /Web Crypto unavailable/);
assert.throws(() => s.export(), /confirmation required/);
assert.throws(() => s.confirm("fake"), /exact current digest/);
});
test("static page wiring and default disabled gates are present", () => {
const html = fs.readFileSync(path.join(__dirname, "index.html"), "utf8");
for (const id of ["rows", "report", "details", "status", "state", "digest", "review", "reset", "confirm", "export"]) {
assert.equal((html.match(new RegExp('id="' + id + '"', "g")) || []).length, 1);
}
for (const id of ["confirm", "export"]) assert.match(html, new RegExp('<button[^>]*id="' + id + '"[^>]*disabled'));
assert.ok(html.indexOf('src="fixture.js"') < html.indexOf('src="core.js"'));
assert.ok(html.indexOf('src="core.js"') < html.indexOf('src="app.js"'));
assert.match(html, /connect-src 'none'/);
assert.match(html, /have not yet been verified/);
});
test("browser source uses local assets and no network or HTML injection APIs", () => {
const files = ["index.html", "app.js", "core.js", "fixture.js", "style.css", "inference.js", "model.js"];
for (const file of files) {
const text = fs.readFileSync(path.join(__dirname, file), "utf8");
assert.doesNotMatch(text, /https?:\/\/|\bfetch\s*\(|XMLHttpRequest|WebSocket|sendBeacon|innerHTML|outerHTML|document\.write|\beval\s*\(/);
}
});