feat(collect): add product status recheck

This commit is contained in:
chengma
2026-07-18 18:09:07 +08:00
parent 3e9052e1b6
commit ba36b9fd8e
14 changed files with 985 additions and 32 deletions
+187
View File
@@ -25,6 +25,7 @@ from app.gui.workers import (
ProductSuiteGenerateWorker,
ProductSuiteHistoryExportWorker,
CollectWorker,
StatusRecheckWorker,
)
@@ -280,6 +281,192 @@ class WorkerTests(unittest.TestCase):
all(task.stage == "collected" for task in db.list_tasks(batch_id=batch_id, path=db_path))
)
def test_status_recheck_worker_updates_only_status_for_historical_generated_tasks(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = os.path.join(temp_dir, "cmshopee.db")
db.init_db(db_path)
batch_id = db.create_batch(["input.xlsx"], path=db_path)
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "Sheet1",
"source_row": index,
"account_name": "店铺",
"alias": "alias",
"item_id": str(51100639700 + index),
}
for index in range(2, 6)
],
path=db_path,
)
tasks = db.list_tasks(batch_id=batch_id, path=db_path)
for index, task in enumerate(tasks):
db.set_collected(task.id, f"旧标题{index}", f"old-{index}.jpg", path=db_path)
db.set_generated(task.id, f"新标题{index}", f"new-{index}.jpg", path=db_path)
db.set_applied(tasks[-1].id, committed=True, path=db_path)
statuses = ["normal", "unlisted", "reviewing", "unknown"]
account = SimpleNamespace(alias="alias", account_name="店铺", debug_port=9222)
def fake_recheck(_account, task, on_step=None):
on_step("read_product_status")
status = statuses.pop(0)
return {
"product_status": status,
"product_status_note": f"{status} 提示",
"close_target_confirmed": True,
}
with mock.patch(
"app.gui.workers.accounts.list_accounts", return_value=[account]
), mock.patch(
"app.gui.workers.accounts.detect_login",
return_value={"logged_in": True, "reason": None},
), mock.patch(
"app.gui.workers.editor.recheck_product_status",
side_effect=fake_recheck,
):
summary = StatusRecheckWorker(
tasks,
db_path=db_path,
preflight=False,
).execute()
self.assertEqual(4, summary["rechecked"])
self.assertEqual(0, summary["failed"])
self.assertEqual(
{"normal": 1, "unlisted": 1, "reviewing": 1, "unknown": 1},
summary["product_status_counts"],
)
refreshed = db.list_tasks(batch_id=batch_id, path=db_path)
self.assertEqual(
["generated", "generated", "generated", "applied"],
[task.stage for task in refreshed],
)
self.assertEqual(
["success", "success", "success", "success"],
[task.status for task in refreshed],
)
self.assertEqual(
[f"旧标题{index}" for index in range(4)],
[task.old_title for task in refreshed],
)
self.assertEqual(
[f"新标题{index}" for index in range(4)],
[task.new_title for task in refreshed],
)
self.assertEqual(
[f"new-{index}.jpg" for index in range(4)],
[task.new_cover_path for task in refreshed],
)
self.assertEqual(
["normal", "unlisted", "reviewing", "unknown"],
[task.product_status for task in refreshed],
)
self.assertTrue(all(task.product_status_at for task in refreshed))
run_log = db.list_run_logs(limit=1, run_type="status_recheck", path=db_path)[0]
self.assertEqual("done", run_log.status)
self.assertEqual(4, run_log.success_count)
def test_status_recheck_worker_failure_keeps_existing_snapshot_and_workflow(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = os.path.join(temp_dir, "cmshopee.db")
db.init_db(db_path)
batch_id = db.create_batch(["input.xlsx"], path=db_path)
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "Sheet1",
"source_row": 2,
"account_name": "店铺",
"alias": "alias",
"item_id": "51100639801",
}
],
path=db_path,
)
task = db.list_tasks(batch_id=batch_id, path=db_path)[0]
db.set_collected(
task.id,
"旧标题",
"old.jpg",
product_status_value="normal",
product_status_note="历史快照",
product_status_at="2026-07-01T10:00:00",
path=db_path,
)
db.set_generated(task.id, "新标题", "new.jpg", path=db_path)
task = db.get_task(task.id, path=db_path)
account = SimpleNamespace(alias="alias", account_name="店铺", debug_port=9222)
with mock.patch(
"app.gui.workers.accounts.list_accounts", return_value=[account]
), mock.patch(
"app.gui.workers.accounts.detect_login",
return_value={"logged_in": True, "reason": None},
), mock.patch(
"app.gui.workers.editor.recheck_product_status",
side_effect=RuntimeError("CDP 商品页读取失败"),
):
summary = StatusRecheckWorker(
[task],
db_path=db_path,
preflight=False,
).execute()
refreshed = db.get_task(task.id, path=db_path)
self.assertEqual(1, summary["failed"])
self.assertEqual("generated", refreshed.stage)
self.assertEqual("success", refreshed.status)
self.assertEqual("normal", refreshed.product_status)
self.assertEqual("历史快照", refreshed.product_status_note)
self.assertEqual("2026-07-01T10:00:00", refreshed.product_status_at)
self.assertEqual("新标题", refreshed.new_title)
self.assertEqual("new.jpg", refreshed.new_cover_path)
def test_status_recheck_worker_cancel_keeps_generated_task_unchanged(self):
with tempfile.TemporaryDirectory() as temp_dir:
db_path = os.path.join(temp_dir, "cmshopee.db")
db.init_db(db_path)
batch_id = db.create_batch(["input.xlsx"], path=db_path)
db.insert_tasks(
batch_id,
[
{
"source_file_abs": os.path.join(temp_dir, "input.xlsx"),
"source_sheet": "Sheet1",
"source_row": 2,
"account_name": "店铺",
"alias": "alias",
"item_id": "51100639802",
}
],
path=db_path,
)
task = db.list_tasks(batch_id=batch_id, path=db_path)[0]
db.set_collected(task.id, "旧标题", "old.jpg", path=db_path)
db.set_generated(task.id, "新标题", "new.jpg", path=db_path)
task = db.get_task(task.id, path=db_path)
worker = StatusRecheckWorker([task], db_path=db_path, preflight=False)
worker.cancel()
with mock.patch("app.gui.workers.accounts.list_accounts", return_value=[]), mock.patch(
"app.gui.workers.editor.recheck_product_status"
) as recheck:
summary = worker.execute()
refreshed = db.get_task(task.id, path=db_path)
self.assertEqual(0, summary["done"])
self.assertEqual(0, summary["rechecked"])
self.assertEqual("generated", refreshed.stage)
self.assertEqual("success", refreshed.status)
recheck.assert_not_called()
run_log = db.list_run_logs(limit=1, run_type="status_recheck", path=db_path)[0]
self.assertEqual("cancelled", run_log.status)
def test_run_worker_rejects_plain_object(self):
with self.assertRaises(TypeError):
run_worker(object(), start=False)