diff --git a/harness/tests/test_session_mutations.py b/harness/tests/test_session_mutations.py new file mode 100644 index 00000000..4d34e7d7 --- /dev/null +++ b/harness/tests/test_session_mutations.py @@ -0,0 +1,69 @@ +"""Tests for session manifest mutations (rename, group, archive, delete).""" +import pytest + +from tht.config import DatabaseConfig +from tht.session.store import ( + SessionError, + create_session, + delete_session, + load_session, + set_archived, + set_group, + set_name, +) + + +def _db(): + return DatabaseConfig( + database="testdb", user="u", password="p", # noqa: S106 + **{"schema": "public"}, + ) + + +def test_manifest_defaults(tmp_path): + m = create_session("domanda", _db(), tmp_path) + assert m.archived is False + assert m.group is None + assert m.name is None + + +def test_set_name(tmp_path): + m = create_session("domanda", _db(), tmp_path) + set_name(m.id, "Pazienti 2024", tmp_path) + assert load_session(m.id, tmp_path).name == "Pazienti 2024" + + +def test_set_name_empty_clears(tmp_path): + m = create_session("domanda", _db(), tmp_path) + set_name(m.id, "x", tmp_path) + set_name(m.id, "", tmp_path) + assert load_session(m.id, tmp_path).name is None + + +def test_set_group_and_clear(tmp_path): + m = create_session("domanda", _db(), tmp_path) + set_group(m.id, "Aritmologia", tmp_path) + assert load_session(m.id, tmp_path).group == "Aritmologia" + set_group(m.id, "", tmp_path) + assert load_session(m.id, tmp_path).group is None + + +def test_set_archived(tmp_path): + m = create_session("domanda", _db(), tmp_path) + set_archived(m.id, True, tmp_path) + assert load_session(m.id, tmp_path).archived is True + set_archived(m.id, False, tmp_path) + assert load_session(m.id, tmp_path).archived is False + + +def test_delete_session(tmp_path): + m = create_session("domanda", _db(), tmp_path) + delete_session(m.id, tmp_path) + assert not (tmp_path / m.id).exists() + with pytest.raises(SessionError): + load_session(m.id, tmp_path) + + +def test_delete_missing_raises(tmp_path): + with pytest.raises(SessionError): + delete_session("nope", tmp_path) diff --git a/harness/tht/cli/session_cmd.py b/harness/tht/cli/session_cmd.py index 95f60962..a5372372 100644 --- a/harness/tht/cli/session_cmd.py +++ b/harness/tht/cli/session_cmd.py @@ -44,6 +44,9 @@ def _list_sessions(sessions_root: Path) -> list[dict]: "created_at": m.created_at.isoformat(), "updated_at": m.updated_at.isoformat() if m.updated_at else None, "author": m.author, + "name": m.name, + "group": m.group, + "archived": m.archived, }) out.sort(key=lambda r: r["created_at"], reverse=True) return out diff --git a/harness/tht/session/models.py b/harness/tht/session/models.py index 6f4107f7..cf25a5f5 100644 --- a/harness/tht/session/models.py +++ b/harness/tht/session/models.py @@ -43,6 +43,8 @@ class SessionManifest(_YamlModel): model: str | None = None thinking: str | None = None name: str | None = None + archived: bool = False + group: str | None = None class Candidate(BaseModel): diff --git a/harness/tht/session/store.py b/harness/tht/session/store.py index 33a1f0df..4cbb635c 100644 --- a/harness/tht/session/store.py +++ b/harness/tht/session/store.py @@ -1,4 +1,5 @@ import os +import shutil from datetime import UTC, datetime from pathlib import Path @@ -127,3 +128,39 @@ def close_session(session_id: str, sessions_root: Path) -> SessionManifest: manifest.updated_by = current_author() manifest.to_yaml(sessions_root / session_id / MANIFEST) return manifest + + +def _save_touched(manifest: SessionManifest, sessions_root: Path) -> SessionManifest: + """Persist `manifest` updating updated_at/updated_by (single save path for mutations).""" + manifest.updated_at = datetime.now(UTC) + manifest.updated_by = current_author() + manifest.to_yaml(sessions_root / manifest.id / MANIFEST) + return manifest + + +def set_name(session_id: str, name: str | None, sessions_root: Path) -> SessionManifest: + """Set the descriptive name (empty/blank clears it back to None).""" + manifest = load_session(session_id, sessions_root) + manifest.name = (name or "").strip() or None + return _save_touched(manifest, sessions_root) + + +def set_group(session_id: str, group: str | None, sessions_root: Path) -> SessionManifest: + """Set the group (empty/blank clears it back to None).""" + manifest = load_session(session_id, sessions_root) + manifest.group = (group or "").strip() or None + return _save_touched(manifest, sessions_root) + + +def set_archived(session_id: str, archived: bool, sessions_root: Path) -> SessionManifest: + """Flip the archived flag. Archiving does NOT change resumability (a finalized + session stays read-only); unarchive only moves it back to the active list.""" + manifest = load_session(session_id, sessions_root) + manifest.archived = archived + return _save_touched(manifest, sessions_root) + + +def delete_session(session_id: str, sessions_root: Path) -> None: + """Hard-delete the session directory. SessionError if it does not exist.""" + load_session(session_id, sessions_root) # raises SessionError if absent + shutil.rmtree(sessions_root / session_id)