← Files DataARCHIVED FILE

tests/data-app-sites-separated-publication.test.mjs

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

↓ Download file

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import test from "node:test";
import { createDataAppWorker } from "../templates/data-app/base/src/data-app-worker.js";
import { fixtureDatabase, reviewedSeed, sqliteUnavailable } from "../templates/data-app/base/tests/snapshot-storage-fixture.mjs";

const hash = value => createHash("sha256").update(value).digest("hex");
const owner = "owner@example.com", token = "synthetic-current-deployment-only";
const authorization = { sha256: hash(token), expiresAt: "2999-01-01T00:00:00.000Z" };
function descriptor(kind, body) {
  const sha256 = hash(body);
  return { key: `data-app/${kind}/${sha256}`, sha256, bytes: Buffer.byteLength(body) };
}
function fixture(seed = reviewedSeed(5), raw = JSON.stringify(seed)) {
  const html = "<!doctype html><html><head></head><body>Reviewed application</body></html>";
  const assets = { html: descriptor("html", html), snapshot: descriptor("snapshot", raw) };
  const stored = new Map([
    [assets.html.key, { raw: html, size: assets.html.bytes, customMetadata: { sha256: assets.html.sha256 } }],
    [assets.snapshot.key, { raw, size: assets.snapshot.bytes, customMetadata: { sha256: assets.snapshot.sha256 } }],
  ]);
  const calls = [];
  let forbidJson = false;
  const bucket = {
    async get(key) {
      calls.push(key);
      const value = stored.get(key);
      if (!value) return null;
      return { size: value.size, customMetadata: value.customMetadata, body: new Response(value.raw).body,
        async json() {
          assert.equal(forbidJson, false, "Immutable asset reads must not parse the snapshot");
          return JSON.parse(value.raw);
        } };
    },
    async put() { assert.fail("Read-only immutable snapshot tests must not write bucket objects"); },
  };
  return { seed, raw, html, assets, stored, calls, bucket, forbidJson() { forbidJson = true; },
    configuration: { deploymentAssets: assets, deploymentUploadAuthorization: authorization } };
}
function request(path, { email, deploymentToken, method = "GET", body, headers = {} } = {}) {
  return new Request(`https://same-site.example${path}`, { method,
    ...(body === undefined ? {} : { body: typeof body === "string" ? body : JSON.stringify(body) }),
    headers: { ...(email ? { "oai-authenticated-user-email": email } : {}), ...(deploymentToken ? { "x-data-app-deployment-token": deploymentToken } : {}), ...headers } });
}
const currentPath = "/api/deployment-assets/snapshot";
const noStorage = {
  DATA_APP_OWNER_EMAIL_SHA256: hash(owner),
  get BUCKET() { assert.fail("Rejected immutable reads must not access the bucket"); },
  get DB() { assert.fail("Immutable asset reads must not access mutable database state"); },
};
function immutableEnvironment(value) {
  value.forbidJson();
  return { DATA_APP_OWNER_EMAIL_SHA256: hash(owner), BUCKET: value.bucket, get DB() { assert.fail("Immutable asset reads must not access mutable database state"); } };
}

test("current immutable snapshot streams exact original bytes for owner or current deployment token", async () => {
  const seed = reviewedSeed(3); seed.queries.reviewed.rows[2].unicode = "café 日本語 🧪".repeat(50_000);
  const raw = JSON.stringify(seed, null, 2) + "\n";
  const value = fixture(seed, raw), worker = createDataAppWorker(value.configuration), environment = immutableEnvironment(value);
  for (const viewer of [{ email: owner.toUpperCase() }, { deploymentToken: token }]) {
    const response = await worker.fetch(request(currentPath, viewer), environment);
    assert.equal(response.status, 200);
    assert.equal(response.headers.get("content-type"), "application/octet-stream");
    assert.equal(response.headers.get("cache-control"), "private, no-store");
    assert.ok(response.body instanceof ReadableStream);
    const bytes = Buffer.from(await response.arrayBuffer());
    assert.equal(bytes.length, value.assets.snapshot.bytes);
    assert.equal(hash(bytes), value.assets.snapshot.sha256);
    assert.equal(bytes.toString(), raw);
  }
  assert.deepEqual(value.calls, [value.assets.snapshot.key, value.assets.snapshot.key]);
});

