← Files DataARCHIVED FILE
templates/data-app/base/tests/deployment-assets.test.mjs
15.5 KB · Sep 30, 2026 · 23:19 UTC
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { after, before, test } from "node:test";
import { gzipSync } from "node:zlib";
import { createDataAppWorker } from "../src/data-app-worker.js";
import { fixtureDatabase, reviewedSeed, sqliteUnavailable } from "./snapshot-storage-fixture.mjs";
const hash = value => createHash("sha256").update(value).digest("hex");
const owner = "owner@example.com", token = "synthetic-deployment-test-only";
const authorization = { sha256: hash(token), expiresAt: "2999-01-01T00:00:00.000Z" };
// Node provides Compression Streams but not Workers' FixedLengthStream. This
// fixture enforces its byte-count contract; native R2/Workers need hosted QA.
const originalFixedLengthStream = globalThis.FixedLengthStream;
before(() => {
if (originalFixedLengthStream) return;
globalThis.FixedLengthStream = class extends TransformStream {
constructor(expectedBytes) {
let bytes = 0;
super({
transform(chunk, controller) {
bytes += chunk.byteLength;
if (bytes > expectedBytes) throw new Error("FixedLengthStream overflow");
controller.enqueue(chunk);
},
flush() {
if (bytes !== expectedBytes) throw new Error("FixedLengthStream underflow");
},
});
}
};
});
after(() => {
if (!originalFixedLengthStream) delete globalThis.FixedLengthStream;
});
function fixture() {
const seed = reviewedSeed(), payloads = { html: Buffer.from("<!doctype html><title>Reviewed</title>"), snapshot: Buffer.from(JSON.stringify(seed)) };
const assets = Object.fromEntries(Object.entries(payloads).map(([kind, body]) => {
const sha256 = hash(body); return [kind, { key: `data-app/${kind}/${sha256}`, sha256, bytes: body.length }];
}));
const stored = new Map(), calls = [];
const bucket = {
async put(key, stream, options) {
calls.push({ key, options });
assert.ok(stream instanceof ReadableStream, "Upload must stay streamed through the Worker");
const body = Buffer.from(await new Response(stream).arrayBuffer());
// R2 rejects an incorrect supplied checksum before changing the object.
if (hash(body) !== options.sha256) throw new Error("R2 checksum mismatch");
const value = { body, options }; stored.set(key, value);
return { size: body.length };
},
async get(key) {
const value = stored.get(key);
if (!value) return null;
return { size: value.body.length, customMetadata: value.options.customMetadata,
body: new Response(value.body).body, json: async () => JSON.parse(value.body) };
},
};
const configuration = { deploymentAssets: assets, deploymentUploadAuthorization: authorization };
return { seed, payloads, assets, stored, calls, bucket, configuration };
}
function upload(kind, body, headers = {}) {
const request = new Request(`https://dashboard.chatgpt.site/api/deployment-assets/${kind}`, {
method: "PUT", body,
headers: { "content-length": String(body.length), "x-data-app-deployment-token": token, ...headers },
});
Object.defineProperty(request, "arrayBuffer", { value() { assert.fail("Worker must not buffer the upload"); } });
return request;
}
const viewerRequest = (path, { email, method = "GET", body, deploymentToken } = {}) => new Request(`https://dashboard.chatgpt.site${path}`, {
method, ...(body === undefined ? {} : { body: JSON.stringify(body) }),
headers: { ...(email ? { "oai-authenticated-user-email": email } : {}), ...(deploymentToken ? { "x-data-app-deployment-token": deploymentToken } : {}) },
});
test("deployment upload streams both exact assets and serves HTML/permalinks without database work", async () => {
const fixtureValue = fixture(), worker = createDataAppWorker(fixtureValue.configuration);
const environment = { BUCKET: fixtureValue.bucket, get DB() { assert.fail("HTML should not initialize D1"); } };
for (const kind of ["html", "snapshot"]) {
const response = await worker.fetch(upload(kind, fixtureValue.payloads[kind]), environment);
assert.equal(response.status, 200);
assert.deepEqual(await response.json(), { kind, sha256: fixtureValue.assets[kind].sha256, bytes: fixtureValue.payloads[kind].length });
}
for (const path of ["/", "/index.html", "/_data/charts/safe-id", "/_data/charts/safe-id/detail", "/_data/components/safe-id"]) {
const response = await worker.fetch(viewerRequest(path), environment);
assert.equal(response.status, 200); assert.equal(await response.text(), fixtureValue.payloads.html.toString());
const head = await worker.fetch(viewerRequest(path, { method: "HEAD" }), environment);
assert.equal(head.status, 200); assert.equal(await head.text(), "");
}
const readback = await worker.fetch(viewerRequest("/api/deployment-assets/html", { deploymentToken: token }), environment);
assert.equal(readback.status, 200);
assert.equal(readback.headers.get("content-type"), "application/octet-stream");
assert.equal(await readback.text(), fixtureValue.payloads.html.toString());
assert.equal(fixtureValue.calls.length, 2);
});
test("gzip deployment uploads store and serve the original bytes for owners and deployment tokens", async () => {
const value = fixture(), worker = createDataAppWorker(value.configuration);
const environment = { BUCKET: value.bucket, DATA_APP_OWNER_EMAIL_SHA256: hash(owner) };
for (const kind of ["html", "snapshot"]) {
for (const authority of [{}, { "x-data-app-deployment-token": "", "oai-authenticated-user-email": owner }]) {
const compressed = gzipSync(value.payloads[kind]);
const result = await worker.fetch(upload(kind, compressed, { "content-encoding": "gzip", ...authority }), environment);
assert.equal(result.status, 200);
assert.deepEqual(await result.json(), { kind, sha256: value.assets[kind].sha256, bytes: value.payloads[kind].length });
assert.deepEqual(value.stored.get(value.assets[kind].key).body, value.payloads[kind]);
const readback = await worker.fetch(viewerRequest(`/api/deployment-assets/${kind}`, { deploymentToken: token }), environment);
assert.equal(readback.status, 200);
assert.equal(readback.headers.get("content-encoding"), null, "Stored assets must remain uncompressed");
assert.deepEqual(Buffer.from(await readback.arrayBuffer()), value.payloads[kind]);
}
}
});
test("gzip upload corruption, truncation, expansion and checksum failures preserve an existing asset", async t => {
const value = fixture(), worker = createDataAppWorker(value.configuration), environment = { BUCKET: value.bucket };
assert.equal((await worker.fetch(upload("snapshot", value.payloads.snapshot), environment)).status, 200);
const original = value.stored.get(value.assets.snapshot.key);
const compressed = gzipSync(value.payloads.snapshot);
const corrupt = Buffer.from(compressed); corrupt[corrupt.length - 8] ^= 1;
const cases = [
["not gzip", value.payloads.snapshot],
["corrupt checksum", corrupt],
["truncated gzip", compressed.subarray(0, compressed.length - 8)],
["decoded bytes too short", gzipSync(value.payloads.snapshot.subarray(1))],
["decoded bytes too large", gzipSync(Buffer.alloc(value.payloads.snapshot.length * 100, 120))],
["wrong content with exact byte count", gzipSync(Buffer.alloc(value.payloads.snapshot.length, 120))],
];
for (const [name, body] of cases) await t.test(name, async () => {
const result = await worker.fetch(upload("snapshot", body, { "content-encoding": "gzip" }), environment);
assert.equal(result.status, 400);
assert.equal(value.stored.get(value.assets.snapshot.key), original, "An invalid upload must not replace the reviewed bytes");
});
});
test("unsupported compression and malformed gzip lengths are rejected before storage", async () => {
const value = fixture(), worker = createDataAppWorker(value.configuration);
const compressed = gzipSync(value.payloads.html);
const headers = [
{ "content-encoding": "br" }, { "content-encoding": "gzip, gzip" },
...["", "0", "-1", "1.5", "01", "1e3", "9007199254740992"].map(length => ({ "content-encoding": "gzip", "content-length": length })),
];
const missingLength = upload("html", compressed, { "content-encoding": "gzip" }); missingLength.headers.delete("content-length");
for (const request of [missingLength, ...headers.map(value => upload("html", compressed, value))]) {
const result = await worker.fetch(request, { get BUCKET() { assert.fail("Invalid transport must not access storage"); } });
assert.equal(result.status, 400);
}
const plain = await worker.fetch(upload("html", value.payloads.html, { "content-encoding": "identity" }), { BUCKET: value.bucket });
assert.equal(plain.status, 200);
});
test("missing, incorrect, expired and malformed deployment authority is denied before storage", async t => {
const cases = [
["missing token", authorization, ""], ["wrong token", authorization, "wrong"],
["oversized token", authorization, "x".repeat(257)], ["missing config", undefined, token],
["expired", { ...authorization, expiresAt: "2000-01-01T00:00:00Z" }, token],
["invalid expiry", { ...authorization, expiresAt: "invalid" }, token],
["invalid hash", { ...authorization, sha256: "invalid" }, token],
];
for (const [name, deploymentUploadAuthorization, supplied] of cases) await t.test(name, async () => {
const value = fixture(), worker = createDataAppWorker({ ...value.configuration, deploymentUploadAuthorization });
const result = await worker.fetch(upload("html", value.payloads.html, { "x-data-app-deployment-token": supplied }), {
get BUCKET() { assert.fail("Unauthorized request must not access storage"); },
});
assert.equal(result.status, 403);
const compressedResult = await worker.fetch(upload("html", gzipSync(value.payloads.html), {
"x-data-app-deployment-token": supplied, "content-encoding": "gzip",
}), { get BUCKET() { assert.fail("Unauthorized compressed request must not access storage"); } });
assert.equal(compressedResult.status, 403);
const readback = await worker.fetch(viewerRequest("/api/deployment-assets/html", { deploymentToken: supplied }), {
get BUCKET() { assert.fail("Unauthorized readback must not access storage"); },
});
assert.equal(readback.status, 403);
});
});
test("upload capability is restricted to known keys, exact size and exact checksum", async () => {
const value = fixture(), worker = createDataAppWorker(value.configuration), environment = { BUCKET: value.bucket };
assert.equal((await worker.fetch(upload("html", value.payloads.html), environment)).status, 200);
const original = value.stored.get(value.assets.html.key);
for (const path of ["unknown", "html/extra", "../query"]) {
assert.equal((await worker.fetch(upload(path, value.payloads.html), environment)).status, 404);
}
const callCount = value.calls.length;
assert.equal((await worker.fetch(upload("html", value.payloads.html, { "content-length": "0" }), environment)).status, 400);
assert.equal(value.calls.length, callCount);
const wrong = Buffer.alloc(value.payloads.html.length, 120);
assert.equal((await worker.fetch(upload("html", wrong), environment)).status, 400);
assert.equal(value.stored.get(value.assets.html.key), original, "Checksum rejection must preserve an existing object");
assert.equal((await worker.fetch(upload("html", value.payloads.html.subarray(1), { "content-length": String(value.payloads.html.length) }), environment)).status, 400);
assert.equal(value.stored.get(value.assets.html.key), original);
assert.equal((await worker.fetch(upload("html", value.payloads.html, { "x-data-app-deployment-token": "", authorization: `Bearer ${token}` }), environment)).status, 403);
});
test("deployment token cannot mutate queries or presentation", async () => {
const value = fixture(), worker = createDataAppWorker(value.configuration);
for (const path of ["/api/queries/reviewed", "/api/presentation"]) {
const response = await worker.fetch(viewerRequest(path, { method: "PUT", deploymentToken: token, body: { rows: [] } }), {
get DB() { assert.fail("A deployment token must not authorize data or presentation writes"); },
});
assert.equal(response.status, 403);
}
});
test("deployment token does not grant viewer edit permission", { skip: sqliteUnavailable }, async t => {
const value = fixture(), worker = createDataAppWorker(value.configuration), storage = fixtureDatabase(); t.after(() => storage.close());
const response = await worker.fetch(viewerRequest("/api/presentation", { deploymentToken: token }), { DB: storage.db });
assert.equal((await response.json()).canEdit, false);
});
test("legitimate owner can upload, edit data and retain presentation across redeployment", { skip: sqliteUnavailable }, async t => {
const value = fixture(), storage = fixtureDatabase(); t.after(() => storage.close());
const configuration = { ...value.configuration, deploymentUploadAuthorization: undefined, initialPresentation: { title: "Original" } };
const worker = createDataAppWorker(configuration), environment = { BUCKET: value.bucket, DB: storage.db, DATA_APP_OWNER_EMAIL_SHA256: hash(owner) };
for (const kind of ["html", "snapshot"]) assert.equal((await worker.fetch(upload(kind, value.payloads[kind], {
"x-data-app-deployment-token": "", "oai-authenticated-user-email": owner,
}), environment)).status, 200);
const editedRows = [{ ownerReviewed: 42 }];
assert.equal((await worker.fetch(viewerRequest("/api/queries/reviewed", { email: owner, method: "PUT", body: { rows: editedRows } }), environment)).status, 200);
const update = await worker.fetch(viewerRequest("/api/presentation", { email: owner, method: "PUT", body: { revision: 0, presentation: { title: "Owner edit" } } }), environment);
assert.equal(update.status, 200);
const redeployed = createDataAppWorker({ ...configuration, initialPresentation: { title: "Different seed" } });
const presentation = await (await redeployed.fetch(viewerRequest("/api/presentation", { email: owner }), environment)).json();
assert.equal(presentation.canEdit, true); assert.equal(presentation.presentation.title, "Owner edit");
const saved = await (await redeployed.fetch(viewerRequest("/api/snapshot"), environment)).json();
assert.deepEqual(saved.queries.reviewed.rows, editedRows);
assert.deepEqual(saved.queries.empty, value.seed.queries.empty);
});
test("missing or mismatched deployment objects fail explicitly before data activation", { skip: sqliteUnavailable }, async t => {
const value = fixture(), storage = fixtureDatabase(); t.after(() => storage.close());
const worker = createDataAppWorker(value.configuration), environment = { BUCKET: value.bucket, DB: storage.db, DATA_APP_OWNER_EMAIL_SHA256: hash(owner) };
assert.equal((await worker.fetch(viewerRequest("/"), environment)).status, 503);
assert.equal((await worker.fetch(viewerRequest("/api/snapshot"), environment)).status, 503);
assert.equal(storage.sqlite.prepare("SELECT COUNT(*) AS n FROM data_app_snapshot_head_v2").get().n, 0);
value.stored.set(value.assets.html.key, { body: value.payloads.html, options: { customMetadata: { sha256: "incorrect" } } });
assert.equal((await worker.fetch(viewerRequest("/"), environment)).status, 503);
assert.equal((await worker.fetch(upload("html", value.payloads.html), {})).status, 503);
});
test("malformed content-addressed descriptors are rejected before requests", () => {
const value = fixture();
for (const html of [undefined, { ...value.assets.html, key: "arbitrary" }, { ...value.assets.html, bytes: 0 }, { ...value.assets.html, sha256: "invalid" }]) {
assert.throws(() => createDataAppWorker({ ...value.configuration, deploymentAssets: { ...value.assets, html } }), /descriptor is invalid/u);
}
});
SHA-256: f9e79835354b5f9d0217c3ac4c0f69e587fa9f8c68a4f4bc28b834ed50ba87fe