schemashift / tests /test_drift.py
yashash04's picture
Phase 3: CalendarAPI with 2 drifts + DriftInjector
27dd958
"""DriftInjector acceptance tests — Phase 3."""
from __future__ import annotations
from drift import DriftInjector
from models import DriftEvent, EpisodeState
from tools.calendar import CalendarAPI
from tools.mail import MailAPI
def _make_state(drifts: list[DriftEvent]) -> EpisodeState:
return EpisodeState(
episode_id="ep-test",
task_id="test-task",
difficulty="easy",
max_steps=10,
token_budget=4000,
token_budget_remaining=4000,
drift_plan=drifts,
ground_truth_final_state={},
)
def _make_tools() -> dict:
mail = MailAPI(seed_data={"messages": [
{"id": "m1", "from": "a@x.com", "to": "u@org.com",
"subject": "s", "body": "b", "folder": "inbox"}
]})
cal = CalendarAPI(seed_data={"events": [
{"event_id": "evt_1", "title": "x",
"start": "2026-04-25T10:00:00Z", "end": "2026-04-25T11:00:00Z",
"attendees": ["a@x.com"], "status": "confirmed"}
]})
return {"mail": mail, "calendar": cal}
def test_single_drift_fires_at_step() -> None:
drift = DriftEvent(
tool="mail", endpoint="list_messages", kind="field_rename",
fires_at_step=3, details={},
)
state = _make_state([drift])
tools = _make_tools()
state.step = 1
assert DriftInjector.tick(state, tools) == []
assert "messages" in tools["mail"].active_schemas["list_messages"].response_shape
state.step = 3
fired = DriftInjector.tick(state, tools)
assert len(fired) == 1
assert fired[0] is drift
mail_shape = tools["mail"].active_schemas["list_messages"].response_shape
assert "items" in mail_shape
assert "messages" not in mail_shape
state.step = 4
assert DriftInjector.tick(state, tools) == []
def test_multiple_drifts_different_steps() -> None:
mail_drift = DriftEvent(
tool="mail", endpoint="send_message", kind="endpoint_deprecation",
fires_at_step=2, details={"replacement": "messages.send"},
)
cal_drift = DriftEvent(
tool="calendar", endpoint="create_event", kind="field_rename",
fires_at_step=5, details={},
)
state = _make_state([mail_drift, cal_drift])
tools = _make_tools()
state.step = 2
fired = DriftInjector.tick(state, tools)
assert [e.tool for e in fired] == ["mail"]
assert "send_message" not in tools["mail"].active_schemas
assert "messages.send" in tools["mail"].active_schemas
assert "attendees" in tools["calendar"].active_schemas["create_event"].params
state.step = 3
assert DriftInjector.tick(state, tools) == []
state.step = 5
fired = DriftInjector.tick(state, tools)
assert [e.tool for e in fired] == ["calendar"]
cal_params = tools["calendar"].active_schemas["create_event"].params
assert "participants" in cal_params
assert "attendees" not in cal_params
state.step = 6
assert DriftInjector.tick(state, tools) == []
def test_drift_not_fired_before_step() -> None:
drift = DriftEvent(
tool="calendar", endpoint="create_event", kind="field_rename",
fires_at_step=5, details={},
)
state = _make_state([drift])
tools = _make_tools()
state.step = 2
fired = DriftInjector.tick(state, tools)
assert fired == []
cal_params = tools["calendar"].active_schemas["create_event"].params
assert "attendees" in cal_params
assert "participants" not in cal_params
def test_detected_by_agent_unchanged_by_tick() -> None:
drift = DriftEvent(
tool="mail", endpoint="list_messages", kind="field_rename",
fires_at_step=1, details={},
)
state = _make_state([drift])
tools = _make_tools()
assert drift.detected_by_agent is False
state.step = 1
fired = DriftInjector.tick(state, tools)
assert len(fired) == 1
assert fired[0].detected_by_agent is False
assert state.drift_plan[0].detected_by_agent is False