feat: 增加采集任务重新执行入口 (#63)
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user