"""Step definitions for correction_model.feature.""" from __future__ import annotations from behave import given, then, when from pydantic import ValidationError as PydanticValidationError from cleveragents.application.services.correction_service import CorrectionService from cleveragents.core.exceptions import ResourceNotFoundError, ValidationError from cleveragents.domain.models.core.correction import ( CorrectionAttempt, CorrectionImpact, CorrectionMode, CorrectionRequest, CorrectionResult, CorrectionStatus, ) # ── Given steps ────────────────────────────────────────────────── @given("a correction service") def step_given_correction_service(context): context.correction_service = CorrectionService() context.correction = None context.impact = None context.result = None context.error = None @given( 'a correction request for plan "{plan_id}" decision "{decision_id}" ' 'in {mode} mode with guidance "{guidance}"' ) def step_given_correction_request(context, plan_id, decision_id, mode, guidance): svc = context.correction_service correction_mode = CorrectionMode(mode) context.correction = svc.request_correction( plan_id=plan_id, target_decision_id=decision_id, mode=correction_mode, guidance=guidance, ) @given('a cancelled correction for plan "{plan_id}" decision "{decision_id}"') def step_given_cancelled_correction(context, plan_id, decision_id): svc = context.correction_service correction = svc.request_correction( plan_id=plan_id, target_decision_id=decision_id, mode=CorrectionMode.REVERT, guidance="To be cancelled", ) svc.cancel_correction(correction.correction_id) context.correction = svc.get_correction(correction.correction_id) @given('an applied correction for plan "{plan_id}" decision "{decision_id}"') def step_given_applied_correction(context, plan_id, decision_id): svc = context.correction_service correction = svc.request_correction( plan_id=plan_id, target_decision_id=decision_id, mode=CorrectionMode.REVERT, guidance="To be applied", ) svc.execute_correction(correction.correction_id) context.correction = svc.get_correction(correction.correction_id) # ── When steps ─────────────────────────────────────────────────── @when( 'I request a correction for plan "{plan_id}" decision "{decision_id}" ' 'in {mode} mode with guidance "{guidance}"' ) def step_when_request_correction(context, plan_id, decision_id, mode, guidance): svc = context.correction_service correction_mode = CorrectionMode(mode) context.correction = svc.request_correction( plan_id=plan_id, target_decision_id=decision_id, mode=correction_mode, guidance=guidance, ) @when( 'I request a dry-run correction for plan "{plan_id}" decision ' '"{decision_id}" in {mode} mode with guidance "{guidance}"' ) def step_when_request_dry_run(context, plan_id, decision_id, mode, guidance): svc = context.correction_service correction_mode = CorrectionMode(mode) context.correction = svc.request_correction( plan_id=plan_id, target_decision_id=decision_id, mode=correction_mode, guidance=guidance, dry_run=True, ) @when("I analyze the correction impact") def step_when_analyze_impact(context): svc = context.correction_service context.impact = svc.analyze_impact(context.correction.correction_id) @when("I cancel the correction") def step_when_cancel(context): svc = context.correction_service context.correction = svc.cancel_correction(context.correction.correction_id) @when("I try to cancel the correction") def step_when_try_cancel(context): svc = context.correction_service context.error = None try: svc.cancel_correction(context.correction.correction_id) except (ValidationError, ResourceNotFoundError) as exc: context.error = exc @when("I execute the correction") def step_when_execute(context): svc = context.correction_service context.result = svc.execute_correction(context.correction.correction_id) # Refresh the correction object context.correction = svc.get_correction(context.correction.correction_id) @when("I try to execute the correction") def step_when_try_execute(context): svc = context.correction_service context.error = None try: svc.execute_correction(context.correction.correction_id) except (ValidationError, ResourceNotFoundError) as exc: context.error = exc @when("I get the correction by its ID") def step_when_get_by_id(context): svc = context.correction_service context.found_correction = svc.get_correction(context.correction.correction_id) @when('I try to get correction "{correction_id}"') def step_when_try_get_nonexistent(context, correction_id): svc = context.correction_service context.error = None try: svc.get_correction(correction_id) except ResourceNotFoundError as exc: context.error = exc @when('I list corrections for plan "{plan_id}"') def step_when_list_by_plan(context, plan_id): svc = context.correction_service context.listed_corrections = svc.list_corrections(plan_id=plan_id) @when("I list all corrections") def step_when_list_all(context): svc = context.correction_service context.listed_corrections = svc.list_corrections() @when("I try to request a correction with empty decision ID") def step_when_empty_decision(context): svc = context.correction_service context.error = None try: svc.request_correction( plan_id="plan-1", target_decision_id="", mode=CorrectionMode.REVERT, guidance="test", ) except ValidationError as exc: context.error = exc @when("I try to request a correction with empty plan ID") def step_when_empty_plan(context): svc = context.correction_service context.error = None try: svc.request_correction( plan_id="", target_decision_id="DEC-001", mode=CorrectionMode.REVERT, guidance="test", ) except ValidationError as exc: context.error = exc @when("I try to request a correction with empty guidance") def step_when_empty_guidance(context): svc = context.correction_service context.error = None context.correction = svc.request_correction( plan_id="plan-1", target_decision_id="DEC-001", mode=CorrectionMode.REVERT, guidance="", ) @when("I try to request a correction with whitespace-only guidance") def step_when_whitespace_guidance(context): svc = context.correction_service context.error = None context.correction = svc.request_correction( plan_id="plan-1", target_decision_id="DEC-001", mode=CorrectionMode.REVERT, guidance=" ", ) @when("I try to create a CorrectionRequest with empty plan_id") def step_when_model_empty_plan(context): context.error = None try: CorrectionRequest( plan_id="", target_decision_id="DEC-001", mode=CorrectionMode.REVERT, guidance="test", ) except PydanticValidationError as exc: context.error = exc @when("I try to create a CorrectionRequest with empty target_decision_id") def step_when_model_empty_decision(context): context.error = None try: CorrectionRequest( plan_id="plan-1", target_decision_id="", mode=CorrectionMode.REVERT, guidance="test", ) except PydanticValidationError as exc: context.error = exc @when("I try to create a CorrectionRequest with empty guidance") def step_when_model_empty_guidance(context): context.error = None context.correction = CorrectionRequest( plan_id="plan-1", target_decision_id="DEC-001", mode=CorrectionMode.REVERT, guidance="", ) @when('I try to create a CorrectionImpact with invalid risk_level "{level}"') def step_when_model_invalid_risk(context, level): context.error = None try: CorrectionImpact(risk_level=level) except PydanticValidationError as exc: context.error = exc @when('I create a CorrectionImpact with risk_level "{level}"') def step_when_create_impact(context, level): context.created_impact = CorrectionImpact(risk_level=level) @when('I create a CorrectionResult with correction_id "{cid}" and status "{status}"') def step_when_create_result(context, cid, status): context.created_result = CorrectionResult( correction_id=cid, status=CorrectionStatus(status), ) @when('I create a CorrectionAttempt for correction "{cid}"') def step_when_create_attempt(context, cid): context.created_attempt = CorrectionAttempt(correction_id=cid) # ── Then steps ─────────────────────────────────────────────────── @then("the correction should be created") def step_then_created(context): assert context.correction is not None assert context.correction.correction_id @then('the correction mode should be "{mode}"') def step_then_mode(context, mode): assert context.correction.mode.value == mode @then('the correction status should be "{status}"') def step_then_status(context, status): assert context.correction.status.value == status @then('the correction plan_id should be "{plan_id}"') def step_then_plan_id(context, plan_id): assert context.correction.plan_id == plan_id @then('the correction target_decision_id should be "{decision_id}"') def step_then_decision_id(context, decision_id): assert context.correction.target_decision_id == decision_id @then('the correction guidance should be "{guidance}"') def step_then_guidance(context, guidance): assert context.correction.guidance == guidance @then("the correction dry_run should be true") def step_then_dry_run(context): assert context.correction.dry_run is True @then("the impact should list affected decisions") def step_then_impact_decisions(context): assert context.impact is not None assert len(context.impact.affected_decisions) > 0 @then('the impact risk level should be "{level}"') def step_then_impact_risk(context, level): assert context.impact.risk_level == level @then('a validation error should be raised with message containing "{text}"') def step_then_validation_error(context, text): assert context.error is not None, "Expected an error but none was raised" error_msg = str(context.error) if hasattr(context.error, "message"): error_msg = context.error.message assert text.lower() in error_msg.lower(), ( f"Expected '{text}' in error message, got: {error_msg}" ) @then("a resource not found error should be raised") def step_then_not_found(context): assert context.error is not None assert isinstance(context.error, ResourceNotFoundError) @then("I should get {count:d} corrections") def step_then_count(context, count): assert len(context.listed_corrections) == count @then("the correction should be found") def step_then_found(context): assert context.found_correction is not None @then("the result should have reverted decisions") def step_then_result_reverted(context): assert context.result is not None assert len(context.result.reverted_decisions) > 0 @then("the result should have no reverted decisions") def step_then_result_no_reverted(context): assert context.result is not None assert len(context.result.reverted_decisions) == 0 @then('the result status should be "{status}"') def step_then_result_status(context, status): assert context.result.status.value == status @then("there should be {count:d} attempt recorded") def step_then_attempt_count(context, count): svc = context.correction_service attempts = svc.list_attempts(context.correction.correction_id) assert len(attempts) == count @then("the attempt should be successful") def step_then_attempt_success(context): svc = context.correction_service attempts = svc.list_attempts(context.correction.correction_id) assert attempts[-1].success is True @then("a pydantic validation error should be raised") def step_then_pydantic_error(context): assert context.error is not None assert isinstance(context.error, PydanticValidationError) @then("the impact should be created") def step_then_impact_created(context): assert context.created_impact is not None @then('CorrectionMode.REVERT should equal "{value}"') def step_then_mode_revert(context, value): assert CorrectionMode.REVERT.value == value @then('CorrectionMode.APPEND should equal "{value}"') def step_then_mode_append(context, value): assert CorrectionMode.APPEND.value == value @then('CorrectionStatus.PENDING should equal "{value}"') def step_then_status_pending(context, value): assert CorrectionStatus.PENDING.value == value @then('CorrectionStatus.ANALYZING should equal "{value}"') def step_then_status_analyzing(context, value): assert CorrectionStatus.ANALYZING.value == value @then('CorrectionStatus.EXECUTING should equal "{value}"') def step_then_status_executing(context, value): assert CorrectionStatus.EXECUTING.value == value @then('CorrectionStatus.APPLIED should equal "{value}"') def step_then_status_applied(context, value): assert CorrectionStatus.APPLIED.value == value @then('CorrectionStatus.FAILED should equal "{value}"') def step_then_status_failed(context, value): assert CorrectionStatus.FAILED.value == value @then('CorrectionStatus.CANCELLED should equal "{value}"') def step_then_status_cancelled(context, value): assert CorrectionStatus.CANCELLED.value == value @then("the result should have empty new_decisions") def step_then_empty_new(context): assert context.created_result.new_decisions == [] @then("the result should have empty reverted_decisions") def step_then_empty_reverted(context): assert context.created_result.reverted_decisions == [] @then("the result error_message should be none") def step_then_error_msg_none(context): assert context.created_result.error_message is None @then("the attempt success should be false") def step_then_attempt_false(context): assert context.created_attempt.success is False @then("the attempt completed_at should be none") def step_then_attempt_completed_none(context): assert context.created_attempt.completed_at is None @then("the attempt details should be empty") def step_then_attempt_details_empty(context): assert context.created_attempt.details == {}