human hand off

This commit is contained in:
debu-2 2026-10-06 07:12:55 +05:30
parent d0fee5c8f7
commit 7af776196a
34 changed files with 2941 additions and 195 deletions

View file

@ -0,0 +1,83 @@
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

View file

@ -1,41 +1,45 @@
import pytest
from app.services.handoff_email_templates import (
build_mirror_handoff_html,
build_mirror_handoff_text,
external_reply_subject,
format_sender_line,
handoff_email_subject,
)
from app.services.handoff_mirror import load_mirror_payload
from app.services.html_sanitizer import sanitize_email_html
from app.services.mime_body_extractor import extract_mirror_content
from app.services.mime_body_extractor import (
extract_mirror_content,
extract_new_reply_mime_content,
)
from app.services.thread_service import threading_fields_from_parent
def test_mirror_template_is_minimal():
html = build_mirror_handoff_html(
sender_line="091rabbytes@gmail.com",
external_subject="lets try one more time",
original_html="<p>one last attempt. i am tired</p>",
agent_email="suyash@gentic.email",
@pytest.mark.asyncio
async def test_mirror_body_is_unchanged_without_gentic_wrapper():
payload = await load_mirror_payload(
{
"body_text": "one last attempt. i am tired",
"body_html": "<p><strong>one last attempt.</strong> i am tired</p>",
}
)
assert "LATEST MESSAGE" not in html
assert "Why you" not in html
assert "gentic-handoff-original" in html
assert "one last attempt. i am tired" in html
assert "✦ Human Handoff" in html
assert "091rabbytes@gmail.com" in html
assert "lets try one more time" in html
assert payload.body_text == "one last attempt. i am tired"
assert payload.body_html == (
"<p><strong>one last attempt.</strong> i am tired</p>"
)
assert "Human Handoff" not in payload.body_text
assert "gentic-handoff" not in (payload.body_html or "")
def test_mirror_plain_text_template():
text = build_mirror_handoff_text(
sender_line="091rabbytes@gmail.com",
external_subject="lets try one more time",
original_body_text="one last attempt. i am tired",
agent_email="suyash@gentic.email",
@pytest.mark.asyncio
async def test_mirror_plain_text_fallback_is_unchanged():
payload = await load_mirror_payload(
{
"body_text": "one last attempt. i am tired",
"body_html": None,
}
)
assert "one last attempt. i am tired" in text
assert "LATEST MESSAGE" not in text
assert "Reply normally" in text
assert payload.body_text == "one last attempt. i am tired"
assert payload.body_html is None
def test_format_sender_with_name():
@ -83,3 +87,42 @@ def test_extract_html_from_multipart():
extracted = extract_mirror_content(raw)
assert "Invoice" in (extracted.body_html or "")
assert "<table>" in (extracted.body_html or "")
def test_rich_human_reply_keeps_new_html_and_removes_private_quote():
raw = (
b"From: owner@example.com\r\n"
b"To: agent@gentic.email\r\n"
b"Subject: Re: Finance handoff\r\n"
b'MIME-Version: 1.0\r\nContent-Type: multipart/alternative; boundary="b"\r\n\r\n'
b"--b\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n"
b"Approved with urgency.\r\n\r\n"
b"On Tue, Oct 6, 2026 at 6:00 AM agent@gentic.email wrote:\r\n"
b"> private mirror\r\n"
b"--b\r\nContent-Type: text/html; charset=utf-8\r\n\r\n"
b'<div><strong>Approved</strong> with <em>urgency</em>.</div>'
b'<div class="gmail_quote">private mirror</div>\r\n'
b"--b--\r\n"
)
extracted = extract_new_reply_mime_content(raw)
assert extracted.body_text == "Approved with urgency."
assert "<strong>Approved</strong>" in (extracted.body_html or "")
assert "private mirror" not in (extracted.body_html or "")
def test_rich_human_reply_keeps_new_attachment():
raw = (
b"From: owner@example.com\r\n"
b"To: agent@gentic.email\r\n"
b"MIME-Version: 1.0\r\n"
b'Content-Type: multipart/mixed; boundary="m"\r\n\r\n'
b"--m\r\nContent-Type: text/plain; charset=utf-8\r\n\r\nApproved\r\n"
b"--m\r\nContent-Type: application/pdf\r\n"
b'Content-Disposition: attachment; filename="approval.pdf"\r\n\r\nPDF\r\n'
b"--m--\r\n"
)
extracted = extract_new_reply_mime_content(raw)
assert extracted.body_text == "Approved"
assert [part.filename for part in extracted.file_attachments] == [
"approval.pdf"
]

View file

@ -1,11 +1,13 @@
from bson import ObjectId
from app.services.handoff_mirror_policy import (
GENERATED_BY_AGENT,
GENERATED_BY_CUSTOMER,
GENERATED_BY_HANDOFF_BRIDGE,
GENERATED_BY_RELAY_ACK,
MESSAGE_ORIGIN_EXTERNAL_CUSTOMER,
MESSAGE_ORIGIN_HUMAN_OWNER,
MESSAGE_ORIGIN_AGENT,
is_relay_acknowledgment_message,
should_mirror_to_human,
)
@ -40,6 +42,27 @@ def test_should_not_mirror_human_origin():
assert should_mirror_to_human(message) is False
def test_should_mirror_agent_authored_external_reply():
message = {
"message_scope": "EXTERNAL",
"direction": "outbound",
"generated_by": GENERATED_BY_AGENT,
"message_origin": MESSAGE_ORIGIN_AGENT,
}
assert should_mirror_to_human(message) is True
def test_should_not_echo_human_relay_back_to_owner():
message = {
"message_scope": "EXTERNAL",
"direction": "outbound",
"generated_by": GENERATED_BY_HANDOFF_BRIDGE,
"message_origin": MESSAGE_ORIGIN_HUMAN_OWNER,
"generated_from_handoff_message_id": ObjectId(),
}
assert should_mirror_to_human(message) is False
def test_is_relay_acknowledgment_detects_legacy_body():
message = {
"direction": "outbound",

View file

@ -0,0 +1,423 @@
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(
rule_name="Finance approvals",
external_sender="customer@example.com",
external_subject="Invoice 42",
)
assert private.startswith("Finance approvals · handoff ·")
assert "Invoice 42" 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": {"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("Finance approvals · handoff ·")
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 == []

View file

@ -1,13 +1,74 @@
from app.services.human_handoff_service import MESSAGE_SCOPE_EXTERNAL, MESSAGE_SCOPE_HANDOFF
from app.services.human_handoff_service import (
MESSAGE_SCOPE_EXTERNAL,
MESSAGE_SCOPE_HANDOFF,
build_handoff_subject,
maybe_trigger_handoff,
process_human_reply,
redact_human_address,
resolve_handoff_from_inbound,
select_matching_handoff_rule,
)
def test_scope_constants_are_distinct():
assert MESSAGE_SCOPE_EXTERNAL != MESSAGE_SCOPE_HANDOFF
def test_human_handoff_runtime_is_disabled():
import app.services.human_handoff_service as module
def test_human_handoff_runtime_is_enabled():
assert callable(maybe_trigger_handoff)
assert callable(process_human_reply)
assert callable(resolve_handoff_from_inbound)
assert not hasattr(module, "maybe_trigger_handoff")
assert not hasattr(module, "process_human_reply")
assert not hasattr(module, "resolve_handoff_from_inbound")
def test_private_subject_contains_rule_sender_and_external_subject():
subject = build_handoff_subject(
rule_name="Finance approvals",
external_sender="customer@example.com",
external_subject="Invoice 42",
)
assert subject == (
"Finance approvals · handoff · customer@example.com · Invoice 42"
)
def test_private_subject_removes_header_line_breaks():
subject = build_handoff_subject(
rule_name="Urgent\r\nBcc: attacker@example.com",
external_sender="customer@example.com",
external_subject="Help",
)
assert "\r" not in subject
assert "\n" not in subject
def test_human_address_is_replaced_before_external_relay():
value = "Regards,\nOwner <private.owner@example.com>"
assert redact_human_address(
value,
human_email="private.owner@example.com",
agent_email="agent@gentic.email",
) == "Regards,\nOwner <agent@gentic.email>"
def test_lowest_priority_matching_rule_wins():
rules = [
{
"name": "Backup",
"priority": 20,
"labels": ["All mail"],
"email_types": ["all"],
"instructions": "",
},
{
"name": "Primary",
"priority": 1,
"labels": ["All mail"],
"email_types": ["all"],
"instructions": "",
},
]
selected = select_matching_handoff_rule(
rules,
email={"subject": "Hello", "body_text": "Need help"},
)
assert selected["name"] == "Primary"

View file

@ -22,6 +22,7 @@ class FakeMessages:
async def update_one(self, *args, **kwargs):
self.updates += 1
return SimpleNamespace(upserted_id=ObjectId(), modified_count=0)
async def find_one(self, *args, **kwargs):
return {"_id": self.id}
@ -33,6 +34,12 @@ class FakeEntries:
async def update_one(self, *args, **kwargs):
self.updates += 1
return SimpleNamespace(upserted_id=ObjectId(), modified_count=0)
class FakeThreads:
async def update_one(self, *args, **kwargs):
return None
class FakeStorage:
@ -84,7 +91,7 @@ async def test_rcpt_rejects_unknown_account(monkeypatch):
async def test_rcpt_accepts_account_and_tracks_owner(monkeypatch):
account = SimpleNamespace(id="account-1")
monkeypatch.setattr(inbound_smtp, "SessionFactory", lambda: FakeSession(account))
target = SimpleNamespace(rcpt_tos=[], rcpt_options=[])
target = SimpleNamespace(rcpt_tos=[], rcpt_options=[], mail_from="sender@example.com")
response = await inbound_smtp.InboundHandler().handle_RCPT(
None,
@ -95,8 +102,8 @@ async def test_rcpt_accepts_account_and_tracks_owner(monkeypatch):
)
assert response.startswith("250 2.1.5")
assert target.rcpt_tos == ["user@gentic.email"]
assert target.account_ids == {"user@gentic.email": "account-1"}
assert target.rcpt_tos == ["user@getall.help"]
assert target.account_ids == {"user@getall.help": "account-1"}
@pytest.mark.asyncio
@ -106,15 +113,98 @@ async def test_data_returns_success_only_after_idempotent_records(monkeypatch):
monkeypatch.setattr(
inbound_smtp,
"mongo_db",
SimpleNamespace(messages=messages, mailbox_entries=entries),
SimpleNamespace(
messages=messages,
mailbox_entries=entries,
email_threads=FakeThreads(),
),
)
monkeypatch.setattr(inbound_smtp, "get_raw_mime_storage", lambda: FakeStorage())
async def no_handoff(*args, **kwargs):
return None
monkeypatch.setattr(inbound_smtp, "resolve_handoff_from_inbound", no_handoff)
monkeypatch.setattr(inbound_smtp, "maybe_trigger_handoff", no_handoff)
async def ensure_thread(*args, **kwargs):
return ObjectId()
monkeypatch.setattr(inbound_smtp, "ensure_thread_for_message", ensure_thread)
response = await inbound_smtp.InboundHandler().handle_DATA(None, None, envelope())
assert response.startswith("250 2.0.0")
assert messages.updates == 1
assert entries.updates == 1
assert messages.updates >= 1
assert entries.updates >= 1
@pytest.mark.asyncio
async def test_human_reply_stays_on_the_private_plane(monkeypatch):
messages = FakeMessages()
entries = FakeEntries()
monkeypatch.setattr(
inbound_smtp,
"mongo_db",
SimpleNamespace(
messages=messages,
mailbox_entries=entries,
email_threads=FakeThreads(),
),
)
monkeypatch.setattr(inbound_smtp, "get_raw_mime_storage", lambda: FakeStorage())
async def resolve(*args, **kwargs):
return {"_id": ObjectId()}
processed = []
async def process(*args, **kwargs):
processed.append(kwargs)
return {"_id": ObjectId()}
monkeypatch.setattr(inbound_smtp, "resolve_handoff_from_inbound", resolve)
monkeypatch.setattr(inbound_smtp, "process_human_reply", process)
response = await inbound_smtp.InboundHandler().handle_DATA(None, None, envelope())
assert response.startswith("250 2.0.0 Handoff message accepted")
assert processed
assert messages.updates == 0
assert entries.updates == 0
@pytest.mark.asyncio
async def test_data_still_accepted_when_handoff_trigger_fails(monkeypatch):
messages = FakeMessages()
entries = FakeEntries()
monkeypatch.setattr(
inbound_smtp,
"mongo_db",
SimpleNamespace(
messages=messages,
mailbox_entries=entries,
email_threads=FakeThreads(),
),
)
monkeypatch.setattr(inbound_smtp, "get_raw_mime_storage", lambda: FakeStorage())
async def no_handoff(*args, **kwargs):
return None
async def boom(*args, **kwargs):
raise RuntimeError("mirror queue unavailable")
monkeypatch.setattr(inbound_smtp, "resolve_handoff_from_inbound", no_handoff)
monkeypatch.setattr(inbound_smtp, "maybe_trigger_handoff", boom)
async def ensure_thread(*args, **kwargs):
return ObjectId()
monkeypatch.setattr(inbound_smtp, "ensure_thread_for_message", ensure_thread)
response = await inbound_smtp.InboundHandler().handle_DATA(None, None, envelope())
assert response.startswith("250 2.0.0")
assert messages.updates >= 1
@pytest.mark.asyncio

View file

@ -1,7 +1,49 @@
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
from app.services.mail_sender import build_message, send_via_smtp, sign_message
import pytest
from app.services.mail_sender import build_message, deliver_email, send_via_smtp, sign_message
def test_build_message_formats_named_sender_header():
message = build_message(
sender="suyash@gentic.email",
sender_name="Suyash Dixit",
recipients=["recipient@example.com"],
cc=[],
subject="Named sender",
body_text="Hello",
body_html=None,
)
assert message["From"] == "Suyash Dixit <suyash@gentic.email>"
def test_build_message_preserves_preformatted_transactional_sender():
message = build_message(
sender="aGentic Email <notifications@gentic.email>",
recipients=["recipient@example.com"],
cc=[],
subject="Welcome",
body_text="Hello",
body_html=None,
)
assert message["From"] == "aGentic Email <notifications@gentic.email>"
def test_build_message_adds_safe_fallback_sender_name():
message = build_message(
sender="notifications@gentic.email",
recipients=["recipient@example.com"],
cc=[],
subject="Notification",
body_text="Hello",
body_html=None,
)
assert message["From"] == "aGentic Email <notifications@gentic.email>"
def test_dkim_sign_uses_selector_before_domain():
@ -29,6 +71,7 @@ def test_dkim_sign_uses_selector_before_domain():
assert signer.call_args.args[2] == b"ritoesis.com"
@pytest.mark.asyncio
async def test_send_via_smtp_uses_configured_client_hostname():
with patch(
"app.services.mail_sender.aiosmtplib.send",
@ -43,6 +86,38 @@ async def test_send_via_smtp_uses_configured_client_hostname():
assert send_mock.await_args.kwargs["local_hostname"] == "backend.gentic.email"
@pytest.mark.asyncio
async def test_deliver_email_uses_bare_address_for_smtp_envelope():
domain = SimpleNamespace(
domain_name="gentic.email",
dkim_selector="agmail",
dkim_private_key="private-key",
)
with (
patch(
"app.services.mail_sender.sign_message",
return_value=b"signed",
),
patch(
"app.services.mail_sender.send_via_smtp",
new_callable=AsyncMock,
) as send_mock,
):
await deliver_email(
sender="notifications@gentic.email",
sender_name="aGentic Email",
recipients=["user@example.com"],
cc=[],
bcc=[],
subject="Welcome",
body_text="Hello",
body_html=None,
domain=domain,
)
assert send_mock.await_args.kwargs["sender"] == "notifications@gentic.email"
def test_build_message_sets_in_reply_to_and_references():
message = build_message(
sender="suyash@gentic.email",

View file

@ -99,6 +99,26 @@ def test_html_reply_preserves_nested_quoted_history():
assert "> > sender: C" in result["quoted_content"]
def test_apple_mail_attribution_is_treated_as_quote():
body = """Approved.
On 6 Oct 2026, at 04:48, Suyash Dixit <suyash@gentic.email> wrote:
sender: C"""
result = extract_new_reply_content(body_text=body)
assert result["new_reply_content"] == "Approved."
assert "sender: C" in result["quoted_content"]
def test_private_handoff_subject_fingerprint_is_blocked():
from app.services.reply_extractor import validate_external_reply_body
leaked = (
"Finance approvals · handoff · customer@example.com · Invoice 42\n"
"Please approve"
)
assert validate_external_reply_body(leaked) is False
def test_outlook_original_message_split():
body = """Thanks for the update.