124 lines
3.6 KiB
Python
124 lines
3.6 KiB
Python
import pytest
|
|
from bson import ObjectId
|
|
|
|
from app.services.human_handoff_service import (
|
|
MESSAGE_SCOPE_EXTERNAL,
|
|
MESSAGE_SCOPE_HANDOFF,
|
|
resolve_handoff_from_inbound,
|
|
)
|
|
|
|
|
|
class FakeCollection:
|
|
def __init__(self, documents=None):
|
|
self.documents = list(documents or [])
|
|
|
|
async def find_one(self, query):
|
|
for document in self.documents:
|
|
if all(document.get(key) == value for key, value in query.items()):
|
|
return document
|
|
if "$ne" in query.get("status", {}):
|
|
if document.get("status") == query["status"]["$ne"]:
|
|
continue
|
|
for document in self.documents:
|
|
matches = True
|
|
for key, value in query.items():
|
|
if key == "status" and isinstance(value, dict) and "$ne" in value:
|
|
if document.get("status") == value["$ne"]:
|
|
matches = False
|
|
break
|
|
elif document.get(key) != value:
|
|
matches = False
|
|
break
|
|
if matches:
|
|
return document
|
|
return None
|
|
|
|
def find(self, query):
|
|
class Cursor:
|
|
def __init__(self, items):
|
|
self.items = items
|
|
|
|
async def to_list(self, length):
|
|
return self.items[:length]
|
|
|
|
filtered = [
|
|
document
|
|
for document in self.documents
|
|
if all(document.get(key) == value for key, value in query.items())
|
|
]
|
|
return Cursor(filtered)
|
|
|
|
|
|
class FakeDatabase:
|
|
def __init__(self):
|
|
self.human_handoff_rules = FakeCollection()
|
|
self.messages = FakeCollection()
|
|
self.mailbox_entries = FakeCollection()
|
|
self.human_handoffs = FakeCollection()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_handoff_requires_owner_and_thread_context():
|
|
database = FakeDatabase()
|
|
handoff_thread_id = ObjectId()
|
|
message_id = ObjectId()
|
|
database.human_handoff_rules.documents = [
|
|
{
|
|
"account_id": "acct-1",
|
|
"owner_email": "owner@gmail.com",
|
|
"active": True,
|
|
}
|
|
]
|
|
database.messages.documents = [
|
|
{
|
|
"_id": message_id,
|
|
"message_id": "<handoff-1@gentic.email>",
|
|
"message_scope": MESSAGE_SCOPE_HANDOFF,
|
|
}
|
|
]
|
|
database.mailbox_entries.documents = [
|
|
{
|
|
"account_id": "acct-1",
|
|
"message_id": message_id,
|
|
"thread_id": handoff_thread_id,
|
|
"message_scope": MESSAGE_SCOPE_HANDOFF,
|
|
}
|
|
]
|
|
database.human_handoffs.documents = [
|
|
{
|
|
"_id": ObjectId(),
|
|
"account_id": "acct-1",
|
|
"handoff_thread_id": handoff_thread_id,
|
|
"human_email": "owner@gmail.com",
|
|
"status": "WAITING_FOR_HUMAN",
|
|
}
|
|
]
|
|
|
|
handoff = await resolve_handoff_from_inbound(
|
|
database,
|
|
account_id="acct-1",
|
|
agent_email="support@gentic.email",
|
|
sender_address="owner@gmail.com",
|
|
in_reply_to="<handoff-1@gentic.email>",
|
|
references=[],
|
|
)
|
|
assert handoff is not None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_handoff_rejects_unknown_sender():
|
|
database = FakeDatabase()
|
|
handoff = await resolve_handoff_from_inbound(
|
|
database,
|
|
account_id="acct-1",
|
|
agent_email="support@gentic.email",
|
|
sender_address="stranger@example.com",
|
|
in_reply_to="<handoff-1@gentic.email>",
|
|
references=[],
|
|
)
|
|
assert handoff is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_external_scope_constant_isolated_from_handoff():
|
|
assert MESSAGE_SCOPE_EXTERNAL != MESSAGE_SCOPE_HANDOFF
|