fix: 合并只读核单多视口证据 (#122)
This commit is contained in:
@@ -1,9 +1,20 @@
|
||||
"""只读订单核对 XML 解析测试;不连接真实手机。"""
|
||||
|
||||
import unittest
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
from src.pdd_purchase_reconcile_adapter import PurchaseReconcileQuery
|
||||
from src.pdd_u2_purchase_reconcile_adapter import parse_order_candidates
|
||||
from src.pdd_purchase_reconcile_adapter import (
|
||||
PurchaseOrderCandidate,
|
||||
PurchaseReconcileQuery,
|
||||
)
|
||||
from src.pdd_u2_purchase_reconcile_adapter import (
|
||||
U2PddPurchaseReconcileAdapter,
|
||||
_is_single_order_detail,
|
||||
_merge_order_evidence,
|
||||
_parse_xml,
|
||||
parse_order_candidates,
|
||||
)
|
||||
from src.purchase_reconcile_service import PurchaseReconcileService
|
||||
|
||||
|
||||
def query() -> PurchaseReconcileQuery:
|
||||
@@ -28,6 +39,63 @@ def xml_card(text: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def xml_detail(text: str) -> str:
|
||||
package = "com.xunmeng.pinduoduo"
|
||||
return (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>'
|
||||
'<hierarchy rotation="0">'
|
||||
f'<node package="{package}" text="{text}" />'
|
||||
f'<node package="{package}" text="去支付" />'
|
||||
f'<node package="{package}" text="取消订单" />'
|
||||
"</hierarchy>"
|
||||
)
|
||||
|
||||
|
||||
class FakeSession(AbstractContextManager):
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
|
||||
def __enter__(self):
|
||||
return self.device
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
return None
|
||||
|
||||
|
||||
class FakeDeviceService:
|
||||
def __init__(self, device):
|
||||
self.device = device
|
||||
|
||||
def connect(self, device_address):
|
||||
return FakeSession(self.device)
|
||||
|
||||
|
||||
class DetailDevice:
|
||||
def __init__(self, pages):
|
||||
self.pages = pages
|
||||
self.page_index = 0
|
||||
self.app_current_calls = 0
|
||||
self.app_start_calls = 0
|
||||
self.swipes = 0
|
||||
|
||||
def dump_hierarchy(self, compressed=False):
|
||||
return self.pages[self.page_index]
|
||||
|
||||
def app_current(self):
|
||||
self.app_current_calls += 1
|
||||
return {"package": "com.android.settings"}
|
||||
|
||||
def app_start(self, package):
|
||||
self.app_start_calls += 1
|
||||
|
||||
def window_size(self):
|
||||
return 1080, 2400
|
||||
|
||||
def swipe(self, *args):
|
||||
self.swipes += 1
|
||||
self.page_index = min(self.page_index + 1, len(self.pages) - 1)
|
||||
|
||||
|
||||
class PddU2PurchaseReconcileAdapterTest(unittest.TestCase):
|
||||
def test_parser_extracts_only_required_unpaid_order_fields(self):
|
||||
candidates = parse_order_candidates(
|
||||
@@ -70,6 +138,70 @@ class PddU2PurchaseReconcileAdapterTest(unittest.TestCase):
|
||||
self.assertEqual(candidates[0].goods_id, "")
|
||||
self.assertEqual(candidates[0].ordered_at, "")
|
||||
|
||||
def test_adapter_merges_one_order_detail_across_viewports(self):
|
||||
device = DetailDevice(
|
||||
[
|
||||
xml_detail(
|
||||
"待付款 商品编号:737116531267 黑色 L "
|
||||
"共2件 合计 ¥84.00"
|
||||
),
|
||||
xml_detail(
|
||||
"订单编号:ORDER-20260810 "
|
||||
"下单时间:2026-08-10 16:03:00"
|
||||
),
|
||||
]
|
||||
)
|
||||
adapter = U2PddPurchaseReconcileAdapter(
|
||||
"serial",
|
||||
device_service=FakeDeviceService(device),
|
||||
settle_seconds=0,
|
||||
max_pages=3,
|
||||
)
|
||||
|
||||
scan = adapter.read_order_candidates(query())
|
||||
adapter.close()
|
||||
|
||||
self.assertEqual(len(scan.candidates), 1)
|
||||
candidate = scan.candidates[0]
|
||||
self.assertEqual(candidate.order_no, "ORDER-20260810")
|
||||
self.assertEqual(candidate.goods_id, "737116531267")
|
||||
self.assertEqual(candidate.options, {"color": "黑色", "size": "L"})
|
||||
self.assertEqual(candidate.quantity, 2)
|
||||
self.assertEqual(candidate.total_price_cent, 8400)
|
||||
self.assertEqual(candidate.ordered_at, "2026-08-10T08:03:00Z")
|
||||
self.assertEqual(candidate.payment_status, "unpaid")
|
||||
self.assertTrue(PurchaseReconcileService._candidate_matches(candidate, query()))
|
||||
self.assertEqual(device.app_current_calls, 0)
|
||||
self.assertEqual(device.app_start_calls, 0)
|
||||
|
||||
def test_conflicting_order_numbers_are_not_merged(self):
|
||||
first = PurchaseOrderCandidate(
|
||||
order_no="ORDER-A",
|
||||
goods_id="737116531267",
|
||||
payment_status="unpaid",
|
||||
)
|
||||
second = PurchaseOrderCandidate(
|
||||
order_no="ORDER-B",
|
||||
quantity=2,
|
||||
total_price_cent=8400,
|
||||
payment_status="unpaid",
|
||||
)
|
||||
|
||||
self.assertIsNone(_merge_order_evidence(first, second))
|
||||
|
||||
def test_multiple_order_actions_disable_detail_evidence_merge(self):
|
||||
root = _parse_xml(
|
||||
'<hierarchy><node package="com.xunmeng.pinduoduo" '
|
||||
'text="订单编号:ORDER-A" />'
|
||||
'<node package="com.xunmeng.pinduoduo" text="去支付" />'
|
||||
'<node package="com.xunmeng.pinduoduo" '
|
||||
'text="订单编号:ORDER-B" />'
|
||||
'<node package="com.xunmeng.pinduoduo" text="去支付" />'
|
||||
"</hierarchy>"
|
||||
)
|
||||
|
||||
self.assertFalse(_is_single_order_detail(root))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user