import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { createRequire } from "node:module"; import test from "node:test"; const require = createRequire(new URL("../../../govoplan-core/webui/package.json", import.meta.url)); const { transformSync } = require("esbuild"); const page = readFileSync(new URL("../src/features/datasources/DatasourcesPage.tsx", import.meta.url), "utf8"); function extract(startMarker, endMarker) { const start = page.indexOf(startMarker); const end = page.indexOf(endMarker, start); assert.ok(start >= 0 && end > start, "test the actual page closures"); return page.slice(start + startMarker.length, end); } const reloadBody = extract(" const reload = useCallback(async (preferredDatasourceRef?: string) => {", " }, [dialogScope]);"); const detailBody = extract(" useEffect(() => {\n if (!selectedDatasourceRef || loading || !catalogueReady)", " }, [selectedDatasourceRef, detailScope, loading, catalogueReady]);"); function evaluate(body, bindings) { const code = transformSync(body, { loader: "ts", target: "es2022" }).code; return new Function(...Object.keys(bindings), code)(...Object.values(bindings)); } const deferred = () => { let resolve, reject; const promise = new Promise((yes, no) => { resolve = yes; reject = no; }); return { promise, resolve, reject }; }; const settle = async () => { await Promise.resolve(); await Promise.resolve(); await Promise.resolve(); }; function harness() { const state = { selectedDatasourceRef: "source-1", loading: false, detailRevision: 0, preview: null, materializations: [], detailLoading: false, catalogueAuthority: 0 }; const settings = {}; const auth = {}; const bindings = { settings, auth, reloadRequestId: { current: 0 }, authority: { current: "authority-A" }, authorityKey: "authority-A", dialogScope: 0, authorityEpoch: { current: { revision: 0 } }, currentDetailScope: { current: null }, apiErrorMessage: String, listDatasources: async () => [{ ref: "source-1" }], listDatasourceStages: async () => [], listDatasourceOrigins: async () => ({ origins: [], available: false }) }; bindings.isCurrentAuthority = () => bindings.authority.current === "authority-A" && bindings.authorityEpoch.current.revision === 0; for (const key of ["loading", "error", "datasources", "stages", "origins", "originsAvailable", "detailRevision", "selectedDatasourceRef", "selectedStageRef", "selectedOriginRef", "preview", "materializations", "detailLoading", "detailResponseScope", "catalogueAuthority", "working", "success", "view", "addOpen", "freezeOpen", "freezeLabel", "retireOpen", "promoteOpen", "governanceOpen", "decisionOpen", "retentionOpen"]) { bindings[`set${key[0].toUpperCase()}${key.slice(1)}`] = (value) => { state[key] = typeof value === "function" ? value(state[key]) : value; }; } return { state, bindings, reload: (preferredDatasourceRef) => evaluate(`return (async () => {${reloadBody}})();`, { ...bindings, preferredDatasourceRef }), startDetail: () => { const preview = deferred(); const history = deferred(); const scope = {}; bindings.currentDetailScope.current = scope; const cleanup = evaluate(`if (!selectedDatasourceRef || loading || !catalogueReady)${detailBody}`, { ...bindings, selectedDatasourceRef: state.selectedDatasourceRef, loading: state.loading, detailScope: scope, catalogueReady: state.catalogueAuthority === bindings.authorityEpoch.current.revision, previewDatasource: () => preview.promise, listDatasourceMaterializations: () => history.promise }); return { preview, history, scope, cleanup }; } }; } test("same-reference reload invalidates preview/history once, including freeze with an unchanged current materialization", async () => { const h = harness(); for (const operation of ["manual reload", "refresh", "freeze", "promotion"]) { const previous = h.state.detailRevision; await h.reload("source-1"); assert.equal(h.state.selectedDatasourceRef, "source-1", operation); assert.equal(h.state.detailRevision, previous + 1, operation); const detail = h.startDetail(); detail.preview.resolve({ rows: [{ value: operation }] }); detail.history.resolve([{ revision: h.state.detailRevision }]); await settle(); assert.equal(h.state.preview.rows[0].value, operation); assert.equal(h.state.materializations[0].revision, h.state.detailRevision); assert.equal(h.state.detailResponseScope, detail.scope); detail.cleanup(); } }); test("a changed scope immediately suppresses old preview, history and errors, even before effect cleanup", async () => { const h = harness(); const old = h.startDetail(); const current = h.startDetail(); current.preview.resolve({ rows: [{ value: "current" }] }); current.history.resolve([{ revision: 2 }]); await settle(); old.preview.reject(new Error("stale error")); old.history.resolve([{ revision: 1 }]); await settle(); assert.equal(h.state.preview.rows[0].value, "current"); assert.equal(h.state.materializations[0].revision, 2); assert.equal(h.state.error, undefined); current.cleanup(); }); test("unmount cancellation and partial detail failures preserve allSettled behavior", async () => { const h = harness(); const cancelled = h.startDetail(); cancelled.cleanup(); cancelled.preview.resolve({ rows: ["obsolete"] }); cancelled.history.resolve([]); await settle(); assert.equal(h.state.preview, null); const current = h.startDetail(); current.preview.reject(new Error("current preview denied")); current.history.resolve([{ revision: 3 }]); await settle(); assert.equal(h.state.preview, null); assert.equal(h.state.materializations[0].revision, 3); assert.match(h.state.error, /current preview denied/); assert.equal(h.state.detailLoading, false); }); test("initial/loading catalogue does not issue duplicate detail reads; scoped rendering cannot flash old rows", () => { const h = harness(); h.state.loading = true; const detail = h.startDetail(); assert.equal(detail.cleanup, undefined); assert.equal(h.state.preview, null); assert.match(page, /useMemo\(\(\) => \(\{\}\), \[selectedDatasourceRef, dialogScope, detailRevision\]\)/); assert.match(page, /preview=\{!loading && detailResponseScope === detailScope \? preview : null\}/); assert.match(page, /materializations=\{!loading && detailResponseScope === detailScope \? materializations : \[\]\}/); }); test("older catalogue reloads and revoked authority cannot invalidate or replace the current catalogue", async () => { const h = harness(); const older = deferred(); h.bindings.listDatasources = () => older.promise; const pending = h.reload(); h.bindings.listDatasources = async () => [{ ref: "source-1", revision: 2 }]; await h.reload(); older.resolve([{ ref: "source-1", revision: 1 }]); await pending; assert.equal(h.state.datasources[0].revision, 2); assert.equal(h.state.detailRevision, 1); const revoked = deferred(); h.bindings.listDatasources = () => revoked.promise; const revokedPending = h.reload(); h.bindings.authority.current = "authority-B"; revoked.resolve([{ ref: "source-1", revision: 3 }]); await revokedPending; assert.equal(h.state.datasources[0].revision, 2); }); test("an old reload invoked only after B is active cannot issue requests or invalidate B state", async () => { const h = harness(); const oldReload = h.reload; h.bindings.authority.current = "authority-B"; h.bindings.authorityEpoch.current.revision = 1; h.state.catalogueAuthority = 1; h.state.datasources = [{ ref: "B-source" }]; h.state.detailRevision = 8; let calls = 0; h.bindings.listDatasources = async () => { calls += 1; return [{ ref: "A-source" }]; }; const requestId = h.bindings.reloadRequestId.current; await oldReload(); assert.equal(calls, 0); assert.equal(h.bindings.reloadRequestId.current, requestId); assert.equal(h.state.datasources[0].ref, "B-source"); assert.equal(h.state.detailRevision, 8); }); test("the first authority-transition render cannot request details from a stale catalogue", () => { const h = harness(); h.bindings.authority.current = "authority-B"; h.bindings.authorityEpoch.current.revision = 1; h.state.loading = false; // The new reload's effect has not committed loading=true yet. const detail = h.startDetail(); assert.equal(detail.cleanup, undefined); assert.equal(h.state.detailLoading, false); assert.match(page, /const datasources = catalogueReady \? loadedDatasources : \[\]/); assert.match(page, /const stages = catalogueReady \? loadedStages : \[\]/); }); test("real-authority setup closes confirmations and clears freeze input; unmount blocks old callbacks", () => { const h = harness(); const body = extract(" useEffect(() => {\n authority.current = authorityKey;", " }, [reload]);"); const flags = ["addOpen", "freezeOpen", "retireOpen", "promoteOpen", "governanceOpen", "decisionOpen", "retentionOpen"]; for (const flag of flags) { h.state[flag] = true; // Hide the old intent already in the first transition render, before the // passive effect clears it; it cannot be clicked against a new selection. assert.ok(page.includes(`open={${flag} && catalogueReady}`), flag); } h.state.freezeLabel = "Evidence from prior authority"; let reloads = 0; const cleanup = evaluate(`authority.current = authorityKey;${body}`, { ...h.bindings, reload: () => { reloads += 1; } }); assert.equal(reloads, 1); for (const flag of flags) assert.equal(h.state[flag], false, flag); assert.equal(h.state.freezeLabel, ""); assert.equal(h.bindings.isCurrentAuthority(), true); cleanup(); assert.equal(h.bindings.isCurrentAuthority(), false); // The reload callback/effect depend on the stable numeric authority epoch, // not cosmetic settings/auth object identities. assert.match(page, /\}, \[dialogScope\]\);\s*useEffect/); }); test("stale mutation success/failure/finally cannot reload, select or overwrite B messages", async () => { for (const operation of ["promoteStage", "refreshSelected", "freezeSelected", "retireSelected"]) { for (const outcome of ["resolve", "reject"]) { const h = harness(); const pending = deferred(); let reloads = 0; const signature = operation === "freezeSelected" ? "async (): Promise" : "async ()"; const body = extract(` const ${operation} = ${signature} => {`, "\n };"); const result = evaluate(`return (async () => {${body}})();`, { ...h.bindings, selectedDatasource: { ref: "A-source", governance: { approval_policy: { required: false } } }, selectedStage: { ref: "A-stage", state: "ready" }, freezeLabel: "Freeze A", reload: async () => { reloads += 1; }, promoteDatasourceStage: () => pending.promise, refreshDatasource: () => pending.promise, freezeDatasource: () => pending.promise, retireDatasource: () => pending.promise }); h.bindings.authority.current = "authority-B"; Object.assign(h.state, { success: "B success", error: "B error", view: "origins", working: true }); if (outcome === "reject") pending.reject(new Error("A error")); else pending.resolve({ datasource: { ref: "A-source", name: "A" }, materialization: { revision: 1 }, revision: 1 }); await result; assert.equal(reloads, 0, operation); assert.equal(h.state.success, "B success", operation); assert.equal(h.state.error, "B error", operation); assert.equal(h.state.view, "origins", operation); assert.equal(h.state.working, true, operation); } } }); test("CSV stage dialog sends its explicit mode and exact text; JSON rows remain independent", async () => { const dialog = page.slice(page.indexOf("function AddDatasourceDialog(")); const start = dialog.indexOf(" const create = async (): Promise => {"); const end = dialog.indexOf("\n };", start); assert.ok(start >= 0 && end > start); const body = dialog.slice(start + " const create = async (): Promise => {".length, end); const csvText = 'code;value\r\n001;" text "\r\n'; for (const format of ["csv", "json"]) { for (const csvValueMode of ["text", "legacy_typed"]) { let payload; const result = await evaluate(`return (async () => {${body}})();`, { kind: "upload", settings: {}, format, csvValueMode, csvText, delimiter: ";", mode: "static", name: "Fixture", sourceName: "fixture", description: "", targetRef: "", rowsText: '[{"code":"001","value":" text "}]', parseRows: JSON.parse, createDatasourceStage: async (_settings, value) => { payload = value; return {}; }, onCreated: async () => {}, setBusy: () => {}, setError: () => {}, apiErrorMessage: String }); assert.equal(result, true); if (format === "csv") { assert.equal(payload.csv_value_mode, csvValueMode); assert.equal(payload.csv_text, csvText); } else { assert.equal("csv_value_mode" in payload, false); assert.deepEqual(payload.rows, [{ code: "001", value: " text " }]); } } } assert.match(dialog, /\[csvValueMode, setCsvValueMode\] = useState<"text" \| "legacy_typed">\("text"\)/); assert.match(dialog, /setCsvValueMode\("text"\)/); assert.match(dialog, /csvValueMode: "text"/); });