← Files DataARCHIVED FILE

tests/data-app-separate.test.mjs

18.3 KB · Sep 30, 2026 · 23:19 UTC

↓ Download file

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { cpSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import test from "node:test";
import { createContext, Script } from "node:vm";

import { assembleDataAppHtml } from "../scripts/data-app-build.mjs";
import { assertHydratedPublicationSource, exportOfflineDataApp, readSeparateDataBundle, resolveSeparateDataBundle } from "../scripts/data-app-separate.mjs";
import { sha256 } from "../scripts/prebuilt/manifest.mjs";
import { inspectDataAppDocument } from "../skills/publish-artifact-to-sites/scripts/publication-assets.mjs";
import { createStreamingJsonParser, parseJsonResponse } from "../templates/data-app/base/src/streaming-json.js";

const RUNTIME = "a".repeat(64);
const SNAPSHOT = { id: "local-test", title: "Complete data", surface: "dashboard", queries: { q: { rows: [{ value: 42 }, { value: 17 }] } } };
const SNAPSHOT_ID = "data-app-reviewed-snapshot";
const write = (path, bytes) => { mkdirSync(dirname(path), { recursive: true }); writeFileSync(path, bytes); };

function assemble(snapshotBytes = Buffer.from(JSON.stringify(SNAPSHOT))) {
  return assembleDataAppHtml({ snapshotBytes, separateData: true, runtimeSha256: RUNTIME,
    protectedStyles: "", printStyles: "",
    appCode: 'globalThis.mounts=[];globalThis.CodexDataAppRuntime={apiVersion:1,createStreamingJsonParser,parseJsonResponse,mount(options){mounts.push(options);options.createContent(options.reviewedSnapshot)}};',
    authored: { themeCss: "", conventionalCss: "", importedCss: "", factorySource: '(function(runtime,snapshot){globalThis.moduleValue=snapshot.queries.q.rows[0].value;globalThis.moduleRows=snapshot.queries.q.rows.length;return {}})' },
  }).html;
}

function fixture(t, bytes = Buffer.from(JSON.stringify(SNAPSHOT)), transformHtml = html => html) {
  const root = realpathSync(mkdtempSync(join(tmpdir(), "data-app-separate-")));
  t.after(() => rmSync(root, { recursive: true, force: true }));
  const html = transformHtml(assemble(bytes)), hash = sha256(bytes);
  const manifest = { version: 1, kind: "separate-data-v1", html: { path: "index.html", sha256: sha256(html), bytes: Buffer.byteLength(html) },
    snapshot: { path: `snapshot.${hash}.json`, sha256: hash, bytes: bytes.length }, runtimeSha256: RUNTIME, sourceSnapshotSha256: hash };
  const manifestPath = join(root, "dist/data-app-build.json");
  const snapshotPath = join(root, "dist", manifest.snapshot.path);
  write(join(root, "dist/index.html"), html); write(snapshotPath, bytes); write(manifestPath, JSON.stringify(manifest));
  return { root, html, bytes, manifest, manifestPath, snapshotPath };
}

function execute(html, { fetch = () => { throw Error("Unexpected fetch"); }, hosted = false } = {}) {
  const inspection = inspectDataAppDocument(html), root = { textContent: "" };
  const context = createContext({ URL, fetch, createStreamingJsonParser, parseJsonResponse, location: { hostname: "localhost" }, document: {
    baseURI: "http://localhost:1234/",
    getElementById(id) { return id === SNAPSHOT_ID ? { textContent: inspection.embedded } : id === "root" ? root : null; },
    querySelectorAll() { return [...html.matchAll(/<script type="application\/json" data-app-snapshot-chunk>([\s\S]*?)<\/script>/gu)].map(match => ({ textContent: match[1] })); },
    querySelector(selector) {
      if (hosted && selector.includes("data-app-snapshot-storage")) return { content: "external-v1" };
      const name = /name="([^"]+)"/u.exec(selector)?.[1];
      const marker = inspection.metadata.find(meta => meta.names.includes(name));
      return marker ? { content: marker.contents[0] } : null;
    },
  } });
  for (const [, attributes, source] of html.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gu)) {
    if (attributes.includes('type="application/json"')) continue;
    const encoded = /src="data:text\/javascript;charset=utf-8;base64,([^"]+)"/u.exec(attributes)?.[1];
    new Script(encoded ? Buffer.from(encoded, "base64").toString() : source).runInContext(context);
  }
  return { context, root };
}

