← Files DataARCHIVED FILE

templates/data-app/base/tests/data-app-context-tools.test.mjs

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

↓ Download file

import assert from "node:assert/strict";
import test from "node:test";

import { createDataAppContextTools, registerDataAppContextTools } from "../src/data-app-context-tools.js";

function fixture({ rowCount = 1_203, surface = "dashboard" } = {}) {
  const state = {
    snapshot: { id: `${surface}:reviewed`, surface, title: "Authored title", generatedAt: "2026-09-06T12:00:00Z",
      filters: [{ id: "region", field: "region", defaultValue: "all", shareInUrl: false }],
      report: { period: "2026-09", comparisonPeriod: "2026-08", methodology: ["Reviewed sources only"] },
      tabs: [{ id: "overview", filterIds: ["region"] }],
      sourcePlan: { sources: ["approved-source"], refreshInstructions: "Preserve all reviewed rows" },
      extension: { caveats: ["Custom authored metadata must survive"] },
      queries: {
        sales: { label: "Sales", declaredColumns: [{ name: "region", type: "string" }],
          source: { label: "Reviewed source", sql: "SELECT region, revenue FROM reviewed_sales", tables: ["reviewed_sales"],
            metricDefinitions: [{ label: "Revenue", formula: "SUM(revenue)", caveat: "Synthetic" }],
            executedAt: "2026-09-06T11:00:00Z", sourceFiles: [{ id: "file:1", label: "Reviewed file" }] },
          methods: [{ id: "review", label: "Verified" }],
          rows: Array.from({ length: rowCount }, (_, index) => ({ id: index, region: index % 2 ? "East" : "West",
            revenue: index * 10, privateRowMarker: `ROW_ONLY_SENTINEL_${index}`, ...(index === rowCount - 1 ? { lastRowOnlyColumn: true } : {}) })) },
        notes: { source: { query: { description: "Source-only narrative", sql: "SELECT notes FROM reviewed_notes" } },
          methodology: ["Narrative source metadata without quantitative rows"] },
      },
    },
    presentation: { title: "Reviewed current title", theme: "original", appearance: "dark",
      hiddenBlocks: ["hidden-card"], componentTitles: { sales: "Edited sales" },
      chartOverrides: { sales: { type: "line", x: "date", y: "revenue" } },
      textEdits: { intro: "Edited narrative" }, blockLayouts: { dashboard: ["sales", "notes"] },
      tabs: [{ id: "overview", label: "Executive view" }], refreshSchedule: { mode: "manual" } },
    view: { surface, tab: "overview", filters: { region: "West" }, assumptions: { activationLift: 3 },
      sections: { regional: { region: "East" } }, charts: { sales: { visibleSeries: ["Revenue"] } },
      focus: { accountId: "selected-account" }, tabViews: { overview: { filters: { region: "West" } } } },
    dataAppReference: { dataAppId: `${surface}:reviewed`, sourceUrl: "https://dashboard.example/?view=1&f.region=West",
      projectDirectory: "/local/app" },
    viewUrl: "https://dashboard.example/?view=1&f.region=West",
    canEdit: false,
    requestHeaders: { authorization: "DO_NOT_READ_HOST_AUTH" },
    session: { cookie: "DO_NOT_READ_BROWSER_SESSION" },
  };
  const registry = createDataAppContextTools({ getContext: () => state });
  return { state, ...registry, call: (name, input) => registry.tools.find(tool => tool.name === name).execute(input) };
}

test("deferred context keeps exact totals and loads only the requested query for pagination", async () => {
  const f = fixture({ rowCount: 7 });
  const original = f.state.snapshot.queries.sales.rows;
  const columns = Object.keys(original.at(-1));
  delete f.state.snapshot.queries.sales.rows;
  f.state.snapshot._dataAppQueryLoading = { version: 1, snapshotSha256: "a".repeat(64),
    queries: { sales: { rowCount: 7, columns } } };
  const requested = [];
  f.state.queryDataStore = {
    getQueries: () => f.state.snapshot.queries,
    async ensure(ids) {
      requested.push(ids);
      f.state.snapshot.queries.sales = { ...f.state.snapshot.queries.sales, rows: original };
    },
  };
  const before = await f.call("get_data_app_context", {});
  assert.equal(before.queries.sales.rowCount, 7);
  assert.deepEqual(before.queries.sales.columns, columns);
  assert.equal(before.queries.sales.loaded, false);
  assert.deepEqual(requested, []);
  const page = await f.call("get_data_app_query_rows", { queryId: "sales", limit: 3, contextVersion: before.contextVersion });
  assert.deepEqual(requested, [["sales"]]);
  assert.equal(page.contextVersion, before.contextVersion, "loading unchanged immutable rows is not a data revision");
  assert.deepEqual(page.rows, original.slice(0, 3));
  assert.equal(page.totalRows, 7);
  assert.equal(page.nextOffset, 3);
  assert.equal((await f.call("get_data_app_context", {})).queries.sales.loaded, true);
});

