116 lines
3.9 KiB
Python
116 lines
3.9 KiB
Python
"""Tests for the durable, idempotent Campaign child-Run claim seam."""
|
|
|
|
from agenteval.models import Campaign, CampaignPlanEntry, CampaignStatus, RunStatus, RunTrigger
|
|
from agenteval.storage.repository import (
|
|
CampaignRepository,
|
|
CampaignRunClaimStatus,
|
|
RunRepository,
|
|
TargetRepository,
|
|
)
|
|
|
|
from tests.unit.test_repository import _make_target
|
|
|
|
|
|
def _running_campaign(session, *, status=CampaignStatus.RUNNING):
|
|
TargetRepository(session).create(_make_target())
|
|
return CampaignRepository(session).create(
|
|
Campaign(
|
|
id="campaign-1",
|
|
name="campaign",
|
|
target_id="t-1",
|
|
status=status,
|
|
window_seconds=60,
|
|
plan=[CampaignPlanEntry(scenario_id="scenario-1", offset_seconds=0, count=2)],
|
|
)
|
|
)
|
|
|
|
|
|
def _claim(repo: RunRepository, *, occurrence_index=0):
|
|
return repo.claim_campaign_run(
|
|
campaign_id="campaign-1",
|
|
scenario_id="scenario-1",
|
|
scenario_version=3,
|
|
plan_index=0,
|
|
occurrence_index=occurrence_index,
|
|
)
|
|
|
|
|
|
def test_claim_creates_pending_campaign_run_with_durable_identity(db_session):
|
|
_running_campaign(db_session)
|
|
|
|
result = _claim(RunRepository(db_session))
|
|
|
|
assert result.status is CampaignRunClaimStatus.CLAIMED
|
|
assert result.accepted is True
|
|
assert result.created is True
|
|
assert result.run is not None
|
|
assert result.run.status is RunStatus.PENDING
|
|
assert result.run.triggered_by is RunTrigger.CAMPAIGN
|
|
assert result.run.target_id == "t-1"
|
|
assert result.run.campaign_id == "campaign-1"
|
|
assert result.run.campaign_plan_index == 0
|
|
assert result.run.campaign_occurrence_index == 0
|
|
|
|
|
|
def test_repeated_claim_returns_same_run_without_duplicate(db_session):
|
|
_running_campaign(db_session)
|
|
repo = RunRepository(db_session)
|
|
|
|
first = _claim(repo)
|
|
second = _claim(repo)
|
|
|
|
assert first.run is not None and second.run is not None
|
|
assert second.status is CampaignRunClaimStatus.EXISTING
|
|
assert second.created is False
|
|
assert second.run.id == first.run.id
|
|
assert len(repo.list_by_campaign("campaign-1")) == 1
|
|
|
|
|
|
def test_claim_rejects_missing_or_non_running_campaign(db_session):
|
|
repo = RunRepository(db_session)
|
|
|
|
missing = _claim(repo)
|
|
assert missing.status is CampaignRunClaimStatus.NOT_FOUND
|
|
assert missing.accepted is False
|
|
|
|
_running_campaign(db_session, status=CampaignStatus.CANCELLED)
|
|
cancelled = _claim(repo)
|
|
assert cancelled.status is CampaignRunClaimStatus.CONFLICT
|
|
assert cancelled.run is None
|
|
|
|
|
|
def test_each_occurrence_has_independent_claim_identity(db_session):
|
|
_running_campaign(db_session)
|
|
repo = RunRepository(db_session)
|
|
|
|
first = _claim(repo, occurrence_index=0)
|
|
second = _claim(repo, occurrence_index=1)
|
|
|
|
assert first.created is True
|
|
assert second.created is True
|
|
assert first.run is not None and second.run is not None
|
|
assert first.run.id != second.run.id
|
|
assert len(repo.list_by_campaign("campaign-1")) == 2
|
|
|
|
|
|
def test_competing_sessions_converge_on_one_claim(tmp_path):
|
|
from agenteval.storage.db import EvalRunDB # noqa: F401 - register metadata
|
|
from sqlmodel import Session, SQLModel, create_engine
|
|
|
|
engine = create_engine(
|
|
f"sqlite:///{tmp_path / 'competing-claims.db'}",
|
|
connect_args={"check_same_thread": False},
|
|
)
|
|
SQLModel.metadata.create_all(engine)
|
|
with Session(engine) as first_session, Session(engine) as second_session:
|
|
_running_campaign(first_session)
|
|
|
|
first = _claim(RunRepository(first_session))
|
|
second = _claim(RunRepository(second_session))
|
|
|
|
assert first.status is CampaignRunClaimStatus.CLAIMED
|
|
assert second.status is CampaignRunClaimStatus.EXISTING
|
|
assert first.run is not None and second.run is not None
|
|
assert second.run.id == first.run.id
|
|
assert len(RunRepository(second_session).list_by_campaign("campaign-1")) == 1
|