diff --git a/features/steps/tdd_tui_session_store_4739_steps.py b/features/steps/tdd_tui_session_store_4739_steps.py new file mode 100644 index 000000000..9a5e8c8a9 --- /dev/null +++ b/features/steps/tdd_tui_session_store_4739_steps.py @@ -0,0 +1,154 @@ +"""Step definitions for TDD Bug #4739 — TUI SQLite session persistence not implemented. + +These steps verify that ``cleveragents.tui.session_store`` exists and that +``TuiSessionStore`` can persist and retrieve session records from SQLite. + +All scenarios are tagged ``@tdd_expected_fail`` so CI passes while the +module is absent. The expected-fail mechanism inverts the result: a +failing assertion means the bug is still present (CI passes); a passing +assertion means the fix has been applied (CI also passes after the +``@tdd_expected_fail`` tag is removed in the bugfix PR). + +Failure mode: ``AssertionError`` (not ``ImportError`` or ``RuntimeError``). +""" + +from __future__ import annotations + +import importlib +import os +import shutil +import tempfile +import types + +from behave import given, then, when +from behave.runner import Context + + +@given("the tdd4739 test environment is set up") +def step_tdd4739_setup(context: Context) -> None: + """Initialise context attributes used across tdd4739 steps.""" + context.tdd4739_module: types.ModuleType | None = None + context.tdd4739_import_error: Exception | None = None + context.tdd4739_store: object | None = None + context.tdd4739_tmpdir: str | None = None + + +@given("the tdd4739 test environment is set up with a temp db path") +def step_tdd4739_setup_with_tmpdir(context: Context) -> None: + """Initialise context and create a temporary directory for the SQLite db.""" + context.tdd4739_module = None + context.tdd4739_import_error = None + context.tdd4739_store = None + + tmpdir = tempfile.mkdtemp(prefix="tdd4739_") + context.tdd4739_tmpdir = tmpdir + context.tdd4739_db_path = os.path.join(tmpdir, "tui.db") + + def _cleanup() -> None: + shutil.rmtree(tmpdir, ignore_errors=True) + + context.add_cleanup(_cleanup) + + +@when("I attempt to import cleveragents.tui.session_store") +def step_tdd4739_import(context: Context) -> None: + """Attempt to import the session_store module; capture any ImportError.""" + try: + mod = importlib.import_module("cleveragents.tui.session_store") + context.tdd4739_module = mod + context.tdd4739_import_error = None + except ImportError as exc: + context.tdd4739_module = None + context.tdd4739_import_error = exc + + +@then("the tdd4739 import should succeed without errors") +def step_tdd4739_import_ok(context: Context) -> None: + """Assert the import succeeded — fails via AssertionError if module is absent.""" + assert context.tdd4739_import_error is None, ( + f"cleveragents.tui.session_store could not be imported: " + f"{context.tdd4739_import_error}" + ) + assert context.tdd4739_module is not None, ( + "cleveragents.tui.session_store module is None after import" + ) + + +@then("the tdd4739 module should expose a TuiSessionStore class") +def step_tdd4739_class_exists(context: Context) -> None: + """Assert TuiSessionStore is present in the module.""" + assert context.tdd4739_import_error is None, ( + f"cleveragents.tui.session_store could not be imported: " + f"{context.tdd4739_import_error}" + ) + mod = context.tdd4739_module + assert mod is not None, "session_store module is None" + assert hasattr(mod, "TuiSessionStore"), ( + "cleveragents.tui.session_store does not expose a TuiSessionStore class" + ) + + +@when("I create a TuiSessionStore pointing at the temp db path") +def step_tdd4739_create_store(context: Context) -> None: + """Instantiate TuiSessionStore with the temp db path.""" + try: + mod = importlib.import_module("cleveragents.tui.session_store") + except ImportError as exc: + raise AssertionError( + f"cleveragents.tui.session_store could not be imported: {exc}" + ) from exc + + assert hasattr(mod, "TuiSessionStore"), ( + "cleveragents.tui.session_store does not expose a TuiSessionStore class" + ) + context.tdd4739_store = mod.TuiSessionStore(db_path=context.tdd4739_db_path) + + +@when('I save a tdd4739 session record with id "{session_id}"') +def step_tdd4739_save_session(context: Context, session_id: str) -> None: + """Save a minimal session record to the store.""" + store = context.tdd4739_store + assert store is not None, "TuiSessionStore was not created" + store.save( # type: ignore[union-attr] + session_id=session_id, + persona_name="default", + actor_identity="anthropic/claude-4-sonnet", + title=f"Test session {session_id}", + prompt_count=0, + total_cost=0.0, + created_at="2026-01-01T00:00:00Z", + last_used="2026-01-01T00:00:00Z", + project_path=None, + meta_json="{}", + ) + + +@then('the tdd4739 session record should be retrievable by id "{session_id}"') +def step_tdd4739_get_session(context: Context, session_id: str) -> None: + """Assert the session record can be retrieved by its id.""" + store = context.tdd4739_store + assert store is not None, "TuiSessionStore was not created" + record = store.get(session_id) # type: ignore[union-attr] + assert record is not None, ( + f"TuiSessionStore.get('{session_id}') returned None — " + f"session was not persisted" + ) + assert getattr(record, "id", None) == session_id or ( + isinstance(record, dict) and record.get("id") == session_id + ), ( + f"Retrieved record id mismatch: expected '{session_id}', " + f"got {getattr(record, 'id', record)!r}" + ) + + +@then("the tdd4739 session store should list {count:d} sessions") +def step_tdd4739_list_sessions(context: Context, count: int) -> None: + """Assert the store lists the expected number of sessions.""" + store = context.tdd4739_store + assert store is not None, "TuiSessionStore was not created" + sessions = store.list_all() # type: ignore[union-attr] + assert sessions is not None, "TuiSessionStore.list_all() returned None" + actual = len(sessions) + assert actual == count, ( + f"Expected {count} sessions in store, got {actual}" + ) diff --git a/features/tdd_tui_session_store_4739.feature b/features/tdd_tui_session_store_4739.feature new file mode 100644 index 000000000..6b924c03a --- /dev/null +++ b/features/tdd_tui_session_store_4739.feature @@ -0,0 +1,39 @@ +@tdd_issue @tdd_issue_4739 +Feature: Bug #4739 — TUI SQLite session persistence not implemented + As a developer + I want to verify that the TUI session persistence layer exists + So that the bug is captured and will be caught by a regression test + + The spec (§Session Persistence and Resume) requires TUI sessions to be + persisted in an SQLite database at ~/.local/state/cleveragents/tui.db + via a TuiSessionStore class in cleveragents.tui.session_store. + + Currently no such module exists — the TUI hardcodes a single in-memory + session and has no SQLite persistence layer. + + @tdd_issue @tdd_issue_4739 @tdd_expected_fail + Scenario: TuiSessionStore module can be imported from cleveragents.tui + Given the tdd4739 test environment is set up + When I attempt to import cleveragents.tui.session_store + Then the tdd4739 import should succeed without errors + + @tdd_issue @tdd_issue_4739 @tdd_expected_fail + Scenario: TuiSessionStore class exists in the session_store module + Given the tdd4739 test environment is set up + When I attempt to import cleveragents.tui.session_store + Then the tdd4739 module should expose a TuiSessionStore class + + @tdd_issue @tdd_issue_4739 @tdd_expected_fail + Scenario: TuiSessionStore can persist a session record to SQLite + Given the tdd4739 test environment is set up with a temp db path + When I create a TuiSessionStore pointing at the temp db path + And I save a tdd4739 session record with id "test-session-001" + Then the tdd4739 session record should be retrievable by id "test-session-001" + + @tdd_issue @tdd_issue_4739 @tdd_expected_fail + Scenario: TuiSessionStore lists all persisted sessions + Given the tdd4739 test environment is set up with a temp db path + When I create a TuiSessionStore pointing at the temp db path + And I save a tdd4739 session record with id "sess-a" + And I save a tdd4739 session record with id "sess-b" + Then the tdd4739 session store should list 2 sessions