test("deferred row reads reject a view change while loading and propagate failures", async () => {
  for (const failure of [false, true]) {
    const f = fixture({ rowCount: 1 });
    const rows = f.state.snapshot.queries.sales.rows;
    delete f.state.snapshot.queries.sales.rows;
    f.state.snapshot._dataAppQueryLoading = { version: 1, snapshotSha256: "a".repeat(64),
      queries: { sales: { rowCount: 1, columns: Object.keys(rows[0]) } } };
    f.state.queryDataStore = { getQueries: () => f.state.snapshot.queries, async ensure() {
      if (failure) throw new Error("Published query is unavailable");
      f.state.snapshot.queries.sales.rows = rows;
      f.state.view.tab = "changed";
    } };
    const { contextVersion } = await f.call("get_data_app_context", {});
    await assert.rejects(f.call("get_data_app_query_rows", { queryId: "sales", contextVersion }),
      failure ? /Published query is unavailable/ : /context changed/);
  }
});

test("context includes exact live presentation and all snapshot metadata without reviewed row values", async () => {
  const f = fixture();
  const result = await f.call("get_data_app_context", {});
  const expectedSnapshot = JSON.parse(JSON.stringify(f.state.snapshot));
  for (const query of Object.values(expectedSnapshot.queries)) delete query.rows;
  assert.deepEqual(result.snapshot, expectedSnapshot);
  assert.deepEqual(result.presentation, f.state.presentation);
  assert.deepEqual(result.view, f.state.view);
  assert.deepEqual(result.dataAppReference, f.state.dataAppReference);
  assert.deepEqual(result.artifact, { id: "dashboard:reviewed", surface: "dashboard", title: "Reviewed current title" });
  assert.equal(result.viewUrl, f.state.viewUrl);
  assert.equal(result.generatedAt, f.state.snapshot.generatedAt);
  assert.equal(result.canEdit, false, "readers may retrieve the same already-loaded reviewed context");
  assert.equal(result.live, true);
  assert.equal(result.immutable, false);
  assert.equal(result.rows.included, false);
  assert.equal(result.queries.sales.rowCount, 1_203);
  assert.deepEqual(result.queries.sales.columns, ["id", "region", "revenue", "privateRowMarker", "lastRowOnlyColumn"]);
  assert.equal(result.queries.sales.sql, f.state.snapshot.queries.sales.source.sql);
  assert.deepEqual(result.queries.notes, { rowCount: 0, columns: [], label: "Source-only narrative", sql: "SELECT notes FROM reviewed_notes" });
  assert.doesNotMatch(JSON.stringify(result), /ROW_ONLY_SENTINEL|DO_NOT_READ_HOST_AUTH|DO_NOT_READ_BROWSER_SESSION/u);
  result.snapshot.queries.sales.source.tables.push("changed-by-consumer");
  result.presentation.hiddenBlocks.push("changed-by-consumer");
  result.view.filters.region = "East";
  assert.deepEqual(f.state.snapshot.queries.sales.source.tables, ["reviewed_sales"]);
  assert.deepEqual(f.state.presentation.hiddenBlocks, ["hidden-card"]);
  assert.equal(f.state.view.filters.region, "West");
});

