AgentEvalTool/tests/integration/test_campaign_analysis_auto_trigger.py

139 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Integration tests for the runner's auto analysis hook (v0.7 ticket 05).
Only 正式线 campaigns (time_scale == 1) enqueue the analysis task on
COMPLETED; accelerated/cancelled campaigns and missing analysis models all
skip silently. The analysis service itself is spied, not executed.
"""
import pytest
from agenteval.evaluation import campaign_runner
from agenteval.evaluation.campaign_runner import request_cancel, start_campaign
from agenteval.models import (
Campaign,
CampaignPlanEntry,
CampaignStatus,
Case,
CaseType,
ChannelType,
EvalTarget,
PlatformType,
Scenario,
TargetStatus,
)
from agenteval.storage.db import utc_now
from agenteval.storage.repository import CampaignRepository, ScenarioRepository, TargetRepository
from tests.unit.mock_channel import MockChannel
TICK = 0.01
@pytest.fixture()
def seeded_db(db_session, monkeypatch):
from agenteval.channels import factory as factory_module
from agenteval.evaluation import engine as engine_module
from agenteval.storage import db as db_module
from agenteval.storage import repository as repo_module
def _test_get_session():
return db_session
monkeypatch.setattr(db_module, "get_session", _test_get_session)
monkeypatch.setattr(repo_module, "get_session", _test_get_session)
monkeypatch.setattr(engine_module, "get_session", _test_get_session)
monkeypatch.setattr(campaign_runner, "get_session", _test_get_session)
channel = MockChannel(reply_delay=0.0)
monkeypatch.setattr(factory_module.ChannelFactory, "create", lambda target: channel)
TargetRepository(db_session).create(EvalTarget(
id="t-1", name="mock-target",
platform=PlatformType.AI_DIGITAL_EMPLOYEE, channel_type=ChannelType.TUTU_API,
channel_config={"base_url": "http://mock", "token": "x"}, status=TargetStatus.ACTIVE,
))
ScenarioRepository(db_session).create(Scenario(
id="s-1", name="mock-scenario",
cases=[Case(id="c1", type=CaseType.SINGLE, messages=["hi"])],
))
return db_session
@pytest.fixture()
def analysis_spy(monkeypatch):
"""Spy the analysis seam: resolvable model, recorded enqueue calls."""
calls: list[tuple[str, str]] = []
monkeypatch.setattr(
campaign_runner, "enqueue_campaign_analysis",
lambda cid, *, triggered_by: calls.append((cid, triggered_by)),
)
monkeypatch.setattr(campaign_runner, "resolve_analysis_model", lambda campaign, session: object())
return calls
def _make_campaign(session, **overrides) -> Campaign:
payload = dict(
name="auto-trigger",
target_id="t-1",
window_seconds=1,
time_scale=1.0, # 正式线1s 窗口真实耗时 ~1s
plan=[CampaignPlanEntry(scenario_id="s-1", offset_seconds=0, count=1)],
)
payload.update(overrides)
return CampaignRepository(session).create(Campaign(**payload))
async def _await_task(campaign_id, timeout=5.0):
import asyncio
task = campaign_runner.campaign_registry.get(campaign_id)
if task is not None:
await asyncio.wait_for(task, timeout=timeout)
async def test_realtime_completion_auto_enqueues_analysis(seeded_db, analysis_spy):
campaign = _make_campaign(seeded_db)
start_campaign(campaign.id, seeded_db, tick_seconds=TICK)
await _await_task(campaign.id)
final = CampaignRepository(seeded_db).get(campaign.id)
assert final.status == CampaignStatus.COMPLETED
assert analysis_spy == [(campaign.id, "auto")]
async def test_accelerated_completion_does_not_enqueue(seeded_db, analysis_spy):
campaign = _make_campaign(seeded_db, time_scale=1000.0) # 加速调试线
start_campaign(campaign.id, seeded_db, tick_seconds=TICK)
await _await_task(campaign.id)
final = CampaignRepository(seeded_db).get(campaign.id)
assert final.status == CampaignStatus.COMPLETED
assert analysis_spy == []
async def test_cancelled_campaign_does_not_enqueue(seeded_db, analysis_spy):
import asyncio
campaign = _make_campaign(seeded_db, window_seconds=100)
start_campaign(campaign.id, seeded_db, tick_seconds=TICK)
await asyncio.sleep(0.05)
repo = CampaignRepository(seeded_db)
current = repo.get(campaign.id)
current.status = CampaignStatus.CANCELLED
current.completed_at = utc_now()
repo.update(current)
request_cancel(campaign.id)
await _await_task(campaign.id)
assert repo.get(campaign.id).status == CampaignStatus.CANCELLED
assert analysis_spy == []
async def test_missing_analysis_model_skips_silently(seeded_db, monkeypatch, analysis_spy):
monkeypatch.setattr(campaign_runner, "resolve_analysis_model", lambda campaign, session: None)
campaign = _make_campaign(seeded_db)
start_campaign(campaign.id, seeded_db, tick_seconds=TICK)
await _await_task(campaign.id)
final = CampaignRepository(seeded_db).get(campaign.id)
assert final.status == CampaignStatus.COMPLETED # 活动完成流程不受影响
assert analysis_spy == []