Files
ai-kefu-admin/tests/test_lead_push_cc.py
T
浅念 d1aed8e722 feat: 吸收上游 7 个测试文件断言与 placeholder 配置化对齐
上游 ai-kefu → phase1 吸收(Task 11 unit 12):
- 拷入 7 个测试文件,import/夹具按目标库重锚(app.engine.* / conftest
  async_db+build_jiancai_runtime / seed_profiles 提示词锚点)
- 补实现(同步自上游): chat_rest 服务端可信 IP(_server_ip/
  _merge_client_info,直连场景自报假 IP 不再原样落库);widget.js 与
  chat.js 的 data-placeholder 读取(叠加 Task 10 管道:data-* > brand >
  中性兜底,tests/test_placeholder_static.py 守卫)
- 断言重锚以 jc 主线为准: ev.contact_info 存裸号+等待尾注(e164 走
  _cc 派生)、source 值 fallback/customer_stated 口径、推送标注键
  号码补全说明、prompt 静态锚按租户(seed_profiles)双测
- 全量回归 459 passed(基线 421 + 吸收 38)

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

142 lines
6.8 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 同源,双钉防漂移);
- 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不外泄
# 双钉: 与常量同源 + 字节级措辞(改常量必须联动,措辞本身不得漂移)
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
# 双钉: 与常量同源 + 字节级措辞(改常量必须联动,措辞本身不得漂移)
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}"