const flush = () => new Promise(resolve => setImmediate(resolve));

test("separate local preview waits for complete data before executing authored module capture", async () => {
  const rows = Array.from({ length: 10001 }, (_, value) => ({ value: value + 42 }));
  const snapshot = { ...SNAPSHOT, queries: { q: { rows } } };
  let release, called = 0;
  const html = assemble(Buffer.from(JSON.stringify(snapshot)));
  const { context, root } = execute(html, { fetch(url, options) {
    called++; assert.equal(String(url), `http://localhost:1234/snapshot.${sha256(JSON.stringify(snapshot))}.json`);
    assert.equal(options.credentials, "same-origin");
    return new Promise(resolve => { release = () => resolve(new Response(JSON.stringify(snapshot))); });
  } });
  assert.equal(called, 1); assert.equal(context.mounts.length, 0); assert.equal(context.moduleValue, undefined);
  assert.equal(root.textContent, "Loading data…");
  release(); await flush();
  assert.equal(context.mounts.length, 1); assert.equal(context.moduleValue, 42); assert.equal(context.moduleRows, rows.length);
  assert.deepEqual(context.mounts[0].reviewedSnapshot, snapshot);
});

test("missing, failed, or malformed local snapshot never mounts an empty authored app", async () => {
  for (const response of [new Error("secret transport detail"), { ok: false }, new Response('{"queries":[]}'),
    new Response('{"secret JSON detail":')]) {
    const { context, root } = execute(assemble(), { fetch: async () => { if (response instanceof Error) throw response; return response; } });
    await flush();
    assert.equal(context.mounts.length, 0); assert.equal(context.moduleValue, undefined);
    assert.match(root.textContent, /reviewed data could not be loaded/u); assert.doesNotMatch(root.textContent, /secret/u);
  }
});

test("hosted separate bootstrap delegates complete-data initialization to the hosted runtime", () => {
  const html = assemble().replace('mounts.push(options);options.createContent(options.reviewedSnapshot)', 'mounts.push(options)');
  const { context } = execute(html, { hosted: true });
  assert.equal(context.mounts.length, 1); assert.equal(context.moduleValue, undefined);
  assert.equal(context.mounts[0].hosted, true);
  assert.equal(Object.keys(context.mounts[0].reviewedSnapshot.queries).length, 0);
  context.mounts[0].createContent(SNAPSHOT);
  assert.equal(context.moduleValue, 42); assert.equal(context.moduleRows, 2);
});

test("bundle validation binds exact raw bytes, content-addressed paths, HTML metadata, and runtime", t => {
  const state = fixture(t, Buffer.from(`\ufeff${JSON.stringify(SNAPSHOT, null, 2)}\n`));
  const result = readSeparateDataBundle({ projectDir: state.root });
  assert.deepEqual(result.manifest, state.manifest); assert.equal(result.snapshotPath, state.snapshotPath);
  assert.deepEqual(readSeparateDataBundle({ projectDir: state.root, manifestPath: state.manifestPath }).manifest, state.manifest);
  write(state.snapshotPath, Buffer.concat([state.bytes, Buffer.from(" ")]));
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root }), /bundle/u);
  write(state.snapshotPath, state.bytes);
  for (const change of [m => { m.snapshot.path = "../src/data.json"; }, m => { m.version = 2; },
    m => { m.runtimeSha256 = "b".repeat(64); }, m => { m.snapshot.bytes++; }, m => { m.extra = true; }]) {
    const manifest = structuredClone(state.manifest); change(manifest); write(state.manifestPath, JSON.stringify(manifest));
    assert.throws(() => readSeparateDataBundle({ projectDir: state.root }), /bundle/u);
  }
});

