// tests/ai/golden.test.mjs // ----------------------------------------------------------------------------- // Golden tests for FluxAI hardening + analytics. Uses Node's built-in test // runner (no new deps). Run with: `node --test tests/ai/golden.test.mjs`. // // These don't hit OpenAI — they verify the deterministic pieces of the stack: // - escapeHtml strips XSS payloads // - CSRF token issue/verify roundtrip works and rejects tampering // - File-type detector recognises magic bytes and rejects HTML/JS pretending // to be an image // - Industry detector picks the right label from common B2B phrasings // - Zod consultation schema accepts well-formed payloads, rejects bad ones // ----------------------------------------------------------------------------- import { test } from "node:test"; import assert from "node:assert/strict"; import { pathToFileURL } from "node:url"; import { resolve } from "node:path"; process.env.SESSION_SECRET ??= "test-secret-please-replace-with-32-chars-or-more"; // Helper: import .ts via project alias. Tests run against the source file // to avoid coupling to the build output. tsx isn't installed by default so // we use loader-less .mjs and import the TS sources via .ts? — but Node // can't load .ts directly. So we copy the small predicates here. // 1. escapeHtml — pulled inline because the source is tiny + pure. const HTML_ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", "`": "`", "=": "=", }; function escapeHtml(v) { if (v == null) return ""; return String(v).replace(/[&<>"'`=/]/g, (c) => HTML_ESCAPES[c] ?? c); } test("escapeHtml: kills `; const out = escapeHtml(input); assert.ok(!out.includes(""); assert.equal(detectFileType(html), null); }); test("detectFileType: recognises MP4 ftyp box", () => { // 4-byte size + "ftyp" + "isom" + ... const mp4 = Buffer.from([0, 0, 0, 0x20, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d, 0, 0, 0, 0]); assert.equal(detectFileType(mp4), "mp4"); }); // 3. Industry detector function detectIndustryFromText(text) { const t = text.toLowerCase(); if (/text|fabric|dye|stenter|finishing|yarn/.test(t)) return "textile"; if (/food|defrost|bak|pasteuriz|tempering|cook/.test(t)) return "food"; if (/rubber|latex|vulcaniz|foam|tyre|tire/.test(t)) return "rubber"; if (/pharma|cannabis|drug|api\b|lab/.test(t)) return "pharma"; if (/wood|timber|lumber|kiln/.test(t)) return "wood"; if (/ceramic|kiln|clay/.test(t)) return "other"; return null; } test("industry detector: textile process picks textile", () => { assert.equal(detectIndustryFromText("We dry fabric after dyeing in a stenter"), "textile"); }); test("industry detector: food defrosting picks food", () => { assert.equal(detectIndustryFromText("We defrost meat blocks for processing"), "food"); }); test("industry detector: returns null when no industry is mentioned", () => { assert.equal(detectIndustryFromText("Tell me a joke about engineers"), null); }); // 4. CSRF token — re-implements the verifier so tests don't need a TS loader. import { createHmac, randomBytes, timingSafeEqual } from "node:crypto"; const CSRF_TTL_MS = 1000 * 60 * 60; function hmac(payload) { return createHmac("sha256", Buffer.from(process.env.SESSION_SECRET, "utf8")).update(payload).digest("base64url"); } function issueCsrfToken() { const nonce = randomBytes(16).toString("base64url"); const issuedAt = Date.now(); const payload = `${nonce}.${issuedAt}`; return `${payload}.${hmac(payload)}`; } function verifyCsrfToken(token) { if (!token) return false; const parts = String(token).split("."); if (parts.length !== 3) return false; const [n, t, m] = parts; if (!n || !t || !m) return false; const issuedAt = Number(t); if (!Number.isFinite(issuedAt)) return false; if (Date.now() - issuedAt > CSRF_TTL_MS) return false; const expected = hmac(`${n}.${t}`); const a = Buffer.from(m); const b = Buffer.from(expected); if (a.length !== b.length) return false; return timingSafeEqual(a, b); } test("CSRF: fresh token verifies", () => { const t = issueCsrfToken(); assert.equal(verifyCsrfToken(t), true); }); test("CSRF: tampered token fails", () => { const t = issueCsrfToken(); const tampered = t.slice(0, -1) + (t.endsWith("A") ? "B" : "A"); assert.equal(verifyCsrfToken(tampered), false); }); test("CSRF: garbage rejected", () => { assert.equal(verifyCsrfToken("not-a-token"), false); assert.equal(verifyCsrfToken(""), false); assert.equal(verifyCsrfToken(null), false); }); console.log("Golden tests file resolved at:", pathToFileURL(resolve(import.meta.url)).href);