fix(api): staff-stats评审修复(GROUP BY显式列防ONLY_FULL_GROUP_BY/金额千分位/detail死参数/2边缘测试)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-25 16:36:07 +08:00
parent b95fc5fa6f
commit 4baa8639ba
2 changed files with 114 additions and 10 deletions
@@ -313,3 +313,84 @@ async def test_detail_unknown_kind_422(make_client):
r = await client.get("/api/staff-stats",
params={"wechat_id": OWNER, "kind": "bogus"})
assert r.status_code == 422
# ---------------------------------------------------------------------------
# 边缘口径
# ---------------------------------------------------------------------------
async def test_amount_thousands_separator(make_client):
"""金额千分位:``¥1,000.50`` → 1000.5(不能截成 1);feedesc 同口径。"""
async def seed(s: AsyncSession) -> None:
s.add(WechatAccount(wx_id=OWNER, user_id=UID))
s.add(Message(msg_id="m_plain", channel="wechat", owner_account_id=OWNER,
talker_id="c1", timestamp=IN, type="transfer",
text="发送转账:¥1,000.50"))
s.add(Message(msg_id="m_xml", channel="wechat", owner_account_id=OWNER,
talker_id="c1", timestamp=IN + 1, type="red_packet",
text="<wcpayinfo><feedesc>¥2,000.00</feedesc></wcpayinfo>"))
await s.commit()
client = await make_client(seed)
r = await client.get(
"/api/staff-stats",
params={"wechat_id": OWNER, "kind": "red_transfer",
"start": WIN_START, "end": WIN_END},
)
assert r.status_code == 200
by = {row["msg_id"]: row["amount"] for row in r.json()["rows"]}
assert by["m_plain"] == 1000.5
assert by["m_xml"] == 2000.0
async def test_voice_calls_account_without_user_id(make_client):
"""账号存在但 user_id 为 NULL(未同步):voice_calls 计 0、明细空,
而非把 user_id IS NULL 的通话误归属(防跨员工串数)。"""
async def seed(s: AsyncSession) -> None:
s.add(WechatAccount(wx_id=OWNER, user_id=None))
s.add(PhoneCall(call_id="pc_null", channel="voice_call", user_id=None,
start_call_time=IN, call_number="c1"))
await s.commit()
client = await make_client(seed)
r_count = await client.get(
"/api/staff-stats",
params={"wechat_id": OWNER, "start": WIN_START, "end": WIN_END},
)
r_detail = await client.get(
"/api/staff-stats",
params={"wechat_id": OWNER, "kind": "voice_calls",
"start": WIN_START, "end": WIN_END},
)
assert r_count.status_code == r_detail.status_code == 200
assert r_count.json()["voice_calls"] == 0
assert r_detail.json()["rows"] == []
async def test_new_customers_fallback_first_msg_before_window(make_client):
"""add_time NULL 兜底排除项:首条消息在窗前 → 不计(窗后/窗内才计)。"""
async def seed(s: AsyncSession) -> None:
s.add(WechatAccount(wx_id=OWNER, user_id=UID))
s.add(Customer(friend_id="c_early", channel="wechat", owner_account_id=OWNER,
add_time=None))
s.add(Message(msg_id="m_early", channel="wechat", owner_account_id=OWNER,
talker_id="c_early", timestamp=WIN_START - 1))
await s.commit()
client = await make_client(seed)
r_count = await client.get(
"/api/staff-stats",
params={"wechat_id": OWNER, "start": WIN_START, "end": WIN_END},
)
r_detail = await client.get(
"/api/staff-stats",
params={"wechat_id": OWNER, "kind": "new_customers",
"start": WIN_START, "end": WIN_END},
)
assert r_count.status_code == r_detail.status_code == 200
assert r_count.json()["new_customers"] == 0
assert r_detail.json()["rows"] == []