feat: 增加任务批量勾选和重新上报 (#89)

This commit is contained in:
chengma
2026-08-10 10:56:48 +08:00
parent d017062d82
commit 34ff39a200
8 changed files with 734 additions and 60 deletions
+169 -6
View File
@@ -10,6 +10,7 @@ from unittest.mock import patch
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from qfluentwidgets import InfoBarPosition
@@ -127,6 +128,25 @@ class RetryableClaimGateway(RecordingClaimGateway):
raise AdminGatewayError("ADMIN_UNAVAILABLE", "Admin 暂时不可用", True)
class RecordingResubmitGateway(RecordingClaimGateway):
"""记录结果重新上报,并可按任务编号模拟失败。"""
def __init__(self, failures=None, delay=0.0):
super().__init__()
self.failures = dict(failures or {})
self.delay = delay
self.submit_calls = []
def submit_result(self, task_id, idempotency_key, result):
self.submit_calls.append((task_id, idempotency_key, result))
if self.delay:
time.sleep(self.delay)
error = self.failures.get(task_id)
if error is not None:
raise error
return SubmissionReceipt(True, f"RESULT-{task_id}", "2026-08-10T08:00:00Z")
class FakeCollectResult:
def to_pdd_data(self):
return {
@@ -249,7 +269,7 @@ class PDDTaskPageEventTest(unittest.TestCase):
"src.pdd_ui_event.AndroidDeviceService.require_connected",
autospec=True,
)
self.device_checker_patch.start()
self.device_checker = self.device_checker_patch.start()
def tearDown(self):
self.device_checker_patch.stop()
@@ -325,24 +345,167 @@ class PDDTaskPageEventTest(unittest.TestCase):
self.assertEqual(row.price_cents, 3990)
page.deleteLater()
def test_rerun_button_follows_current_row_selection(self):
def test_rerun_button_requires_exactly_one_checked_task(self):
self._add_task(1)
self._add_task(2)
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)
page.taskModel.setData(
page.taskModel.index(0, 0), Qt.Checked, Qt.CheckStateRole
)
self.app.processEvents()
self.assertTrue(page.rerunButton.isEnabled())
page.taskTable.clearSelection()
page.taskModel.setData(
page.taskModel.index(1, 0), Qt.Checked, Qt.CheckStateRole
)
self.app.processEvents()
self.assertFalse(page.rerunButton.isEnabled())
events.shutdown()
page.deleteLater()
def test_batch_resubmit_reuses_outbox_and_keeps_skipped_checked(self):
for number in range(1, 4):
self._add_task(number)
expected = {}
for task_id in ("PDD-001", "PDD-002"):
started = self.repository.start_collect_run(task_id, "USB-001")
event = self.repository.save_collect_result(
task_id, started.attempt_id, FakeCollectResult().to_pdd_data()
)
self.repository.mark_outbox_sent(event.id)
expected[task_id] = (event.idempotency_key, event.payload_json)
page = PDDTaskPage()
gateway = RecordingResubmitGateway()
events = PDDTaskPageEvent(
page,
self.repository,
claim_gateway=gateway,
settings_repository=self._saved_settings(),
)
events.load_initial_tasks()
for row in range(3):
page.taskModel.setData(
page.taskModel.index(row, 0), Qt.Checked, Qt.CheckStateRole
)
with patch("src.pdd_ui_event.MessageBox") as message_box:
message_box.return_value.exec.return_value = True
page.resubmitButton.click()
self.assertTrue(
wait_until(self.app, lambda: not events._resubmit_busy),
"重新上报线程没有按时结束",
)
calls = {
task_id: (key, payload)
for task_id, key, payload in gateway.submit_calls
}
self.assertEqual(calls, expected)
self.assertEqual(page.taskModel.checked_task_ids(), ("PDD-003",))
self.assertIn("成功 2 条,失败 0 条,跳过 1 条", page.statusLabel.text())
self.device_checker.assert_not_called()
events.shutdown()
page.deleteLater()
def test_batch_resubmit_records_retryable_and_permanent_failures(self):
for number in range(1, 3):
self._add_task(number)
event_ids = {}
for task_id in ("PDD-001", "PDD-002"):
started = self.repository.start_collect_run(task_id, "USB-001")
event = self.repository.save_collect_result(
task_id, started.attempt_id, FakeCollectResult().to_pdd_data()
)
self.repository.mark_outbox_sent(event.id)
event_ids[task_id] = event.id
gateway = RecordingResubmitGateway(
{
"PDD-001": AdminGatewayError("TEMP", "暂时不可用", True),
"PDD-002": AdminGatewayError("REJECTED", "结果被拒绝", False),
}
)
page = PDDTaskPage()
events = PDDTaskPageEvent(page, self.repository, claim_gateway=gateway)
events.load_initial_tasks()
for row in range(2):
page.taskModel.setData(
page.taskModel.index(row, 0), Qt.Checked, Qt.CheckStateRole
)
with patch("src.pdd_ui_event.MessageBox") as message_box:
message_box.return_value.exec.return_value = True
page.resubmitButton.click()
self.assertTrue(wait_until(self.app, lambda: not events._resubmit_busy))
self.assertEqual(
self.repository.get_outbox_event(event_ids["PDD-001"]).status.value,
"pending",
)
self.assertEqual(
self.repository.get_outbox_event(event_ids["PDD-002"]).status.value,
"failed",
)
self.assertEqual(
set(page.taskModel.checked_task_ids()), {"PDD-001", "PDD-002"}
)
self.assertIn("失败 2 条", page.statusLabel.text())
events.shutdown()
page.deleteLater()
def test_resubmit_confirmation_cancel_does_not_submit(self):
self._add_task(1)
page = PDDTaskPage()
gateway = RecordingResubmitGateway()
events = PDDTaskPageEvent(page, self.repository, claim_gateway=gateway)
events.load_initial_tasks()
page.taskModel.setData(
page.taskModel.index(0, 0), Qt.Checked, Qt.CheckStateRole
)
with patch("src.pdd_ui_event.MessageBox") as message_box:
message_box.return_value.exec.return_value = False
page.resubmitButton.click()
self.assertEqual(gateway.submit_calls, [])
self.assertFalse(events._resubmit_busy)
events.shutdown()
page.deleteLater()
def test_resubmit_rejects_duplicate_start_and_shutdown_waits_worker(self):
self._add_task(1)
started = self.repository.start_collect_run("PDD-001", "USB-001")
event = self.repository.save_collect_result(
"PDD-001", started.attempt_id, FakeCollectResult().to_pdd_data()
)
self.repository.mark_outbox_sent(event.id)
page = PDDTaskPage()
gateway = RecordingResubmitGateway(delay=0.1)
events = PDDTaskPageEvent(page, self.repository, claim_gateway=gateway)
events.load_initial_tasks()
page.taskModel.setData(
page.taskModel.index(0, 0), Qt.Checked, Qt.CheckStateRole
)
with patch("src.pdd_ui_event.MessageBox") as message_box:
message_box.return_value.exec.return_value = True
page.resubmitButton.click()
events.request_resubmit(("PDD-001",))
self.assertTrue(
wait_until(self.app, lambda: bool(gateway.submit_calls))
)
events.shutdown()
self.assertEqual(len(gateway.submit_calls), 1)
self.assertFalse(
events._resubmit_thread and events._resubmit_thread.isRunning()
)
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")