From 4a63db51e83bacb536b3a1a980930ddccee400fe Mon Sep 17 00:00:00 2001 From: mptyl Date: Mon, 29 Jun 2026 12:29:14 +0200 Subject: [PATCH] feat(backend): ThtRunner session mutation + documents methods --- backend/src/tht/tht-runner.ts | 20 ++++++++++++++++ backend/test/tht-runner.test.ts | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/backend/src/tht/tht-runner.ts b/backend/src/tht/tht-runner.ts index 2568bf51..e14d6f7d 100644 --- a/backend/src/tht/tht-runner.ts +++ b/backend/src/tht/tht-runner.ts @@ -18,6 +18,14 @@ export interface SessionRow { author: string | null; } +export interface SessionDocument { + phase: string; + key: string; + title: string; + format: string; + content: string; +} + export class ThtRunner { constructor(private cfg: ThtConfig) {} @@ -61,6 +69,11 @@ export class ThtRunner { return JSON.parse(stdout) as T; } + private async ok(args: string[]): Promise { + const { code, stderr } = await this.run(args); + if (code !== 0) throw new Error(`tht ${args.join(" ")} exit ${code}: ${stderr.trim()}`); + } + async sessionNew(o: { question: string; provider?: string; @@ -109,4 +122,11 @@ export class ThtRunner { if (code !== 0) throw new Error(`tht sql export exit ${code}: ${stderr.trim()}`); return { path: stdout.trim() }; } + + setName(id: string, name: string) { return this.ok(["session", "set-name", id, "--name", name]); } + setGroup(id: string, group: string) { return this.ok(["session", "set-group", id, "--group", group]); } + archive(id: string) { return this.ok(["session", "archive", id]); } + unarchive(id: string) { return this.ok(["session", "unarchive", id]); } + deleteSession(id: string) { return this.ok(["session", "delete", id]); } + documents(id: string) { return this.json(["session", "documents", id, "--json"]); } } diff --git a/backend/test/tht-runner.test.ts b/backend/test/tht-runner.test.ts index 7e04a72a..a4f1d7e5 100644 --- a/backend/test/tht-runner.test.ts +++ b/backend/test/tht-runner.test.ts @@ -73,3 +73,44 @@ test("sqlPreview argv has no positional file — uses --session to resolve path" expect(calledArgs).toEqual(["sql", "preview", "--session", "ses2", "--json", "--limit", "20", "--offset", "0"]); expect(calledArgs).not.toContain("sessions/ses2/sql_final.sql"); }); + +test("setName builds the right argv", async () => { + const calls: string[][] = []; + const r = new ThtRunner({ thtBin: "tht", harnessDir: "/h", configPath: "config/tht.yaml" }); + r.run = async (args) => { calls.push(args); return { code: 0, stdout: "", stderr: "" }; }; + await r.setName("sid", "Mio nome"); + expect(calls[0]).toEqual(["session", "set-name", "sid", "--name", "Mio nome"]); +}); + +test("setGroup / archive / unarchive / deleteSession build argv", async () => { + const calls: string[][] = []; + const r = new ThtRunner({ thtBin: "tht", harnessDir: "/h", configPath: "config/tht.yaml" }); + r.run = async (args) => { calls.push(args); return { code: 0, stdout: "", stderr: "" }; }; + await r.setGroup("sid", "G1"); + await r.archive("sid"); + await r.unarchive("sid"); + await r.deleteSession("sid"); + expect(calls).toEqual([ + ["session", "set-group", "sid", "--group", "G1"], + ["session", "archive", "sid"], + ["session", "unarchive", "sid"], + ["session", "delete", "sid"], + ]); +}); + +test("ok() throws on non-zero exit", async () => { + const r = new ThtRunner({ thtBin: "tht", harnessDir: "/h", configPath: "config/tht.yaml" }); + r.run = async () => ({ code: 1, stdout: "", stderr: "ERRORE: nope" }); + await expect(r.archive("sid")).rejects.toThrow(/nope/); +}); + +test("documents parses the JSON array", async () => { + const r = new ThtRunner({ thtBin: "tht", harnessDir: "/h", configPath: "config/tht.yaml" }); + r.run = async () => ({ + code: 0, + stdout: '[{"phase":"—","key":"question","title":"Domanda originale","format":"text","content":"q"}]', + stderr: "", + }); + const docs = await r.documents("sid"); + expect(docs[0].key).toBe("question"); +});