test(service): add persisted-mode integration scenarios for coverage

Add 9 Behave scenarios exercising all database-backed code paths in
DecisionService: record, get, list, list_by_type, get_path_to_root,
get_superseded, mark_superseded, delete, and duplicate detection.
File-level coverage rises from 81% to 96%.

ISSUES CLOSED: #172
This commit is contained in:
khyari hamza
2026-02-26 03:08:00 +00:00
parent db81c4cfd6
commit e179f34266
2 changed files with 129 additions and 0 deletions
+67
View File
@@ -303,3 +303,70 @@ Feature: Decision recording and snapshot store
Given a standalone snapshot store
When I list snapshots for decisions with missing entries
Then the dsvc standalone plan snapshots should have 0 entries
# --- Persisted-mode (database-backed) ---
Scenario: Persisted record and get decision round-trips through database
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Persisted root"
And I get the decision by its ID
Then the dsvc retrieved decision should match the recorded decision
Scenario: Persisted get non-existent decision raises DecisionNotFoundError
Given a persisted decision service
When I try to get decision "01NONEXISTENT00000000000000"
Then a dsvc decision not found error should be raised
Scenario: Persisted list decisions returns ordered results from database
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "First persisted"
And I record a strategy_choice decision for plan "P1" with question "Second persisted"
And I list decisions for plan "P1"
Then the dsvc decision list should have 2 entries
And the dsvc decision list should be ordered by sequence number
Scenario: Persisted list by type filters through database
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Root"
And I record a strategy_choice decision for plan "P1" with question "Strategy"
And I record a strategy_choice decision for plan "P1" with question "Strategy 2"
And I filter decisions for plan "P1" by type "strategy_choice"
Then the dsvc decision list should have 2 entries
Scenario: Persisted get path to root navigates through database
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Root"
And I record a strategy_choice decision for plan "P1" with parent as child "Level 1"
And I record an implementation_choice decision for plan "P1" with second parent as child "Level 2"
And I get the path to root from the last decision
Then the dsvc path should have 3 decisions
And the dsvc last path decision should be the root
Scenario: Persisted get superseded returns superseded decisions from database
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Original"
And I record a strategy_choice decision for plan "P1" with question "Replacement"
And I mark the first decision as superseded by the second
And I get superseded decisions for plan "P1"
Then the dsvc superseded list should have 1 entry
Scenario: Persisted mark superseded updates database and cache
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Old"
And I record a strategy_choice decision for plan "P1" with question "New"
And I mark the first decision as superseded by the second
Then the dsvc first decision should be superseded
And the dsvc first decision superseded_by should match the second decision
Scenario: Persisted delete removes decision from database
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Ephemeral"
And I delete the recorded decision
Then the dsvc decision should be deleted
And the dsvc plan "P1" should have 0 decisions
Scenario: Persisted duplicate decision raises DuplicateDecisionError
Given a persisted decision service
When I record a prompt_definition decision for plan "P1" with question "Original"
And I try to store a duplicate of the recorded decision
Then a dsvc duplicate decision error should be raised
@@ -7,7 +7,9 @@ defines similar assertion patterns for the domain model layer.
from __future__ import annotations
import os
import re
import tempfile
from behave import given, then, when
from behave.runner import Context
@@ -948,3 +950,63 @@ def step_list_snapshots_missing(context: Context) -> None:
@then("the dsvc standalone plan snapshots should have {count:d} entries")
def step_standalone_plan_snapshots_count(context: Context, count: int) -> None:
assert len(context.standalone_plan_snapshots) == count
# ---------------------------------------------------------------------------
# Persisted-mode (database-backed) steps
# ---------------------------------------------------------------------------
@given("a persisted decision service")
def step_persisted_decision_service(context: Context) -> None:
"""Create a DecisionService backed by a real SQLite database via UnitOfWork."""
from cleveragents.infrastructure.database.unit_of_work import UnitOfWork
db_path = tempfile.mktemp(suffix=".db", prefix="dsvc_persisted_")
context._dsvc_db_path = db_path
uow = UnitOfWork(f"sqlite:///{db_path}")
uow.init_database()
context.decision_service = DecisionService(unit_of_work=uow)
context.recorded_decisions = []
context.decision_error = None
context.decision_result = None
context.decision_list = None
context.tree_result = None
context.path_result = None
context.superseded_result = None
context.snapshot_result = None
context.snapshots_dict = None
context.hash_query_result = None
context.delete_result = None
context.explicit_snapshot = None
context._plan_id_registry = {}
# Register cleanup to remove temp DB file
def _cleanup_db() -> None:
for suffix in ("", "-journal", "-wal", "-shm"):
try:
os.unlink(db_path + suffix)
except OSError:
pass
if not hasattr(context, "_cleanup_handlers"):
context._cleanup_handlers = []
context._cleanup_handlers.append(_cleanup_db)
@when("I try to store a duplicate of the recorded decision")
def step_try_store_duplicate(context: Context) -> None:
"""Attempt to store a decision with the same ID that already exists."""
try:
decision = context.decision_result
context.decision_service._store_decision(decision)
context.decision_error = None
except DuplicateDecisionError as exc:
context.decision_error = exc
@then("a dsvc duplicate decision error should be raised")
def step_duplicate_error_raised(context: Context) -> None:
assert context.decision_error is not None
assert isinstance(context.decision_error, DuplicateDecisionError)