← Files DataARCHIVED FILE

tests/data-app-upload.test.mjs

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

↓ Download file

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { access, mkdtemp, mkdir, readFile, realpath, stat, writeFile, rm, symlink } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { gunzipSync } from "node:zlib";
import { uploadDataAppAssets } from "../skills/publish-artifact-to-sites/scripts/upload-data-app-assets.mjs";

const fingerprint = value => ({ sha256: createHash("sha256").update(value).digest("hex"), bytes: Buffer.byteLength(value) });
async function fixture(t, rawSnapshot) {
  const projectDir = await mkdtemp(path.join(os.tmpdir(), "data-upload-test-"));
  t.after(() => rm(projectDir, { recursive: true, force: true }));
  const directory = path.join(projectDir, ".data-app-assets");
  await mkdir(directory);
  const bodies = { html: "<html>reviewed dashboard</html>", snapshot: rawSnapshot ?? JSON.stringify({ queries: { q: { rows: [{ payload: "x".repeat(1100000) }] } } }) };
  const manifest = { version: 1, projectId: "project_test", thinBootstrap: true,
    assets: Object.fromEntries(Object.entries(bodies).map(([kind, body]) => {
      const digest = fingerprint(body);
      return [kind, { ...digest, key: `data-app/${kind}/${digest.sha256}`, path: `${kind}.txt` }];
    })), snapshotResponse: { ...fingerprint(bodies.snapshot), queryCount: 1, rowCount: 1 } };
  await Promise.all(Object.entries(bodies).map(([kind, body]) => writeFile(path.join(directory, `${kind}.txt`), body)));
  const save = () => writeFile(path.join(directory, "manifest.json"), JSON.stringify(manifest));
  await save();
  return { projectDir, directory, manifest, bodies, save,
    input: { projectDir, projectId: "project_test", siteUrl: "https://example.openai.chatgpt.site/", deploymentToken: "d".repeat(64), sitesAuthorization: "test-ingress-token" } };
}

test("streams exact payloads and verifies complete hosted HTML and snapshot before readiness", async t => {
  const { input, manifest, bodies } = await fixture(t);
  const requests = [];
  const request = async (url, options) => {
    requests.push({ url: String(url), method: options.method ?? "GET" });
    assert.equal(options.redirect, "error");
    assert.equal(url.origin, "https://example.openai.chatgpt.site");
    assert.equal(options.headers["OAI-Sites-Authorization"], "Bearer test-ingress-token");
    if (options.method === "PUT") {
      const kind = url.pathname.split("/").at(-1);
      assert.equal(options.duplex, "half");
      assert.equal(options.headers["content-encoding"], undefined);
      assert.equal(options.headers["content-length"], String(manifest.assets[kind].bytes));
      assert.equal(options.headers["x-data-app-deployment-token"], input.deploymentToken);
      assert.equal(typeof options.body[Symbol.asyncIterator], "function");
      const hash = createHash("sha256");
      let bytes = 0;
      for await (const chunk of options.body) { hash.update(chunk); bytes += chunk.length; }
      assert.equal(hash.digest("hex"), manifest.assets[kind].sha256);
      return Response.json({ kind, bytes, sha256: manifest.assets[kind].sha256 });
    }
    if (options.method === "HEAD") return new Response(null, { headers: { "content-type": "text/html; charset=utf-8" } });
    assert.equal(options.headers["x-data-app-deployment-token"], url.pathname.endsWith("/html") ? input.deploymentToken : undefined);
    return new Response(url.pathname.endsWith("/html") ? bodies.html : bodies.snapshot);
  };
  const receipt = await uploadDataAppAssets(input, { request });
  assert.equal(receipt.ready, true);
  assert.equal(receipt.rowCount, 1);
  assert.deepEqual(receipt.uploads.map(({ transport }) => transport),
    ["html", "snapshot"].map(kind => ({ encoding: "identity", bytes: manifest.assets[kind].bytes })));
  assert.equal(requests.length, 5);
  assert.equal(receipt.readback.snapshot.sha256, manifest.snapshotResponse.sha256);
  assert.doesNotMatch(JSON.stringify(receipt), /test-ingress-token|dddddddd/u);
});

function readbackRequest(item, snapshot) {
  return async (url, options) => {
    if (options.method === "PUT") {
      for await (const _chunk of options.body) { /* consume the verified upload */ }
      const kind = url.pathname.split("/").at(-1);
      return Response.json({ kind, ...item.manifest.assets[kind] });
    }
    if (options.method === "HEAD") return new Response(null, { headers: { "content-type": "text/html" } });
    return new Response(url.pathname.endsWith("/html") ? item.bodies.html : snapshot);
  };
}

