feat(harness): tht session documents --json read command
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
"""Tests for `tht session documents --json` and build_documents."""
|
||||||
|
import json
|
||||||
|
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from tht.config import DatabaseConfig
|
||||||
|
from tht.session.store import build_documents, create_session
|
||||||
|
from tht.cli.session_cmd import session_app
|
||||||
|
|
||||||
|
|
||||||
|
def _db():
|
||||||
|
return DatabaseConfig(
|
||||||
|
database="testdb", user="u", password="p", # noqa: S106
|
||||||
|
**{"schema": "public"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_documents_always_has_original_question(tmp_path):
|
||||||
|
m = create_session("quante ablazioni nel 2024", _db(), tmp_path)
|
||||||
|
docs = build_documents(m, tmp_path / m.id)
|
||||||
|
keys = [d["key"] for d in docs]
|
||||||
|
assert keys[0] == "question"
|
||||||
|
assert docs[0]["format"] == "text"
|
||||||
|
assert docs[0]["content"] == "quante ablazioni nel 2024"
|
||||||
|
# question.md is written by create_session -> revised_question present
|
||||||
|
assert "revised_question" in keys
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_documents_includes_existing_artifacts_only(tmp_path):
|
||||||
|
m = create_session("q", _db(), tmp_path)
|
||||||
|
sdir = tmp_path / m.id
|
||||||
|
(sdir / "sql_final.sql").write_text("SELECT 1")
|
||||||
|
(sdir / "schema_linking.json").write_text('{"question":"q","candidates":[]}')
|
||||||
|
docs = {d["key"]: d for d in build_documents(m, sdir)}
|
||||||
|
assert docs["sql"]["format"] == "sql"
|
||||||
|
assert docs["sql"]["content"] == "SELECT 1"
|
||||||
|
assert docs["schema_linking"]["format"] == "schema-linking"
|
||||||
|
assert "validation_report" not in docs # not written
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_documents_json(tmp_path, monkeypatch):
|
||||||
|
m = create_session("q", _db(), tmp_path)
|
||||||
|
from tht.cli import session_cmd
|
||||||
|
|
||||||
|
class FakePaths:
|
||||||
|
sessions = tmp_path
|
||||||
|
|
||||||
|
class FakeCfg:
|
||||||
|
paths = FakePaths()
|
||||||
|
database = _db()
|
||||||
|
|
||||||
|
monkeypatch.setattr(session_cmd, "_load_config_or_exit", lambda _: FakeCfg())
|
||||||
|
res = CliRunner().invoke(session_app, ["documents", m.id, "--json"])
|
||||||
|
assert res.exit_code == 0, res.output
|
||||||
|
docs = json.loads(res.output)
|
||||||
|
assert isinstance(docs, list)
|
||||||
|
assert docs[0]["key"] == "question"
|
||||||
@@ -218,6 +218,25 @@ def delete_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT)
|
|||||||
typer.secho(f"OK: sessione {session_id} eliminata.", fg=typer.colors.GREEN)
|
typer.secho(f"OK: sessione {session_id} eliminata.", fg=typer.colors.GREEN)
|
||||||
|
|
||||||
|
|
||||||
|
@session_app.command("documents")
|
||||||
|
def documents_cmd(
|
||||||
|
session_id: str = typer.Argument(...),
|
||||||
|
json_out: bool = typer.Option(False, "--json", help="Emetti JSON su stdout (pristine)."),
|
||||||
|
config: Path = CONFIG_OPT,
|
||||||
|
) -> None:
|
||||||
|
"""Documenti di sola lettura della sessione (domanda, rivista, schema, SQL, report, decisioni)."""
|
||||||
|
from tht.session.store import build_documents
|
||||||
|
|
||||||
|
cfg = _load_config_or_exit(config)
|
||||||
|
manifest = load_session_or_exit(cfg, session_id)
|
||||||
|
docs = build_documents(manifest, session_dir(cfg, session_id))
|
||||||
|
if json_out:
|
||||||
|
typer.echo(json.dumps(docs, ensure_ascii=False, indent=2))
|
||||||
|
return
|
||||||
|
for d in docs:
|
||||||
|
typer.echo(f"[{d['phase']}] {d['title']} ({d['format']})")
|
||||||
|
|
||||||
|
|
||||||
def session_problems(cfg, session_id: str) -> list[str]:
|
def session_problems(cfg, session_id: str) -> list[str]:
|
||||||
"""Problemi del Blocco 3 (decisioni + schema_linking). Riusato da check e finalize."""
|
"""Problemi del Blocco 3 (decisioni + schema_linking). Riusato da check e finalize."""
|
||||||
import json
|
import json
|
||||||
|
|||||||
@@ -164,3 +164,27 @@ def delete_session(session_id: str, sessions_root: Path) -> None:
|
|||||||
"""Hard-delete the session directory. SessionError if it does not exist."""
|
"""Hard-delete the session directory. SessionError if it does not exist."""
|
||||||
load_session(session_id, sessions_root) # raises SessionError if absent
|
load_session(session_id, sessions_root) # raises SessionError if absent
|
||||||
shutil.rmtree(sessions_root / session_id)
|
shutil.rmtree(sessions_root / session_id)
|
||||||
|
|
||||||
|
|
||||||
|
def build_documents(manifest: SessionManifest, session_dir: Path) -> list[dict]:
|
||||||
|
"""Ordered, read-only document bundle for the UI panel. Only documents that exist
|
||||||
|
on disk are returned. CTE artifacts (F6) are intentionally excluded (intermediate)."""
|
||||||
|
docs: list[dict] = [{
|
||||||
|
"phase": "—", "key": "question", "title": "Domanda originale",
|
||||||
|
"format": "text", "content": manifest.question,
|
||||||
|
}]
|
||||||
|
spec = [
|
||||||
|
("question.md", "F3", "revised_question", "Domanda rivista", "markdown"),
|
||||||
|
("schema_linking.json", "F4", "schema_linking", "Schema linking", "schema-linking"),
|
||||||
|
("sql_final.sql", "F7", "sql", "SQL finale", "sql"),
|
||||||
|
("validation_report.md", "finalize", "validation_report", "Report di validazione", "markdown"),
|
||||||
|
("review_decisions.jsonl", "—", "decisions", "Decisioni", "decisions"),
|
||||||
|
]
|
||||||
|
for filename, phase, key, title, fmt in spec:
|
||||||
|
path = session_dir / filename
|
||||||
|
if path.exists():
|
||||||
|
docs.append({
|
||||||
|
"phase": phase, "key": key, "title": title,
|
||||||
|
"format": fmt, "content": path.read_text(),
|
||||||
|
})
|
||||||
|
return docs
|
||||||
|
|||||||
Reference in New Issue
Block a user