test("current immutable snapshot rejects absent, foreign, incorrect, expired and malformed authority before storage", async t => {
  for (const [name, viewer, override, ownerValue = hash(owner)] of [
    ["anonymous", {}, {}], ["nonowner viewer", { email: "viewer@example.com" }, {}],
    ["incorrect token", { deploymentToken: "incorrect" }, {}], ["oversized token", { deploymentToken: "x".repeat(257) }, {}],
    ["expired token", { deploymentToken: token }, { deploymentUploadAuthorization: { ...authorization, expiresAt: "2000-01-01T00:00:00Z" } }],
    ["malformed expiry", { deploymentToken: token }, { deploymentUploadAuthorization: { ...authorization, expiresAt: "invalid" } }],
    ["no token configuration", { deploymentToken: token }, { deploymentUploadAuthorization: undefined }],
    ["invalid owner configuration", { email: owner }, {}, "invalid"],
  ]) await t.test(name, async () => {
    const value = fixture(), worker = createDataAppWorker({ ...value.configuration, ...override });
    const environment = Object.create(noStorage);
    environment.DATA_APP_OWNER_EMAIL_SHA256 = ownerValue;
    assert.equal((await worker.fetch(request(currentPath, viewer), environment)).status, 403);
  });
});

test("historical snapshots are owner-only fixed content-addressed reads independent of the current descriptor", async () => {
  const value = fixture(), worker = createDataAppWorker(value.configuration);
  const priorRaw = JSON.stringify({ id: "prior", queries: { old: { rows: [{ value: 13, note: "exact old bytes" }] } } }, null, 3) + "\n";
  const prior = descriptor("snapshot", priorRaw);
  value.stored.set(prior.key, { raw: priorRaw, size: prior.bytes, customMetadata: { sha256: prior.sha256 } });
  const environment = immutableEnvironment(value), path = `${currentPath}?sha256=${prior.sha256}`;
  for (const viewer of [{}, { email: "viewer@example.com" }, { deploymentToken: token }, { deploymentToken: token, email: "viewer@example.com" }]) {
    assert.equal((await worker.fetch(request(path, viewer), noStorage)).status, 403);
  }
  const response = await worker.fetch(request(path, { email: owner }), environment);
  assert.equal(response.status, 200);
  assert.equal(response.headers.get("content-type"), "application/octet-stream");
  assert.equal(response.headers.get("cache-control"), "private, no-store");
  assert.equal(await response.text(), priorRaw);
  assert.deepEqual(value.calls, [prior.key]);
  // Supplying even the current digest through the historical API requires owner auth.
  assert.equal((await worker.fetch(request(`${currentPath}?sha256=${value.assets.snapshot.sha256}`, { deploymentToken: token }), noStorage)).status, 403);
});

test("historical query validation rejects unknown, repeated, malformed or escaping selectors before storage", async () => {
  const value = fixture(), worker = createDataAppWorker(value.configuration), valid = value.assets.snapshot.sha256;
  for (const query of ["sha256=", "sha256=abc", `sha256=${"A".repeat(64)}`, `sha256=${valid}&sha256=${valid}`, `sha256=${valid}&unknown=x`,
    `unknown=${valid}`, "sha256=..%2f..%2fprivate", "sha256=%00", `key=data-app%2Fsnapshot%2F${valid}`]) {
    assert.equal((await worker.fetch(request(`${currentPath}?${query}`, { email: owner }), noStorage)).status, 400, query);
  }
  for (const path of [`${currentPath}/../private`, `${currentPath}%2fprivate`, `${currentPath}/extra`, "/api/deployment-assets/%73napshot"]) {
    assert.equal((await worker.fetch(request(path, { email: owner }), noStorage)).status, 404, path);
  }
});