test("row pagination returns every reviewed row once, exact page totals, and detached values", async () => {
  const f = fixture();
  const { contextVersion, generatedAt } = await f.call("get_data_app_context", {});
  const rows = [];
  let offset = 0;
  do {
    const page = await f.call("get_data_app_query_rows", { queryId: "sales", offset, limit: 500, contextVersion });
    assert.equal(page.totalRows, 1_203);
    assert.equal(page.contextVersion, contextVersion);
    assert.equal(page.generatedAt, generatedAt);
    assert.equal(page.returnedRows, page.rows.length);
    assert.equal(page.returnedRows, offset < 1_000 ? 500 : 203);
    assert.equal(page.offset, offset);
    assert.equal(page.hasMore, page.nextOffset !== null);
    assert.equal(page.live, true);
    assert.equal(page.immutable, false);
    rows.push(...page.rows);
    offset = page.nextOffset;
  } while (offset !== null);
  assert.deepEqual(rows, f.state.snapshot.queries.sales.rows);
  rows[0].revenue = -1;
  assert.equal(f.state.snapshot.queries.sales.rows[0].revenue, 0);
  for (const offset of [1_203, 5_000]) {
    const page = await f.call("get_data_app_query_rows", { queryId: "sales", offset });
    assert.deepEqual(page.rows, []);
    assert.equal(page.totalRows, 1_203);
    assert.equal(page.nextOffset, null);
    assert.equal(page.hasMore, false);
  }
  const defaults = await f.call("get_data_app_query_rows", { queryId: "sales" });
  assert.equal(defaults.limit, 100);
  assert.equal(defaults.returnedRows, 100);
  assert.equal(defaults.nextOffset, 100);
  const narrative = await f.call("get_data_app_query_rows", { queryId: "notes" });
  assert.deepEqual(narrative.rows, []);
  assert.equal(narrative.totalRows, 0);
  assert.equal(narrative.nextOffset, null);
});

test("schemas are enforced even when the host does not validate tool arguments", async () => {
  const f = fixture();
  assert.ok(f.tools.every(tool => tool.annotations.readOnlyHint && tool.annotations.untrustedContentHint));
  await assert.rejects(f.call("get_data_app_context", { includeRows: true }), /Invalid/u);
  for (const input of [null, [], "sales", {}, { queryId: "" }, { queryId: 1 }, { queryId: "x".repeat(1_001) },
    { queryId: "sales", includeRows: true }, Object.create({ queryId: "sales" }),
    ...[-1, 0, 501, 1.5, NaN, Infinity, "10", null].map(limit => ({ queryId: "sales", limit })),
    ...[-1, 1.5, NaN, Infinity, "10", null, Number.MAX_SAFE_INTEGER + 1].map(offset => ({ queryId: "sales", offset })),
    ...[null, false, "", "x".repeat(201)].map(contextVersion => ({ queryId: "sales", contextVersion }))]) {
    await assert.rejects(f.call("get_data_app_query_rows", input));
  }
  await assert.rejects(f.call("get_data_app_query_rows", { queryId: "unknown" }), /does not exist/u);
  await assert.rejects(f.call("get_data_app_query_rows", { queryId: "constructor" }), /does not exist/u);
});

test("exact query identities are safe even with prototype-like names or punctuation", async () => {
  const f = fixture();
  f.state.snapshot.queries = Object.fromEntries(["__proto__", "constructor", 'query["one"]', "same-title"].map((id, index) =>
    [id, { title: "Same title", rows: [{ index }], source: { sql: `SELECT ${index}` } }]));
  const context = await f.call("get_data_app_context", {});
  assert.deepEqual(Object.keys(context.snapshot.queries), ["__proto__", "constructor", 'query["one"]', "same-title"]);
  for (const [index, queryId] of Object.keys(context.snapshot.queries).entries()) {
    const page = await f.call("get_data_app_query_rows", { queryId });
    assert.deepEqual(page.rows, [{ index }]);
  }
  assert.equal(Object.getPrototypeOf(context.snapshot.queries), Object.prototype);
  assert.equal({}.polluted, undefined);
});

