Files
cleveragents-core/features/decision_service_coverage.feature
T
khyari hamza 0e36755db9
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / build (pull_request) Successful in 14s
CI / quality (pull_request) Successful in 16s
CI / security (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 43s
CI / unit_tests (pull_request) Successful in 2m18s
CI / integration_tests (pull_request) Successful in 2m53s
CI / docker (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 3m58s
CI / benchmark-regression (pull_request) Successful in 24m44s
fix(service): address review findings for decision service
- Rehydrate sequence counter from DB on restart (BUG-1)
- Add uniqueness guard raising SequenceConflictError (BUG-2)
- Fix delete_decision consistency between persisted/in-memory (BUG-3)
- Validate new_decision_id exists in mark_superseded (BUG-4)
- Include orphaned subtrees in get_tree output (BUG-5)
- Remove dead _decision_seq/_next_seq from plan_lifecycle (BUG-7)
- Export SequenceConflictError from services __init__ (SPEC-2)
- Replace list[Any] with list[ArtifactRef] typing (SPEC-4)
- Add actor_reasoning max_length validation (SEC-1)
- Use SELECT COUNT(*) in count_decisions (PERF-1)
- Replace MagicMock with create_autospec(Settings) (TEST-3)
- Eliminate UnitOfWork.__new__() anti-pattern (TEST-4)
- Use exact assertion counts in DI tests (TEST-5)
- Add restart rehydration, BFS order, invalid type, and
  confidence boundary test scenarios (TEST-1/2/6/7)
- Fix decision_service_coverage.feature to match actual API

ISSUES CLOSED: #172
2026-03-03 12:53:28 +00:00

125 lines
6.2 KiB
Gherkin

Feature: DecisionService application-layer coverage
As a developer
I want thorough unit tests for every DecisionService method
So that decision_service.py achieves full line and branch coverage
# All steps use the "dsvc-" prefix to avoid collisions with other step files.
# ------------------------------------------------------------------
# Constructor
# ------------------------------------------------------------------
Scenario: dsvc- constructor stores settings and unit_of_work
Given dsvc- a mock settings object and a mock UnitOfWork
When dsvc- I construct a DecisionService with those dependencies
Then dsvc- the service should store the settings
And dsvc- the service should store the unit_of_work
And dsvc- the service should have a bound structlog logger
# ------------------------------------------------------------------
# record_decision
# ------------------------------------------------------------------
Scenario: dsvc- record_decision persists and returns the decision
Given dsvc- a DecisionService with a mocked UnitOfWork
When dsvc- I call record_decision with plan_id and required args
Then dsvc- the UoW transaction should have been entered
And dsvc- ctx.decisions.create should have been called once
And dsvc- the returned decision should have the correct plan_id
Scenario: dsvc- record_decision logs info on success
Given dsvc- a DecisionService with a mocked UnitOfWork and captured logger
When dsvc- I call record_decision with plan_id and required args
Then dsvc- the logger should have recorded an info call with "decision.recorded"
# ------------------------------------------------------------------
# get_decision
# ------------------------------------------------------------------
Scenario: dsvc- get_decision retrieves a decision by ID
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo get method returns a Decision
When dsvc- I call get_decision with a known ID
Then dsvc- ctx.decisions.get should have been called with the ID
And dsvc- the returned value should be the expected Decision
Scenario: dsvc- get_decision raises DecisionNotFoundError when not found
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo get method returns None
When dsvc- I call get_decision with an unknown ID expecting not-found
Then dsvc- a DecisionNotFoundError should have been raised
# ------------------------------------------------------------------
# list_decisions
# ------------------------------------------------------------------
Scenario: dsvc- list_decisions returns list of decisions
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo get_by_plan method returns 3 decisions
When dsvc- I call list_decisions with a plan ID
Then dsvc- ctx.decisions.get_by_plan should have been called with the plan ID
And dsvc- the returned list should have 3 decisions
Scenario: dsvc- list_decisions returns empty list when none exist
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo get_by_plan method returns 0 decisions
When dsvc- I call list_decisions with a plan ID
Then dsvc- the returned list should have 0 decisions
# ------------------------------------------------------------------
# get_tree
# ------------------------------------------------------------------
Scenario: dsvc- get_tree returns BFS-ordered list
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo get_by_plan method returns 4 decisions as a tree
When dsvc- I call get_tree with a plan ID
Then dsvc- the returned tree list should have 4 decisions
# ------------------------------------------------------------------
# get_path_to_root
# ------------------------------------------------------------------
Scenario: dsvc- get_path_to_root returns leaf-to-root path
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo get_path_to_root method returns 3 decisions
When dsvc- I call get_path_to_root with a leaf ID
Then dsvc- ctx.decisions.get_path_to_root should have been called with the leaf ID
And dsvc- the returned path list should have 3 decisions
# ------------------------------------------------------------------
# mark_superseded
# ------------------------------------------------------------------
Scenario: dsvc- mark_superseded updates and returns the decision
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo has the replacement decision in cache
And dsvc- the mock repo update_superseded_by method returns a superseded Decision
When dsvc- I call mark_superseded with old and new IDs
Then dsvc- ctx.decisions.update_superseded_by should have been called with both IDs
And dsvc- the returned decision should be the superseded one
Scenario: dsvc- mark_superseded logs info with both IDs
Given dsvc- a DecisionService with a mocked UnitOfWork and captured logger
And dsvc- the mock repo has the replacement decision in cache
And dsvc- the mock repo update_superseded_by method returns a superseded Decision
When dsvc- I call mark_superseded with old and new IDs
Then dsvc- the logger should have recorded an info call with "decision.superseded"
# ------------------------------------------------------------------
# list_by_type
# ------------------------------------------------------------------
Scenario: dsvc- list_by_type returns filtered decisions
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo list_by_type method returns 2 decisions
When dsvc- I call list_by_type with plan ID and type "strategy_choice"
Then dsvc- ctx.decisions.list_by_type should have been called with plan ID and type
And dsvc- the returned type list should have 2 decisions
Scenario: dsvc- list_by_type returns empty when no matches
Given dsvc- a DecisionService with a mocked UnitOfWork
And dsvc- the mock repo list_by_type method returns 0 decisions
When dsvc- I call list_by_type with plan ID and type "error_recovery"
Then dsvc- the returned type list should have 0 decisions