from datetime import datetime, timezone import pytest from bson import ObjectId from app.services import human_handoff_service from app.services.handoff_delivery import ( STATUS_AMBIGUOUS, STATUS_PENDING, STATUS_SENT, process_handoff_delivery_once, ) class FakeJobs: def __init__(self): self.job = { "_id": ObjectId(), "kind": "MIRROR_TO_HUMAN", "account_id": "account-1", "handoff_id": ObjectId(), "source_message_id": ObjectId(), "status": STATUS_PENDING, "created_at": datetime.now(timezone.utc), "next_attempt_at": datetime.now(timezone.utc), } async def find_one_and_update(self, query, update, **kwargs): if self.job["status"] != STATUS_PENDING: return None self.job["status"] = "SENDING" self.job["attempt_count"] = self.job.get("attempt_count", 0) + 1 return dict(self.job) async def update_one(self, query, update): self.job.update(update.get("$set", {})) class FakeMessages: async def update_one(self, query, update): return None class FakeDatabase: def __init__(self): self.handoff_delivery_jobs = FakeJobs() self.messages = FakeMessages() @pytest.mark.asyncio async def test_delivery_job_is_claimed_and_sent_once(monkeypatch): database = FakeDatabase() calls = 0 async def deliver(database, job): nonlocal calls calls += 1 return ObjectId() monkeypatch.setattr(human_handoff_service, "deliver_handoff_job", deliver) assert await process_handoff_delivery_once(database) is True assert await process_handoff_delivery_once(database) is False assert calls == 1 assert database.handoff_delivery_jobs.job["status"] == STATUS_SENT @pytest.mark.asyncio async def test_ambiguous_smtp_failure_is_not_automatically_retried(monkeypatch): database = FakeDatabase() calls = 0 async def deliver(database, job): nonlocal calls calls += 1 raise TimeoutError("SMTP acknowledgement lost") monkeypatch.setattr(human_handoff_service, "deliver_handoff_job", deliver) assert await process_handoff_delivery_once(database) is True assert await process_handoff_delivery_once(database) is False assert calls == 1 assert database.handoff_delivery_jobs.job["status"] == STATUS_AMBIGUOUS