test("live context changes invalidate an existing pagination version instead of mixing revisions", async () => {
  const f = fixture();
  const changes = [
    () => { f.state.view.filters.region = "East"; },
    () => { f.state.view.tab = "details"; },
    () => { f.state.presentation.chartOverrides.sales.type = "bar"; },
    () => { f.state.presentation.textEdits.intro = "New reviewed narrative"; },
    () => { f.state.snapshot.generatedAt = "2026-09-06T13:00:00Z"; },
    () => { f.state.snapshot.queries.sales.source.sql = "SELECT region, revenue FROM updated_source"; },
    () => { f.state.snapshot.queries.sales.rows = f.state.snapshot.queries.sales.rows.map(row => ({ ...row, revenue: row.revenue + 1 })); },
    () => { f.state.snapshot.queries.sales.rows = [...f.state.snapshot.queries.sales.rows, { id: 9_999, revenue: 1 }]; },
    () => { f.state.canEdit = true; },
    () => { f.state.viewUrl += "&tab=details"; },
  ];
  for (const change of changes) {
    const before = await f.call("get_data_app_context", {});
    const unchanged = await f.call("get_data_app_context", {});
    assert.equal(unchanged.contextVersion, before.contextVersion);
    change();
    await assert.rejects(f.call("get_data_app_query_rows", { queryId: "sales", offset: 100, contextVersion: before.contextVersion }),
      /context changed/u);
    const after = await f.call("get_data_app_context", {});
    assert.notEqual(after.contextVersion, before.contextVersion);
    const page = await f.call("get_data_app_query_rows", { queryId: "sales", contextVersion: after.contextVersion });
    assert.equal(page.contextVersion, after.contextVersion);
  }
});

test("large dashboards and reports retain complete metadata and presentation without a URL-sized truncation cap", async () => {
  for (const surface of ["dashboard", "report"]) {
    for (const queryCount of [1, 5, 20, 80, 200]) {
      const f = fixture({ rowCount: 3, surface });
      f.state.snapshot.queries = Object.fromEntries(Array.from({ length: queryCount }, (_, index) =>
        [`query-${index}`, { ...f.state.snapshot.queries.sales, title: `Query ${index}` }]));
      f.state.presentation.textEdits = Object.fromEntries(Array.from({ length: 600 }, (_, index) =>
        [`paragraph-${index}`, `Reviewed narrative ${index}: ${"Full paragraph text. ".repeat(20)}`]));
      f.state.presentation.chartOverrides = Object.fromEntries(Array.from({ length: queryCount * 3 }, (_, index) =>
        [`chart-${index}`, { type: index % 2 ? "line" : "bar", x: "region", y: "revenue", title: `Chart ${index}` }]));
      const context = await f.call("get_data_app_context", {});
      assert.equal(Object.keys(context.queries).length, queryCount);
      assert.deepEqual(context.presentation, f.state.presentation);
      assert.deepEqual(context.snapshot.extension, f.state.snapshot.extension);
      assert.equal(context.artifact.surface, surface);
      assert.ok(JSON.stringify(context.presentation).length > 65_536);
      const last = await f.call("get_data_app_query_rows", { queryId: `query-${queryCount - 1}`, contextVersion: context.contextVersion });
      assert.deepEqual(last.rows, f.state.snapshot.queries[`query-${queryCount - 1}`].rows);
    }
  }
});

test("missing or invalid reviewed state fails explicitly and disposal prevents further reads", async () => {
  let state = null;
  const registry = createDataAppContextTools({ getContext: () => state });
  const context = registry.tools[0], rows = registry.tools[1];
  await assert.rejects(context.execute({}), /not available/u);
  state = { snapshot: { queries: { bad: { rows: null } } } };
  await assert.rejects(context.execute({}), /unavailable/u);
  state = { snapshot: { queries: { bad: null } } };
  await assert.rejects(context.execute({}), /unavailable/u);
  state = { snapshot: { queries: {} } };
  await context.execute({});
  registry.dispose();
  await assert.rejects(context.execute({}), /no longer open/u);
  await assert.rejects(rows.execute({ queryId: "bad" }), /no longer open/u);
});

test("registration exposes read-only context tools, reads updated refs, and aborts/unregisters on cleanup", async () => {
  const f = fixture({ rowCount: 1 });
  let current = f.state;
  const registered = [], removed = [], failures = [];
  const cleanup = registerDataAppContextTools({
    registerTool: (tool, options) => { registered.push({ tool, options }); },
    unregisterTool: name => removed.push(name),
  }, () => current, (...args) => failures.push(args));
  assert.deepEqual(registered.map(({ tool }) => tool.name), ["get_data_app_context", "get_data_app_query_rows", "get_data_app_text"]);
  assert.ok(registered.every(({ tool, options }) => tool.annotations.readOnlyHint && !options.signal.aborted));
  const before = await registered[0].tool.execute({});
  current = { ...current, presentation: { ...current.presentation, title: "Updated ref title" } };
  const after = await registered[0].tool.execute({});
  assert.equal(after.artifact.title, "Updated ref title");
  assert.notEqual(before.contextVersion, after.contextVersion);
  cleanup();
  assert.ok(registered.every(({ options }) => options.signal.aborted));
  assert.deepEqual(removed, ["get_data_app_context", "get_data_app_query_rows", "get_data_app_text"]);
  assert.deepEqual(failures, []);
  await assert.rejects(registered[0].tool.execute({}), /no longer open/u);
});