test("accepts only the two exact approved encodings, including a longer legacy encoding", async t => {
  const item = await fixture(t, '{"queries":{"q":{"rows":[{"n":1e20}]}}}');
  const canonical = JSON.stringify(JSON.parse(item.bodies.snapshot));
  item.manifest.legacySnapshotResponse = { ...fingerprint(canonical), queryCount: 1, rowCount: 1 };
  await item.save();
  assert.ok(item.manifest.legacySnapshotResponse.bytes > item.manifest.snapshotResponse.bytes);
  for (const [body, label] of [[item.bodies.snapshot, "snapshotResponse"], [canonical, "legacySnapshotResponse"]]) {
    const receipt = await uploadDataAppAssets(item.input, { request: readbackRequest(item, body) });
    assert.equal(receipt.ready, true);
    assert.equal(receipt.readback.snapshot.matchedEncoding, label);
    assert.deepEqual({ sha256: receipt.readback.snapshot.sha256, bytes: receipt.readback.snapshot.bytes }, fingerprint(body));
  }
  // Same row count is not enough: changed owner data and a third whitespace
  // encoding both fail exact equality, even though the latter parses identically.
  for (const body of [canonical.replace("100000000000000000000", "200000000000000000000"), item.bodies.snapshot + " "]) {
    await assert.rejects(uploadDataAppAssets(item.input, { request: readbackRequest(item, body) }), /differs from the packaged artifact/u);
  }
  await assert.rejects(uploadDataAppAssets(item.input, { request: readbackRequest(item, canonical + " ") }), /exceeds its expected byte length/u);
});

test("identical approved fingerprints do not claim which storage path served them", async t => {
  const item = await fixture(t, '{"queries":{"q":{"rows":[{}]}}}');
  item.manifest.legacySnapshotResponse = { ...item.manifest.snapshotResponse };
  await item.save();
  const receipt = await uploadDataAppAssets(item.input, { request: readbackRequest(item, item.bodies.snapshot) });
  assert.equal(receipt.readback.snapshot.matchedEncoding, "snapshotResponse+legacySnapshotResponse");
});

test("invalid or incomplete alternate fingerprints fail before any upload", async t => {
  const item = await fixture(t);
  const request = () => assert.fail("must reject malformed alternate before network writes");
  const valid = { ...item.manifest.snapshotResponse };
  for (const legacy of [null, {}, { ...valid, sha256: "invalid" }, { ...valid, bytes: -1 },
    { ...valid, queryCount: 2 }, { ...valid, rowCount: 2 }, { ...valid, rowCount: 0.5 }]) {
    item.manifest.legacySnapshotResponse = legacy;
    await item.save();
    await assert.rejects(uploadDataAppAssets(item.input, { request }), /same complete reviewed snapshot/u);
  }
});

test("rejects changed local data, wrong Site identity and escaping files before network writes", async t => {
  const { input, directory, manifest, save } = await fixture(t);
  const request = () => { assert.fail("must fail before network"); };
  await assert.rejects(uploadDataAppAssets({ ...input, projectId: "another_project" }, { request }), /selected Site/u);
  await writeFile(path.join(directory, "snapshot.txt"), "tampered");
  await assert.rejects(uploadDataAppAssets(input, { request }), /local integrity/u);
  await rm(path.join(directory, "html.txt"));
  await symlink(path.join(input.projectDir, "outside.html"), path.join(directory, "html.txt"));
  await writeFile(path.join(input.projectDir, "outside.html"), "<html>reviewed dashboard</html>");
  await assert.rejects(uploadDataAppAssets(input, { request }), /local integrity/u);
  manifest.assets.html.path = "../outside.html";
  await save();
  await assert.rejects(uploadDataAppAssets(input, { request }), /descriptor/u);
});

test("refuses a mismatched acknowledgement and omits failed response content", async t => {
  const { input } = await fixture(t);
  await assert.rejects(uploadDataAppAssets(input, { request: async (_url, options) => {
    for await (const _chunk of options.body) { /* consume upload */ }
    return Response.json({ kind: "html", bytes: 2, sha256: "a".repeat(64) });
  } }), /acknowledgement/u);
  await assert.rejects(uploadDataAppAssets(input, { request: async () => new Response("private failure data", { status: 403 }) }), error => {
    assert.match(error.message, /HTTP 403/u);
    assert.doesNotMatch(error.message, /private failure data/u);
    return true;
  });
});

test("requires an exact HTTPS origin and never follows a failed request", async t => {
  const { input } = await fixture(t);
  const request = () => assert.fail("must reject invalid origin before network");
  for (const siteUrl of ["http://example.openai.chatgpt.site/", "https://name:secret@example.openai.chatgpt.site/", "https://example.openai.chatgpt.site/?token=secret"]) {
    await assert.rejects(uploadDataAppAssets({ ...input, siteUrl }, { request }), /exact HTTPS/u);
  }
  await assert.rejects(uploadDataAppAssets(input, { request: async (_url, options) => {
    assert.equal(options.redirect, "error");
    throw new Error("redirect to secret-ingress-token");
  } }), error => {
    assert.match(error.message, /Site request failed/u);
    assert.doesNotMatch(error.message, /secret-ingress-token/u);
    return true;
  });
});


