Files
temp/features/steps/resume_model_coverage_boost_steps.py
freemo a808c395f9 test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

235 lines
8.0 KiB
Python

"""Step definitions for Resume model coverage boost tests.
Targets uncovered lines in resume.py:
- Line 150: ValueError in record_checkpoint for negative step_index
- Lines 219-233: ResumeSummary.as_cli_dict method
"""
from behave import given, then, when
from behave.runner import Context
from ulid import ULID
from cleveragents.domain.models.core.resume import (
ResumeCheckpoint,
ResumeMetadata,
ResumeSummary,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
_VALID_ULID = str(ULID())
_PLAN_ULID = str(ULID())
# ---------------------------------------------------------------------------
# Given steps
# ---------------------------------------------------------------------------
@given("I have a ResumeMetadata instance with {total:d} total steps")
def step_create_resume_metadata(context: Context, total: int) -> None:
"""Create a ResumeMetadata for checkpoint recording tests."""
context.resume_metadata = ResumeMetadata(total_steps=total)
context.error = None
@given("I have a ResumeSummary with no optional fields")
def step_summary_no_optionals(context: Context) -> None:
"""Create a ResumeSummary with all optional fields as None."""
context.summary = ResumeSummary(
plan_id=_VALID_ULID,
phase="execute",
processing_state="processing",
last_completed_step=0,
next_step_index=1,
total_steps=5,
decision_id=None,
last_checkpoint_id=None,
sandbox_ref=None,
)
context.cli_dict = None
@given('I have a ResumeSummary with decision_id "{decision_id}"')
def step_summary_with_decision(context: Context, decision_id: str) -> None:
"""Create a ResumeSummary with only decision_id set."""
context.summary = ResumeSummary(
plan_id=_VALID_ULID,
phase="execute",
processing_state="processing",
last_completed_step=0,
next_step_index=1,
total_steps=5,
decision_id=decision_id,
)
context.cli_dict = None
@given('I have a ResumeSummary with last_checkpoint_id "{chk_id}"')
def step_summary_with_checkpoint(context: Context, chk_id: str) -> None:
"""Create a ResumeSummary with only last_checkpoint_id set."""
context.summary = ResumeSummary(
plan_id=_VALID_ULID,
phase="execute",
processing_state="processing",
last_completed_step=1,
next_step_index=2,
total_steps=5,
last_checkpoint_id=chk_id,
)
context.cli_dict = None
@given('I have a ResumeSummary with sandbox_ref "{sandbox_ref}"')
def step_summary_with_sandbox(context: Context, sandbox_ref: str) -> None:
"""Create a ResumeSummary with only sandbox_ref set."""
context.summary = ResumeSummary(
plan_id=_VALID_ULID,
phase="execute",
processing_state="processing",
last_completed_step=1,
next_step_index=2,
total_steps=5,
sandbox_ref=sandbox_ref,
)
context.cli_dict = None
@given("I have a ResumeSummary with all optional fields set")
def step_summary_all_optionals(context: Context) -> None:
"""Create a ResumeSummary with every optional field populated."""
context.summary = ResumeSummary(
plan_id=_VALID_ULID,
phase="execute",
processing_state="processing",
last_completed_step=2,
next_step_index=3,
total_steps=5,
decision_id="DEC_ALL",
last_checkpoint_id=str(ULID()),
sandbox_ref="/tmp/sandbox-all",
)
context.cli_dict = None
@given(
'I have a ResumeSummary with plan_id "{plan_id}" and phase "{phase}" '
'and state "{state}" and last_step {last:d} and next_step {nxt:d} '
"and total {total:d}"
)
def step_summary_with_specific_values(
context: Context,
plan_id: str,
phase: str,
state: str,
last: int,
nxt: int,
total: int,
) -> None:
"""Create a ResumeSummary with explicit base field values."""
context.summary = ResumeSummary(
plan_id=plan_id,
phase=phase,
processing_state=state,
last_completed_step=last,
next_step_index=nxt,
total_steps=total,
)
context.cli_dict = None
# ---------------------------------------------------------------------------
# When steps
# ---------------------------------------------------------------------------
@when("I record a checkpoint with step_index {index:d}")
def step_record_bad_checkpoint(context: Context, index: int) -> None:
"""Attempt to record a checkpoint (may raise ValueError for negative index)."""
try:
checkpoint = ResumeCheckpoint(
checkpoint_id=str(ULID()),
plan_id=_PLAN_ULID,
decision_id="DEC_NEG",
step_index=max(index, 0), # Pydantic ge=0 would reject negative
step_text="Negative step",
)
# Manually set to negative to bypass Pydantic field validation
# and hit the domain guard inside record_checkpoint
object.__setattr__(checkpoint, "step_index", index)
context.resume_metadata.record_checkpoint(checkpoint)
except ValueError as exc:
context.error = exc
@when("I call as_cli_dict on the resume summary")
def step_call_as_cli_dict(context: Context) -> None:
"""Call as_cli_dict on the stored ResumeSummary."""
context.cli_dict = context.summary.as_cli_dict()
# ---------------------------------------------------------------------------
# Then steps
# ---------------------------------------------------------------------------
@then('a ValueError should be raised with message "{msg}"')
def step_assert_value_error(context: Context, msg: str) -> None:
"""Assert a ValueError was raised with the expected message."""
assert context.error is not None, "Expected a ValueError but none was raised"
assert isinstance(context.error, ValueError), (
f"Expected ValueError, got {type(context.error).__name__}"
)
assert msg in str(context.error), f"Expected message '{msg}' in '{context.error}'"
@then('the cli dict should have key "{key}"')
def step_assert_dict_has_key(context: Context, key: str) -> None:
"""Assert the cli dict contains the given key."""
assert context.cli_dict is not None, "cli_dict is None; was as_cli_dict called?"
assert key in context.cli_dict, (
f"Key '{key}' not found in cli_dict: {list(context.cli_dict.keys())}"
)
@then('the cli dict should lack key "{key}"')
def step_assert_dict_missing_key(context: Context, key: str) -> None:
"""Assert the cli dict does NOT contain the given key."""
assert context.cli_dict is not None, "cli_dict is None; was as_cli_dict called?"
assert key not in context.cli_dict, (
f"Key '{key}' should not be in cli_dict but was found with value "
f"'{context.cli_dict[key]}'"
)
@then('the cli dict value for "{key}" should be "{value}"')
def step_assert_dict_key_value(context: Context, key: str, value: str) -> None:
"""Assert the cli dict contains the key with the expected string value."""
assert context.cli_dict is not None
assert key in context.cli_dict, f"Key '{key}' not in cli_dict"
assert str(context.cli_dict[key]) == value, (
f"Expected '{value}', got '{context.cli_dict[key]}'"
)
@then("the cli dict should have exactly {count:d} keys")
def step_assert_dict_key_count(context: Context, count: int) -> None:
"""Assert the cli dict has exactly N keys."""
assert context.cli_dict is not None
actual = len(context.cli_dict)
assert actual == count, (
f"Expected {count} keys, got {actual}: {list(context.cli_dict.keys())}"
)
@then('the cli dict integer for "{key}" should be {value:d}')
def step_assert_dict_key_equals_int(context: Context, key: str, value: int) -> None:
"""Assert a cli dict key equals an integer value."""
assert context.cli_dict is not None
assert key in context.cli_dict, f"Key '{key}' not in cli_dict"
assert context.cli_dict[key] == value, (
f"Expected {value}, got {context.cli_dict[key]}"
)