"""The /agent endpoint + autonomous /ingest (route functions called directly).""" import base64 import pytest from fastapi import HTTPException import app as app_module from app import agent, ingest from calendar_out.ics import events_to_ics from server import dedup, events as bus from server.pipeline import AgentRequest from server.schema import Event TOKEN = "Bearer test-token" # matches conftest INGEST_TOKEN @pytest.fixture(autouse=True) def _clean(): dedup.reset() yield dedup.reset() def test_agent_requires_token(): with pytest.raises(HTTPException) as e: agent(AgentRequest(thread="lunch 1pm tomorrow"), authorization="") assert e.value.status_code == 401 def test_agent_returns_plan(): resp = agent(AgentRequest(thread="lunch 1pm tomorrow"), authorization=TOKEN) assert len(resp.plan.events) == 1 assert resp.plan.events[0].start # ISO start present def test_agent_return_ics(): resp = agent(AgentRequest(thread="lunch 1pm tomorrow", return_ics=True), authorization=TOKEN) assert resp.ics_base64 assert b"BEGIN:VEVENT" in base64.b64decode(resp.ics_base64) def test_agent_detects_conflict_from_existing_ics(): # stub turns "lunch 1pm tomorrow" (now=Jun 5) into Jun 6 13:00-14:00 busy_ics = events_to_ics([Event(title="Standup", start="2026-06-06T13:00:00", end="2026-06-06T14:00:00")]) resp = agent( AgentRequest( thread="lunch 1pm tomorrow", now="2026-06-05T09:00:00", existing_ics=base64.b64encode(busy_ics).decode(), ), authorization=TOKEN, ) assert resp.plan.conflicts assert resp.plan.conflicts[0].severity == "overlap" def _msg(text, chat="Mom"): return {"chat": chat, "sender": chat, "text": text, "timestamp": "2026-06-05T10:00:00", "images": []} def _ingest_and_flush(batch, authorization): """Call the route directly, then run its queued background tasks — autonomous work now happens off the request (collector-timeout fix).""" from fastapi import BackgroundTasks bt = BackgroundTasks() out = ingest(batch, bt, authorization=authorization) for task in bt.tasks: task.func(*task.args, **task.kwargs) return out def test_ingest_requires_token(): from app import IngestBatch from fastapi import BackgroundTasks with pytest.raises(HTTPException): ingest(IngestBatch(messages=[]), BackgroundTasks(), authorization="") def test_autonomous_ingest_dedupes(monkeypatch, tmp_path): from app import IngestBatch import calendar_out.gcal as gcal monkeypatch.setattr(app_module, "AUTONOMOUS", True) monkeypatch.setattr(app_module, "TRIGGER_ON", "any") # this test isn't about direction monkeypatch.setattr(app_module, "FEED_PATH", tmp_path / "feed.json") # events are only marked seen after a SUCCESSFUL push — stub one in monkeypatch.setattr(gcal, "push_events", lambda evs, calendar_id="primary": [""] * len(evs)) _ingest_and_flush( IngestBatch(messages=[app_module.IngestMessage(**_msg("dinner 6pm tomorrow"))]), authorization=TOKEN) seen_after_first = len(dedup._load()) assert seen_after_first >= 1 # a follow-up message in the same chat must not re-create the same event _ingest_and_flush( IngestBatch(messages=[app_module.IngestMessage(**_msg("see you then"))]), authorization=TOKEN) assert len(dedup._load()) == seen_after_first