feat(task-10): SQL routes + /workspaces + /models + fix sql preview path bug
Harness:
- preview_cmd FILE positional arg made optional; when omitted with --session,
path is derived via _session_sql_file (mirrors export_cmd) — fixes the
deferred Task-5 bug where the backend passed sessions/<id>/sql_final.sql
relative to harnessDir, which broke for workspace-dependent paths.
- New pytest: test_preview_session_no_file_resolves_sql_final
Backend:
- ThtRunner.sqlPreview: drop positional file arg; use --session only
- New routes/sql.ts: POST /sessions/:id/sql/preview + /export
- New routes/meta.ts: GET /workspaces (yaml scan) + GET /models (injectable
seam + graceful fallback to {models:[]})
- app.ts: register sqlRoutes + metaRoutes; add listModels to BuildAppDeps
- tht-runner.test.ts: add sqlPreview argv assertion (no file path)
- test/routes-sql-meta.test.ts: 9 tests (sql preview/export + meta routes)
Tests: harness 233 passed; backend 29 passed; build clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,3 +39,28 @@ test("sessionNew with missing workspace file falls back to default configPath ar
|
||||
expect(bin).toBe("tht");
|
||||
expect(argv).toEqual(["-c", "config/tht.yaml", "session", "new", "q", "--json"]);
|
||||
});
|
||||
|
||||
test("sqlPreview argv has no positional file — uses --session to resolve path", async () => {
|
||||
// The harness preview_cmd now resolves sql_final.sql from the session workspace;
|
||||
// the backend must NOT pass a sessions/<id>/sql_final.sql positional arg.
|
||||
(spawn as any).mockClear();
|
||||
const r = new ThtRunner({ thtBin: "tht", harnessDir: "/nope", configPath: "config/tht.yaml" });
|
||||
// stub json() via run() — just need spawn call captured
|
||||
r.run = async () => ({ code: 0, stdout: '{"columns":[],"rows":[],"execution_ms":1,"truncated":false}', stderr: "" });
|
||||
await r.sqlPreview("ses1", { limit: 10, offset: 5 });
|
||||
// Verify via the patched run — we stub run() so spawn isn't called again.
|
||||
// Instead confirm directly that sqlPreview builds the right args by inspecting run calls.
|
||||
// We swap back to a spy on run itself.
|
||||
const runSpy = vi.fn().mockResolvedValue({
|
||||
code: 0,
|
||||
stdout: '{"columns":["c"],"rows":[[1]],"execution_ms":2,"truncated":false}',
|
||||
stderr: "",
|
||||
});
|
||||
const r2 = new ThtRunner({ thtBin: "tht", harnessDir: "/nope", configPath: "config/tht.yaml" });
|
||||
r2.run = runSpy;
|
||||
await r2.sqlPreview("ses2", { limit: 20, offset: 0 });
|
||||
const [calledArgs] = runSpy.mock.calls[0];
|
||||
// Must NOT include any positional file path before --session
|
||||
expect(calledArgs).toEqual(["sql", "preview", "--session", "ses2", "--json", "--limit", "20", "--offset", "0"]);
|
||||
expect(calledArgs).not.toContain("sessions/ses2/sql_final.sql");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user