"""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]}" )