"""Tests for atomic Campaign cancellation and settlement ordering.""" import pytest from agenteval.evaluation.campaign_lifecycle import CampaignCancelError, cancel_campaign from agenteval.models import Campaign, CampaignPlanEntry, CampaignStatus from agenteval.storage.repository import CampaignRepository def _campaign(session, status=CampaignStatus.RUNNING): return CampaignRepository(session).create( Campaign( id="campaign-1", name="campaign", target_id="t-1", status=status, window_seconds=60, plan=[CampaignPlanEntry(scenario_id="s-1", offset_seconds=0, count=1)], ) ) def test_cancel_is_atomic_and_stop_happens_after_durable_state(db_session): _campaign(db_session) observed = [] def stop(campaign_id): observed.append(CampaignRepository(db_session).get(campaign_id).status) cancelled = cancel_campaign(db_session, "campaign-1", stop=stop) assert cancelled.status is CampaignStatus.CANCELLED assert cancelled.completed_at is not None assert observed == [CampaignStatus.CANCELLED] def test_cancel_distinguishes_missing_and_terminal_campaign(db_session): with pytest.raises(CampaignCancelError) as missing: cancel_campaign(db_session, "missing") assert missing.value.status_code == 404 _campaign(db_session, CampaignStatus.COMPLETED) with pytest.raises(CampaignCancelError) as terminal: cancel_campaign(db_session, "campaign-1") assert terminal.value.status_code == 400 def test_cancel_is_idempotency_guarded_by_status(db_session): _campaign(db_session) cancel_campaign(db_session, "campaign-1") with pytest.raises(CampaignCancelError) as again: cancel_campaign(db_session, "campaign-1") assert again.value.status_code == 400