"""Fixture payloads for ``POST /wechatweb/callRecord/queryWeChatVoiceList`` (M2). These records mirror the real field names confirmed by the Phase-0 capture (see phase0-findings.md §4 语音通话): ``id, userId/userName/userPhone/ userWeChatNickName/userWeChatHeadImg`` (staff), ``talker/talkerNickName/ talkerAlias/talkerHeadImg`` (customer), ``callType, isSend(0呼入/1呼出), callStatus(1接通有录音/2无应答), ossFileName(录音URL, may be null), ossFileExist, duration(秒,字符串)/durationFile(秒,int), startTime/endTime, weChatId, remark, isRoom, callFileId``. Live ``total`` is 333. The envelope is the same flat shape as ``wxUser/list`` (no ``basicResult`` wrapper): the row list + pagination fields sit directly under ``data``:: {"message":"调用成功","code":"10000","success":true,"traceId":"...", "data":{"data":[...], "total":.., "pageNum":.., "pageSize":..}} The cassette ships four records across 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, ``total=4`` * page 2 -> the other 2 records, ``total=4`` * (page 3 -> empty ``data`` array, served inline by the test) Records deliberately exercise the documented mapping edges: * ``vc_1001`` — incoming connected call WITH a recording (``isSend=0`` -> ``incoming_call=1``; ``callStatus=1``; non-null ``ossFileName``; ``duration`` as a string). * ``vc_1002`` — outgoing no-answer call with NO recording (``isSend=1`` -> ``incoming_call=0``; ``callStatus=2``; ``ossFileName=null``). * ``vc_1003`` — outgoing connected call with a recording. * ``vc_1004`` — incoming connected call inside a chatroom (``isRoom=1`` and ``talker`` is a ``@chatroom`` id), exercising the room-call path. """ # The module name mirrors the upstream endpoint name (``callRecord/ # queryWeChatVoiceList`` → ``voiceCall``); the M2 spec uses this naming family. # ruff: noqa: N999 from __future__ import annotations from typing import Any # --------------------------------------------------------------------------- # Capture-shaped voice-call records (4 total across 2 pages). # --------------------------------------------------------------------------- VOICE_RECORDS: list[dict[str, Any]] = [ { # Incoming connected call with a recording. "id": "vc_1001", "userId": "staff_user_001", "userName": "Alice Wang", "userPhone": "13800000001", "userWeChatNickName": "Alice WeChat", "userWeChatHeadImg": "https://wx.qlogo.cn/alice.png", "talker": "wxid_friend_0001", "talkerNickName": "VIP Customer A", "talkerAlias": "vip_a", "talkerHeadImg": "https://wx.qlogo.cn/vip_a.png", "callType": 1, "isSend": 0, # 0=incoming -> incoming_call=1 "callStatus": 1, # 1=connected w/ recording "ossFileName": ( "https://yunke-pcfile.oss-cn-beijing.aliyuncs.com/wechat-call/" "8m0atd/imei001/ossCallback/1700000000000_1700000151000.mp3" ), "ossFileExist": 1, "duration": "151", # string seconds -> call_duration int 151 "durationFile": 151, "startTime": "2026-01-15 10:30:00", # parsed to ms-epoch UTC "endTime": "2026-01-15 10:32:31", "weChatId": "wxid_staff_alice01", "remark": "通话时长02:31", "isRoom": 0, "callFileId": "cf_1001", "version": "v1.20260731.vc1001", }, { # Outgoing no-answer call, no recording. "id": "vc_1002", "userId": "staff_user_001", "userName": "Alice Wang", "userPhone": "13800000001", "userWeChatNickName": "Alice WeChat", "userWeChatHeadImg": "https://wx.qlogo.cn/alice.png", "talker": "wxid_friend_0002", "talkerNickName": "Lead B", "talkerAlias": "", "talkerHeadImg": "https://wx.qlogo.cn/lead_b.png", "callType": 1, "isSend": 1, # 1=outgoing -> incoming_call=0 "callStatus": 2, # 2=no answer "ossFileName": None, # no recording -> record_file None "ossFileExist": 0, "duration": "0", "durationFile": 0, "startTime": "2026-01-15 11:00:00", "endTime": "2026-01-15 11:00:00", "weChatId": "wxid_staff_alice01", "remark": "对方无应答", "isRoom": 0, "callFileId": "cf_1002", "version": "v1.20260731.vc1002", }, { # Outgoing connected call with a recording. "id": "vc_1003", "userId": "staff_user_002", "userName": "Bob Chen", "userPhone": "13800000002", "userWeChatNickName": "Bob WeChat", "userWeChatHeadImg": "https://wx.qlogo.cn/bob.png", "talker": "wxid_friend_0003", "talkerNickName": "Customer C", "talkerAlias": "c_alias", "talkerHeadImg": "https://wx.qlogo.cn/customer_c.png", "callType": 1, "isSend": 1, # outgoing -> incoming_call=0 "callStatus": 1, # connected w/ recording "ossFileName": ( "https://yunke-pcfile.oss-cn-beijing.aliyuncs.com/wechat-call/" "8m0atd/imei002/ossCallback/1700000003000_1700000045000.mp3" ), "ossFileExist": 1, "duration": "45", "durationFile": 45, "startTime": "2026-01-15 12:00:00", "endTime": "2026-01-15 12:00:45", "weChatId": "wxid_staff_bob02", "remark": "通话时长00:45", "isRoom": 0, "callFileId": "cf_1003", "version": "v1.20260731.vc1003", }, { # Incoming connected call inside a chatroom (a group voice call). "id": "vc_1004", "userId": "staff_user_001", "userName": "Alice Wang", "userPhone": "13800000001", "userWeChatNickName": "Alice WeChat", "userWeChatHeadImg": "https://wx.qlogo.cn/alice.png", # For a room call the "talker" is the chatroom id. "talker": "12345678901@chatroom", "talkerNickName": "VIP客户交流群", "talkerAlias": "", "talkerHeadImg": "https://wx.qlogo.cn/room_12345678901.png", "callType": 2, "isSend": 0, # incoming -> incoming_call=1 "callStatus": 1, # connected w/ recording "ossFileName": ( "https://yunke-pcfile.oss-cn-beijing.aliyuncs.com/wechat-call/" "8m0atd/imei001/ossCallback/1700000005000_1700000212000.mp3" ), "ossFileExist": 1, "duration": "207", "durationFile": 207, "startTime": "2026-01-16 09:00:00", "endTime": "2026-01-16 09:03:27", "weChatId": "wxid_staff_alice01", "remark": "群通话03:27", "isRoom": 1, # this is a group voice call "callFileId": "cf_1004", "version": "v1.20260731.vc1004", }, ] # Default page size for the fixture (and the scraper). total=4 means the # fixture fills exactly 2 pages of size 2 plus a terminating empty page. TOTAL = len(VOICE_RECORDS) DEFAULT_PAGE_SIZE = 2 def make_voice_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 ``queryWeChatVoiceList`` response envelope. Shape (no ``basicResult`` wrapper; pagination fields directly under ``data``):: {"message":"调用成功","code":"10000","success":true,"traceId":"...", "data":{"data":[...], "total":.., "pageNum":.., "pageSize":..}} """ return { "message": "调用成功", "code": "10000", "success": True, "traceId": trace_id or f"trace-{page_num}", "data": { "data": list(records), "total": total, "pageNum": page_num, "pageSize": page_size, }, }