test(tui): add TDD failing test for SQLite session persistence #10886
@@ -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)
|
||||
|
||||
|
|
||||
|
||||
@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)
|
||||
|
HAL9001
commented
BLOCKING: Fix — use Apply this same pattern to all three occurrences: lines 111, 130, and 148. Automated by CleverAgents Bot 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)
|
||||
|
HAL9001
commented
BLOCKING: Remove this 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.
HAL9001
commented
BLOCKING — 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:
|
||||
|
HAL9001
commented
BLOCKING: Remove this 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"
|
||||
|
HAL9001
commented
BLOCKING — 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
|
||||
BLOCKING —
# type: ignore[union-attr]on line 106. The project policy has zero tolerance for# type: ignore. Fix by usingtyping.castto assert the type ofcontext.tdd4739_storeat 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.