test("historical selectors cannot write snapshots, including with owner or deployment authority", async () => {
  const value = fixture(), worker = createDataAppWorker(value.configuration), path = `${currentPath}?sha256=${value.assets.snapshot.sha256}`;
  for (const viewer of [{ email: owner }, { deploymentToken: token }]) {
    const response = await worker.fetch(request(path, { ...viewer, method: "PUT", body: value.raw, headers: { "content-length": String(value.assets.snapshot.bytes) } }), noStorage);
    assert.equal(response.status, 400);
  }
  for (const method of ["POST", "DELETE", "PATCH", "HEAD"]) {
    assert.equal((await worker.fetch(request(path, { email: owner, method }), noStorage)).status, 404);
  }
});

test("missing storage, absent assets, invalid sizes and mismatched metadata fail without parsing or database use", async t => {
  for (const historical of [false, true]) await t.test(historical ? "historical" : "current", async () => {
    const value = fixture(), worker = createDataAppWorker(value.configuration);
    const path = currentPath + (historical ? `?sha256=${value.assets.snapshot.sha256}` : "");
    const saved = value.stored.get(value.assets.snapshot.key), environment = immutableEnvironment(value);
    assert.equal((await worker.fetch(request(path, { email: owner }), { DATA_APP_OWNER_EMAIL_SHA256: hash(owner) })).status, 503);
    assert.equal((await worker.fetch(request(path, { email: owner }), { DATA_APP_OWNER_EMAIL_SHA256: hash(owner), BUCKET: { async get() { throw Error("Private storage detail"); } } })).status, 503);
    value.stored.delete(value.assets.snapshot.key);
    assert.equal((await worker.fetch(request(path, { email: owner }), environment)).status, 503);
    for (const invalid of [{ ...saved, customMetadata: undefined }, { ...saved, customMetadata: { sha256: "f".repeat(64) } },
      ...[0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, "42", NaN].map(size => ({ ...saved, size }))]) {
      value.stored.set(value.assets.snapshot.key, invalid);
      const response = await worker.fetch(request(path, { email: owner }), environment);
      assert.equal(response.status, 503);
      assert.equal((await response.json()).error, "Immutable snapshot is unavailable.");
    }
    if (!historical) {
      value.stored.set(value.assets.snapshot.key, { ...saved, size: saved.size + 1 });
      assert.equal((await worker.fetch(request(path, { email: owner }), environment)).status, 503, "Current read checks configured size exactly");
    }
  });
});

test("invalid explicit canonical seed hashes fail before any request and omitted hashes stay compatible", () => {
  const value = fixture();
  for (const seedSnapshotSha256 of [null, false, 0, "", "a".repeat(63), "A".repeat(64), `${"a".repeat(64)}\n`]) {
    assert.throws(() => createDataAppWorker({ ...value.configuration, seedSnapshotSha256 }), /canonical Data app seed fingerprint is invalid/);
  }
  assert.doesNotThrow(() => createDataAppWorker(value.configuration));
  assert.doesNotThrow(() => createDataAppWorker({ ...value.configuration, seedSnapshotSha256: hash(value.raw) }));
});

