fix(harness): recover schema table name typos

This commit is contained in:
2026-07-20 17:53:24 +02:00
parent 3b52c8c08c
commit ec9b12dff4
2 changed files with 145 additions and 13 deletions
@@ -4,8 +4,9 @@
//
// HARNESS ADAPTATIONS (no existing test invokes a tool's execute() end-to-end):
// 1. tht-gate.js uses ESM `import { execFileSync } from "node:child_process"`. Under
// CommonJS `require(esm)` the named binding tracks the module namespace, so patching
// `child_process.execFileSync` BEFORE requiring tht-gate.js is observed by tht().
// CommonJS `require(esm)` the named binding is snapshotted at load time. A single
// delegating stub is installed ONCE before requiring tht-gate.js; each test swaps the
// inner handler via `_handler` so every stub is visible to the frozen ESM binding.
// 2. tht()'s loadEnvFromDotenv calls `require("node:fs")`; that `require` is undefined
// in the ESM module scope when loaded via require(esm) (in production Pi loads the
// extension as CommonJS, where require is native). We install a globalThis.require
@@ -33,12 +34,20 @@ const CATALOG = {
],
};
// Adaptation #1: the ESM import of execFileSync is frozen at require(GATE) time.
// A single delegating function is installed before the first load; each test swaps
// the inner _handler and clears it in finally — no save/restore of cp.execFileSync.
let _handler = null;
const _origExecFileSync = cp.execFileSync;
cp.execFileSync = function delegatingStub(file, args, opts) {
if (_handler) return _handler(file, args, opts);
return _origExecFileSync.call(cp, file, args, opts);
};
test("reviewer_schema_linking records table/column decisions and syncs schema_linking", async () => {
const calls = [];
const inputs = [];
// Adaptation #1: stub the shell BEFORE requiring tht-gate.js.
const origExecFileSync = cp.execFileSync;
cp.execFileSync = (file, args, opts) => {
_handler = (file, args, opts) => {
calls.push(args.join(" "));
inputs.push(opts?.input);
if (args[0] === "phase" && args[1] === "meta")
@@ -119,13 +128,79 @@ test("reviewer_schema_linking records table/column decisions and syncs schema_li
assert.match(result.content[0].text, /Schema linking registrato/);
} finally {
cp.execFileSync = origExecFileSync;
_handler = null;
}
});
test("reviewer_schema_linking auto-corrects typos in table names via fuzzy matching", async () => {
const calls = [];
const inputs = [];
_handler = (file, args, opts) => {
calls.push(args.join(" "));
inputs.push(opts?.input);
if (args[0] === "phase" && args[1] === "meta")
return JSON.stringify({ phases: [{ num: 4, id: "F4" }] });
if (args[0] === "phase" && args[1] === "show") return "Fase corrente: 4\n";
// The typo name fails:
if (args[0] === "schema" && args[1] === "columns" && args[2] === "dim_patientt") {
const err = new Error("Command failed");
err.stderr = "ERRORE: tabella 'dim_patientt' non trovata nel catalogo";
throw err;
}
// The correct name works:
if (args[0] === "schema" && args[1] === "columns" && args[2] === "dim_patient")
return JSON.stringify(CATALOG);
// catalogTableNames calls schema render:
if (args[0] === "schema" && args[1] === "render")
return "CREATE TABLE dim_patient (\n cod_paz bigint\n);\n";
return "";
};
try {
const gate = require(GATE);
const { createFakePi } = require("./fake_pi_runtime.js");
const { pi, ctx, tools } = createFakePi();
ctx.cwd = "/nonexistent-thothii-test-cwd-fuzzy"; // distinct key → fresh cache
gate.default(pi);
let capturedDescriptor = null;
ctx.ui.input = async (title) => {
capturedDescriptor = JSON.parse(title);
return JSON.stringify({
id: capturedDescriptor.id,
kind: "schema-linking",
tables: [{ id: "dim_patient", enacted: true, columns: ["cod_paz"] }],
});
};
const tool = tools.get("reviewer_schema_linking");
const result = await tool.def.execute(
"call-1",
{
session: "s1",
title: "Schema linking",
tables: [{ id: "t-pat", name: "dim_patientt", kind: "promote", suggested_columns: ["cod_paz"] }],
},
null,
null,
ctx,
);
// The gate should have auto-corrected "dim_patientt" → "dim_patient".
assert.equal(capturedDescriptor.tables[0].name, "dim_patient");
assert.match(result.content[0].text, /Schema linking registrato/);
// The persisted decisions use the corrected name.
const batchIdx = calls.findIndex((c) => c === "decision add-batch --session s1 --doc -");
assert.ok(batchIdx !== -1, "expected atomic decision batch");
const batch = JSON.parse(inputs[batchIdx]);
assert.equal(batch[0].subject, "dim_patient");
} finally {
_handler = null;
}
});
test("reviewer_schema_linking returns a textResult (not a throw) when a proposed table is not in the catalog", async () => {
const origExecFileSync = cp.execFileSync;
cp.execFileSync = (file, args) => {
_handler = (file, args) => {
if (args[0] === "phase" && args[1] === "meta")
return JSON.stringify({ phases: [{ num: 4, id: "F4" }] });
if (args[0] === "phase" && args[1] === "show") return "Fase corrente: 4\n";
@@ -167,6 +242,6 @@ test("reviewer_schema_linking returns a textResult (not a throw) when a proposed
assert.match(result.content[0].text, /badtable/);
} finally {
cp.execFileSync = origExecFileSync;
_handler = null;
}
});