feat: 安全移除本地任务记录 (#108)

This commit is contained in:
chengma
2026-08-10 17:23:24 +08:00
parent 9d227b431a
commit b2721071c9
10 changed files with 619 additions and 30 deletions
+119 -1
View File
@@ -14,7 +14,12 @@ from src.task_models import (
TaskStatus,
TaskType,
)
from src.task_repository import CollectRerunError, DuplicateTaskError, TaskRepository
from src.task_repository import (
CollectRerunError,
DuplicateTaskError,
TaskRemovalError,
TaskRepository,
)
class TaskRepositoryTests(unittest.TestCase):
@@ -26,6 +31,17 @@ class TaskRepositoryTests(unittest.TestCase):
def tearDown(self) -> None:
self._temporary_directory.cleanup()
def _set_task_status(self, task_id: str, status: TaskStatus) -> None:
connection = open_database(self.db_path)
try:
with connection:
connection.execute(
"UPDATE pdd_tasks SET status = ? WHERE remote_task_id = ?",
(status.value, task_id),
)
finally:
connection.close()
@staticmethod
def _task(
remote_task_id: str,
@@ -516,6 +532,108 @@ class TaskRepositoryTests(unittest.TestCase):
OutboxStatus.PENDING,
)
def test_terminal_task_is_soft_removed_but_audit_record_remains(self):
self.repository.add_claimed_task(self._task("REMOVE-OK"))
self._set_task_status("REMOVE-OK", TaskStatus.SUCCEEDED)
removed_count = self.repository.remove_tasks_from_list(("REMOVE-OK",))
self.assertEqual(removed_count, 1)
self.assertEqual(self.repository.list_tasks(), [])
self.assertEqual(self.repository.count_tasks(), 0)
self.assertIsNotNone(self.repository.get_task("REMOVE-OK"))
connection = open_database(self.db_path)
try:
row = connection.execute(
"SELECT removed_at FROM pdd_tasks"
" WHERE remote_task_id = 'REMOVE-OK'"
).fetchone()
finally:
connection.close()
self.assertIsNotNone(row[0])
with self.assertRaises(DuplicateTaskError):
self.repository.add_claimed_task(self._task("REMOVE-OK"))
def test_batch_remove_is_all_or_nothing_for_non_terminal_task(self):
self.repository.add_claimed_task(self._task("REMOVE-ENDED"))
self.repository.add_claimed_task(self._task("REMOVE-RUNNING"))
self._set_task_status("REMOVE-ENDED", TaskStatus.FAILED)
self._set_task_status("REMOVE-RUNNING", TaskStatus.RUNNING)
with self.assertRaisesRegex(TaskRemovalError, "尚未结束"):
self.repository.remove_tasks_from_list(
("REMOVE-ENDED", "REMOVE-RUNNING")
)
self.assertEqual(self.repository.count_tasks(), 2)
def test_task_with_unsent_outbox_cannot_be_removed(self):
self.repository.add_claimed_task(self._task("REMOVE-UNSENT"))
started = self.repository.start_collect_run("REMOVE-UNSENT", "USB-001")
self.repository.save_collect_failure(
"REMOVE-UNSENT",
started.attempt_id,
TaskStatus.FAILED,
"TEST_FAILURE",
"测试失败",
False,
)
with self.assertRaisesRegex(TaskRemovalError, "未发送完成"):
self.repository.remove_tasks_from_list(("REMOVE-UNSENT",))
self.assertEqual(self.repository.count_tasks(), 1)
def test_irreversible_task_cannot_be_removed_even_when_terminal(self):
self.repository.add_claimed_task(
self._task(
"REMOVE-IRREVERSIBLE",
TaskType.PURCHASE,
execution_mode="live",
)
)
started = self.repository.start_purchase_run(
"REMOVE-IRREVERSIBLE", "USB-001"
)
self.repository.mark_purchase_irreversible(
"REMOVE-IRREVERSIBLE", started.attempt_id
)
self._set_task_status("REMOVE-IRREVERSIBLE", TaskStatus.SUCCEEDED)
with self.assertRaisesRegex(TaskRemovalError, "不可逆阶段"):
self.repository.remove_tasks_from_list(("REMOVE-IRREVERSIBLE",))
self.assertEqual(self.repository.count_tasks(), 1)
def test_sent_outbox_does_not_block_soft_remove(self):
self.repository.add_claimed_task(self._task("REMOVE-SENT"))
started = self.repository.start_collect_run("REMOVE-SENT", "USB-001")
event = self.repository.save_collect_result(
"REMOVE-SENT",
started.attempt_id,
{"goods_id": "10001", "title": "测试商品", "skus": []},
)
self.repository.mark_outbox_sent(event.id)
self.assertEqual(
self.repository.remove_tasks_from_list(("REMOVE-SENT",)), 1
)
self.assertEqual(self.repository.count_tasks(), 0)
connection = open_database(self.db_path)
try:
run_count = connection.execute(
"SELECT COUNT(*) FROM task_runs r JOIN pdd_tasks t"
" ON t.id = r.task_id WHERE t.remote_task_id = 'REMOVE-SENT'"
).fetchone()[0]
outbox_row = connection.execute(
"SELECT o.status FROM outbox_events o JOIN pdd_tasks t"
" ON t.id = o.task_id WHERE t.remote_task_id = 'REMOVE-SENT'"
).fetchone()
finally:
connection.close()
self.assertEqual(run_count, 1)
self.assertEqual(outbox_row[0], OutboxStatus.SENT.value)
if __name__ == "__main__":
unittest.main()