1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富 统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」; 同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。 2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本), 新增《文档规整方案与开发前待决事项-2026-09-17》。 3) 客服agent 四份交付文档首次纳入本分支。
89 lines
3.9 KiB
Python
89 lines
3.9 KiB
Python
"""客户画像候选批准到画像快照的真实 MySQL 集成验证。"""
|
|
|
|
from datetime import UTC, datetime
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from sqlalchemy import delete, select
|
|
|
|
from app.infrastructure.db import SessionFactory
|
|
from app.model.memory import MemoryConflict, MemorySyncOutbox, MemoryUnit, ProfileSnapshot
|
|
from app.service.customer_profile_candidate_service import CustomerProfileCandidateService
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_approved_candidate_creates_current_snapshot_and_outbox(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""批准候选必须原子生成当前画像快照和两个投影事件。"""
|
|
customer_id = uuid4().int % 10**15 + 10**15
|
|
now = datetime.now(UTC).replace(tzinfo=None)
|
|
async with SessionFactory() as session, session.begin():
|
|
candidate = MemoryUnit(
|
|
memory_uuid=str(uuid4()), customer_id=customer_id,
|
|
memory_key="preference:risk_level", content="稳健型",
|
|
memory_type="preference", source_type="AI对话提取",
|
|
source_confidence=0.9, confidence=0.9, evidence_count=1,
|
|
conflict_count=0, recall_count=0, status="verified", valid_from=now,
|
|
version=1, created_at=now, updated_at=now,
|
|
)
|
|
session.add(candidate)
|
|
await session.flush()
|
|
candidate_id = int(candidate.id)
|
|
monkeypatch.setattr(
|
|
"app.service.customer_profile_candidate_service.get_memory_cache_adapter",
|
|
lambda: None,
|
|
)
|
|
try:
|
|
async with SessionFactory() as session, session.begin():
|
|
target = await session.scalar(
|
|
select(MemoryUnit).where(MemoryUnit.id == candidate_id).with_for_update()
|
|
)
|
|
assert target is not None
|
|
await CustomerProfileCandidateService()._promote(session, target, reviewer_id=9003)
|
|
|
|
async with SessionFactory() as session:
|
|
snapshot = await session.scalar(
|
|
select(ProfileSnapshot).where(
|
|
ProfileSnapshot.customer_id == customer_id,
|
|
ProfileSnapshot.current_customer_id == customer_id,
|
|
)
|
|
)
|
|
assert snapshot is not None
|
|
assert snapshot.version == 1
|
|
assert snapshot.snapshot["customer_service_preferences"][
|
|
"preference:risk_level"
|
|
]["value"] == "稳健型"
|
|
events = list(await session.scalars(
|
|
select(MemorySyncOutbox).where(
|
|
MemorySyncOutbox.aggregate_uuid == snapshot.profile_uuid
|
|
)
|
|
))
|
|
assert {event.target_store for event in events} == {"milvus", "neo4j"}
|
|
finally:
|
|
async with SessionFactory() as session, session.begin():
|
|
# Outbox uses the snapshot UUID as an aggregate reference; remove it first.
|
|
await session.execute(delete(MemorySyncOutbox).where(
|
|
MemorySyncOutbox.aggregate_uuid.in_(
|
|
select(ProfileSnapshot.profile_uuid).where(
|
|
ProfileSnapshot.customer_id == customer_id
|
|
)
|
|
)
|
|
))
|
|
# Delete snapshots by customer directly to avoid MySQL error 1093
|
|
# (target table referenced by its own subquery).
|
|
await session.execute(delete(ProfileSnapshot).where(
|
|
ProfileSnapshot.customer_id == customer_id
|
|
))
|
|
await session.execute(delete(MemoryConflict).where(
|
|
MemoryConflict.left_memory_id.in_(
|
|
select(MemoryUnit.id).where(MemoryUnit.customer_id == customer_id)
|
|
)
|
|
| MemoryConflict.right_memory_id.in_(
|
|
select(MemoryUnit.id).where(MemoryUnit.customer_id == customer_id)
|
|
)
|
|
))
|
|
await session.execute(delete(MemoryUnit).where(MemoryUnit.customer_id == customer_id))
|