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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user