Files
yunke/yunke-server/tests/cassettes/chatroom_fixture.py
T
浅念 2a74d3888d feat: wechat group+member and talker scrapers
Add scrape_wechat_groups (POST /wechatweb/chatroom/getChatroomList) and
scrape_wechat_talkers (POST /wechatweb/wxFriends/talkerList), both reusing
the page-number paginator. Groups parse the semicolon-joined memberIds into
a member_ids_list so upsert_chat_group can mirror GroupMember rows
(composite PK channel/room_id/member_id) idempotently alongside the
ChatGroup row. Talkers are a sync-planning input (member_count>0 marks a
group conversation); no dedicated table.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-01 13:54:16 +08:00

138 lines
5.0 KiB
Python

"""Fixture payloads for ``POST /wechatweb/chatroom/getChatroomList`` (M2).
These records mirror the real field names captured from the live CRM (see the
data dictionary §2: ``chatRoomId(@chatroom), chatRoomName, memberCount,
memberIds(分号分隔全部成员wxid), headImageUrl, wechatId/wechatNickName(归属),
userName/userPhone(群主员工), messageCount/ownMessageCount, lossMembers/
addMembers, lastChatTime/updateTime``; total=1515).
The envelope is the same flat shape as ``wxUser/list`` (no ``basicResult``
wrapper): pagination fields sit directly under ``data``::
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
"data":{"data":[...], "total":.., "page":.., "pageCount":..}}
The cassette ships three records across two pages so a test can drive the
page-number paginator across the empty-page stop signal:
* page 1 -> 2 of the 3 fixture records, ``total=3``
* page 2 -> the remaining 1 record, ``total=3``
* (page 3 -> empty ``data`` array, served inline by the test)
Records carry every field with a target ``ChatGroup`` column so the test can
assert a full mapping end-to-end. The ``memberIds`` field is the
semicolon-joined member wxid list; record 2 deliberately includes a trailing
empty segment (``"...;"``) so the parser's drop-empties path is exercised.
"""
# The module name mirrors the upstream endpoint name (``chatroom``); the M2
# spec mandates this exact filename.
from __future__ import annotations
from typing import Any
# ---------------------------------------------------------------------------
# Capture-shaped chatroom records (3 total across 2 pages).
# ---------------------------------------------------------------------------
CHATROOM_RECORDS: list[dict[str, Any]] = [
{
"chatRoomId": "12345678901@chatroom",
"chatRoomName": "VIP客户交流群",
"memberCount": 3,
# 3 members, no empties -> member_ids_list length 3.
"memberIds": "wxid_member_a1;wxid_member_a2;csf7169708",
"headImageUrl": "https://wx.qlogo.cn/room_12345678901.png",
"wechatId": "wxid_staff_alice01",
"wechatNickName": "Alice WeChat",
"userId": "staff_user_001",
"userName": "Alice Wang",
"userPhone": "13800000001",
"messageCount": 4521,
"ownMessageCount": 1204,
"lossMembers": 2,
"addMembers": 5,
"lastChatTime": 1700000000123,
"updateTime": 1700000001000,
"version": "v1.20260731.room1",
},
{
"chatRoomId": "98765432109@chatroom",
"chatRoomName": "华东销售大群",
"memberCount": 2,
# Trailing empty segment -> member_ids_list length 2 (empties dropped).
"memberIds": "wxid_member_b1;wxid_member_b2;",
"headImageUrl": "https://wx.qlogo.cn/room_98765432109.png",
"wechatId": "wxid_staff_bob02",
"wechatNickName": "Bob WeChat",
"userId": "staff_user_002",
"userName": "Bob Chen",
"userPhone": "13800000002",
"messageCount": 9821,
"ownMessageCount": 211,
"lossMembers": 0,
"addMembers": 1,
"lastChatTime": 1700000000456,
"updateTime": 1700000000800,
"version": "v1.20260731.room2",
},
{
# A group whose owner has no resolved staff id (userId None) — the
# scraper must still yield it; the repo must still upsert its members.
"chatRoomId": "55555555555@chatroom",
"chatRoomName": "External Coordination",
"memberCount": 1,
"memberIds": "wxid_member_c1",
"headImageUrl": "https://wx.qlogo.cn/room_55555555555.png",
"wechatId": "wxid_staff_orphan04",
"wechatNickName": "Orphan WeChat",
"userId": None,
"userName": None,
"userPhone": None,
"messageCount": 12,
"ownMessageCount": 0,
"lossMembers": 0,
"addMembers": 0,
"lastChatTime": 1700000000789,
"updateTime": 1700000000900,
"version": "v1.20260731.room3",
},
]
# Default page size for the fixture (and the scraper). total=3 means the
# fixture fills exactly 2 pages of size 2 plus a terminating empty page.
TOTAL = len(CHATROOM_RECORDS)
DEFAULT_PAGE_SIZE = 2
def make_chatroom_list_response(
records: list[dict[str, Any]],
*,
total: int,
page_num: int = 1,
page_size: int = DEFAULT_PAGE_SIZE,
trace_id: str | None = None,
) -> dict[str, Any]:
"""Build a capture-shaped ``getChatroomList`` response envelope.
Shape (no ``basicResult`` wrapper; pagination fields directly under
``data``)::
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
"data":{"data":[...], "total":.., "page":.., "pageCount":..}}
"""
return {
"message": "调用成功",
"code": "10000",
"success": True,
"traceId": trace_id or f"trace-{page_num}",
"data": {
"data": list(records),
"total": total,
"page": page_num,
"pageCount": (
(total + page_size - 1) // page_size if page_size else 1
),
},
}