feat: 执行并提交 PDD 采集任务 (#32)
This commit is contained in:
@@ -62,6 +62,18 @@ class LoadingDevice(FakeCollectDevice):
|
||||
return '<hierarchy><node text="正在加载" /></hierarchy>'
|
||||
|
||||
|
||||
class DisconnectingDevice(FakeCollectDevice):
|
||||
def __init__(self, home_xml, spec_xml):
|
||||
super().__init__(home_xml, spec_xml)
|
||||
self.current_calls = 0
|
||||
|
||||
def app_current(self):
|
||||
self.current_calls += 1
|
||||
if self.current_calls > 1:
|
||||
raise RuntimeError("device not found")
|
||||
return super().app_current()
|
||||
|
||||
|
||||
def keep_only_one_sku(xml_data: str) -> str:
|
||||
"""从脱敏固件中删除蓝色和 L,只保留一个组合。"""
|
||||
|
||||
@@ -114,6 +126,19 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
["M", "L"],
|
||||
)
|
||||
|
||||
def test_truncated_spec_name_prefers_full_content_description(self):
|
||||
xml_data = self.spec_xml.replace(
|
||||
'content-desc="红色" clickable="true"',
|
||||
'text="红…" content-desc="红色" clickable="true"',
|
||||
)
|
||||
result = parse_spec_panel(xml_data)
|
||||
self.assertEqual(result.dimensions[0].values[0].text, "红色")
|
||||
|
||||
without_description = xml_data.replace(' content-desc="红色"', "")
|
||||
with self.assertRaises(PddCollectError) as raised:
|
||||
parse_spec_panel(without_description)
|
||||
self.assertEqual(raised.exception.code, "PDD_DATA_SKU_NAME_TRUNCATED")
|
||||
|
||||
def test_login_and_invalid_xml_have_different_error_codes(self):
|
||||
with self.assertRaises(PddCollectError) as login:
|
||||
parse_goods_page('<hierarchy><node text="手机号登录" /></hierarchy>')
|
||||
@@ -150,14 +175,46 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
data = result.to_pdd_data()
|
||||
|
||||
self.assertEqual(data["schema_version"], 1)
|
||||
self.assertEqual(data["goods"]["goods_id"], "123")
|
||||
self.assertEqual(data["goods_id"], "123")
|
||||
self.assertEqual(data["title"], "测试纯棉短袖商品")
|
||||
self.assertEqual(data["shop_name"], "测试服饰旗舰店")
|
||||
self.assertEqual(data["price_granularity"], "color")
|
||||
self.assertEqual(data["metrics"]["sales"]["value"], 12000)
|
||||
self.assertEqual(data["dimensions"][0]["key"], "color")
|
||||
self.assertEqual(data["skus"][0]["options"], {"color": "红色", "size": "M"})
|
||||
self.assertEqual(data["skus"][0]["price_cent"], 1000)
|
||||
self.assertEqual(
|
||||
data["skus"][0]["price_observed_at"],
|
||||
{"color": "红色", "size": "M"},
|
||||
)
|
||||
self.assertEqual(data["source"]["device_address"], "USB-001")
|
||||
self.assertIsNone(data["purchase"])
|
||||
|
||||
def test_price_is_sampled_once_per_available_color(self):
|
||||
device = FakeCollectDevice(self.home_xml, self.spec_xml)
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: device),
|
||||
"USB-001",
|
||||
"client-001",
|
||||
sleeper=lambda _seconds: None,
|
||||
max_page_swipes=0,
|
||||
max_spec_swipes=0,
|
||||
)
|
||||
|
||||
data = service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
).to_pdd_data()
|
||||
|
||||
self.assertEqual(len(data["skus"]), 4)
|
||||
red_skus = [
|
||||
item for item in data["skus"] if item["options"]["color"] == "红色"
|
||||
]
|
||||
self.assertEqual(
|
||||
{item["price_observed_at"]["size"] for item in red_skus}, {"M"}
|
||||
)
|
||||
# 1 次打开规格面板,另有红色点 1 次;尺码只读不点击。
|
||||
self.assertEqual(len(device.clicks), 2)
|
||||
|
||||
def test_missing_goods_id_is_structured_error(self):
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: object()),
|
||||
@@ -195,7 +252,7 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
|
||||
def test_page_timeout_has_specific_error_code(self):
|
||||
device = LoadingDevice(self.home_xml, self.spec_xml)
|
||||
ticks = iter((0.0, 2.0))
|
||||
ticks = iter((0.0, 0.0, 0.0, 2.0))
|
||||
service = PddCollectService(
|
||||
PddDeviceService(lambda _serial: device),
|
||||
"USB-001",
|
||||
@@ -211,7 +268,21 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(raised.exception.code, "PDD_PAGE_TIMEOUT")
|
||||
|
||||
def test_incomplete_goods_data_does_not_return_partial_success(self):
|
||||
def test_runtime_device_disconnect_has_stable_error_code(self):
|
||||
service = PddCollectService(
|
||||
PddDeviceService(
|
||||
lambda _serial: DisconnectingDevice(self.home_xml, self.spec_xml)
|
||||
),
|
||||
"USB-001",
|
||||
"client-001",
|
||||
)
|
||||
with self.assertRaises(PddCollectError) as raised:
|
||||
service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
)
|
||||
self.assertEqual(raised.exception.code, "DEVICE_DISCONNECTED")
|
||||
|
||||
def test_missing_shop_name_is_allowed(self):
|
||||
incomplete_home = self.home_xml.replace(
|
||||
'<node class="android.widget.TextView" text="店铺:测试服饰旗舰店" '
|
||||
'bounds="[30,1050][600,1110]" visible-to-user="true" '
|
||||
@@ -227,11 +298,10 @@ class PddCollectParserTest(unittest.TestCase):
|
||||
max_page_swipes=0,
|
||||
)
|
||||
|
||||
with self.assertRaises(PddCollectError) as raised:
|
||||
service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
)
|
||||
self.assertEqual(raised.exception.code, "PDD_DATA_SHOP_MISSING")
|
||||
result = service.collect(
|
||||
FakeTask("https://mobile.yangkeduo.com/goods.html?goods_id=123")
|
||||
)
|
||||
self.assertIsNone(result.shop_name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user