Files
cleveragents-core/features/subplan_service_coverage_boost.feature
HAL9000 93567d1453
CI / lint (pull_request) Successful in 35s
CI / build (pull_request) Successful in 35s
CI / helm (pull_request) Successful in 43s
CI / quality (pull_request) Successful in 51s
CI / push-validation (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 57s
CI / security (pull_request) Successful in 1m22s
CI / unit_tests (pull_request) Successful in 4m17s
CI / docker (pull_request) Successful in 1m30s
CI / integration_tests (pull_request) Successful in 9m26s
CI / coverage (pull_request) Successful in 9m6s
CI / status-check (pull_request) Successful in 3s
fix(subplans): repair SubplanSpawnError lazy import and invariant BDD steps
Fixes the four root causes behind PR #8733's red unit_tests + Robot
integration_tests gates after the post-rebase landing of the v3.3.0
spec + invariant enforcement work.

1. Lazy-import module path used a slash instead of a dot — the
   `__getattr__` lookup in `application/services/__init__.py` could
   never resolve `SubplanSpawnError` because `importlib.import_module`
   only accepts dotted module paths. This broke both the
   `svcov3 lazy-load SubplanSpawnError` Behave scenario AND the
   `Test Services Package Exports` Robot scenario (the
   `from cleveragents.application.services import *` star-import walks
   `__all__` and trips on the bad entry).

2. `subplan_service_coverage_boost.feature:11` contained the literal
   placeholder text `{1:d}` instead of the literal value `1`. Gherkin
   does not interpolate `{n:d}` in feature bodies — only in step
   patterns — so the existing
   `the SubplanSpawnError message should contain {n:d} semicolons`
   step could not match.

3. `step_load_invariants_plan_only` used `@when(re.compile(...))`
   without `use_step_matcher("re")`, so behave silently treated it as
   an undefined step. Switched the file's matcher to `re` for that
   single step (auto-anchored, no end marker — behave's `re` matcher
   refuses `$`) and back to `parse` for the rest, so it no longer
   collides with the parse-matched "with project" variant whose
   `{plan_id}` field is greedy.

4. `step_check_action_against_invariants` used the parse field
   `{action_text}` which doesn't match empty strings, so the
   "Empty action text" scenario reported the When step as undefined.
   Extracted the body into `_check_action_against_loaded` and added a
   literal-pattern `'I check action "" against loaded invariants'`
   step that delegates to it. Both step variants now also write
   `context.error` so the shared
   `the error message should contain "..."` step in `service_steps.py`
   (which reads `context.error`) works against either error type.

5. `step_attempt_strategy_decision` / `step_create_strategy_decision`
   only checked `context.strategize_invariants`, which is empty when
   the scenario adds a global invariant via `Given` but never runs the
   explicit `I start the Strategize phase` step. Added
   `_strategize_active_invariants` helper that falls back to all
   active invariants on the service — mirrors the same fallback
   pattern already used by `_check_action_against_loaded`.

6. `step_winning_scope` in `invariant_reconciliation_actor_steps.py`
   only read `context.reconciliation_result`, so reusing the
   `the winning invariant for "X" should be from "Y" scope` assertion
   in a non-reconciliation scenario errored with AttributeError. Now
   falls back to `context.loaded_invariants` (which
   `InvariantService.load_active_invariants` already merges with the
   plan > project > global precedence the scenario asserts).

Verified locally:
- `unit_tests` gate passes (16504 scenarios, 0 failed, 0 errored).
- `lint` gate passes.
- `Test Services Package Exports` star-import path resolves
  `SubplanSpawnError` cleanly via the package `__getattr__`.

ISSUES CLOSED: #8725
2026-06-02 19:50:41 -04:00

114 lines
5.1 KiB
Gherkin

@phase1 @subplan @coverage
Feature: Subplan Service Coverage Boost
Additional scenarios targeting uncovered code paths in
SubplanService, SubplanSpawnError, and related helpers.
# --- SubplanSpawnError direct instantiation (v3.3.0) ---
@error_object
Scenario: SubplanSpawnError message format includes errors
When a SubplanSpawnError is created for errors "bad resource" and "missing merge"
Then the SubplanSpawnError message should contain 1 semicolons
And the SubplanSpawnError message should contain "Spawn validation failed"
And the SubplanSpawnError message should contain "bad resource"
And the SubplanSpawnError message should contain "missing merge"
# --- decision_service property (line 167) ---
@property_access
Scenario: Accessing the decision_service property returns the injected service
Given a subplan service constructed with a mock decision service
When the decision_service property is accessed
Then the returned decision service should be the injected mock
# --- spawn() with config=None (line 207) ---
@null_guard @spawn
Scenario: Spawn raises ValueError when config is None
Given a subplan service constructed with a mock decision service
And a parent plan for coverage tests
When spawn is called with config set to None
Then a ValueError should be raised with text "config must not be None"
# --- spawn() with spawn_entries=None (line 209) ---
@null_guard @spawn
Scenario: Spawn raises ValueError when spawn_entries is None
Given a subplan service constructed with a mock decision service
And a parent plan for coverage tests
And a default subplan config for coverage tests
When spawn is called with spawn_entries set to None
Then a ValueError should be raised with text "spawn_entries must not be None"
# --- spawn() raising SubplanSpawnError (v3.3.0) ---
@spawn_validation_error
Scenario: Spawn raises SubplanSpawnError when validation fails
Given a subplan service constructed with a mock decision service
And a parent plan for coverage tests
And a subplan config with merge strategy forcibly set to None
And a spawn entry with a valid spawn decision for coverage tests
When spawn is called and validation is expected to fail
Then a SubplanSpawnError should be raised
And the SubplanSpawnError should mention "merge strategy"
# --- validate_spawn() with config=None (line 301) ---
@null_guard @validate
Scenario: Validate spawn raises ValueError when config is None
Given a subplan service constructed with a mock decision service
And a spawn entry with a valid spawn decision for coverage tests
When validate_spawn is called with config set to None
Then a ValueError should be raised with text "config must not be None"
# --- validate_spawn() with spawn_entries=None (line 303) ---
@null_guard @validate
Scenario: Validate spawn raises ValueError when spawn_entries is None
Given a subplan service constructed with a mock decision service
And a default subplan config for coverage tests
When validate_spawn is called with spawn_entries set to None
Then a ValueError should be raised with text "spawn_entries must not be None"
# --- Empty action_name validation (lines 334-336) ---
@validation @action_name
Scenario: Validate spawn detects empty action_name on spawn entry
Given a subplan service constructed with a mock decision service
And a default subplan config for coverage tests
And a spawn entry with an empty action_name
When validate_spawn is called for coverage tests
Then the validation result should not be valid
And the validation errors should mention "empty action_name"
@validation @action_name
Scenario: Validate spawn detects whitespace-only action_name on spawn entry
Given a subplan service constructed with a mock decision service
And a default subplan config for coverage tests
And a spawn entry with a whitespace-only action_name
When validate_spawn is called for coverage tests
Then the validation result should not be valid
And the validation errors should mention "empty action_name"
# --- get_spawn_decisions() with empty plan_id (line 375) ---
@null_guard @get_decisions
Scenario: Get spawn decisions raises ValueError for empty plan_id
Given a subplan service constructed with a mock decision service
When get_spawn_decisions is called with an empty plan_id
Then a ValueError should be raised with text "plan_id must not be empty"
@null_guard @get_decisions
Scenario: Get spawn decisions raises ValueError for whitespace-only plan_id
Given a subplan service constructed with a mock decision service
When get_spawn_decisions is called with a whitespace-only plan_id
Then a ValueError should be raised with text "plan_id must not be empty"
# --- build_spawn_entries() with decisions=None (line 414) ---
@null_guard @build_entries
Scenario: Build spawn entries raises ValueError when decisions is None
Given a subplan service constructed with a mock decision service
When build_spawn_entries is called with decisions set to None
Then a ValueError should be raised with text "decisions must not be None"