// harness/tests/fake_pi/test_fake_pi_contract.mjs — run: node --test import test from "node:test"; import assert from "node:assert"; import { spawn } from "node:child_process"; import path from "node:path"; // Avvia il fake, manda il prompt e — quando arriva il widget — risponde con l'id RPC // indicato da `respondWith` (una funzione widget -> id). Mirror del vero Pi: la risposta // si correla sull'id RPC top-level, non sull'id interno del descriptor. function driveReactive(scriptPath, respondWith) { return new Promise((resolve) => { const fp = spawn("node", [path.join(import.meta.dirname, "fake_pi_rpc.mjs"), scriptPath]); const events = []; let buf = ""; fp.stdout.on("data", (c) => { buf += c.toString("utf8"); for (let nl; (nl = buf.indexOf("\n")) !== -1; ) { const line = buf.slice(0, nl).replace(/\r$/, ""); buf = buf.slice(nl + 1); if (!line) continue; const evt = JSON.parse(line); events.push(evt); if (evt.type === "extension_ui_request" && evt.method === "input") { const desc = JSON.parse(evt.title); fp.stdin.write(JSON.stringify({ type: "extension_ui_response", id: respondWith(evt), value: JSON.stringify({ id: desc.id, choices: ["a"], decision: { type: "concept_clarified" } }), }) + "\n"); } } }); fp.on("exit", () => resolve(events)); fp.stdin.write(JSON.stringify({ type: "prompt", message: '/nuova-domanda "x"' }) + "\n"); setTimeout(() => fp.stdin.end(), 300); }); } test("on prompt emette il widget F1 con id RPC distinto dal descriptor; rispondere con l'id RPC sblocca", async () => { const sp = path.join(import.meta.dirname, "scripts/f1_disambiguation.json"); // Host corretto: risponde con l'id RPC top-level del widget. const events = await driveReactive(sp, (w) => w.id); const widget = events.find((e) => e.type === "extension_ui_request"); assert.equal(widget.method, "input"); // shape nativa di Pi const descriptor = JSON.parse(widget.title); // il descriptor viaggia nel title assert.equal(descriptor.widget, "select"); assert.equal(descriptor.id, "u1"); assert.notEqual(widget.id, descriptor.id); // l'id RPC di Pi NON e' l'id del descriptor // La risposta correlata sblocca il follow-up (testo + agent_end). assert.ok(events.some((e) => e.type === "message_update")); assert.ok(events.some((e) => e.type === "agent_end")); }); test("rispondere con l'id del descriptor (sbagliato) viene scartato: nessun follow-up", async () => { const sp = path.join(import.meta.dirname, "scripts/f1_disambiguation.json"); // Host bug: risponde con l'id interno del descriptor invece dell'id RPC -> drop. const events = await driveReactive(sp, (w) => JSON.parse(w.title).id); assert.ok(events.some((e) => e.type === "extension_ui_request")); assert.ok(!events.some((e) => e.type === "agent_end")); // mai sbloccato assert.ok(!events.some((e) => e.type === "message_update")); });