agentic.email/backend/tests/test_handoff_runtime.py
2026-10-06 21:03:37 +05:30

424 lines
14 KiB
Python

from datetime import datetime, timezone
from types import SimpleNamespace
import pytest
from bson import ObjectId
from app.services.handoff_delivery import enqueue_handoff_delivery
from app.services.handoff_mirror_policy import (
GENERATED_BY_AGENT,
GENERATED_BY_HANDOFF_BRIDGE,
MESSAGE_ORIGIN_AGENT,
MESSAGE_ORIGIN_HUMAN_OWNER,
RELAY_STATUS_BLOCKED,
should_mirror_to_human,
)
from app.services import human_handoff_service
from app.services.human_handoff_service import (
MESSAGE_SCOPE_EXTERNAL,
MESSAGE_SCOPE_HANDOFF,
build_handoff_subject,
maybe_trigger_handoff,
process_human_reply,
resolve_handoff_from_inbound,
)
from app.services.mailbox_scope import apply_external_scope
from app.services.external_reply_service import build_external_reply_plan
class FakeCursor:
def __init__(self, items):
self.items = items
def sort(self, *_args, **_kwargs):
return self
async def to_list(self, length=None):
return list(self.items)[:length]
class FakeCollection:
def __init__(self, items=None):
self.items = list(items or [])
self.inserted = []
self.updated = []
async def find_one(self, query, projection=None):
for item in self.items:
if _matches(item, query):
return item
return None
def find(self, query=None):
query = query or {}
return FakeCursor([item for item in self.items if _matches(item, query)])
async def insert_one(self, document):
document = dict(document)
document.setdefault("_id", ObjectId())
self.items.append(document)
self.inserted.append(document)
return SimpleNamespace(inserted_id=document["_id"])
async def update_one(self, query, update, upsert=False):
self.updated.append((query, update, upsert))
existing = await self.find_one(query)
if existing is None and upsert:
inserted = {"_id": ObjectId(), **query, **update.get("$setOnInsert", {})}
self.items.append(inserted)
return SimpleNamespace(upserted_id=inserted["_id"], modified_count=0)
if existing is not None:
existing.update(update.get("$set", {}))
return SimpleNamespace(upserted_id=None, modified_count=1)
return SimpleNamespace(upserted_id=None, modified_count=0)
async def find_one_and_update(self, query, update, **kwargs):
existing = await self.find_one(query)
if existing is None:
return None
existing.update(update.get("$set", {}))
return existing
def _matches(document, query):
for key, expected in query.items():
if key == "$or":
if not any(_matches(document, clause) for clause in expected):
return False
continue
if key == "$ne":
continue
actual = document.get(key)
if isinstance(expected, dict):
if "$ne" in expected and actual == expected["$ne"]:
return False
if "$in" in expected and actual not in expected["$in"]:
return False
continue
if actual != expected:
return False
return True
def test_external_mailbox_filter_excludes_handoff_scope():
match = {"account_id": "account-1", "mailbox": "inbox"}
apply_external_scope(match)
assert match["$or"] == [
{"message_scope": "EXTERNAL"},
{"message_scope": {"$exists": False}},
]
def test_bridge_plan_never_references_private_ids():
customer = {
"_id": ObjectId(),
"internet_message_id": "<A@gmail.com>",
"message_id": "<A@gmail.com>",
"message_scope": MESSAGE_SCOPE_EXTERNAL,
"references": [],
"subject": "Invoice 42",
}
private_id = "<H1@gentic.email>"
plan = build_external_reply_plan(
parent_external_message=customer,
body_text="Approved",
recipients=["customer@gmail.com"],
)
assert plan.threading.in_reply_to == "<A@gmail.com>"
assert private_id not in plan.threading.references
assert plan.subject == "Re: Invoice 42"
assert "handoff" not in plan.subject.lower()
@pytest.mark.asyncio
async def test_existing_handoff_is_sticky_without_reevaluating_rules():
external_thread_id = ObjectId()
source_id = ObjectId()
handoff = {
"_id": ObjectId(),
"account_id": "account-1",
"external_thread_id": external_thread_id,
"status": "ACTIVE",
"rule_name": "Finance approvals",
}
database = SimpleNamespace(
human_handoffs=FakeCollection([handoff]),
human_handoff_rules=FakeCollection(
[
{
"_id": ObjectId(),
"name": "Finance approvals",
"priority": 1,
"labels": ["Finance"],
"email_types": ["transactional"],
"instructions": "only invoices",
"active": True,
"created_at": datetime.now(timezone.utc),
}
]
),
handoff_delivery_jobs=FakeCollection(),
)
message = {
"_id": source_id,
"message_scope": MESSAGE_SCOPE_EXTERNAL,
"direction": "inbound",
"sender": {"address": "customer@example.com"},
"subject": "Hello there",
"body_text": "just checking in",
"snippet": "just checking in",
}
result = await maybe_trigger_handoff(
database,
account_id="account-1",
agent_email="agent@gentic.email",
external_message=message,
external_thread_id=external_thread_id,
)
assert result["_id"] == handoff["_id"]
assert database.handoff_delivery_jobs.items
assert (
database.handoff_delivery_jobs.items[0]["kind"] == "MIRROR_TO_HUMAN"
)
@pytest.mark.asyncio
async def test_duplicate_delivery_jobs_share_an_idempotency_key():
database = SimpleNamespace(handoff_delivery_jobs=FakeCollection())
handoff_id = ObjectId()
source_id = ObjectId()
first = await enqueue_handoff_delivery(
database,
kind="MIRROR_TO_HUMAN",
account_id="account-1",
handoff_id=handoff_id,
source_message_id=source_id,
agent_email="agent@gentic.email",
)
second = await enqueue_handoff_delivery(
database,
kind="MIRROR_TO_HUMAN",
account_id="account-1",
handoff_id=handoff_id,
source_message_id=source_id,
agent_email="agent@gentic.email",
)
assert first["idempotency_key"] == second["idempotency_key"]
assert len(database.handoff_delivery_jobs.items) == 1
@pytest.mark.asyncio
async def test_human_reply_resolves_only_to_private_handoff():
parent_id = ObjectId()
thread_id = ObjectId()
handoff = {
"_id": ObjectId(),
"account_id": "account-1",
"handoff_thread_id": thread_id,
"human_email": "owner@example.com",
"agent_email": "agent@gentic.email",
"status": "ACTIVE",
}
database = SimpleNamespace(
messages=FakeCollection(
[
{
"_id": parent_id,
"message_scope": MESSAGE_SCOPE_HANDOFF,
"message_id": "<H1@gentic.email>",
"message_id_folded": "<h1@gentic.email>",
}
]
),
mailbox_entries=FakeCollection(
[
{
"account_id": "account-1",
"message_id": parent_id,
"message_scope": MESSAGE_SCOPE_HANDOFF,
"thread_id": thread_id,
}
]
),
human_handoffs=FakeCollection([handoff]),
)
resolved = await resolve_handoff_from_inbound(
database,
account_id="account-1",
agent_email="agent@gentic.email",
sender_address="owner@example.com",
in_reply_to="<H1@gentic.email>",
references=["<H1@gentic.email>"],
)
assert resolved["_id"] == handoff["_id"]
def test_human_echo_and_agent_mirror_policy():
assert should_mirror_to_human(
{
"message_scope": MESSAGE_SCOPE_EXTERNAL,
"direction": "outbound",
"generated_by": GENERATED_BY_HANDOFF_BRIDGE,
"message_origin": MESSAGE_ORIGIN_HUMAN_OWNER,
"generated_from_handoff_message_id": ObjectId(),
}
) is False
assert should_mirror_to_human(
{
"message_scope": MESSAGE_SCOPE_EXTERNAL,
"direction": "outbound",
"generated_by": GENERATED_BY_AGENT,
"message_origin": MESSAGE_ORIGIN_AGENT,
}
) is True
def test_private_subject_stays_off_the_external_plane():
private = build_handoff_subject(
sender_name="Parth Dixit",
external_sender="customer@example.com",
external_subject="Invoice 42",
)
assert private.startswith("handoff · Parth Dixit ·")
assert "Invoice 42" in private
assert "Finance approvals" not in private
@pytest.mark.asyncio
async def test_first_matching_rule_creates_a_new_linked_handoff():
external_thread_id = ObjectId()
source_id = ObjectId()
rule = {
"_id": ObjectId(),
"account_id": "account-1",
"name": "Finance approvals",
"priority": 1,
"owner_email": "owner@example.com",
"owner_name": "Owner",
"labels": ["All mail"],
"email_types": ["all"],
"instructions": "",
"active": True,
"allow_replies": True,
"created_at": datetime.now(timezone.utc),
}
database = SimpleNamespace(
human_handoffs=FakeCollection(),
human_handoff_rules=FakeCollection([rule]),
handoff_delivery_jobs=FakeCollection(),
email_threads=FakeCollection(),
)
message = {
"_id": source_id,
"message_scope": MESSAGE_SCOPE_EXTERNAL,
"direction": "inbound",
"sender": {"name": "Parth Dixit", "address": "customer@example.com"},
"subject": "Invoice 42",
"body_text": "please review this invoice",
"snippet": "please review this invoice",
}
result = await maybe_trigger_handoff(
database,
account_id="account-1",
agent_email="agent@gentic.email",
external_message=message,
external_thread_id=external_thread_id,
)
assert result["rule_name"] == "Finance approvals"
assert result["external_thread_id"] == external_thread_id
assert result["handoff_thread_id"]
assert result["private_subject"].startswith("handoff · Parth Dixit ·")
assert database.handoff_delivery_jobs.items[0]["kind"] == "MIRROR_TO_HUMAN"
@pytest.mark.asyncio
async def test_maybe_trigger_does_not_echo_human_origin_messages():
database = SimpleNamespace(
human_handoffs=FakeCollection(
[
{
"_id": ObjectId(),
"account_id": "account-1",
"external_thread_id": ObjectId(),
"status": "ACTIVE",
}
]
),
handoff_delivery_jobs=FakeCollection(),
)
result = await maybe_trigger_handoff(
database,
account_id="account-1",
agent_email="agent@gentic.email",
external_message={
"_id": ObjectId(),
"message_scope": MESSAGE_SCOPE_EXTERNAL,
"direction": "outbound",
"generated_by": GENERATED_BY_HANDOFF_BRIDGE,
"message_origin": MESSAGE_ORIGIN_HUMAN_OWNER,
"generated_from_handoff_message_id": ObjectId(),
"sender": {"address": "agent@gentic.email"},
},
external_thread_id=database.human_handoffs.items[0]["external_thread_id"],
)
assert result is None
assert database.handoff_delivery_jobs.items == []
@pytest.mark.asyncio
async def test_leaked_private_subject_blocks_human_relay(monkeypatch):
stored = {}
async def store(_database, **kwargs):
stored.update(kwargs)
return {"_id": ObjectId(), **kwargs}
enqueued = []
async def enqueue(*args, **kwargs):
enqueued.append(kwargs)
return {}
class BrokenStorage:
async def get(self, *_args, **_kwargs):
raise RuntimeError("raw mime unavailable")
monkeypatch.setattr(human_handoff_service, "store_handoff_message", store)
monkeypatch.setattr(human_handoff_service, "enqueue_handoff_delivery", enqueue)
monkeypatch.setattr(human_handoff_service, "get_raw_mime_storage", lambda: BrokenStorage())
database = SimpleNamespace(messages=FakeCollection(), human_handoffs=FakeCollection())
await process_human_reply(
database,
account_id="account-1",
agent_email="agent@gentic.email",
handoff={
"_id": ObjectId(),
"handoff_thread_id": ObjectId(),
"allow_replies": True,
},
parsed={
"sender": {"name": "Owner", "address": "owner@example.com"},
"to": [{"address": "agent@gentic.email"}],
"subject": "Re: Finance approvals · handoff · customer@example.com · Invoice 42",
"message_id": "<H2@gmail.com>",
"in_reply_to": "<H1@gentic.email>",
"references": ["<H1@gentic.email>"],
"attachments": [],
"raw_headers": {},
},
body_text=(
"Finance approvals · handoff · customer@example.com · Invoice 42\n"
"Please approve"
),
body_html=None,
raw_mime={"bucket": "mail", "name": "raw.eml"},
delivery_key="delivery-1",
)
assert stored["relay_status"] == RELAY_STATUS_BLOCKED
assert enqueued == []