"""PDD 页面分类与商品深链状态机测试。""" from pathlib import Path import unittest import xml.etree.ElementTree as ET from src.pdd_page_classifier import ( ACTION_NETWORK_ERROR, ACTION_OUT_OF_STOCK, ACTION_READY, ACTION_REFRESH, ACTION_REOPEN, ACTION_UNAVAILABLE, ACTION_WAIT, PAGE_CAPTCHA, PAGE_EXTERNAL, PAGE_GOODS, PAGE_HOME, PAGE_LOADING, PAGE_LOGIN_REQUIRED, PAGE_NETWORK_ERROR, PAGE_OUT_OF_STOCK, PAGE_RISK_CONTROL, GoodsOpenTracker, classify_pdd_page, ) FIXTURES = Path(__file__).parent / "fixtures" PDD = "com.xunmeng.pinduoduo" def observation(text: str, package: str = PDD): xml = ( '' '' ).format(package, text) return classify_pdd_page(ET.fromstring(xml), package) class PddPageClassifierTest(unittest.TestCase): def test_real_home_fixture_ignores_incorrect_focused_package(self) -> None: root = ET.parse(FIXTURES / "pdd_home_page.xml").getroot() result = classify_pdd_page(root, "com.android.settings") self.assertEqual(PAGE_HOME, result.kind) self.assertTrue(result.pdd_hierarchy) def test_special_pages_are_not_home(self) -> None: cases = { "加载中": PAGE_LOADING, "网络不给力": PAGE_NETWORK_ERROR, "手机号登录": PAGE_LOGIN_REQUIRED, "请完成验证": PAGE_CAPTCHA, "操作频繁": PAGE_RISK_CONTROL, "立即购买": PAGE_GOODS, "商品已售罄,推荐以下相似商品": PAGE_OUT_OF_STOCK, } for label, expected in cases.items(): with self.subTest(label=label): self.assertEqual(expected, observation(label).kind) def test_browser_word_does_not_count_as_goods(self) -> None: result = observation("立即购买", "com.android.chrome") self.assertEqual(PAGE_EXTERNAL, result.kind) def test_transient_home_can_reach_goods_without_reopen(self) -> None: home = classify_pdd_page( ET.parse(FIXTURES / "pdd_home_page.xml").getroot(), PDD ) tracker = GoodsOpenTracker(None, 0.0) self.assertEqual(ACTION_WAIT, tracker.observe(home, 2.0).action) self.assertEqual(ACTION_WAIT, tracker.observe(home, 2.1).action) self.assertEqual( ACTION_READY, tracker.observe(observation("立即购买"), 2.2).action ) def test_stable_home_reopens_once_then_marks_unavailable(self) -> None: home = classify_pdd_page( ET.parse(FIXTURES / "pdd_home_page.xml").getroot(), PDD ) tracker = GoodsOpenTracker(None, 0.0) for at in (2.0, 2.1): self.assertEqual(ACTION_WAIT, tracker.observe(home, at).action) self.assertEqual(ACTION_REOPEN, tracker.observe(home, 2.2).action) tracker.reopened(3.0) for at in (5.0, 5.1): self.assertEqual(ACTION_WAIT, tracker.observe(home, at).action) self.assertEqual(ACTION_UNAVAILABLE, tracker.observe(home, 5.2).action) def test_old_goods_tree_is_not_accepted_as_new_target(self) -> None: old = observation("旧商品标题 立即购买") tracker = GoodsOpenTracker(old, 0.0) self.assertEqual(ACTION_WAIT, tracker.observe(old, 0.2).action) self.assertTrue(tracker.stale_goods_seen) new = observation("目标商品标题 立即购买") self.assertEqual(ACTION_READY, tracker.observe(new, 0.3).action) def test_network_error_has_own_decision(self) -> None: tracker = GoodsOpenTracker(None, 0.0) decision = tracker.observe(observation("网络不给力"), 3.0) self.assertEqual(ACTION_NETWORK_ERROR, decision.action) def test_sold_out_page_refreshes_once_then_can_recover(self) -> None: tracker = GoodsOpenTracker(None, 0.0) sold_out = observation("商品已售罄,推荐以下相似商品") self.assertEqual(ACTION_REFRESH, tracker.observe(sold_out, 0.2).action) tracker.refreshed_out_of_stock(0.3) self.assertTrue(tracker.out_of_stock_refreshed) self.assertEqual( ACTION_READY, tracker.observe(observation("目标商品 立即购买"), 0.8).action, ) def test_sold_out_after_refresh_is_unavailable_without_second_refresh(self) -> None: tracker = GoodsOpenTracker(None, 0.0) sold_out = observation("该商品已售罄") self.assertEqual(ACTION_REFRESH, tracker.observe(sold_out, 0.2).action) tracker.refreshed_out_of_stock(0.3) self.assertEqual( ACTION_OUT_OF_STOCK, tracker.observe(sold_out, 0.8).action, ) with self.assertRaises(RuntimeError): tracker.refreshed_out_of_stock(0.9) def test_sku_sold_out_text_is_still_a_normal_goods_page(self) -> None: result = observation("立即购买 尺码 L(售罄)") self.assertEqual(PAGE_GOODS, result.kind) if __name__ == "__main__": unittest.main()