Files
group_xinghuo_jinrong/tests/test_sprint3_kyc_session.py
T
zhanghongyu_0626 70aa861983 feat(advisor-agent): Introduce advisor agent functionalities with compliance, KYC, and script templates
- Added new modules for advisor compliance, KYC sessions, and script templates, enhancing the advisor agent's capabilities.
- Implemented a comprehensive API structure under the `/api/advisor-agent` prefix, ensuring clear organization and access to new features.
- Established database models and repositories for compliance rules and KYC sessions, facilitating robust data management.
- Integrated exception handling and response models to improve error management and user feedback.
- Updated settings to include new configurations for compliance and KYC features, ensuring flexibility and adaptability.

This update significantly expands the advisor agent's functionality, providing essential tools for compliance and customer interaction while maintaining a structured API design.
2026-09-12 16:33:07 +08:00

207 lines
7.1 KiB
Python

from datetime import datetime, timedelta, timezone
from uuid import uuid4
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import inspect
import app.api.kyc as kyc_api
from app.advisor_db import AgentSessionLocal, agent_engine
from app.main import app
from app.model.entities import AgentSession
from app.model.entities_advisor import KycSession
from app.model.advisor_schemas import AuthContext, KycSessionCreate
from app.repository.kyc_session_repository import KycSessionRepository
from app.service.kyc_session_service import KycSessionService
from app.utils.exceptions import AppError
client = TestClient(app)
class FakeCoreRepository:
def get_customer_l0(self, customer_id: str) -> dict | None:
if customer_id == "CUST-KYC-001":
return {
"customer_id": customer_id,
"display_name": "客户·KYC测试",
"age": 32,
}
return None
class AllowOwnership:
def assert_customer_access(self, auth: AuthContext, customer_id: str) -> None:
assert auth.advisor_id == "ADV-KYC-001"
assert customer_id == "CUST-KYC-001"
def advisor_context() -> AuthContext:
return AuthContext(
user_id="advisor_test",
display_name="测试顾问",
roles=["advisor"],
permissions=["kyc:create", "kyc:chat"],
trace_id="trace-kyc-session-unit",
advisor_id="ADV-KYC-001",
)
def session_service() -> KycSessionService:
return KycSessionService(
repository=KycSessionRepository(),
core_repository=FakeCoreRepository(),
ownership_service=AllowOwnership(),
)
def test_kyc_session_migration_and_creation_associate_agent_session():
inspector = inspect(agent_engine)
assert "kyc_session" in inspector.get_table_names()
columns = {column["name"] for column in inspector.get_columns("kyc_session")}
assert {
"session_id",
"trace_id",
"advisor_id",
"customer_id",
"session_type",
"status",
"current_node",
"collected_fields",
"missing_fields",
"progress_pct",
"dialog_turns",
"started_at",
"completed_at",
"duration_seconds",
}.issubset(columns)
service = session_service()
result = service.create_session(
KycSessionCreate(customer_id="CUST-KYC-001", session_type="new_customer"),
auth=advisor_context(),
trace_id=f"trace-kyc-create-{uuid4().hex}",
)
assert result.status == "in_progress"
assert result.current_node == "basic_info"
assert result.collected_fields == {}
assert result.progress_pct == 0
assert result.suggested_question
assert "年龄" in result.suggested_question
with AgentSessionLocal() as session:
agent_session = (
session.query(AgentSession)
.filter(AgentSession.session_id == result.session_id)
.one()
)
assert agent_session.customer_id == "CUST-KYC-001"
assert agent_session.agent_type == "advisor"
assert agent_session.metadata_["kyc_session_id"] == result.session_id
def test_kyc_session_can_resume_and_complete():
service = session_service()
auth = advisor_context()
created = service.create_session(
KycSessionCreate(customer_id="CUST-KYC-001", session_type="periodic_review"),
auth=auth,
trace_id=f"trace-kyc-resume-{uuid4().hex}",
)
resumed = service.get_session(created.session_id, auth=auth)
assert resumed.session_id == created.session_id
assert resumed.status == "in_progress"
assert resumed.current_node == "basic_info"
completed = service.complete_session(
created.session_id,
auth=auth,
trace_id=f"trace-kyc-complete-{uuid4().hex}",
)
assert completed.status == "completed"
assert completed.current_node == "profile_generation"
assert completed.completed_at is not None
assert completed.duration_seconds is not None
def test_expired_in_progress_session_is_archived_as_abandoned():
service = session_service()
auth = advisor_context()
created = service.create_session(
KycSessionCreate(customer_id="CUST-KYC-001", session_type="deep_kyc"),
auth=auth,
trace_id=f"trace-kyc-expire-create-{uuid4().hex}",
)
old_time = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=5)
with AgentSessionLocal() as session:
row = session.query(KycSession).filter(KycSession.session_id == created.session_id).one()
row.started_at = old_time
row.updated_at = old_time
session.commit()
expired = service.expire_stale_sessions(
now=datetime.now(timezone.utc),
ttl_hours=4,
trace_id=f"trace-kyc-expire-{uuid4().hex}",
)
assert expired == 1
archived = service.get_session(created.session_id, auth=auth)
assert archived.status == "abandoned"
assert archived.completed_at is None
def test_completed_session_cannot_be_completed_again():
service = session_service()
auth = advisor_context()
created = service.create_session(
KycSessionCreate(customer_id="CUST-KYC-001", session_type="new_customer"),
auth=auth,
trace_id=f"trace-kyc-repeat-{uuid4().hex}",
)
service.complete_session(
created.session_id,
auth=auth,
trace_id=f"trace-kyc-complete-first-{uuid4().hex}",
)
with pytest.raises(AppError) as exc_info:
service.complete_session(
created.session_id,
auth=auth,
trace_id=f"trace-kyc-complete-second-{uuid4().hex}",
)
assert exc_info.value.code == "40901"
assert exc_info.value.status_code == 409
def test_kyc_session_api_creates_and_resumes_session(monkeypatch):
monkeypatch.setattr(kyc_api, "kyc_session_service", KycSessionService())
login = client.post("/api/auth/login", json={"actor_id": "STAFF-10086", "token_type": "staff"})
token = login.json()["data"]["access_token"]
trace_id = f"trace-kyc-api-{uuid4().hex}"
created = client.post(
"/api/advisor-agent/kyc/sessions",
json={
"customer_id": "CUST-1001",
"session_type": "new_customer",
"customer_display_name": "客户·王**",
},
headers={"Authorization": f"Bearer {token}", "X-Trace-Id": trace_id},
)
assert created.status_code == 200
assert created.json()["data"]["status"] == "in_progress"
assert created.json()["data"]["current_node"] == "basic_info"
assert created.json()["trace_id"] == trace_id
session_id = created.json()["data"]["session_id"]
resumed = client.get(
f"/api/advisor-agent/kyc/sessions/{session_id}",
headers={"Authorization": f"Bearer {token}", "X-Trace-Id": trace_id},
)
assert resumed.status_code == 200
assert resumed.json()["data"]["session_id"] == session_id
assert resumed.json()["data"]["suggested_question"]
def test_kyc_session_api_requires_authentication():
response = client.post(
"/api/advisor-agent/kyc/sessions",
json={"customer_id": "CUST-1001", "session_type": "new_customer"},
)
assert response.status_code == 401