test(tui): add TDD failing test for SQLite session persistence #10886

Merged
HAL9000 merged 4 commits from tdd/m8-tui-sqlite-session-persistence into master 2026-06-07 05:21:13 +00:00
2 changed files with 191 additions and 0 deletions
@@ -0,0 +1,152 @@
"""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 typing import Any, cast
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)
Outdated
Review

BLOCKING — # type: ignore[union-attr] on line 106. The project policy has zero tolerance for # type: ignore. Fix by using typing.cast to assert the type of context.tdd4739_store at the call site, or declare a properly typed local variable after the null check. Since this is a TDD test where the concrete class does not yet exist, typing.cast(typing.T.Any, store) before calling the method is appropriate.

BLOCKING — `# type: ignore[union-attr]` on line 106. The project policy has zero tolerance for `# type: ignore`. Fix by using `typing.cast` to assert the type of `context.tdd4739_store` at the call site, or declare a properly typed local variable after the null check. Since this is a TDD test where the concrete class does not yet exist, `typing.cast(typing.T.Any, store)` before calling the method is appropriate.
@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 = cast(Any, context.tdd4739_store)
Outdated
Review

BLOCKING: typing.T.Any is invalid Python. typing.T is a TypeVar and has no .Any attribute — this raises AttributeError at runtime and will fail Pyright strict checking.

Fix — use typing.cast with from typing import Any:

from typing import Any, cast
...
assert context.tdd4739_store is not None
store = cast(Any, context.tdd4739_store)
store.save(...)  # no type suppression needed

Apply this same pattern to all three occurrences: lines 111, 130, and 148.


Automated by CleverAgents Bot
Supervisor: PR Review | Agent: pr-review-worker

BLOCKING: `typing.T.Any` is invalid Python. `typing.T` is a TypeVar and has no `.Any` attribute — this raises `AttributeError` at runtime and will fail Pyright strict checking. Fix — use `typing.cast` with `from typing import Any`: ```python from typing import Any, cast ... assert context.tdd4739_store is not None store = cast(Any, context.tdd4739_store) store.save(...) # no type suppression needed ``` Apply this same pattern to all three occurrences: lines 111, 130, and 148. --- Automated by CleverAgents Bot Supervisor: PR Review | Agent: pr-review-worker
assert store is not None, "TuiSessionStore was not created"
store.save(
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 = cast(Any, context.tdd4739_store)
Outdated
Review

BLOCKING: Remove this # type: ignore[union-attr] — zero tolerance for type suppression per project policy. Use typing.cast(Any, store) instead, or add a duck-typing assertion: assert hasattr(store, "save"). Since this is a TDD test for a class that does not yet exist, cast is the cleaner approach.

BLOCKING: Remove this `# type: ignore[union-attr]` — zero tolerance for type suppression per project policy. Use `typing.cast(Any, store)` instead, or add a duck-typing assertion: `assert hasattr(store, "save")`. Since this is a TDD test for a class that does not yet exist, `cast` is the cleaner approach.
Outdated
Review

BLOCKING — # type: ignore[union-attr] on line 130. Same issue as above — the zero-tolerance policy on # type: ignore applies. Please replace with a typing.cast approach.

BLOCKING — `# type: ignore[union-attr]` on line 130. Same issue as above — the zero-tolerance policy on `# type: ignore` applies. Please replace with a `typing.cast` approach.
assert store is not None, "TuiSessionStore was not created"
record = store.get(session_id)
assert record is not None, (
f"TuiSessionStore.get('{session_id}') returned None — 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:
Outdated
Review

BLOCKING: Remove this # type: ignore[union-attr] — zero tolerance for type suppression per project policy. Use typing.cast(Any, store) instead.

BLOCKING: Remove this `# type: ignore[union-attr]` — zero tolerance for type suppression per project policy. Use `typing.cast(Any, store)` instead.
"""Assert the store lists the expected number of sessions."""
store = cast(Any, context.tdd4739_store)
assert store is not None, "TuiSessionStore was not created"
sessions = store.list_all()
assert sessions is not None, "TuiSessionStore.list_all() returned None"
Outdated
Review

BLOCKING — # type: ignore[union-attr] on line 150. Same issue as above — replace with cast-based type resolution.

BLOCKING — `# type: ignore[union-attr]` on line 150. Same issue as above — replace with cast-based type resolution.
actual = len(sessions)
assert actual == count, f"Expected {count} sessions in store, got {actual}"
@@ -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