Spaces:
Sleeping
Sleeping
| """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 | |
| 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 | |