test("bundle validation rejects live marker ambiguity, escapes, and symlinked inputs", t => {
  const state = fixture(t);
  const invalid = state.html.replace('<meta name="data-app-build-layout"', '<meta name="data-app-build-layout" content="separate-data-v1"><meta name="data-app-build-layout"');
  write(join(state.root, "dist/index.html"), invalid);
  write(state.manifestPath, JSON.stringify({ ...state.manifest, html: { ...state.manifest.html, bytes: Buffer.byteLength(invalid), sha256: sha256(invalid) } }));
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root }), /bundle/u);
  write(join(state.root, "dist/index.html"), state.html); write(state.manifestPath, JSON.stringify(state.manifest));
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root, manifestPath: "../data-app-build.json" }), /bundle/u);
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root, manifestPath: join(dirname(state.root), "data-app-build.json") }), /bundle/u);
  write(join(state.root, "raw.json"), state.bytes); rmSync(state.snapshotPath); symlinkSync(join(state.root, "raw.json"), state.snapshotPath);
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root }), /bundle/u);
});

test("malformed manifest and bootstrap errors do not expose input text", t => {
  const state = fixture(t);
  write(state.manifestPath, '{"password":"synthetic-sensitive-value",broken}');
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root }), error => /bundle/u.test(error.message) && !/password|sensitive/u.test(error.message));
});

test("offline export streams all rows and escaping across UTF-8 boundaries without fetching or recompiling", async t => {
  const snapshot = structuredClone(SNAPSHOT);
  snapshot.queries.q.rows = Array.from({ length: 20001 }, (_, value) => ({ value, text: "é😀</script>\u2028\u2029" }));
  snapshot.queries.q.rows[0].value = 42;
  const raw = Buffer.from(`\ufeff${JSON.stringify(snapshot, null, 2)}\n`), state = fixture(t, raw);
  write(join(state.root, "src/data.json"), JSON.stringify({ queries: {} }));
  const exported = await exportOfflineDataApp({ projectDir: state.root, outputPath: ".data-app-offline/exports/reviewed.html" });
  const html = readFileSync(exported.htmlPath, "utf8"), inspection = inspectDataAppDocument(html);
  assert.equal(sha256(html), exported.htmlSha256); assert.equal(Buffer.byteLength(html), exported.htmlBytes);
  assert.equal(inspection.metadata.some(meta => meta.names.includes("data-app-local-snapshot") || meta.names.includes("data-app-build-layout")), false);
  const parts = [...html.matchAll(/<script type="application\/json"[^>]*>([\s\S]*?)<\/script>/gu)].map(match => match[1]);
  assert.ok(parts.length > 1);
  assert.ok(parts.every(part => part.length <= 6 * 65536), "Each escaped DOM string stays bounded");
  assert.deepEqual(JSON.parse(parts.join("")), snapshot); assert.doesNotMatch(parts.join(""), /[<\u2028\u2029]/u);
  const { context } = execute(html);
  assert.equal(context.moduleValue, 42); assert.equal(context.moduleRows, 20001); assert.equal(context.mounts.length, 1);
  assert.deepEqual(readFileSync(state.snapshotPath), raw);
});

test("encoded streaming bootstraps retain bounded offline data elements", async t => {
  const snapshot = structuredClone(SNAPSHOT);
  snapshot.queries.q.rows = Array.from({ length: 20001 }, (_, value) => ({ value }));
  const state = fixture(t, Buffer.from(JSON.stringify(snapshot)), html => {
    const start = html.lastIndexOf("<script>"), end = html.indexOf("</script>", start);
    const encoded = Buffer.from(html.slice(start + 8, end)).toString("base64");
    return html.slice(0, start) + `<script src="data:text/javascript;charset=utf-8;base64,${encoded}"></script>` + html.slice(end + 9);
  });
  const result = await exportOfflineDataApp({ projectDir: state.root });
  const html = readFileSync(result.htmlPath, "utf8");
  assert.match(html, /<script type="application\/json" data-app-snapshot-chunk>/u);
  assert.equal(execute(html).context.moduleRows, 20001);
});

