149 lines
5.0 KiB
Python
149 lines
5.0 KiB
Python
|
|
"""Fixture payloads for ``POST /qqweb/WhatsAppUser/businessSalesList`` (M3).
|
||
|
|
|
||
|
|
These records mirror the real field names captured from the live CRM (see the
|
||
|
|
data dictionary §3 and phase0-findings.md §5):
|
||
|
|
|
||
|
|
``uid`` (PK, ``<phone>@s.whatsapp.net``), ``userId`` / ``userName`` (owning
|
||
|
|
staff real name), ``nickName``, ``phone`` / ``salePhone`` / ``salesBindPhone``,
|
||
|
|
``contactsNum`` / ``groupNum`` / ``talkerCount`` / ``friendAddNum``,
|
||
|
|
``isOnJob``, ``lastUpdate``.
|
||
|
|
|
||
|
|
The envelope differs from the WeChat list endpoints: rows sit under
|
||
|
|
``data.overviews`` (NOT ``data.data``) and the declared row count is
|
||
|
|
``data.totalCount`` (NOT ``data.total``); the page count is ``data.pageCount``::
|
||
|
|
|
||
|
|
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
|
||
|
|
"data":{"page":.., "pageCount":.., "totalCount":.., "overviews":[...]}}
|
||
|
|
|
||
|
|
The cassette ships two pages so a test can drive the page-number paginator
|
||
|
|
across the empty-page stop signal:
|
||
|
|
|
||
|
|
* page 1 -> 2 of the 4 fixture records, ``totalCount=4``
|
||
|
|
* page 2 -> the other 2 records, ``totalCount=4``
|
||
|
|
* (page 3 -> empty ``overviews`` array, served inline by the test)
|
||
|
|
|
||
|
|
Records carry every field with a target ``WhatsappAccount`` column so the test
|
||
|
|
can assert a full mapping end-to-end, plus the embedded ``userId`` + ``userName``
|
||
|
|
staff pair (the owning employee) so the embedded-staff repo upsert can be
|
||
|
|
exercised against capture-shaped input (including a no-``userId`` orphan to
|
||
|
|
exercise the best-effort skip path).
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
# Capture-shaped WhatsApp staff-account records (4 total across 2 pages).
|
||
|
|
# ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
WA_ACCOUNT_RECORDS: list[dict[str, Any]] = [
|
||
|
|
{
|
||
|
|
"uid": "8613800000001@s.whatsapp.net",
|
||
|
|
"userId": "wa_staff_001",
|
||
|
|
"userName": "Alice Wang",
|
||
|
|
"nickName": "Alice WA",
|
||
|
|
"phone": "8613800000001",
|
||
|
|
"salePhone": "8613800000001",
|
||
|
|
"salesBindPhone": "8613900000001",
|
||
|
|
"contactsNum": 421,
|
||
|
|
"groupNum": 12,
|
||
|
|
"talkerCount": 38,
|
||
|
|
"friendAddNum": 7,
|
||
|
|
"isOnJob": 1,
|
||
|
|
"lastUpdate": 1700000000123,
|
||
|
|
"version": "v1.20260731.alice.wa",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"uid": "8613800000002@s.whatsapp.net",
|
||
|
|
"userId": "wa_staff_002",
|
||
|
|
"userName": "Bob Chen",
|
||
|
|
"nickName": "Bob WA",
|
||
|
|
"phone": "8613800000002",
|
||
|
|
"salePhone": "8613800000002",
|
||
|
|
"salesBindPhone": "8613900000002",
|
||
|
|
"contactsNum": 58,
|
||
|
|
"groupNum": 2,
|
||
|
|
"talkerCount": 9,
|
||
|
|
"friendAddNum": 1,
|
||
|
|
"isOnJob": 1,
|
||
|
|
"lastUpdate": 1700000000456,
|
||
|
|
"version": "v1.20260731.bob.wa",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"uid": "8613800000003@s.whatsapp.net",
|
||
|
|
"userId": "wa_staff_003",
|
||
|
|
"userName": "Carol Liu",
|
||
|
|
"nickName": "Carol WA",
|
||
|
|
"phone": "8613800000003",
|
||
|
|
"salePhone": "8613800000003",
|
||
|
|
"salesBindPhone": "8613900000003",
|
||
|
|
"contactsNum": 1899,
|
||
|
|
"groupNum": 64,
|
||
|
|
"talkerCount": 210,
|
||
|
|
"friendAddNum": 33,
|
||
|
|
"isOnJob": 0, # departed staff — exercises the falsy isOnJob path
|
||
|
|
"lastUpdate": 1700000000789,
|
||
|
|
"version": "v1.20260731.carol.wa",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
# Account with NO embedded staff (userId missing) — the scraper must
|
||
|
|
# still yield it; the embedded-staff repo upsert must skip it cleanly.
|
||
|
|
"uid": "8613800000004@s.whatsapp.net",
|
||
|
|
"userId": None,
|
||
|
|
"userName": None,
|
||
|
|
"nickName": "Orphan WA",
|
||
|
|
"phone": "8613800000004",
|
||
|
|
"salePhone": "8613800000004",
|
||
|
|
"salesBindPhone": "8613900000004",
|
||
|
|
"contactsNum": 3,
|
||
|
|
"groupNum": 0,
|
||
|
|
"talkerCount": 1,
|
||
|
|
"friendAddNum": 0,
|
||
|
|
"isOnJob": 0,
|
||
|
|
"lastUpdate": 1700000001000,
|
||
|
|
"version": "v1.20260731.orphan.wa",
|
||
|
|
},
|
||
|
|
]
|
||
|
|
|
||
|
|
# Default page size for the fixture (and the scraper). totalCount=4 means the
|
||
|
|
# fixture fills exactly 2 pages of size 2 plus a terminating empty page.
|
||
|
|
TOTAL = len(WA_ACCOUNT_RECORDS)
|
||
|
|
DEFAULT_PAGE_SIZE = 2
|
||
|
|
|
||
|
|
|
||
|
|
def make_wa_accounts_response(
|
||
|
|
records: list[dict[str, Any]],
|
||
|
|
*,
|
||
|
|
total_count: int,
|
||
|
|
page: int = 1,
|
||
|
|
page_size: int = DEFAULT_PAGE_SIZE,
|
||
|
|
page_count: int | None = None,
|
||
|
|
trace_id: str | None = None,
|
||
|
|
) -> dict[str, Any]:
|
||
|
|
"""Build a capture-shaped ``businessSalesList`` response envelope.
|
||
|
|
|
||
|
|
Shape::
|
||
|
|
|
||
|
|
{"message":"调用成功","code":"10000","success":true,"traceId":"...",
|
||
|
|
"data":{"page":.., "pageCount":.., "totalCount":.., "overviews":[...]}}
|
||
|
|
|
||
|
|
``page_count`` defaults to ``ceil(total_count / page_size)`` when omitted.
|
||
|
|
"""
|
||
|
|
if page_count is None:
|
||
|
|
import math
|
||
|
|
|
||
|
|
page_count = math.ceil(total_count / page_size) if page_size else 1
|
||
|
|
return {
|
||
|
|
"message": "调用成功",
|
||
|
|
"code": "10000",
|
||
|
|
"success": True,
|
||
|
|
"traceId": trace_id or f"trace-wa-acct-{page}",
|
||
|
|
"data": {
|
||
|
|
"page": page,
|
||
|
|
"pageCount": page_count,
|
||
|
|
"totalCount": total_count,
|
||
|
|
"overviews": list(records),
|
||
|
|
},
|
||
|
|
}
|