| """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"
|
|
|
|
|
| @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
|
|
|
|
|
| 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():
|
|
|
| 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")
|
| monkeypatch.setattr(app_module, "FEED_PATH", tmp_path / "feed.json")
|
|
|
| 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
|
|
|
|
|
| _ingest_and_flush(
|
| IngestBatch(messages=[app_module.IngestMessage(**_msg("see you then"))]),
|
| authorization=TOKEN)
|
| assert len(dedup._load()) == seen_after_first
|
|
|