"""任务详情数据整理和窗口的离屏测试。""" import os import unittest from dataclasses import replace os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") from PyQt5.QtCore import Qt from PyQt5.QtTest import QTest from PyQt5.QtWidgets import QApplication, QWidget from src.task_detail_view import ( CopyableBodyLabel, CopyableStrongBodyLabel, TaskDetailWindow, build_task_detail_view_data, ) from src.task_models import TaskDetail, TaskStatus, TaskType def make_detail(pdd_data=None) -> TaskDetail: return TaskDetail( id=1, remote_task_id="COL-001", task_type=TaskType.COLLECT, goods_id="123", goods_url="https://mobile.yangkeduo.com/goods.html?goods_id=123", title="本地标题", target_color=None, target_size=None, price_cent=None, quantity=None, status=TaskStatus.SUCCEEDED, current_step="completed", priority=0, version=1, admin_payload={}, pdd_data=pdd_data, retry_count=0, last_error_code=None, last_error_message=None, received_at="2026-08-08T08:00:00Z", started_at="2026-08-08T08:01:00Z", finished_at="2026-08-08T08:02:00Z", created_at="2026-08-08T08:00:00Z", updated_at="2026-08-08T08:02:00Z", ) def collected_data(): return { "goods_id": "123", "title": "采集标题", "shop_name": "测试店铺", "captured_at": "2026-08-08T08:02:00Z", "metrics": { "sales": {"value": 1200, "raw": "已拼1.2千件"}, "reviews": {"value": 68, "raw": "68条评价"}, }, "dimensions": [ { "key": "color", "name": "颜色", "values": [ {"text": "黑色", "available": True}, {"text": "白色", "available": False}, {"text": "黑色", "available": True}, ], }, { "key": "size", "name": "尺码", "values": [ {"text": "M", "available": True}, {"text": "L", "available": True}, {"text": "M", "available": True}, ], }, ], "skus": [ {"options": {"color": "黑色", "size": "M"}, "price_cent": 1000}, {"options": {"color": "黑色", "size": "L"}, "price_cent": 1200}, {"options": {"color": "白色", "size": "M"}, "price_cent": None}, ], } class TaskDetailViewTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.app = QApplication.instance() or QApplication([]) def test_colors_keep_order_deduplicate_and_show_price_state(self): data = build_task_detail_view_data(make_detail(collected_data())) self.assertEqual([item.text for item in data.colors], ["黑色", "白色"]) self.assertEqual(data.colors[0].price_text, "¥10.00~¥12.00") self.assertTrue(data.colors[0].price_inconsistent) self.assertEqual(data.colors[1].price_text, "价格未采集") self.assertFalse(data.colors[1].available) self.assertEqual(data.sizes, ["M", "L"]) def test_old_data_can_restore_dimensions_from_skus(self): data = collected_data() data["dimensions"] = [] view_data = build_task_detail_view_data(make_detail(data)) self.assertEqual([item.text for item in view_data.colors], ["黑色", "白色"]) self.assertEqual(view_data.sizes, ["M", "L"]) def test_empty_collection_data_has_clear_empty_values(self): data = build_task_detail_view_data(make_detail()) self.assertEqual(data.colors, []) self.assertEqual(data.sizes, []) self.assertEqual(data.shop_name, "未采集") self.assertEqual(data.sales, "未采集") self.assertEqual(data.current_step, "已完成") def test_unknown_current_step_does_not_show_english_to_buyer(self): detail = replace(make_detail(), current_step="future_internal_step") data = build_task_detail_view_data(detail) self.assertEqual(data.current_step, "未知步骤") def test_failure_status_uses_task_type_and_keeps_reason(self): collect = replace( make_detail(), status=TaskStatus.RETRY_WAIT, last_error_code="PDD_PAGE_TIMEOUT", last_error_message="商品页面加载超时", ) purchase = replace( make_detail(), remote_task_id="PUR-001", task_type=TaskType.PURCHASE, status=TaskStatus.FAILED, last_error_code="PURCHASE_PRICE_EXCEEDED", last_error_message="价格超过上限", ) collect_data = build_task_detail_view_data(collect) purchase_data = build_task_detail_view_data(purchase) self.assertEqual(collect_data.status, "采集失败") self.assertIn("商品页面加载超时", collect_data.last_error) self.assertEqual(purchase_data.status, "采购失败") self.assertIn("价格超过上限", purchase_data.last_error) def test_purchase_recovery_steps_are_clear_chinese(self): cases = { "purchase_dry_run_stopped": "采购演练已在提交前停止", "reconcile_purchase": "订单已提交,只允许核对未付款订单", "purchase_order_matched_pending_report": ( "已提交待付款,等待向 Admin 上报" ), "reconcile_manual_review": ( "订单结果不确定或存在多个候选,需人工处理" ), } for step, expected in cases.items(): with self.subTest(step=step): detail = replace(make_detail(), current_step=step) self.assertEqual( build_task_detail_view_data(detail).current_step, expected, ) def test_purchase_result_shows_order_and_prefers_raw_order_time(self): purchase_data = { "purchase": { "mode": "live", "confirmed": { "options": {"color": "黑色", "size": "L"}, "quantity": 2, "unit_price_cent": 3990, "total_price_cent": 7980, }, "order_no": "ORDER-20260811", "ordered_at": "2026-08-11T08:00:00+08:00", "ordered_at_raw": "2026-08-11 08:00:00", "match_status": "matched", "payment_status": "unpaid", } } detail = replace( make_detail(purchase_data), remote_task_id="PUR-001", task_type=TaskType.PURCHASE, ) data = build_task_detail_view_data(detail) self.assertEqual(data.purchase.mode, "真实采购") self.assertEqual(data.purchase.confirmed_spec, "黑色 / L") self.assertEqual(data.purchase.order_no, "ORDER-20260811") self.assertEqual(data.purchase.ordered_at, "2026-08-11 08:00:00") self.assertEqual(data.purchase.match_status, "已匹配") self.assertEqual(data.purchase.payment_status, "待付款") window = TaskDetailWindow(detail) self.assertIsNotNone(window.findChild(QWidget, "purchaseResultCard")) self.assertTrue( any( label.text() == "ORDER-20260811" for label in window.findChildren(CopyableBodyLabel) ) ) window.close() self.app.processEvents() def test_collect_detail_does_not_show_purchase_result_card(self): window = TaskDetailWindow(make_detail(collected_data())) self.assertIsNone(window.findChild(QWidget, "purchaseResultCard")) window.close() self.app.processEvents() def test_window_builds_color_section_before_size_section(self): window = TaskDetailWindow(make_detail(collected_data())) self.assertEqual(len(window.colorItems), 2) self.assertEqual(len(window.sizeItems), 2) self.assertIn("COL-001", window.windowTitle()) self.assertTrue(window.isWindow()) window.close() self.app.processEvents() def test_double_click_copies_full_field_and_updates_feedback(self): window = TaskDetailWindow(make_detail(collected_data())) title = next( label for label in window.findChildren(CopyableBodyLabel) if label.text() == "采集标题" ) QTest.mouseDClick(title, Qt.LeftButton) self.assertEqual(QApplication.clipboard().text(), "采集标题") self.assertEqual(window.copyStatusLabel.text(), "已复制:商品标题") self.assertTrue( title.textInteractionFlags() & Qt.TextSelectableByKeyboard ) window.close() self.app.processEvents() def test_ctrl_c_copies_price_without_changing_link_behavior(self): window = TaskDetailWindow(make_detail(collected_data())) price = next( label for label in window.findChildren(CopyableStrongBodyLabel) if label.text() == "¥10.00~¥12.00" ) price.setFocus() QTest.keyClick(price, Qt.Key_C, Qt.ControlModifier) self.assertEqual(QApplication.clipboard().text(), "¥10.00~¥12.00") self.assertEqual(window.copyStatusLabel.text(), "已复制:颜色价格") window.close() self.app.processEvents() if __name__ == "__main__": unittest.main()