AgentEvalTool/tests/unit/test_campaign_summary.py
sinohqb aa40c8e0d8
Some checks failed
CI / test (push) Failing after 12s
refactor(campaign): type Campaign.summary as CampaignSummary VO
Campaign.summary was a bare Optional[dict] while RunSummary is a typed VO —
scheduler state (spawned_indices/errors) flowed untyped through
set_/get_summary. Introduce CampaignSummary + SchedulerState (extra=allow,
validate_assignment), mirroring RunSummary; campaign_runner reads/writes the
VO. Also converge the ~10 repeated JSON column get/set pairs onto
_json_dumps/_json_loads helpers, unifying ensure_ascii=False and fixing the
set_modalities ensure_ascii=True trap.
2026-07-31 15:09:10 +08:00

75 lines
2.5 KiB
Python

"""CampaignSummary VO — typed campaign scheduler state, restart-safe (ADR-0003).
Mirrors RunSummary's typed treatment: Campaign.summary is no longer a bare
dict. Legacy dict summaries coerce; unknown top-level keys survive
(extra=allow) so older records keep parsing.
"""
from agenteval.models import (
Campaign,
CampaignPlanEntry,
CampaignSummary,
SchedulerState,
)
from agenteval.storage.repository import CampaignRepository, TargetRepository
from tests.unit.test_repository import _make_target
def _campaign(**summary_kw) -> Campaign:
kw = dict(
name="c",
target_id="t-1",
window_seconds=3600,
plan=[CampaignPlanEntry(scenario_id="s-1", offset_seconds=0, count=1)],
)
kw.update(summary_kw)
return Campaign(**kw)
def test_summary_defaults_are_empty():
s = CampaignSummary()
assert s.scheduler.spawned_indices == []
assert s.scheduler.errors == []
def test_scheduler_state_holds_progress():
s = SchedulerState(spawned_indices=[0, 2], errors=[{"index": 1, "error": "boom"}])
assert s.spawned_indices == [0, 2]
assert s.errors[0]["error"] == "boom"
def test_legacy_dict_summary_coerces():
c = _campaign(summary={"scheduler": {"spawned_indices": [0, 1], "errors": [{"index": 2, "error": "x"}]}})
assert isinstance(c.summary, CampaignSummary)
assert c.summary.scheduler.spawned_indices == [0, 1]
assert c.summary.scheduler.errors[0]["error"] == "x"
def test_assigning_dict_coerces_to_vo():
c = _campaign()
c.summary = {"scheduler": {"spawned_indices": [3]}}
assert isinstance(c.summary, CampaignSummary)
assert c.summary.scheduler.spawned_indices == [3]
def test_unknown_top_level_keys_survive():
c = _campaign(summary={"scheduler": {}, "future_axis": {"availability": 0.9}})
assert isinstance(c.summary, CampaignSummary)
assert c.summary.model_extra["future_axis"] == {"availability": 0.9}
def test_summary_survives_repository_round_trip(db_session):
TargetRepository(db_session).create(_make_target())
repo = CampaignRepository(db_session)
repo.create(
_campaign(
id="cp-1",
summary=CampaignSummary(scheduler=SchedulerState(spawned_indices=[0, 1], errors=[{"index": 2}])),
)
)
fetched = repo.get("cp-1")
assert fetched is not None
assert isinstance(fetched.summary, CampaignSummary)
assert fetched.summary.scheduler.spawned_indices == [0, 1]
assert fetched.summary.scheduler.errors == [{"index": 2}]