Files
ai-kefu-admin/tests/test_lead_push_cc.py
T
浅念 3d6064dc14 docs: 上游吸收审查修补(注释纠偏/上游标点恢复/交叉引用闭合)
- test_lead_push_cc: 两处"双钉"注释与模块头改为如实的跨文件互补分工
  (常量同源钉本文件,字节级措辞钉 test_lead_push_service.py /
  test_email_rendering.py)
- chat_rest: _server_ip/_merge_client_info docstring 与调用点注释恢复
  上游半角标点原文(逐字同步,便于将来 diff 式跟上游)
- chat.py WS 自报注释补 REST 侧交叉引用,闭合悬空不对称说明

全量回归 459 passed(不变)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 23:44:02 +08:00

145 lines
6.9 KiB
Python

"""推送侧 CC 补全渲染(同步自上游 ai-kefu tests/test_lead_push_cc.py)。
惯例照搬 tests/test_lead_push_service.py: async_db fixture +
monkeypatch settings.PUSH_ENABLED → dispatch_lead_push → 查 request_payload。
断言按 jc(Task 6)主线重锚,语义等价:
- 派生键名: 上游 "区号未确认" → jc "号码补全说明"(号码补全的推送披露口);
- 条目措辞: 上游 "渠道(标注)" → jc completion_note 的 "原文→e164+标注"
(标注常量与 cc_completion 同源;字节级措辞钉在 test_lead_push_service.py /
test_email_rendering.py,跨文件互补);
- NOTE_CUSTOMER 已在 jc 并入 NOTE_UNCONFIRMED(customer_stated 未确认与
IP 推测同口径标注,上游的措辞区分被主线统一);
- confirmed 入参传裸号原文(jc 生产契约: dispatch 收 normalize_contacts
的裸号, e164 由 _cc 派生),上游传 e164——canon 口径下两者等价。
"""
import asyncio
from sqlalchemy import select
from app.engine.models import (
Conversation, CustomerProfile, Evaluation, IntentLevel,
)
from app.engine.services.cc_completion import NOTE_MISSING_CC, NOTE_UNCONFIRMED
def _cc(confirmed=False, source="ip_proposed", e164="+254712345678"):
return {"e164": e164, "intl": "+254 712 345678", "confirmed": confirmed,
"attempts": 0, "source": source, "canon": "0712345678"}
def _seed(async_db, raw_info):
from app.engine.models import ConversationSource
async def run():
async with async_db() as db:
conv = Conversation(
session_id="s-push-cc", source=ConversationSource.widget,
client_info={"ip": "8.8.8.8", "lang": "en"},
)
db.add(conv)
await db.flush()
profile = CustomerProfile(conversation_id=conv.id, raw_info=raw_info)
ev = Evaluation(
conversation_id=conv.id,
intent_level=IntentLevel.interested, contact_info={})
db.add_all([profile, ev])
await db.commit()
asyncio.run(run())
def _dispatch(async_db, confirmed):
from app.engine.services import lead_push_service as svc
async def run():
async with async_db() as db:
conv = (await db.execute(select(Conversation))).scalar_one()
profile = (await db.execute(select(CustomerProfile))).scalar_one()
ev = (await db.execute(select(Evaluation))).scalar_one()
log = await svc.dispatch_lead_push(
db, ev, conv, profile, confirmed, "lead",
{"intent_level": "interested"}, True)
await db.commit()
return log
return asyncio.run(run())
def test_push_payload_uses_e164_and_filters_underscore(async_db, monkeypatch):
"""消费 _cc → 过滤 `_`键(顺序钉死 spec §3.5);未确认 IP 推测 → 标注字段。"""
from app.engine.services import lead_push_service as svc
monkeypatch.setattr(svc.settings, "PUSH_ENABLED", True)
_seed(async_db, {"目标国家": "肯尼亚", "_cc": {"whatsapp": _cc()},
"_secret": "x"})
log = _dispatch(async_db, {"whatsapp": "0712345678"})
p = log.request_payload
assert p["contact"]["whatsapp"] == "+254712345678" # 契约值=e164(Task6投影)
assert not [k for k in p["profile"] if str(k).startswith("_")] # _cc/_secret不外泄
# 常量同源钉在本文件,字节级措辞钉在 test_lead_push_service.py /
# test_email_rendering.py(跨文件互补)
assert p["profile"]["号码补全说明"] == f"0712345678→+254712345678{NOTE_UNCONFIRMED}"
def test_push_payload_confirmed_no_note(async_db, monkeypatch):
"""confirmed=true → 无标注字段;`_` 内部态同样不得外泄。"""
from app.engine.services import lead_push_service as svc
monkeypatch.setattr(svc.settings, "PUSH_ENABLED", True)
_seed(async_db, {"目标国家": "肯尼亚",
"_cc": {"whatsapp": _cc(confirmed=True)}})
log = _dispatch(async_db, {"whatsapp": "0712345678"})
p = log.request_payload
assert "号码补全说明" not in p["profile"]
assert not [k for k in p["profile"] if str(k).startswith("_")]
def test_push_payload_no_ip_note(async_db, monkeypatch):
"""no_ip(e164=None,值留原裸号) → "缺国家区号"标注;contact 值=原号。
phone 态显式构造(照上游混合渠道用例的形态): jc 的 cc_state_for 按
canon 匹配(canon≠裸号数字一律判 None,旧补全永不跟随新号),故 canon
必须与渠道值一致——生产 _cc.canon 恒为客户裸号纯数字。"""
from app.engine.services import lead_push_service as svc
monkeypatch.setattr(svc.settings, "PUSH_ENABLED", True)
_seed(async_db, {"目标国家": "肯尼亚",
"_cc": {"phone": {"e164": None, "intl": None,
"confirmed": False, "attempts": 0,
"source": "no_ip",
"canon": "18325698745"}}})
log = _dispatch(async_db, {"phone": "18325698745"})
p = log.request_payload
assert p["profile"]["号码补全说明"] == f"18325698745{NOTE_MISSING_CC}"
assert p["contact"]["phone"] == "18325698745"
def test_push_payload_customer_stated_note(async_db, monkeypatch):
"""customer_stated 未确认 → 标注在位(jc 主线: 与 IP 推测同口径
NOTE_UNCONFIRMED,上游 NOTE_CUSTOMER 的独立措辞已统一)。"""
from app.engine.services import lead_push_service as svc
monkeypatch.setattr(svc.settings, "PUSH_ENABLED", True)
_seed(async_db, {"目标国家": "肯尼亚",
"_cc": {"whatsapp": _cc(source="customer_stated",
e164="+256712345678")}})
log = _dispatch(async_db, {"whatsapp": "0712345678"})
p = log.request_payload
# 常量同源钉在本文件,字节级措辞钉在 test_lead_push_service.py /
# test_email_rendering.py(跨文件互补)
assert p["profile"]["号码补全说明"] == f"0712345678→+256712345678{NOTE_UNCONFIRMED}"
assert p["contact"]["whatsapp"] == "+256712345678"
def test_push_payload_mixed_channels_join(async_db, monkeypatch):
"""混合渠道: whatsapp 已确认(不标注) + phone no_ip 未确认(标注)——
只记未确认渠道,join 格式钉死。"""
from app.engine.services import lead_push_service as svc
monkeypatch.setattr(svc.settings, "PUSH_ENABLED", True)
_seed(async_db, {"目标国家": "肯尼亚", "_cc": {
"whatsapp": _cc(confirmed=True, e164="+254712345678"),
"phone": {"e164": None, "intl": None, "confirmed": False,
"attempts": 0, "source": "no_ip",
"canon": "18325698745"}}})
log = _dispatch(async_db, {"whatsapp": "0712345678",
"phone": "18325698745"})
p = log.request_payload
assert p["contact"]["whatsapp"] == "+254712345678"
assert p["contact"]["phone"] == "18325698745"
assert p["profile"]["号码补全说明"] == f"18325698745{NOTE_MISSING_CC}"