feat: 增加采集任务重新执行入口 (#63)

This commit is contained in:
chengma
2026-08-09 21:44:29 +08:00
parent 72821eaaef
commit eda3661604
12 changed files with 509 additions and 27 deletions
+36 -1
View File
@@ -4,7 +4,12 @@ import tempfile
import unittest
from pathlib import Path
from src.admin_gateway import AdminTask, ClaimCapabilities, ClientInfo
from src.admin_gateway import (
AdminTask,
ClaimCapabilities,
ClientInfo,
SubmissionReceipt,
)
from src.collect_task_service import CollectTaskService
from src.mock_admin_gateway import MockAdminGateway
from src.pdd_collect_service import PddCollectError
@@ -159,6 +164,36 @@ class CollectTaskServiceTest(unittest.TestCase):
)
self.assertEqual(self.gateway.submission_count, 1)
def test_execute_selected_only_runs_requested_local_task(self):
for task_id in ("COL-SELECTED", "COL-OTHER"):
self.repository.add_claimed_task(
NewClaimedTask(
remote_task_id=task_id,
task_type=TaskType.COLLECT,
goods_url=f"https://example.test/{task_id}",
)
)
calls = []
class AcceptGateway:
def submit_result(self, *_args):
return SubmissionReceipt(True, "RESULT-001", "2026-08-07T08:00:00Z")
service = CollectTaskService(
AcceptGateway(),
self.repository,
self.client,
"USB-001",
collect_service_factory=lambda *_args: FakeCollector(calls),
)
outcome = service.execute_selected("COL-SELECTED")
self.assertEqual(outcome.kind, "succeeded")
self.assertEqual(calls, ["COL-SELECTED"])
self.assertEqual(
self.repository.get_task("COL-OTHER").status, TaskStatus.CLAIMED
)
if __name__ == "__main__":
unittest.main()
+47 -3
View File
@@ -1,4 +1,4 @@
"""SQLite 初始化和 v1 数据库结构测试。"""
"""SQLite 初始化和数据库迁移测试。"""
import sqlite3
import tempfile
@@ -6,6 +6,7 @@ import unittest
from pathlib import Path
from src.db import DatabaseVersionError, initialize_database, open_database
from src.db_schema import MIGRATION_1
EXPECTED_TABLES = {
@@ -36,7 +37,7 @@ class DatabaseInitializationTests(unittest.TestCase):
def tearDown(self) -> None:
self._temporary_directory.cleanup()
def test_initialize_creates_v1_tables_and_indexes(self) -> None:
def test_initialize_creates_latest_tables_and_indexes(self) -> None:
result_path = initialize_database(self.db_path)
self.assertEqual(result_path, self.db_path)
@@ -62,7 +63,50 @@ class DatabaseInitializationTests(unittest.TestCase):
self.assertTrue(EXPECTED_TABLES.issubset(tables))
self.assertTrue(EXPECTED_INDEXES.issubset(indexes))
self.assertEqual(version, 1)
self.assertEqual(version, 2)
def test_v1_database_is_upgraded_without_losing_task_runs(self) -> None:
connection = open_database(self.db_path)
try:
with connection:
for statement in MIGRATION_1:
connection.execute(statement)
connection.execute("PRAGMA user_version = 1")
connection.execute(
"INSERT INTO pdd_tasks"
" (remote_task_id, task_type, goods_url, status, received_at,"
" created_at, updated_at) VALUES"
" ('TASK-OLD', 'collect', 'https://example.test', 'claimed',"
" '2026-08-06T00:00:00Z', '2026-08-06T00:00:00Z',"
" '2026-08-06T00:00:00Z')"
)
connection.execute(
"INSERT INTO task_runs"
" (task_id, attempt_id, attempt_no, device_address, run_status,"
" started_at, created_at, updated_at) VALUES"
" (1, 'ATTEMPT-OLD', 1, 'USB-001', 'succeeded',"
" '2026-08-06T00:00:00Z', '2026-08-06T00:00:00Z',"
" '2026-08-06T00:00:00Z')"
)
finally:
connection.close()
initialize_database(self.db_path)
connection = open_database(self.db_path)
try:
columns = {
row[1] for row in connection.execute("PRAGMA table_info(task_runs)")
}
attempt_id = connection.execute(
"SELECT attempt_id FROM task_runs"
).fetchone()[0]
version = connection.execute("PRAGMA user_version").fetchone()[0]
finally:
connection.close()
self.assertIn("result_data", columns)
self.assertEqual(attempt_id, "ATTEMPT-OLD")
self.assertEqual(version, 2)
def test_initialize_can_run_twice_without_losing_data(self) -> None:
initialize_database(self.db_path)
+51
View File
@@ -6,6 +6,7 @@ import threading
import time
import unittest
from pathlib import Path
from unittest.mock import patch
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
@@ -242,6 +243,56 @@ class PDDTaskPageEventTest(unittest.TestCase):
self.assertEqual(row.price_cents, 3990)
page.deleteLater()
def test_rerun_button_follows_current_row_selection(self):
self._add_task(1)
page = PDDTaskPage()
events = PDDTaskPageEvent(page, self.repository)
events.load_initial_tasks()
page.taskTable.clearSelection()
self.app.processEvents()
self.assertFalse(page.rerunButton.isEnabled())
page.taskTable.selectRow(0)
self.app.processEvents()
self.assertTrue(page.rerunButton.isEnabled())
page.taskTable.clearSelection()
self.app.processEvents()
self.assertFalse(page.rerunButton.isEnabled())
events.shutdown()
page.deleteLater()
def test_confirmed_rerun_executes_selected_terminal_collect_task(self):
self._add_task(1)
first = self.repository.start_collect_run("PDD-001", "USB-001")
event = self.repository.save_collect_result(
"PDD-001", first.attempt_id, FakeCollectResult().to_pdd_data()
)
self.repository.mark_outbox_sent(event.id)
page = PDDTaskPage()
gateway = RecordingClaimGateway()
events = PDDTaskPageEvent(
page,
self.repository,
claim_gateway=gateway,
settings_repository=self._saved_settings(),
collect_service_factory=fake_collect_factory,
)
with patch("src.pdd_ui_event.MessageBox") as message_box:
message_box.return_value.exec.return_value = True
page.rerunRequested.emit("PDD-001")
self.assertTrue(
wait_until(self.app, lambda: not events._claim_busy),
"重新采集线程没有按时结束",
)
detail = self.repository.get_task("PDD-001")
self.assertEqual(detail.status, TaskStatus.SUCCEEDED)
self.assertIn("采集完成", page.statusLabel.text())
self.assertEqual(gateway.calls, [])
events.shutdown()
page.deleteLater()
def test_query_failure_shows_readable_error(self):
page = PDDTaskPage()
events = PDDTaskPageEvent(page, BrokenRepository())
+59 -1
View File
@@ -14,7 +14,7 @@ from src.task_models import (
TaskStatus,
TaskType,
)
from src.task_repository import DuplicateTaskError, TaskRepository
from src.task_repository import CollectRerunError, DuplicateTaskError, TaskRepository
class TaskRepositoryTests(unittest.TestCase):
@@ -209,6 +209,64 @@ class TaskRepositoryTests(unittest.TestCase):
TaskStatus.SUCCEEDED,
)
def test_prepare_rerun_preserves_old_result_and_creates_new_attempt(self):
self.repository.add_claimed_task(self._task("TASK-RERUN"))
first = self.repository.start_collect_run("TASK-RERUN", "USB-001")
first_data = {"goods_id": "10001", "title": "旧标题", "skus": []}
first_event = self.repository.save_collect_result(
"TASK-RERUN", first.attempt_id, first_data
)
self.repository.mark_outbox_sent(first_event.id)
prepared = self.repository.prepare_collect_rerun("TASK-RERUN")
second = self.repository.start_collect_run("TASK-RERUN", "USB-001")
second_data = {"goods_id": "10001", "title": "新标题", "skus": []}
self.repository.save_collect_result(
"TASK-RERUN", second.attempt_id, second_data
)
connection = open_database(self.db_path)
try:
rows = connection.execute(
"SELECT attempt_id, result_data FROM task_runs"
" WHERE task_id = ? ORDER BY attempt_no",
(prepared.id,),
).fetchall()
finally:
connection.close()
self.assertEqual(second.attempt_no, 2)
self.assertIn("旧标题", rows[0]["result_data"])
self.assertIn("新标题", rows[1]["result_data"])
self.assertEqual(self.repository.get_task("TASK-RERUN").title, "新标题")
def test_rerun_rejects_purchase_active_and_unsent_tasks(self):
self.repository.add_claimed_task(
self._task("PURCHASE-RERUN", TaskType.PURCHASE)
)
with self.assertRaisesRegex(CollectRerunError, "采购任务"):
self.repository.validate_collect_rerun("PURCHASE-RERUN")
self.repository.add_claimed_task(self._task("ACTIVE-RERUN"))
with self.assertRaisesRegex(CollectRerunError, "待执行"):
self.repository.validate_collect_rerun("ACTIVE-RERUN")
self.repository.add_claimed_task(self._task("UNSENT-RERUN"))
started = self.repository.start_collect_run("UNSENT-RERUN", "USB-001")
self.repository.save_collect_result(
"UNSENT-RERUN", started.attempt_id, {"title": "结果", "skus": []}
)
connection = open_database(self.db_path)
try:
with connection:
connection.execute(
"UPDATE pdd_tasks SET status = 'failed'"
" WHERE remote_task_id = 'UNSENT-RERUN'"
)
finally:
connection.close()
with self.assertRaisesRegex(CollectRerunError, "未发送"):
self.repository.validate_collect_rerun("UNSENT-RERUN")
def test_recovery_restores_sending_and_interrupted_running(self):
self.repository.add_claimed_task(self._task("TASK-RECOVER"))
started = self.repository.start_collect_run("TASK-RECOVER", "USB-001")