test: 补充知识处理、检索与 Agent 回归用例
覆盖切块、向量检索、工具调用和历史预算;模型相关用例使用桩。
This commit is contained in:
1 parent
362d79a5c2
commit
f4d8019829
123 files changed
+29005
No files matched your search
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Copyright 2024-2026 ChatterMate
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Customer-identity capture on native ticket creation: the email/name the
|
||||
customer states in chat must be validated, folded onto the (usually anonymous)
|
||||
customer, and surfaced to the investigator — the …@noemail.com placeholder must
|
||||
never be presented as a real identity.
|
||||
"""
|
||||
|
||||
from contextlib import nullcontext
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.customer import Customer
|
||||
from app.models.session_to_agent import SessionStatus, SessionToAgent
|
||||
from app.models.ticket import Ticket
|
||||
from app.tools.ticket_toolkit import TicketTools, _clean_email, _clean_name
|
||||
|
||||
|
||||
class TestCleanEmail:
|
||||
def test_normalizes_and_lowercases(self):
|
||||
assert _clean_email(" Admin@ChatterMate.Chat ") == "admin@chattermate.chat"
|
||||
|
||||
def test_rejects_junk(self):
|
||||
for bad in (None, "", "not-an-email", "no@domain", "@no-local.com", "a b@c.com"):
|
||||
assert _clean_email(bad) is None
|
||||
|
||||
|
||||
class TestCleanName:
|
||||
def test_trims(self):
|
||||
assert _clean_name(" Arun ") == "Arun"
|
||||
|
||||
def test_empty_is_none(self):
|
||||
assert _clean_name(None) is None
|
||||
assert _clean_name(" ") is None
|
||||
|
||||
|
||||
class TestCustomerIdentitySection:
|
||||
"""The worker's trusted identity block fed to the investigator."""
|
||||
|
||||
def _section(self, **customer_fields):
|
||||
from app.workers.ticket_investigator import _customer_identity_section
|
||||
|
||||
fields = {"full_name": None, "email": None, "phone": None, "meta_data": None}
|
||||
fields.update(customer_fields)
|
||||
customer = SimpleNamespace(**fields)
|
||||
ticket = SimpleNamespace(customer=customer)
|
||||
return _customer_identity_section(ticket)
|
||||
|
||||
def test_none_without_customer(self):
|
||||
from app.workers.ticket_investigator import _customer_identity_section
|
||||
|
||||
assert _customer_identity_section(SimpleNamespace(customer=None)) is None
|
||||
|
||||
def test_real_identity_included(self):
|
||||
section = self._section(
|
||||
full_name="Arun Kumar", email="admin@chattermate.chat", phone="+441234567890"
|
||||
)
|
||||
assert section is not None
|
||||
assert "admin@chattermate.chat" in section
|
||||
assert "Arun Kumar" in section
|
||||
assert "+441234567890" in section
|
||||
|
||||
def test_placeholder_email_omitted(self):
|
||||
# Anonymous widget visitor with no real identity -> no section at all.
|
||||
assert self._section(email="1784547346351@noemail.com") is None
|
||||
|
||||
def test_placeholder_email_but_real_name_keeps_name_only(self):
|
||||
section = self._section(
|
||||
full_name="Arun", email="1784547346351@noemail.com"
|
||||
)
|
||||
assert section is not None
|
||||
assert "Arun" in section
|
||||
assert "noemail.com" not in section
|
||||
|
||||
def test_meta_data_identifiers_included(self):
|
||||
section = self._section(
|
||||
full_name="Arun", meta_data={"account_id": "acc_991", "plan": "pro"}
|
||||
)
|
||||
assert "acc_991" in section
|
||||
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ticket_tools_db(db):
|
||||
"""TicketTools opens its own SessionLocal; hand it the test database."""
|
||||
with patch("app.tools.ticket_toolkit.SessionLocal", return_value=nullcontext(db)), \
|
||||
patch("app.services.ticket.embed_ticket_text", return_value=None):
|
||||
yield db
|
||||
|
||||
|
||||
class TestCreateTicketIdentity:
|
||||
"""A ticket only carries the identity of a customer the conversation belongs to:
|
||||
a visitor bound to a customer by an email they typed must not hand the
|
||||
investigator that customer's records."""
|
||||
|
||||
def _conversation(self, db, org_id, agent_id, session_key, customer_key, email=None, name=None):
|
||||
customer = Customer(email=email or f"{uuid4().hex}@noemail.com", full_name=name,
|
||||
organization_id=org_id, conversation_key=customer_key)
|
||||
db.add(customer); db.commit(); db.refresh(customer)
|
||||
session = SessionToAgent(session_id=uuid4(), customer_id=customer.id, agent_id=agent_id,
|
||||
organization_id=org_id, status=SessionStatus.OPEN,
|
||||
conversation_key=session_key)
|
||||
db.add(session); db.commit()
|
||||
return customer, session
|
||||
|
||||
def _create(self, db, org_id, agent_id, session, **identity) -> Ticket:
|
||||
TicketTools(str(agent_id), str(org_id), str(session.session_id)).create_ticket(
|
||||
summary="Login fails", description="Every attempt errors.", **identity)
|
||||
return db.query(Ticket).filter(Ticket.organization_id == org_id).order_by(Ticket.created_at.desc()).first()
|
||||
|
||||
def test_the_customers_own_conversation_names_them(self, ticket_tools_db, test_organization_id, test_agent):
|
||||
db = ticket_tools_db
|
||||
customer, session = self._conversation(db, test_organization_id, test_agent.id, "browser-a", "browser-a")
|
||||
|
||||
ticket = self._create(db, test_organization_id, test_agent.id, session, customer_name="Own Name")
|
||||
|
||||
db.refresh(customer)
|
||||
assert ticket.customer_id == customer.id
|
||||
assert customer.full_name == "Own Name"
|
||||
|
||||
def test_a_claimed_customer_is_left_off_the_ticket(self, ticket_tools_db, test_organization_id, test_agent):
|
||||
db = ticket_tools_db
|
||||
victim, session = self._conversation(db, test_organization_id, test_agent.id, "browser-b", "browser-a",
|
||||
email="victim@acme.com", name="Victim")
|
||||
|
||||
ticket = self._create(db, test_organization_id, test_agent.id, session,
|
||||
customer_email="victim@acme.com", customer_name="Someone Else")
|
||||
|
||||
db.refresh(victim)
|
||||
assert ticket.customer_id is None
|
||||
assert victim.full_name == "Victim"
|
||||
|
||||
def test_a_typed_email_never_resolves_to_another_customer(self, ticket_tools_db, test_organization_id, test_agent):
|
||||
db = ticket_tools_db
|
||||
other = Customer(email="other@acme.com", organization_id=test_organization_id)
|
||||
db.add(other); db.commit()
|
||||
_, session = self._conversation(db, test_organization_id, test_agent.id, "browser-b", "browser-a")
|
||||
|
||||
ticket = self._create(db, test_organization_id, test_agent.id, session, customer_email="other@acme.com")
|
||||
|
||||
assert ticket.customer_id is None
|
||||
Reference in new issue
Block a user