2a74d3888d
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>
138 lines
5.0 KiB
Python
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
|
|
),
|
|
},
|
|
}
|