feat(collect): show skipped reason summaries

This commit is contained in:
chengma
2026-07-13 14:31:58 +08:00
parent a7ac8522bf
commit 0a3a6b99a1
7 changed files with 239 additions and 15 deletions
+47
View File
@@ -0,0 +1,47 @@
import unittest
from app.collect_skip import (
ALIAS_UNMATCHED,
LOGIN_REQUIRED,
OTHER,
classify_skip_reason,
format_skip_reason_summary,
normalize_skip_reason_counts,
skipped_stage_text,
)
class CollectSkipReasonTests(unittest.TestCase):
def test_classifies_supported_and_unknown_historical_reasons(self):
self.assertEqual(ALIAS_UNMATCHED, classify_skip_reason("别名未匹配账号"))
self.assertEqual(LOGIN_REQUIRED, classify_skip_reason("采集中途掉登录:LOGIN_PAGE"))
self.assertEqual(OTHER, classify_skip_reason("NO_SESSION_COOKIE"))
self.assertEqual(OTHER, classify_skip_reason(""))
def test_unmatched_alias_has_priority_over_reason_text(self):
self.assertEqual(
ALIAS_UNMATCHED,
classify_skip_reason("账号未登录", alias_matched=False),
)
self.assertEqual(
"略过 · 别名未匹配",
skipped_stage_text("", alias_matched=False),
)
def test_summary_fills_unclassified_count_as_other(self):
counts = normalize_skip_reason_counts(
{ALIAS_UNMATCHED: 2, LOGIN_REQUIRED: 1},
skipped_total=4,
)
self.assertEqual(1, counts[OTHER])
self.assertEqual(
"略过4:别名未匹配2,账号未登录1,其他原因1",
format_skip_reason_summary(counts, skipped_total=4),
)
def test_zero_skips_has_no_empty_summary(self):
self.assertEqual("", format_skip_reason_summary({}, skipped_total=0))
if __name__ == "__main__":
unittest.main()
+68 -4
View File
@@ -6959,7 +6959,11 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertNotIn("padding", tab.delete_batch_button.styleSheet())
self.assertEqual("Excel副店", tab.model.index(1, 0).data())
self.assertEqual("missing", tab.model.index(1, 1).data())
self.assertEqual("略过", tab.model.index(1, 3).data())
self.assertEqual("略过 · 别名未匹配", tab.model.index(1, 3).data())
self.assertEqual(
"别名未匹配账号,采集时将略过",
tab.model.index(1, 3).data(Qt.ToolTipRole),
)
self.assert_foreground(tab.model, 1, 3, gui.COLOR_MUTED)
self.assertIn("2 行", tab.summary_label.text())
self.assertIn("有效2/无效0", tab.summary_label.text())
@@ -7061,7 +7065,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
tab.status_filter.setCurrentIndex(tab.status_filter.findData("skipped"))
self.assertEqual(1, tab.model.rowCount())
self.assertEqual("missing", tab.model.index(0, 1).data())
self.assertEqual("略过", tab.model.index(0, 3).data())
self.assertEqual("略过 · 别名未匹配", tab.model.index(0, 3).data())
tab.item_filter.setText("no-match")
self.assertEqual(0, tab.model.rowCount())
@@ -7234,7 +7238,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual(1, tab.model.rowCount())
self.assertEqual("missing", tab.model.index(0, 1).data())
self.assertEqual("略过", tab.model.index(0, 3).data())
self.assertEqual("略过 · 别名未匹配", tab.model.index(0, 3).data())
tab.show_all_tasks()
@@ -7242,6 +7246,36 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assert_removed(temp_dir)
def test_collect_task_model_shows_historical_skip_reasons_and_tooltips(self):
model = gui.TaskTableModel()
account = SimpleNamespace(alias="alias-a", account_name="主店")
login_reason = "采集中途掉登录: 账号未登录: LOGIN_PAGE"
tasks = [
SimpleNamespace(
alias="alias-a",
account_name="主店",
item_id="1001",
stage="imported",
status="skipped",
last_error=login_reason,
),
SimpleNamespace(
alias="alias-a",
account_name="主店",
item_id="1002",
stage="imported",
status="skipped",
last_error="运营手动略过",
),
]
model.set_tasks(tasks, [account])
self.assertEqual("略过 · 账号未登录", model.index(0, 3).data())
self.assertEqual(login_reason, model.index(0, 3).data(Qt.ToolTipRole))
self.assertEqual("略过 · 其他原因", model.index(1, 3).data())
self.assertEqual("运营手动略过", model.index(1, 3).data(Qt.ToolTipRole))
def test_collect_tab_import_button_imports_excel_and_refreshes_tasks(self):
with self.make_temp_dir() as temp_dir:
cfg = self.make_config(temp_dir)
@@ -7418,6 +7452,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual(1, summary["collected"])
self.assertEqual(2, summary["skipped"])
self.assertEqual(0, summary["failed"])
self.assertEqual(
{"alias_unmatched": 1, "login_required": 1, "other": 0},
summary["skip_reason_counts"],
)
self.assertEqual([batch_id], summary["batch_ids"])
self.assertIsInstance(summary["run_id"], int)
collect.assert_called_once()
@@ -7587,11 +7625,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
tab = CollectTab(config=cfg, status_callback=statuses.append)
self.addCleanup(tab.close)
with mock.patch.object(tab, "_start_write_back") as start_write_back:
with mock.patch.object(tab, "_start_write_back") as start_write_back, \
mock.patch("app.gui.QMessageBox.information") as information:
tab._on_collect_finished({"collected": 0, "skipped": 1, "failed": 0})
start_write_back.assert_not_called()
self.assertEqual("采集完成:成功0,略过1,失败0", statuses[-1])
self.assertIn("略过1:其他原因1", information.call_args[0][2])
self.assert_removed(temp_dir)
@@ -7761,6 +7801,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertFalse(summary.get("blocked"))
self.assertTrue(summary["ok"])
self.assertEqual(1, summary["skipped"])
self.assertEqual(
{"alias_unmatched": 0, "login_required": 1, "other": 0},
summary["skip_reason_counts"],
)
self.assertEqual("alias-a", summary["logged_out"][0]["alias"])
self.assertIn("账号未登录", summary["logged_out"][0]["reason"])
self.assertEqual("alias-a", summary["login_required_accounts"][0]["alias"])
@@ -7834,6 +7878,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertEqual(3, summary["done"])
self.assertEqual(1, summary["collected"])
self.assertEqual(2, summary["skipped"])
self.assertEqual(
{"alias_unmatched": 0, "login_required": 2, "other": 0},
summary["skip_reason_counts"],
)
self.assertEqual("alias-a", summary["login_required_accounts"][0]["alias"])
self.assertIn("采集中途掉登录", summary["login_required_accounts"][0]["reason"])
self.assertEqual(["alias-a", "alias-b", "alias-a", "alias-b"], login_calls)
@@ -7903,6 +7951,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertTrue(summary["ok"])
self.assertEqual(2, summary["collected"])
self.assertEqual(0, summary["skipped"])
self.assertEqual(
{"alias_unmatched": 0, "login_required": 0, "other": 0},
summary["skip_reason_counts"],
)
self.assertEqual([], summary["login_required_accounts"])
self.assertEqual(["51100639510", "51100639511"], collected_items)
self.assertEqual(3, detect_login.call_count)
@@ -7958,6 +8010,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertTrue(summary["ok"])
self.assertEqual(1, summary["collected"])
self.assertEqual(0, summary["skipped"])
self.assertEqual(
{"alias_unmatched": 0, "login_required": 0, "other": 0},
summary["skip_reason_counts"],
)
self.assertEqual([], summary["login_required_accounts"])
self.assertEqual(3, detect_login.call_count)
self.assertEqual(2, sleep.call_count)
@@ -8128,6 +8184,11 @@ class GuiTests(TempDirMixin, unittest.TestCase):
"collected": 0,
"skipped": 2,
"failed": 0,
"skip_reason_counts": {
"alias_unmatched": 1,
"login_required": 1,
"other": 0,
},
"login_required_accounts": [
{
"account_name": "主店",
@@ -8152,6 +8213,9 @@ class GuiTests(TempDirMixin, unittest.TestCase):
self.assertIn("主店(alias-a)", message)
self.assertIn("已自动启动账号 Chrome", message)
self.assertIn("不会自动关闭账号 Chrome", message)
self.assertIn("略过2:别名未匹配1,账号未登录1", message)
self.assertIn("检查 Excel 别名", message)
self.assertIn("④账号管理", message)
self.assert_removed(temp_dir)