fix: 等待微信返回后再只读核单 (#124)
This commit is contained in:
@@ -8,6 +8,7 @@ from src.pdd_purchase_reconcile_adapter import (
|
||||
PurchaseReconcileQuery,
|
||||
)
|
||||
from src.pdd_u2_purchase_reconcile_adapter import (
|
||||
PddPurchaseReconcileError,
|
||||
U2PddPurchaseReconcileAdapter,
|
||||
_is_single_order_detail,
|
||||
_merge_order_evidence,
|
||||
@@ -96,6 +97,51 @@ class DetailDevice:
|
||||
self.page_index = min(self.page_index + 1, len(self.pages) - 1)
|
||||
|
||||
|
||||
class FakeClock:
|
||||
def __init__(self):
|
||||
self.now = 0.0
|
||||
|
||||
def monotonic(self):
|
||||
return self.now
|
||||
|
||||
def sleep(self, seconds):
|
||||
self.now += seconds
|
||||
|
||||
|
||||
def xml_package_page(package: str, text: str = "") -> str:
|
||||
return (
|
||||
'<hierarchy>'
|
||||
f'<node package="{package}" text="{text}" />'
|
||||
f'<node package="{package}" text="" />'
|
||||
f'<node package="{package}" text="" />'
|
||||
"</hierarchy>"
|
||||
)
|
||||
|
||||
|
||||
class TransitionDevice(DetailDevice):
|
||||
def __init__(self, pages, current_package="com.tencent.mm"):
|
||||
super().__init__(pages)
|
||||
self.current_package = current_package
|
||||
self.dump_calls = 0
|
||||
self.back_calls = 0
|
||||
|
||||
def dump_hierarchy(self, compressed=False):
|
||||
index = min(self.dump_calls, len(self.pages) - 1)
|
||||
self.dump_calls += 1
|
||||
return self.pages[index]
|
||||
|
||||
def app_current(self):
|
||||
self.app_current_calls += 1
|
||||
return {"package": self.current_package}
|
||||
|
||||
def press(self, key):
|
||||
if key == "back":
|
||||
self.back_calls += 1
|
||||
|
||||
def swipe(self, *args):
|
||||
self.swipes += 1
|
||||
|
||||
|
||||
class PddU2PurchaseReconcileAdapterTest(unittest.TestCase):
|
||||
def test_parser_extracts_only_required_unpaid_order_fields(self):
|
||||
candidates = parse_order_candidates(
|
||||
@@ -202,6 +248,132 @@ class PddU2PurchaseReconcileAdapterTest(unittest.TestCase):
|
||||
|
||||
self.assertFalse(_is_single_order_detail(root))
|
||||
|
||||
def test_waits_for_pdd_detail_after_external_transition(self):
|
||||
complete_detail = xml_detail(
|
||||
"待付款 订单编号:ORDER-20260810 "
|
||||
"商品编号:737116531267 黑色 L 共2件 合计 ¥84.00 "
|
||||
"下单时间:2026-08-10 16:03:00"
|
||||
)
|
||||
device = TransitionDevice(
|
||||
[
|
||||
xml_package_page("com.tencent.mm", "微信登录"),
|
||||
xml_package_page("com.xunmeng.pinduoduo", "页面加载中"),
|
||||
complete_detail,
|
||||
]
|
||||
)
|
||||
clock = FakeClock()
|
||||
adapter = U2PddPurchaseReconcileAdapter(
|
||||
"serial",
|
||||
device_service=FakeDeviceService(device),
|
||||
settle_seconds=1,
|
||||
transition_timeout_seconds=30,
|
||||
max_pages=2,
|
||||
monotonic=clock.monotonic,
|
||||
sleep=clock.sleep,
|
||||
)
|
||||
|
||||
scan = adapter.read_order_candidates(query())
|
||||
adapter.close()
|
||||
|
||||
self.assertEqual(len(scan.candidates), 1)
|
||||
self.assertEqual(device.app_start_calls, 1)
|
||||
self.assertEqual(device.app_current_calls, 0)
|
||||
self.assertLess(clock.now, 30)
|
||||
|
||||
def test_payment_back_is_pressed_at_most_once_while_page_changes(self):
|
||||
payment_page = xml_package_page(
|
||||
"com.xunmeng.pinduoduo", "立即支付"
|
||||
)
|
||||
device = TransitionDevice(
|
||||
[payment_page, payment_page, xml_detail("待付款")],
|
||||
current_package="com.xunmeng.pinduoduo",
|
||||
)
|
||||
clock = FakeClock()
|
||||
adapter = U2PddPurchaseReconcileAdapter(
|
||||
"serial",
|
||||
device_service=FakeDeviceService(device),
|
||||
settle_seconds=1,
|
||||
transition_timeout_seconds=30,
|
||||
monotonic=clock.monotonic,
|
||||
sleep=clock.sleep,
|
||||
)
|
||||
|
||||
adapter._open_unpaid_orders(device)
|
||||
|
||||
self.assertEqual(device.back_calls, 1)
|
||||
|
||||
def test_transition_timeout_reports_last_waiting_reason(self):
|
||||
device = TransitionDevice(
|
||||
[xml_package_page("com.xunmeng.pinduoduo", "页面加载中")],
|
||||
current_package="com.xunmeng.pinduoduo",
|
||||
)
|
||||
clock = FakeClock()
|
||||
adapter = U2PddPurchaseReconcileAdapter(
|
||||
"serial",
|
||||
device_service=FakeDeviceService(device),
|
||||
settle_seconds=1,
|
||||
transition_timeout_seconds=2,
|
||||
monotonic=clock.monotonic,
|
||||
sleep=clock.sleep,
|
||||
)
|
||||
|
||||
with self.assertRaises(PddPurchaseReconcileError) as captured:
|
||||
adapter._open_unpaid_orders(device)
|
||||
|
||||
self.assertEqual(captured.exception.code, "RECONCILE_ORDER_PAGE_TIMEOUT")
|
||||
self.assertIn("页面仍在过渡", str(captured.exception))
|
||||
|
||||
def test_transition_wait_can_be_cancelled(self):
|
||||
device = TransitionDevice(
|
||||
[xml_package_page("com.xunmeng.pinduoduo", "页面加载中")],
|
||||
current_package="com.xunmeng.pinduoduo",
|
||||
)
|
||||
clock = FakeClock()
|
||||
checks = iter((False, False, True))
|
||||
adapter = U2PddPurchaseReconcileAdapter(
|
||||
"serial",
|
||||
device_service=FakeDeviceService(device),
|
||||
cancelled=lambda: next(checks, True),
|
||||
settle_seconds=1,
|
||||
transition_timeout_seconds=30,
|
||||
monotonic=clock.monotonic,
|
||||
sleep=clock.sleep,
|
||||
)
|
||||
|
||||
with self.assertRaises(PddPurchaseReconcileError) as captured:
|
||||
adapter.read_order_candidates(query())
|
||||
|
||||
self.assertEqual(captured.exception.code, "RECONCILE_CANCELLED")
|
||||
|
||||
def test_security_pages_still_stop_transition_wait(self):
|
||||
cases = (
|
||||
("请完成验证", "RECONCILE_CAPTCHA"),
|
||||
("手机号登录", "RECONCILE_LOGIN_REQUIRED"),
|
||||
("操作频繁", "RECONCILE_RISK_CONTROL"),
|
||||
)
|
||||
for marker, error_code in cases:
|
||||
with self.subTest(marker=marker):
|
||||
device = TransitionDevice(
|
||||
[xml_package_page("com.xunmeng.pinduoduo", marker)],
|
||||
current_package="com.xunmeng.pinduoduo",
|
||||
)
|
||||
clock = FakeClock()
|
||||
adapter = U2PddPurchaseReconcileAdapter(
|
||||
"serial",
|
||||
device_service=FakeDeviceService(device),
|
||||
settle_seconds=1,
|
||||
transition_timeout_seconds=30,
|
||||
monotonic=clock.monotonic,
|
||||
sleep=clock.sleep,
|
||||
)
|
||||
|
||||
with self.assertRaises(PddPurchaseReconcileError) as captured:
|
||||
adapter._open_unpaid_orders(device)
|
||||
|
||||
self.assertEqual(captured.exception.code, error_code)
|
||||
self.assertEqual(device.app_start_calls, 0)
|
||||
self.assertEqual(device.back_calls, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user