test("unsupported hosts and registration failures do not break dashboard rendering", async () => {
  registerDataAppContextTools(null, () => { throw new Error("Must not read unsupported host"); })();
  const failures = [], attempted = [];
  const cleanup = registerDataAppContextTools({ registerTool: tool => {
    attempted.push(tool.name);
    if (tool.name === "get_data_app_context") throw new Error("sync failure");
    return Promise.reject(new Error("async failure"));
  } }, () => fixture().state, (...args) => failures.push(args));
  await new Promise(resolve => setImmediate(resolve));
  assert.equal(attempted.length, 3);
  assert.equal(failures.length, 3);
  cleanup();
  let reject;
  const lateFailures = [];
  const dispose = registerDataAppContextTools({ registerTool: () => new Promise((_, failure) => { reject = failure; }) },
    () => fixture().state, (...args) => lateFailures.push(args));
  dispose();
  reject(new Error("too late"));
  await new Promise(resolve => setImmediate(resolve));
  assert.deepEqual(lateFailures, []);
});


test("rendered text retrieval preserves complete authored prose and reads only the requested current target", async () => {
  const f = fixture({ surface: "report" });
  const longText = "Full reviewed narrative paragraph. ".repeat(4_000);
  const calls = [];
  f.state.getText = id => {
    calls.push(id);
    return id === "report-executive-summary" ? longText : id === "empty" ? "" : null;
  };
  const context = await f.call("get_data_app_context", {});
  assert.deepEqual(calls, [], "Default context does not copy page text or invoke the text reader");
  assert.equal(Object.hasOwn(context, "getText"), false);
  assert.equal(Object.hasOwn(context, "renderedText"), false);
  const text = await f.call("get_data_app_text", { id: "report-executive-summary", contextVersion: context.contextVersion });
  assert.equal(text.text, longText);
  assert.equal(text.contextVersion, context.contextVersion);
  assert.equal(text.generatedAt, context.generatedAt);
  assert.equal(text.artifact.surface, "report");
  assert.equal(text.live, true);
  assert.equal(text.immutable, false);
  assert.deepEqual(calls, ["report-executive-summary"]);
  assert.equal((await f.call("get_data_app_text", { id: "empty" })).text, "");
  await assert.rejects(f.call("get_data_app_text", { id: "unmounted" }), /not visible/u);
  f.state.getText = id => id === "report-executive-summary" ? "Updated callback result" : null;
  const updated = await f.call("get_data_app_context", {});
  assert.equal(updated.contextVersion, context.contextVersion, "Callback identity is not serialized view state");
  assert.equal((await f.call("get_data_app_text", { id: "report-executive-summary" })).text, "Updated callback result");
});

test("text retrieval refuses invalid, ambiguous, unavailable, stale, and disposed reads", async () => {
  const f = fixture();
  for (const input of [null, [], {}, { id: "" }, { id: 1 }, { id: "x".repeat(1_001) },
    { id: "summary", includeHidden: true }, { id: "summary", contextVersion: null }]) {
    await assert.rejects(f.call("get_data_app_text", input));
  }
  await assert.rejects(f.call("get_data_app_text", { id: "summary" }), /unavailable/u);
  f.state.getText = () => { throw new Error("Duplicate narrative ID is ambiguous."); };
  await assert.rejects(f.call("get_data_app_text", { id: "summary" }), /Duplicate/u);
  f.state.getText = () => ({ text: "ambiguous" });
  await assert.rejects(f.call("get_data_app_text", { id: "summary" }), /unambiguously/u);
  const before = await f.call("get_data_app_context", {});
  f.state.view.filters.region = "East";
  f.state.getText = () => { throw new Error("Must not read stale text"); };
  await assert.rejects(f.call("get_data_app_text", { id: "summary", contextVersion: before.contextVersion }), /context changed/u);
  f.dispose();
  await assert.rejects(f.call("get_data_app_text", { id: "summary" }), /no longer open/u);
});

SHA-256: bb80654572bb514f27496f50760530951d9593603366643fcec1723a44785242