test("large upload transport uses a private gzip stream while preserving raw identity and readback", async t => {
  const item = await fixture(t);
  const compressedPaths = [];
  const request = async (url, options) => {
    if (options.method !== "PUT") return readbackRequest(item, item.bodies.snapshot)(url, options);
    const kind = url.pathname.split("/").at(-1);
    const chunks = [];
    for await (const chunk of options.body) chunks.push(chunk);
    const transportBytes = Buffer.concat(chunks);
    assert.equal(Number(options.headers["content-length"]), transportBytes.length);
    if (kind === "snapshot") {
      assert.equal(options.headers["content-encoding"], "gzip");
      assert.ok(transportBytes.length < item.manifest.assets.snapshot.bytes);
      assert.deepEqual(gunzipSync(transportBytes), Buffer.from(item.bodies.snapshot));
      compressedPaths.push(options.body.path);
      assert.notEqual(path.dirname(options.body.path), item.directory);
      if (process.platform !== "win32") {
        assert.equal((await stat(options.body.path)).mode & 0o777, 0o600);
        assert.equal((await stat(path.dirname(options.body.path))).mode & 0o777, 0o700);
      }
    } else {
      assert.equal(options.headers["content-encoding"], undefined);
      assert.equal(transportBytes.toString(), item.bodies.html);
    }
    return Response.json({ kind, ...item.manifest.assets[kind] });
  };
  const receipt = await uploadDataAppAssets(item.input, {
    request, gzipThresholdBytes: item.manifest.assets.snapshot.bytes,
  });
  assert.equal(receipt.ready, true);
  assert.equal(receipt.uploads[0].transport.encoding, "identity");
  assert.equal(receipt.uploads[1].transport.encoding, "gzip");
  assert.ok(receipt.uploads[1].transport.bytes < receipt.uploads[1].bytes);
  assert.equal(receipt.readback.snapshot.sha256, item.manifest.snapshotResponse.sha256);
  assert.equal(compressedPaths.length, 1);
  for (const file of compressedPaths) {
    await assert.rejects(access(file), { code: "ENOENT" });
    await assert.rejects(access(path.dirname(file)), { code: "ENOENT" });
    assert.ok(!JSON.stringify(receipt).includes(file));
  }
  for (const kind of ["html", "snapshot"]) {
    assert.deepEqual(await readFile(path.join(item.directory, `${kind}.txt`)), Buffer.from(item.bodies[kind]));
  }
});

test("gzip candidates that do not shrink use the original plain payload", async t => {
  const item = await fixture(t, '{"queries":{}}');
  const request = async (url, options) => {
    if (options.method === "PUT") {
      const kind = url.pathname.split("/").at(-1);
      assert.equal(options.headers["content-encoding"], undefined);
      assert.equal(options.body.path, await realpath(path.join(item.directory, `${kind}.txt`)));
      assert.equal(Number(options.headers["content-length"]), item.manifest.assets[kind].bytes);
    }
    return readbackRequest(item, item.bodies.snapshot)(url, options);
  };
  const receipt = await uploadDataAppAssets(item.input, { request, gzipThresholdBytes: 1 });
  assert.ok(receipt.uploads.every(upload => upload.transport.encoding === "identity" && upload.transport.bytes === upload.bytes));
});

test("gzip temporary files are removed after request, acknowledgement and readback failures", async t => {
  for (const failure of ["request", "acknowledgement", "readback"]) await t.test(failure, async t => {
    const item = await fixture(t);
    let compressedFile;
    const request = async (url, options) => {
      if (options.method === "PUT" && url.pathname.endsWith("/snapshot")) {
        compressedFile = options.body.path;
        assert.equal(options.headers["content-encoding"], "gzip");
        if (failure === "request") throw new Error("Synthetic upload interruption");
        for await (const _chunk of options.body) { /* consume the gzip stream */ }
        if (failure === "acknowledgement") return Response.json({ kind: "snapshot", bytes: 1, sha256: "a".repeat(64) });
        return Response.json({ kind: "snapshot", ...item.manifest.assets.snapshot });
      }
      if (failure === "readback" && url.pathname === "/api/snapshot") return new Response("changed");
      return readbackRequest(item, item.bodies.snapshot)(url, options);
    };
    await assert.rejects(uploadDataAppAssets(item.input, { request, gzipThresholdBytes: 1024 }),
      /Site request failed|acknowledgement|differs from the packaged artifact/u);
    assert.ok(compressedFile);
    await assert.rejects(access(compressedFile), { code: "ENOENT" });
    await assert.rejects(access(path.dirname(compressedFile)), { code: "ENOENT" });
    assert.equal(await readFile(path.join(item.directory, "snapshot.txt"), "utf8"), item.bodies.snapshot);
  });
});

SHA-256: 98e9630b74b6034be34b505113ace47955b53afa5639bbd97ed235a65ac742ff