Files
group_xinghuo_jinrong/tests/test_sprint3_kyc_session.py
T

219 lines
7.0 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.config.database import AgentSessionLocal, agent_engine
from app.main import app
from app.model.entities import AgentSession, KycSession
from app.model.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_json["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/v1/auth/login",
json={"username": "admin_test", "password": "admin_test"},
)
token = login.json()["data"]["access_token"]
trace_id = f"trace-kyc-api-{uuid4().hex}"
created = client.post(
"/api/v1/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/v1/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/v1/kyc/sessions",
json={"customer_id": "CUST-1001", "session_type": "new_customer"},
)
assert response.status_code == 401