feat(collect): show skipped reason summaries
This commit is contained in:
@@ -0,0 +1,70 @@
|
|||||||
|
"""Pure helpers for classifying and presenting collection skip reasons."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
ALIAS_UNMATCHED = "alias_unmatched"
|
||||||
|
LOGIN_REQUIRED = "login_required"
|
||||||
|
OTHER = "other"
|
||||||
|
|
||||||
|
SKIP_REASON_ORDER = (ALIAS_UNMATCHED, LOGIN_REQUIRED, OTHER)
|
||||||
|
SKIP_REASON_LABELS = {
|
||||||
|
ALIAS_UNMATCHED: "别名未匹配",
|
||||||
|
LOGIN_REQUIRED: "账号未登录",
|
||||||
|
OTHER: "其他原因",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def empty_skip_reason_counts():
|
||||||
|
return {reason: 0 for reason in SKIP_REASON_ORDER}
|
||||||
|
|
||||||
|
|
||||||
|
def classify_skip_reason(reason="", *, alias_matched=True):
|
||||||
|
"""Classify historical skip text without treating uncertain login checks as logout."""
|
||||||
|
if not alias_matched:
|
||||||
|
return ALIAS_UNMATCHED
|
||||||
|
|
||||||
|
text = str(reason or "").strip().lower()
|
||||||
|
if "别名未匹配" in text or "alias_unmatched" in text:
|
||||||
|
return ALIAS_UNMATCHED
|
||||||
|
if (
|
||||||
|
"账号未登录" in text
|
||||||
|
or "采集中途掉登录" in text
|
||||||
|
or "login_page" in text
|
||||||
|
or ("accounts.shopee." in text and "/seller/login" in text)
|
||||||
|
):
|
||||||
|
return LOGIN_REQUIRED
|
||||||
|
return OTHER
|
||||||
|
|
||||||
|
|
||||||
|
def skipped_stage_text(reason="", *, alias_matched=True):
|
||||||
|
reason_code = classify_skip_reason(reason, alias_matched=alias_matched)
|
||||||
|
return f"略过 · {SKIP_REASON_LABELS[reason_code]}"
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_skip_reason_counts(counts=None, *, skipped_total=0):
|
||||||
|
normalized = empty_skip_reason_counts()
|
||||||
|
source = counts if isinstance(counts, dict) else {}
|
||||||
|
for reason in SKIP_REASON_ORDER:
|
||||||
|
try:
|
||||||
|
normalized[reason] = max(0, int(source.get(reason, 0) or 0))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
normalized[reason] = 0
|
||||||
|
|
||||||
|
total = max(0, int(skipped_total or 0))
|
||||||
|
accounted = sum(normalized.values())
|
||||||
|
if accounted < total:
|
||||||
|
normalized[OTHER] += total - accounted
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def format_skip_reason_summary(counts=None, *, skipped_total=0):
|
||||||
|
total = max(0, int(skipped_total or 0))
|
||||||
|
if total == 0:
|
||||||
|
return ""
|
||||||
|
normalized = normalize_skip_reason_counts(counts, skipped_total=total)
|
||||||
|
parts = [
|
||||||
|
f"{SKIP_REASON_LABELS[reason]}{normalized[reason]}"
|
||||||
|
for reason in SKIP_REASON_ORDER
|
||||||
|
if normalized[reason] > 0
|
||||||
|
]
|
||||||
|
return f"略过{total}:" + ",".join(parts)
|
||||||
+17
-5
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .. import diagnostics
|
||||||
|
from ..collect_skip import skipped_stage_text
|
||||||
from .widgets import *
|
from .widgets import *
|
||||||
|
|
||||||
|
|
||||||
class TaskTableModel(QAbstractTableModel):
|
class TaskTableModel(QAbstractTableModel):
|
||||||
"""Table model for task rows shared by workflow tabs."""
|
"""Table model for task rows shared by workflow tabs."""
|
||||||
|
|
||||||
@@ -88,11 +92,15 @@ class TaskTableModel(QAbstractTableModel):
|
|||||||
if role == Qt.ForegroundRole and index.column() == 3:
|
if role == Qt.ForegroundRole and index.column() == 3:
|
||||||
return self._stage_color(task)
|
return self._stage_color(task)
|
||||||
if role == Qt.ToolTipRole:
|
if role == Qt.ToolTipRole:
|
||||||
if self.is_unmatched(task):
|
|
||||||
return "别名未匹配账号,采集时将略过"
|
|
||||||
error = getattr(task, "last_error", "") or ""
|
error = getattr(task, "last_error", "") or ""
|
||||||
|
if self.is_unmatched(task) or getattr(task, "status", "") == "skipped":
|
||||||
|
if error:
|
||||||
|
return diagnostics.redact_log_text(str(error))
|
||||||
|
if self.is_unmatched(task):
|
||||||
|
return "别名未匹配账号,采集时将略过"
|
||||||
|
return "未记录具体略过原因"
|
||||||
if error:
|
if error:
|
||||||
return str(error)
|
return diagnostics.redact_log_text(str(error))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def flags(self, index):
|
def flags(self, index):
|
||||||
@@ -124,8 +132,12 @@ class TaskTableModel(QAbstractTableModel):
|
|||||||
return bool(error) and any(marker.lower() in error for marker in self.PRODUCT_UNAVAILABLE_MARKERS)
|
return bool(error) and any(marker.lower() in error for marker in self.PRODUCT_UNAVAILABLE_MARKERS)
|
||||||
|
|
||||||
def _stage_text(self, task) -> str:
|
def _stage_text(self, task) -> str:
|
||||||
if self.is_unmatched(task):
|
unmatched = self.is_unmatched(task)
|
||||||
return "略过"
|
if unmatched or getattr(task, "status", "") == "skipped":
|
||||||
|
return skipped_stage_text(
|
||||||
|
getattr(task, "last_error", ""),
|
||||||
|
alias_matched=not unmatched,
|
||||||
|
)
|
||||||
if self._is_product_unavailable(task):
|
if self._is_product_unavailable(task):
|
||||||
return "商品失效"
|
return "商品失效"
|
||||||
if task.status in self.STATUS_TEXT and task.status != "pending":
|
if task.status in self.STATUS_TEXT and task.status != "pending":
|
||||||
|
|||||||
+22
-3
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from ...collect_skip import (
|
||||||
|
ALIAS_UNMATCHED,
|
||||||
|
LOGIN_REQUIRED,
|
||||||
|
format_skip_reason_summary,
|
||||||
|
normalize_skip_reason_counts,
|
||||||
|
)
|
||||||
from ..models import TaskTableModel
|
from ..models import TaskTableModel
|
||||||
from ..widgets import *
|
from ..widgets import *
|
||||||
from ..workers import CollectWorker as _RealCollectWorker, WriteBackWorker as _RealWriteBackWorker
|
from ..workers import CollectWorker as _RealCollectWorker, WriteBackWorker as _RealWriteBackWorker
|
||||||
@@ -694,9 +700,19 @@ class CollectTab(QWidget):
|
|||||||
launched = payload.get("launched_accounts") or []
|
launched = payload.get("launched_accounts") or []
|
||||||
reused = payload.get("reused_accounts") or []
|
reused = payload.get("reused_accounts") or []
|
||||||
login_required = payload.get("login_required_accounts") or []
|
login_required = payload.get("login_required_accounts") or []
|
||||||
if not launched and not reused and not login_required:
|
skipped = max(0, int(payload.get("skipped", 0) or 0))
|
||||||
|
skip_counts = normalize_skip_reason_counts(
|
||||||
|
payload.get("skip_reason_counts"),
|
||||||
|
skipped_total=skipped,
|
||||||
|
)
|
||||||
|
if not launched and not reused and not login_required and skipped == 0:
|
||||||
return
|
return
|
||||||
lines = [message]
|
lines = [message]
|
||||||
|
skip_summary = format_skip_reason_summary(skip_counts, skipped_total=skipped)
|
||||||
|
if skip_summary:
|
||||||
|
lines.append(skip_summary)
|
||||||
|
if skip_counts[ALIAS_UNMATCHED] > 0:
|
||||||
|
lines.append("请检查 Excel 别名是否与④账号管理中的账号别名一致。")
|
||||||
if launched:
|
if launched:
|
||||||
lines.append(
|
lines.append(
|
||||||
"本轮已自动启动账号 Chrome:"
|
"本轮已自动启动账号 Chrome:"
|
||||||
@@ -707,9 +723,12 @@ class CollectTab(QWidget):
|
|||||||
"以下账号需要补登录:"
|
"以下账号需要补登录:"
|
||||||
+ "、".join(self._account_label(item) for item in login_required)
|
+ "、".join(self._account_label(item) for item in login_required)
|
||||||
)
|
)
|
||||||
lines.append("采集结束后不会自动关闭账号 Chrome,请按需自行关闭。")
|
if skip_counts[LOGIN_REQUIRED] > 0:
|
||||||
|
lines.append("请到④账号管理完成对应账号登录后,再重新采集略过任务。")
|
||||||
|
if launched or reused or login_required:
|
||||||
|
lines.append("采集结束后不会自动关闭账号 Chrome,请按需自行关闭。")
|
||||||
text = "\n".join(lines)
|
text = "\n".join(lines)
|
||||||
if login_required:
|
if login_required or skip_counts[LOGIN_REQUIRED] > 0:
|
||||||
QMessageBox.warning(self, "采集完成", text)
|
QMessageBox.warning(self, "采集完成", text)
|
||||||
else:
|
else:
|
||||||
QMessageBox.information(self, "采集完成", text)
|
QMessageBox.information(self, "采集完成", text)
|
||||||
|
|||||||
+10
-1
@@ -8,6 +8,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from .. import image_studio, image_studio_export, image_studio_generation, image_studio_images
|
from .. import image_studio, image_studio_export, image_studio_generation, image_studio_images
|
||||||
|
from ..collect_skip import ALIAS_UNMATCHED, LOGIN_REQUIRED, empty_skip_reason_counts
|
||||||
from .widgets import *
|
from .widgets import *
|
||||||
|
|
||||||
|
|
||||||
@@ -1623,6 +1624,7 @@ class CollectWorker(BaseWorker):
|
|||||||
login_skip_reasons = {}
|
login_skip_reasons = {}
|
||||||
login_required_accounts = {}
|
login_required_accounts = {}
|
||||||
preflight_info = {}
|
preflight_info = {}
|
||||||
|
skip_reason_counts = empty_skip_reason_counts()
|
||||||
|
|
||||||
self._run_id = self._create_run_log(eligible, batch_ids)
|
self._run_id = self._create_run_log(eligible, batch_ids)
|
||||||
self._log_run_event(
|
self._log_run_event(
|
||||||
@@ -1642,7 +1644,10 @@ class CollectWorker(BaseWorker):
|
|||||||
failed=failed,
|
failed=failed,
|
||||||
batch_ids=batch_ids,
|
batch_ids=batch_ids,
|
||||||
blocked=True,
|
blocked=True,
|
||||||
extra=blocked,
|
extra={
|
||||||
|
**blocked,
|
||||||
|
"skip_reason_counts": dict(skip_reason_counts),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self._finish_run_log("blocked", summary)
|
self._finish_run_log("blocked", summary)
|
||||||
return summary
|
return summary
|
||||||
@@ -1665,6 +1670,7 @@ class CollectWorker(BaseWorker):
|
|||||||
account = account_by_alias.get(str(task.alias).strip())
|
account = account_by_alias.get(str(task.alias).strip())
|
||||||
if account is None:
|
if account is None:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
|
skip_reason_counts[ALIAS_UNMATCHED] += 1
|
||||||
done += 1
|
done += 1
|
||||||
reason = "别名未匹配账号"
|
reason = "别名未匹配账号"
|
||||||
db.mark_skipped(task.id, reason, path=self.db_path)
|
db.mark_skipped(task.id, reason, path=self.db_path)
|
||||||
@@ -1684,6 +1690,7 @@ class CollectWorker(BaseWorker):
|
|||||||
alias = str(task.alias).strip()
|
alias = str(task.alias).strip()
|
||||||
if alias in login_skip_reasons:
|
if alias in login_skip_reasons:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
|
skip_reason_counts[LOGIN_REQUIRED] += 1
|
||||||
done += 1
|
done += 1
|
||||||
reason = login_skip_reasons[alias]
|
reason = login_skip_reasons[alias]
|
||||||
db.mark_skipped(task.id, reason, path=self.db_path)
|
db.mark_skipped(task.id, reason, path=self.db_path)
|
||||||
@@ -1704,6 +1711,7 @@ class CollectWorker(BaseWorker):
|
|||||||
if self._is_definitive_logged_out(status):
|
if self._is_definitive_logged_out(status):
|
||||||
alias = str(task.alias).strip()
|
alias = str(task.alias).strip()
|
||||||
skipped += 1
|
skipped += 1
|
||||||
|
skip_reason_counts[LOGIN_REQUIRED] += 1
|
||||||
done += 1
|
done += 1
|
||||||
reason = self._midrun_login_skip_reason(status)
|
reason = self._midrun_login_skip_reason(status)
|
||||||
login_skip_reasons[alias] = reason
|
login_skip_reasons[alias] = reason
|
||||||
@@ -1840,6 +1848,7 @@ class CollectWorker(BaseWorker):
|
|||||||
extra={
|
extra={
|
||||||
**preflight_info,
|
**preflight_info,
|
||||||
"login_required_accounts": list(login_required_accounts.values()),
|
"login_required_accounts": list(login_required_accounts.values()),
|
||||||
|
"skip_reason_counts": dict(skip_reason_counts),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self._finish_run_log("cancelled" if self.should_cancel() else "done", summary)
|
self._finish_run_log("cancelled" if self.should_cancel() else "done", summary)
|
||||||
|
|||||||
+5
-2
@@ -3,7 +3,7 @@ id: T-620
|
|||||||
title: 采集略过原因可见化与结束分类汇总
|
title: 采集略过原因可见化与结束分类汇总
|
||||||
phase: 2
|
phase: 2
|
||||||
deps: [T-563, T-515]
|
deps: [T-563, T-515]
|
||||||
status: TODO
|
status: DONE
|
||||||
created: 2026-07-13
|
created: 2026-07-13
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -48,4 +48,7 @@ T-563 已修复 `NO_SESSION_COOKIE`、CDP短暂异常等不确定登录状态导
|
|||||||
|
|
||||||
## 执行记录
|
## 执行记录
|
||||||
|
|
||||||
(完成后记录实现、验证命令与结果。)
|
- 2026-07-13:新增 `app/collect_skip.py` 集中处理历史略过原因分类、阶段短文案、分类计数归一化和结束摘要;明确 `NO_SESSION_COOKIE` 归入未知兼容类型,不作为账号未登录依据。
|
||||||
|
- 2026-07-13:①列表阶段列显示“略过 · 别名未匹配 / 账号未登录 / 其他原因”,tooltip展示脱敏完整原因;未新增列,也未修改数据库字段。
|
||||||
|
- 2026-07-13:`CollectWorker` 按现有别名未匹配、采集前明确未登录、采集中明确掉登录三个结构化分支累计 `skip_reason_counts`;采集结束弹窗增加分类摘要和对应处理指引。
|
||||||
|
- 2026-07-13:增加纯逻辑、表格历史记录、Worker分类汇总、结束弹窗及T-563 `NO_SESSION_COOKIE` 回归测试。为隔离工作区其它未提交改动,在仅含T-620差异的干净worktree验证:`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`py -3.10 -m unittest discover -s tests`(424项通过)、`git diff --check`,结果全部通过。
|
||||||
|
|||||||
@@ -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
@@ -6959,7 +6959,11 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertNotIn("padding", tab.delete_batch_button.styleSheet())
|
self.assertNotIn("padding", tab.delete_batch_button.styleSheet())
|
||||||
self.assertEqual("Excel副店", tab.model.index(1, 0).data())
|
self.assertEqual("Excel副店", tab.model.index(1, 0).data())
|
||||||
self.assertEqual("missing", tab.model.index(1, 1).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.assert_foreground(tab.model, 1, 3, gui.COLOR_MUTED)
|
||||||
self.assertIn("2 行", tab.summary_label.text())
|
self.assertIn("2 行", tab.summary_label.text())
|
||||||
self.assertIn("有效2/无效0", 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"))
|
tab.status_filter.setCurrentIndex(tab.status_filter.findData("skipped"))
|
||||||
self.assertEqual(1, tab.model.rowCount())
|
self.assertEqual(1, tab.model.rowCount())
|
||||||
self.assertEqual("missing", tab.model.index(0, 1).data())
|
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")
|
tab.item_filter.setText("no-match")
|
||||||
self.assertEqual(0, tab.model.rowCount())
|
self.assertEqual(0, tab.model.rowCount())
|
||||||
@@ -7234,7 +7238,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(1, tab.model.rowCount())
|
self.assertEqual(1, tab.model.rowCount())
|
||||||
self.assertEqual("missing", tab.model.index(0, 1).data())
|
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()
|
tab.show_all_tasks()
|
||||||
|
|
||||||
@@ -7242,6 +7246,36 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
|
|
||||||
self.assert_removed(temp_dir)
|
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):
|
def test_collect_tab_import_button_imports_excel_and_refreshes_tasks(self):
|
||||||
with self.make_temp_dir() as temp_dir:
|
with self.make_temp_dir() as temp_dir:
|
||||||
cfg = self.make_config(temp_dir)
|
cfg = self.make_config(temp_dir)
|
||||||
@@ -7418,6 +7452,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertEqual(1, summary["collected"])
|
self.assertEqual(1, summary["collected"])
|
||||||
self.assertEqual(2, summary["skipped"])
|
self.assertEqual(2, summary["skipped"])
|
||||||
self.assertEqual(0, summary["failed"])
|
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.assertEqual([batch_id], summary["batch_ids"])
|
||||||
self.assertIsInstance(summary["run_id"], int)
|
self.assertIsInstance(summary["run_id"], int)
|
||||||
collect.assert_called_once()
|
collect.assert_called_once()
|
||||||
@@ -7587,11 +7625,13 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
tab = CollectTab(config=cfg, status_callback=statuses.append)
|
tab = CollectTab(config=cfg, status_callback=statuses.append)
|
||||||
self.addCleanup(tab.close)
|
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})
|
tab._on_collect_finished({"collected": 0, "skipped": 1, "failed": 0})
|
||||||
|
|
||||||
start_write_back.assert_not_called()
|
start_write_back.assert_not_called()
|
||||||
self.assertEqual("采集完成:成功0,略过1,失败0", statuses[-1])
|
self.assertEqual("采集完成:成功0,略过1,失败0", statuses[-1])
|
||||||
|
self.assertIn("略过1:其他原因1", information.call_args[0][2])
|
||||||
|
|
||||||
self.assert_removed(temp_dir)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
@@ -7761,6 +7801,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertFalse(summary.get("blocked"))
|
self.assertFalse(summary.get("blocked"))
|
||||||
self.assertTrue(summary["ok"])
|
self.assertTrue(summary["ok"])
|
||||||
self.assertEqual(1, summary["skipped"])
|
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.assertEqual("alias-a", summary["logged_out"][0]["alias"])
|
||||||
self.assertIn("账号未登录", summary["logged_out"][0]["reason"])
|
self.assertIn("账号未登录", summary["logged_out"][0]["reason"])
|
||||||
self.assertEqual("alias-a", summary["login_required_accounts"][0]["alias"])
|
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(3, summary["done"])
|
||||||
self.assertEqual(1, summary["collected"])
|
self.assertEqual(1, summary["collected"])
|
||||||
self.assertEqual(2, summary["skipped"])
|
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.assertEqual("alias-a", summary["login_required_accounts"][0]["alias"])
|
||||||
self.assertIn("采集中途掉登录", summary["login_required_accounts"][0]["reason"])
|
self.assertIn("采集中途掉登录", summary["login_required_accounts"][0]["reason"])
|
||||||
self.assertEqual(["alias-a", "alias-b", "alias-a", "alias-b"], login_calls)
|
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.assertTrue(summary["ok"])
|
||||||
self.assertEqual(2, summary["collected"])
|
self.assertEqual(2, summary["collected"])
|
||||||
self.assertEqual(0, summary["skipped"])
|
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([], summary["login_required_accounts"])
|
||||||
self.assertEqual(["51100639510", "51100639511"], collected_items)
|
self.assertEqual(["51100639510", "51100639511"], collected_items)
|
||||||
self.assertEqual(3, detect_login.call_count)
|
self.assertEqual(3, detect_login.call_count)
|
||||||
@@ -7958,6 +8010,10 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertTrue(summary["ok"])
|
self.assertTrue(summary["ok"])
|
||||||
self.assertEqual(1, summary["collected"])
|
self.assertEqual(1, summary["collected"])
|
||||||
self.assertEqual(0, summary["skipped"])
|
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([], summary["login_required_accounts"])
|
||||||
self.assertEqual(3, detect_login.call_count)
|
self.assertEqual(3, detect_login.call_count)
|
||||||
self.assertEqual(2, sleep.call_count)
|
self.assertEqual(2, sleep.call_count)
|
||||||
@@ -8128,6 +8184,11 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
"collected": 0,
|
"collected": 0,
|
||||||
"skipped": 2,
|
"skipped": 2,
|
||||||
"failed": 0,
|
"failed": 0,
|
||||||
|
"skip_reason_counts": {
|
||||||
|
"alias_unmatched": 1,
|
||||||
|
"login_required": 1,
|
||||||
|
"other": 0,
|
||||||
|
},
|
||||||
"login_required_accounts": [
|
"login_required_accounts": [
|
||||||
{
|
{
|
||||||
"account_name": "主店",
|
"account_name": "主店",
|
||||||
@@ -8152,6 +8213,9 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
|||||||
self.assertIn("主店(alias-a)", message)
|
self.assertIn("主店(alias-a)", message)
|
||||||
self.assertIn("已自动启动账号 Chrome", message)
|
self.assertIn("已自动启动账号 Chrome", 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)
|
self.assert_removed(temp_dir)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user