← Files DataARCHIVED FILE
templates/data-app/base/tests/query-refresh.test.mjs
11.7 KB · Oct 4, 2026 · 12:03 UTC
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { createDataAppWorker } from "../src/data-app-worker.js";
import { createQueryDataStore } from "../src/query-data-store.js";
// The runtime also supports Node 20; SQLite integration tests use Node 22.13+.
let DatabaseSync;
try { ({ DatabaseSync } = await import("node:sqlite")); } catch (error) {
if (error.code !== "ERR_UNKNOWN_BUILTIN_MODULE") throw error;
}
const sqliteTest = (name, run) => test(name, { skip: !DatabaseSync && "Requires node:sqlite" }, run);
const owner = "publisher@example.com";
const executedAt = "2026-09-07T09:30:00.000Z";
const seed = {
generatedAt: "2026-09-01T12:00:00.000Z",
queries: {
sql: { source: { label: "SQL", sql: "SELECT amount FROM totals", executedAt: "2026-09-01T11:00:00.000Z" }, rows: [{ amount: 10 }] },
api: { source: { label: "API", evidenceFlow: [{ title: "Get totals", detail: "Fetch the current month" }] }, methods: [{ language: "calculation", code: "Sum amounts" }], rows: [{ amount: 20 }] },
untouched: { source: { label: "Other" }, rows: [{ amount: 30 }] },
},
};
const updates = [
{ queryId: "sql", rows: [{ amount: 11 }], executedAt },
{ queryId: "api", rows: [{ amount: 22 }], executedAt: "2026-09-07T09:35:00.000Z" },
];
// Exercise real SQL and transaction rollback through the D1 interface.
function database(t) {
const sqlite = new DatabaseSync(":memory:");
t.after(() => sqlite.close());
const db = {
sqlite,
prepare(sql) {
const stmt = {
values: [],
bind(...values) { this.values = values; return this; },
async first() { return sqlite.prepare(sql).get(...this.values) ?? null; },
async all() { return { results: sqlite.prepare(sql).all(...this.values) }; },
async run() {
const prepared = sqlite.prepare(sql);
if (sql.startsWith("SELECT")) return this.all();
return { meta: { changes: prepared.run(...this.values).changes } };
},
};
return stmt;
},
async batch(statements) {
sqlite.exec("BEGIN");
try {
const results = [];
for (const statement of statements) results.push(await statement.run());
sqlite.exec("COMMIT");
return results;
} catch (error) {
sqlite.exec("ROLLBACK");
throw error;
}
},
};
return db;
}
function client(db) {
const worker = createDataAppWorker({ html: "<main>Dashboard</main>", seedSnapshot: seed });
const environment = { DB: db, DATA_APP_OWNER_EMAIL_SHA256: createHash("sha256").update(owner).digest("hex") };
return {
snapshot: async () => (await worker.fetch(new Request("https://dashboard.chatgpt.site/api/snapshot"), environment)).json(),
put: (body, path = "/api/queries", email = owner) => worker.fetch(new Request(`https://dashboard.chatgpt.site${path}`, {
method: "PUT", headers: { "content-type": "application/json", "oai-authenticated-user-email": email }, body: JSON.stringify(body),
}), environment),
};
}
sqliteTest("a multi-source refresh commits rows and execution times together and survives reload", async (t) => {
const db = database(t);
const api = client(db);
assert.equal((await api.put({ updates })).status, 200);
const saved = await client(db).snapshot();
for (const update of updates) {
const original = seed.queries[update.queryId];
assert.deepEqual(saved.queries[update.queryId], { ...original, rows: update.rows,
source: { ...original.source, executedAt: update.executedAt } });
}
assert.deepEqual(saved.queries.untouched, seed.queries.untouched);
assert.notEqual(saved.generatedAt, seed.generatedAt);
});
sqliteTest("a later database failure rolls back every dataset and freshness value", async (t) => {
const db = database(t);
const api = client(db);
await api.snapshot();
db.sqlite.exec("CREATE TRIGGER reject_api BEFORE INSERT ON data_app_query_rows WHEN NEW.query_id = 'api' BEGIN SELECT RAISE(ABORT, 'injected failure'); END");
assert.equal((await api.put({ updates })).status, 503);
assert.deepEqual(await client(db).snapshot(), seed);
});
sqliteTest("invalid batches and unauthorized callers cannot partially update a dashboard", async (t) => {
const api = client(database(t));
for (const body of [
{ updates: [] }, { updates: [updates[0], updates[0]] },
{ updates: [updates[0], { ...updates[1], queryId: "missing" }] },
{ updates: [updates[0], { ...updates[1], rows: [null] }] },
{ updates: [updates[0], { ...updates[1], executedAt: "yesterday" }] },
{ updates: [updates[0], { ...updates[1], executedAt: undefined }] },
{ updates, queryId: "sql", rows: [] }, { updates: null }, null,
{ updates: [updates[0], { ...updates[1], executedAt: "2026-09-07T09:00:00" }] },
{ updates: [{ ...updates[0], rows: Array(10_001).fill({ amount: 1 }) }] },
]) {
assert.ok([400, 404].includes((await api.put(body)).status), "invalid refresh must be rejected");
assert.deepEqual(await api.snapshot(), seed);
}
assert.equal((await api.put({ updates }, "/api/queries", "viewer@example.com")).status, 403);
assert.deepEqual(await api.snapshot(), seed);
});
sqliteTest("single-query callers can supply execution time or preserve the existing one", async (t) => {
const api = client(database(t));
assert.equal((await api.put({ rows: [], executedAt }, "/api/queries/sql")).status, 200);
assert.equal((await api.snapshot()).queries.sql.source.executedAt, executedAt);
assert.equal((await api.put({ rows: [{ amount: 12 }] }, "/api/queries/sql")).status, 200);
assert.equal((await api.snapshot()).queries.sql.source.executedAt, executedAt);
});
async function registeredTools(t, { initialSnapshot = seed, queryDataStore } = {}) {
const tools = new Map();
let snapshot = structuredClone(initialSnapshot);
let stateUpdates = 0;
const effects = [];
const hooks = {
useCallback: (fn) => fn, useEffect: (fn) => effects.push(fn), useRef: (current) => ({ current }),
useState: (value) => [typeof value === "function" ? value() : value, () => { stateUpdates++; }],
useSyncExternalStore: (_subscribe, getSnapshot) => getSnapshot(),
};
const oldDocument = globalThis.document;
const oldFetch = globalThis.fetch;
globalThis.__queryRefreshHooks = hooks;
globalThis.document = { modelContext: { registerTool: (tool) => tools.set(tool.name, tool), unregisterTool: (name) => tools.delete(name) } };
t.after(() => { globalThis.document = oldDocument; globalThis.fetch = oldFetch; delete globalThis.__queryRefreshHooks; });
const source = (await readFile(new URL("../src/use-data-app.js", import.meta.url), "utf8"))
.replace('from "react"', '= globalThis.__queryRefreshHooks')
.replace('import { useCallback, useEffect, useRef, useState, useSyncExternalStore }', 'const { useCallback, useEffect, useRef, useState, useSyncExternalStore }')
.replace('"./source-provenance.js"', JSON.stringify(new URL("../src/source-provenance.js", import.meta.url).href));
const { useDataApp } = await import(`data:text/javascript;base64,${Buffer.from(source).toString("base64")}#${encodeURIComponent(t.name)}`);
const render = () => {
useDataApp(snapshot, { hosted: true, queryDataStore, onSnapshotChange: (change) => { snapshot = change(snapshot); } });
for (const effect of effects.splice(0)) effect();
};
render();
return { tools, render, snapshot: () => snapshot, stateUpdates: () => stateUpdates };
}
test("query tools mark source content untrusted and publish a refresh only after commit", async (t) => {
const harness = await registeredTools(t);
const list = harness.tools.get("list_data_app_queries");
assert.deepEqual(list.annotations, { readOnlyHint: true, untrustedContentHint: true });
const update = harness.tools.get("update_data_app_query");
globalThis.fetch = async () => Response.json({ error: "write failed" }, { status: 500 });
await assert.rejects(update.execute({ updates }), /write failed/);
assert.deepEqual(harness.snapshot(), seed);
assert.equal(harness.stateUpdates(), 0);
globalThis.fetch = async (url, options) => {
assert.equal(url, "/api/queries");
assert.deepEqual(JSON.parse(options.body), { updates });
return Response.json({ updates, generatedAt: executedAt });
};
await update.execute({ updates });
assert.equal(harness.stateUpdates(), 1);
const definitions = await list.execute();
for (const update of updates) {
assert.deepEqual(harness.snapshot().queries[update.queryId].rows, update.rows);
const definition = definitions.find(({ queryId }) => queryId === update.queryId);
assert.equal(definition.source.executedAt, update.executedAt);
assert.equal(Object.hasOwn(definition, "rows"), false);
}
});
test("deferred queries retain full definitions and committed batch execution times through the query store", async (t) => {
const initialSnapshot = { ...seed,
queries: Object.fromEntries(Object.entries(seed.queries).map(([id, { rows, ...definition }]) => [id, definition])),
_dataAppQueryLoading: { queries: Object.fromEntries(Object.keys(seed.queries).map(id =>
[id, { columns: ["amount"], rowCount: 1 }])) },
};
const reads = [];
const store = createQueryDataStore(initialSnapshot, { snapshotSha256: "a".repeat(64), request: async url => {
const id = new URL(url, "https://dashboard.chatgpt.site").searchParams.get("queryId");
reads.push(id);
return Response.json(seed.queries[id].rows);
} });
t.after(() => store.dispose());
const harness = await registeredTools(t, { initialSnapshot, queryDataStore: store });
const before = await harness.tools.get("list_data_app_queries").execute();
assert.deepEqual(reads, []);
for (const query of before) {
assert.equal(query.loaded, false);
assert.equal(query.rowCount, 1);
assert.deepEqual(query.columns, ["amount"]);
assert.deepEqual(query.source, seed.queries[query.queryId].source);
assert.deepEqual(query.methods, seed.queries[query.queryId].methods);
assert.equal(Object.hasOwn(query, "rows"), false);
}
globalThis.fetch = async () => Response.json({ error: "write failed" }, { status: 500 });
await assert.rejects(harness.tools.get("update_data_app_query").execute({ updates }), /write failed/);
assert.deepEqual([...reads].sort(), Object.keys(seed.queries).sort());
assert.deepEqual(store.getQueries(), seed.queries);
assert.equal(harness.snapshot().generatedAt, seed.generatedAt);
assert.equal(harness.stateUpdates(), 0);
globalThis.fetch = async (url, options) => {
assert.equal(url, "/api/queries");
assert.deepEqual(JSON.parse(options.body), { updates });
return Response.json({ updates, generatedAt: executedAt });
};
const counts = await harness.tools.get("update_data_app_query").execute({ updates });
assert.deepEqual(counts, { updates: updates.map(({ queryId, rows }) => ({ queryId, rowCount: rows.length })) });
harness.render();
const after = await harness.tools.get("list_data_app_queries").execute();
for (const update of updates) {
const expected = { ...seed.queries[update.queryId], rows: update.rows,
source: { ...seed.queries[update.queryId].source, executedAt: update.executedAt } };
assert.deepEqual(store.getQueries()[update.queryId], expected);
assert.deepEqual(harness.snapshot().queries[update.queryId], expected);
const listed = after.find(({ queryId }) => queryId === update.queryId);
assert.equal(listed.loaded, true);
assert.deepEqual(listed.source, expected.source);
assert.deepEqual(listed.methods, expected.methods);
assert.equal(Object.hasOwn(listed, "rows"), false);
}
assert.deepEqual(store.getQueries().untouched, seed.queries.untouched);
assert.equal(harness.snapshot()._dataAppQueryLoading, undefined);
assert.equal(harness.snapshot().generatedAt, executedAt);
});
SHA-256: d7b7ce2b33f7a63583e692d674089aabfccb95024380e33b622fcafddf6cc3aa