test("older compiled bootstraps export complete data without requiring a runtime upgrade", async t => {
  const snapshot = structuredClone(SNAPSHOT);
  snapshot.queries.q.rows = Array.from({ length: 20001 }, (_, value) => ({ value }));
  snapshot.queries.q.rows[0].value = 42;
  const state = fixture(t, Buffer.from(JSON.stringify(snapshot)), html => html
    .replace(/const continuation = document\.querySelectorAll\([^\n]+\n/u, "")
    .replace(/const reviewedSnapshot = continuation\.length[^\n]+\n/u, "const reviewedSnapshot = JSON.parse(snapshotElement.textContent);\n"));
  const result = await exportOfflineDataApp({ projectDir: state.root });
  const html = readFileSync(result.htmlPath, "utf8");
  assert.doesNotMatch(html, /<script type="application\/json" data-app-snapshot-chunk>/u);
  assert.deepEqual(JSON.parse(inspectDataAppDocument(html).embedded), snapshot);
  const { context } = execute(html);
  assert.equal(context.moduleRows, 20001);
});

test("post-publication export reads only the hash-bound preserved bundle and refuses stale recovery", async t => {
  const state = fixture(t), preserved = join(state.root, ".data-app-offline/separate-v1");
  cpSync(join(state.root, "dist"), preserved, { recursive: true });
  rmSync(join(state.root, "dist"), { recursive: true });
  const hosted = "<html>Reviewed hosted page</html>";
  write(join(state.root, "dist/index.html"), hosted);
  write(join(state.root, "src/data.json"), '{"queries":{}}');
  const publication = { version: 1, source: { layout: "separate-data-v1", buildManifestPath: ".data-app-offline/separate-v1/data-app-build.json",
    htmlSha256: state.manifest.html.sha256, snapshotSha256: state.manifest.snapshot.sha256 },
    assets: { html: { sha256: sha256(hosted), bytes: Buffer.byteLength(hosted) }, snapshot: state.manifest.snapshot } };
  write(join(state.root, ".data-app-assets/manifest.json"), JSON.stringify(publication));
  assert.equal(resolveSeparateDataBundle({ projectDir: state.root }).snapshotPath, join(preserved, state.manifest.snapshot.path));
  const result = await exportOfflineDataApp({ projectDir: state.root });
  assert.equal(result.htmlPath, join(state.root, ".data-app-offline/exports/data-app-offline.html"));
  assert.deepEqual(JSON.parse(inspectDataAppDocument(readFileSync(result.htmlPath, "utf8")).embedded), SNAPSHOT);
  write(join(state.root, "dist/index.html"), `${hosted}changed`);
  await assert.rejects(exportOfflineDataApp({ projectDir: state.root }), /bundle/u);
  assert.equal(sha256(readFileSync(result.htmlPath)), result.htmlSha256);
});

test("offline output cannot overwrite source, build, preserved bundle, or symlink targets", async t => {
  const state = fixture(t);
  for (const outputPath of ["src/custom.html", "dist/export.html", ".data-app-offline/separate-v1/export.html", "../export.html", "data-app-offline.html", "exports/reviewed.html", ".data-app-offline/exports/../outside.html", ".data-app-offline/exports-lookalike/file.html", ".data-app-offline/exports/raw.json"]) {
    await assert.rejects(exportOfflineDataApp({ projectDir: state.root, outputPath }), /\.data-app-offline\/exports\//u);
  }
  write(join(state.root, ".data-app-offline/exports/unchanged.html"), "original");
  symlinkSync(join(state.root, ".data-app-offline/exports/unchanged.html"), join(state.root, ".data-app-offline/exports/export.html"));
  await assert.rejects(exportOfflineDataApp({ projectDir: state.root, outputPath: ".data-app-offline/exports/export.html" }));
  assert.equal(readFileSync(join(state.root, ".data-app-offline/exports/unchanged.html"), "utf8"), "original");
});

test("a snapshot changed during streaming leaves the previous offline export intact", t => {
  const state = fixture(t), output = join(state.root, ".data-app-offline/exports/data-app-offline.html"), script = join(state.root, "changed-input.mjs");
  write(output, "previous reviewed export");
  write(script, `import fs from "node:fs";
import { syncBuiltinESMExports } from "node:module";
const original = fs.createReadStream;
fs.createReadStream = function(path, ...args) {
  fs.writeFileSync(path, ${JSON.stringify(Buffer.from(JSON.stringify(SNAPSHOT)).toString().replace('"value":42', '"value":43'))});
  return original.call(this, path, ...args);
};
syncBuiltinESMExports();
const { exportOfflineDataApp } = await import(${JSON.stringify(new URL("../scripts/data-app-separate.mjs", import.meta.url).href)});
await exportOfflineDataApp({projectDir: ${JSON.stringify(state.root)}});
`);
  const result = spawnSync(process.execPath, [script], { encoding: "utf8", timeout: 10000 });
  assert.equal(result.error, undefined); assert.notEqual(result.status, 0); assert.match(result.stderr, /bundle is missing, invalid, or changed/u);
  assert.equal(readFileSync(output, "utf8"), "previous reviewed export");
});

test("publication source projection requires exactly the pinned hydrated bytes", t => {
  const state = fixture(t);
  write(join(state.root, ".openai/data-app-publication-source.json"), JSON.stringify({ version: 1, kind: "data-app-publication-source-v1",
    sourceSnapshotSha256: state.manifest.snapshot.sha256, snapshot: state.manifest.snapshot }));
  assert.throws(() => assertHydratedPublicationSource(state.root), /exact verified snapshot hydration/u);
  write(join(state.root, "src/data.json"), '{"queries":{}}');
  assert.throws(() => assertHydratedPublicationSource(state.root), /exact verified snapshot hydration/u);
  write(join(state.root, "src/data.json"), state.bytes);
  assert.doesNotThrow(() => assertHydratedPublicationSource(state.root));
});

test("a valid separate bundle from another snapshot cannot override a hydrated projection pin", async t => {
  const state = fixture(t), other = fixture(t, Buffer.from(JSON.stringify({ ...SNAPSHOT, title: "Different reviewed snapshot" })));
  write(join(state.root, ".openai/data-app-publication-source.json"), JSON.stringify({ version: 1, kind: "data-app-publication-source-v1",
    sourceSnapshotSha256: state.manifest.snapshot.sha256, snapshot: state.manifest.snapshot }));
  write(join(state.root, "src/data.json"), state.bytes);
  cpSync(join(other.root, "dist"), join(state.root, "dist"), { recursive: true });
  write(join(state.root, ".data-app-offline/exports/data-app-offline.html"), "preserved export");
  assert.throws(() => readSeparateDataBundle({ projectDir: state.root }), /bundle/u);
  await assert.rejects(exportOfflineDataApp({ projectDir: state.root }), /bundle/u);
  assert.equal(readFileSync(join(state.root, ".data-app-offline/exports/data-app-offline.html"), "utf8"), "preserved export");
});

test("invalid source JSON diagnostics do not retain parser excerpts or causes", () => {
  assert.throws(() => assemble(Buffer.from('{"queries":{},"password":"synthetic-sensitive-value", broken}')), error =>
    error.message === "The reviewed Data app snapshot must be valid UTF-8 JSON." && error.cause === undefined);
});

SHA-256: c023a3428f1265b88cf6f705ac7c62fd4ef9c615ab02cc98ada34b1c11e1cdec