test("formatting-only raw snapshot changes preserve legacy canonical seed edits and mutable presentation", { skip: sqliteUnavailable }, async t => {
  const original = fixture(), storage = fixtureDatabase(); t.after(() => storage.close());
  const environment = { DATA_APP_OWNER_EMAIL_SHA256: hash(owner), DB: storage.db, BUCKET: original.bucket };
  const initial = createDataAppWorker({ ...original.configuration, initialPresentation: { title: "Initial seed title" } });
  assert.deepEqual(await (await initial.fetch(request("/api/snapshot"), environment)).json(), original.seed);
  const editedRows = [{ value: 99, note: "Owner-reviewed correction" }];
  assert.equal((await initial.fetch(request("/api/queries/reviewed", { email: owner, method: "PUT", body: { rows: editedRows } }), environment)).status, 200);
  assert.equal((await initial.fetch(request("/api/presentation", { email: owner, method: "PUT", body: { revision: 0, presentation: { title: "Saved owner title" } } }), environment)).status, 200);
  const headBefore = storage.sqlite.prepare("SELECT current_generation, seed_sha256 FROM data_app_snapshot_head_v2 WHERE id='current'").get();
  assert.equal(headBefore.seed_sha256, original.assets.snapshot.sha256, "Omitted canonical config retains the existing asset-hash seed contract");

  const formatted = fixture(original.seed, JSON.stringify(original.seed, null, 2) + "\n");
  assert.notEqual(formatted.assets.snapshot.sha256, original.assets.snapshot.sha256);
  const canonicalSeedHash = hash(JSON.stringify(JSON.parse(formatted.raw)));
  assert.equal(canonicalSeedHash, original.assets.snapshot.sha256);
  formatted.stored.set(original.assets.snapshot.key, original.stored.get(original.assets.snapshot.key));
  const redeployed = createDataAppWorker({ ...formatted.configuration, seedSnapshotSha256: canonicalSeedHash, initialPresentation: { title: "New packaged default" } });
  const nextEnvironment = { DATA_APP_OWNER_EMAIL_SHA256: hash(owner), DB: storage.db, BUCKET: formatted.bucket };
  const after = await (await redeployed.fetch(request("/api/snapshot"), nextEnvironment)).json();
  assert.deepEqual(after.queries.reviewed.rows, editedRows);
  assert.deepEqual(after.queries.__proto__, original.seed.queries.__proto__);
  assert.deepEqual(after.queries.empty, original.seed.queries.empty);
  assert.deepEqual(storage.sqlite.prepare("SELECT current_generation, seed_sha256 FROM data_app_snapshot_head_v2 WHERE id='current'").get(), headBefore);
  const presentation = await (await redeployed.fetch(request("/api/presentation", { email: owner }), nextEnvironment)).json();
  assert.equal(presentation.presentation.title, "Saved owner title");
  assert.equal(presentation.canEdit, true);
  assert.deepEqual(formatted.calls, [], "Warm same-seed reads do not parse either full source asset");
  const immutable = await redeployed.fetch(request(currentPath, { email: owner }), immutableEnvironment(formatted));
  assert.equal(await immutable.text(), formatted.raw, "Immutable source remains the reviewed seed, separate from mutable owner rows");
  const historical = await redeployed.fetch(request(`${currentPath}?sha256=${original.assets.snapshot.sha256}`, { email: owner }), immutableEnvironment(formatted));
  assert.equal(await historical.text(), original.raw);
});

test("changed canonical seed data activates the new complete source without resetting presentation", { skip: sqliteUnavailable }, async t => {
  const original = fixture(), storage = fixtureDatabase(); t.after(() => storage.close());
  const first = createDataAppWorker(original.configuration), environment = { DATA_APP_OWNER_EMAIL_SHA256: hash(owner), DB: storage.db, BUCKET: original.bucket };
  await (await first.fetch(request("/api/snapshot"), environment)).json();
  await first.fetch(request("/api/presentation", { email: owner, method: "PUT", body: { revision: 0, presentation: { title: "Preserve layout title" } } }), environment);
  const changedSeed = structuredClone(original.seed); changedSeed.queries.reviewed.rows[0].value = 456;
  const changed = fixture(changedSeed, JSON.stringify(changedSeed, null, 2)), next = createDataAppWorker({ ...changed.configuration, seedSnapshotSha256: hash(JSON.stringify(changedSeed)) });
  const nextEnvironment = { DATA_APP_OWNER_EMAIL_SHA256: hash(owner), DB: storage.db, BUCKET: changed.bucket };
  assert.deepEqual(await (await next.fetch(request("/api/snapshot"), nextEnvironment)).json(), changedSeed);
  assert.equal((await (await next.fetch(request("/api/presentation", { email: owner }), nextEnvironment)).json()).presentation.title, "Preserve layout title");
});

SHA-256: 9ef5725169540d2a4cd62e1c5d1596e98e131171a7ee42e494d2b3631d377526