Files
ThothII/harness/.pi/extensions/gate/__tests__/enrich.test.js
T
marcopanandClaude Fable 5 b59b57c4e6 fix(review-gates): green success badges, null-safe preview rows, optional section items
- Extract shared statusBadgeClass() helper (src/viewers/statusBadge.ts) so
  SqlViewer, CteResultViewer and PhaseSummaryViewer can't drift: ok/success/
  passed/promoted render green (--success), warn renders amber (--warning),
  error/failed stay destructive red, everything else stays neutral outline.
  Previously CteResultViewer/PhaseSummaryViewer mapped "ok"/"promoted" to the
  default badge variant, which is bg-primary (GSD red) — success states
  rendered red.
- enrich.js: buildCteResultV2 now falls back preview.rows to [] instead of
  null when last_test.preview_rows is missing (pre-upgrade ok records), and
  CteResultViewer reads result.preview?.rows?.length with a null-safe
  fallback so it degrades to the "No preview rows" empty state instead of
  crashing.
- PhaseSummaryViewer: section.items is optional (model-authored sections can
  be prose-only); render (section.items ?? []) instead of crashing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 01:37:35 +02:00

202 lines
9.0 KiB
JavaScript

// Pure merge/enrichment functions (contracts.md A/B/C). Catalog lookups are injected
// as a `getColumns` function parameter so these are testable with no `tht` shell-out.
const test = require("node:test");
const assert = require("node:assert");
const {
enrichCtePlanV2,
buildCteResultV2,
enrichPhaseSummaryV2,
} = require("../enrich.js");
// --- enrichCtePlanV2 --------------------------------------------------------------
test("enrichCtePlanV2 assigns 1-based index in ctes[] order", () => {
const data = {
schema_version: 2, question: "q", strategy: "s",
ctes: [
{ name: "a", tables: [], filters: [] },
{ name: "b", tables: [], filters: [] },
],
};
const out = enrichCtePlanV2(data, () => null);
assert.equal(out.ctes[0].index, 1);
assert.equal(out.ctes[1].index, 2);
});
test("enrichCtePlanV2 fills tables[].description from the catalog", () => {
const getColumns = (table) =>
table === "dim_patient" ? { description: "Anagrafica pazienti", columns: [{ name: "cod_paz", description: "Codice paziente" }] } : null;
const data = {
schema_version: 2, question: "q", strategy: "s",
ctes: [{ name: "a", tables: [{ name: "dim_patient" }], filters: [] }],
};
const out = enrichCtePlanV2(data, getColumns);
assert.equal(out.ctes[0].tables[0].description, "Anagrafica pazienti");
});
test("enrichCtePlanV2 fills filters[].description by resolving table.column", () => {
const getColumns = (table) =>
table === "dim_patient"
? { description: "Anagrafica", columns: [{ name: "flag_attivo", description: "Flag attivo" }] }
: null;
const data = {
schema_version: 2, question: "q", strategy: "s",
ctes: [{ name: "a", tables: [], filters: [{ column: "dim_patient.flag_attivo", op: "IS", value: "TRUE" }] }],
};
const out = enrichCtePlanV2(data, getColumns);
assert.equal(out.ctes[0].filters[0].description, "Flag attivo");
});
test("enrichCtePlanV2: catalog miss -> empty description string, never throws", () => {
const out = enrichCtePlanV2(
{
schema_version: 2, question: "q", strategy: "s",
ctes: [{ name: "a", tables: [{ name: "nonexistent_table" }], filters: [{ column: "nonexistent_table.x", op: "IS", value: "1" }] }],
},
() => null,
);
assert.equal(out.ctes[0].tables[0].description, "");
assert.equal(out.ctes[0].filters[0].description, "");
});
test("enrichCtePlanV2: getColumns throwing is tolerated by the caller contract (function itself does not catch)", () => {
// enrich.js documents that getColumns must itself be safe (try/catch -> null on miss);
// enrichCtePlanV2 does not need its own try/catch around calls, since getColumns never throws.
const getColumns = () => null; // simulates the safe wrapper's miss behavior
const out = enrichCtePlanV2(
{ schema_version: 2, question: "q", strategy: "s", ctes: [{ name: "a", tables: [{ name: "t" }], filters: [] }] },
getColumns,
);
assert.equal(out.ctes[0].tables[0].description, "");
});
test("enrichCtePlanV2 preserves model-authored fields (purpose/rationale/depends_on/keys/output_columns)", () => {
const data = {
schema_version: 2, question: "q", strategy: "s",
ctes: [{
name: "a", purpose: "scopo", rationale: "motivo", depends_on: [],
keys: ["cod_paz"], output_columns: ["cod_paz"], tables: [], filters: [],
}],
};
const out = enrichCtePlanV2(data, () => null);
assert.equal(out.ctes[0].purpose, "scopo");
assert.equal(out.ctes[0].rationale, "motivo");
assert.deepEqual(out.ctes[0].keys, ["cod_paz"]);
assert.deepEqual(out.ctes[0].output_columns, ["cod_paz"]);
});
// --- buildCteResultV2 --------------------------------------------------------------
test("buildCteResultV2 merges name/index/total/sql from cteInfo", () => {
const thin = { schema_version: 2, note: "nota" };
const cteInfo = {
name: "base_pazienti", index: 1, total: 3, sql: "WITH base_pazienti AS (SELECT 1)",
doc: null, last_test: { status: "ok", execution_ms: 42, row_sample: 5, warnings: [], sql_hash: "h1", columns: ["x"], preview_rows: [[1]] },
};
const out = buildCteResultV2(thin, cteInfo);
assert.equal(out.name, "base_pazienti");
assert.equal(out.index, 1);
assert.equal(out.total, 3);
assert.equal(out.sql, "WITH base_pazienti AS (SELECT 1)");
assert.equal(out.note, "nota");
});
test("buildCteResultV2 prefers doc purpose/rationale/depends_on, falls back to thin data", () => {
const cteInfoWithDoc = {
name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)",
doc: { purpose: "scopo dal doc", rationale: "motivo dal doc", depends_on: ["x"] },
last_test: { status: "ok", execution_ms: 1, row_sample: 0, warnings: [], sql_hash: "h", columns: [], preview_rows: [] },
};
const out1 = buildCteResultV2({ schema_version: 2, purpose: "scopo thin" }, cteInfoWithDoc);
assert.equal(out1.purpose, "scopo dal doc");
assert.equal(out1.rationale, "motivo dal doc");
assert.deepEqual(out1.depends_on, ["x"]);
const cteInfoNoDoc = { ...cteInfoWithDoc, doc: null };
const out2 = buildCteResultV2({ schema_version: 2, purpose: "scopo thin", rationale: "motivo thin" }, cteInfoNoDoc);
assert.equal(out2.purpose, "scopo thin");
assert.equal(out2.rationale, "motivo thin");
});
test("buildCteResultV2 builds preview from last_test.columns/preview_rows", () => {
const cteInfo = {
name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: null,
last_test: { status: "ok", execution_ms: 10, row_sample: 2, warnings: [], sql_hash: "h1", columns: ["cod_paz", "eta"], preview_rows: [[1, 30], [2, 45]] },
};
const out = buildCteResultV2({ schema_version: 2 }, cteInfo);
assert.deepEqual(out.preview, { columns: ["cod_paz", "eta"], rows: [[1, 30], [2, 45]] });
assert.equal(out.status, "ok");
assert.equal(out.execution_ms, 10);
assert.equal(out.row_sample, 2);
assert.equal(out.sql_hash, "h1");
assert.deepEqual(out.warnings, []);
});
test("buildCteResultV2: last_test.preview_rows null (pre-upgrade ok records) -> preview.rows is []", () => {
const cteInfo = {
name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: null,
last_test: { status: "ok", execution_ms: 1, row_sample: 1, warnings: [], sql_hash: "h", columns: ["cod_paz"], preview_rows: null },
};
const out = buildCteResultV2({ schema_version: 2 }, cteInfo);
assert.deepEqual(out.preview.rows, []);
});
test("buildCteResultV2: columns[] carries name, with description left as an empty placeholder (filled separately by the gate)", () => {
const cteInfo = {
name: "a", index: 1, total: 1, sql: "WITH a AS (SELECT 1)", doc: null,
last_test: { status: "ok", execution_ms: 1, row_sample: 1, warnings: [], sql_hash: "h", columns: ["cod_paz"], preview_rows: [[1]] },
};
const out = buildCteResultV2({ schema_version: 2 }, cteInfo);
assert.equal(out.columns.length, 1);
assert.equal(out.columns[0].name, "cod_paz");
assert.equal(out.columns[0].description, "");
});
// --- enrichPhaseSummaryV2 -----------------------------------------------------------
test("enrichPhaseSummaryV2 fills phase from phaseMeta", () => {
const data = { schema_version: 2, summary: "riepilogo" };
const phaseMeta = { id: "F5", num: 5, name: "sintesi" };
const out = enrichPhaseSummaryV2(data, phaseMeta, () => null);
assert.deepEqual(out.phase, { id: "F5", num: 5, name: "sintesi" });
});
test("enrichPhaseSummaryV2 enriches tables[]/tables[].columns[]/sections[].items[] descriptions", () => {
const getColumns = (table) =>
table === "dim_patient"
? { description: "Anagrafica", columns: [{ name: "cod_paz", description: "Codice paziente" }] }
: null;
const data = {
schema_version: 2, summary: "s",
tables: [{ name: "dim_patient", role: "promoted", columns: [{ name: "cod_paz" }] }],
sections: [{ title: "Criteri", items: [{ label: "id paziente", table: "dim_patient", column: "cod_paz" }] }],
};
const out = enrichPhaseSummaryV2(data, { id: "F5", num: 5, name: "sintesi" }, getColumns);
assert.equal(out.tables[0].description, "Anagrafica");
assert.equal(out.tables[0].columns[0].description, "Codice paziente");
assert.equal(out.sections[0].items[0].description, "Codice paziente");
});
test("enrichPhaseSummaryV2: catalog miss -> empty description, never throws", () => {
const data = {
schema_version: 2, summary: "s",
tables: [{ name: "nope", role: "excluded", columns: [{ name: "x" }] }],
sections: [{ title: "t", items: [{ label: "l", table: "nope", column: "x" }] }],
};
const out = enrichPhaseSummaryV2(data, { id: "F5", num: 5, name: "sintesi" }, () => null);
assert.equal(out.tables[0].description, "");
assert.equal(out.tables[0].columns[0].description, "");
assert.equal(out.sections[0].items[0].description, "");
});
test("enrichPhaseSummaryV2 preserves open_questions/checks untouched", () => {
const data = {
schema_version: 2, summary: "s",
checks: [{ label: "x", status: "ok" }],
open_questions: ["domanda aperta"],
};
const out = enrichPhaseSummaryV2(data, { id: "F1", num: 1, name: "chiarimento" }, () => null);
assert.deepEqual(out.checks, [{ label: "x", status: "ok" }]);
assert.deepEqual(out.open_questions, ["domanda aperta"]);
});