feat: complete transfer and eviction execution

This commit is contained in:
2026-07-23 09:14:40 +00:00
parent 884aed9921
commit b6de490b7e
17 changed files with 1445 additions and 81 deletions
+61 -3
View File
@@ -6,7 +6,11 @@ from unittest.mock import Mock, patch
from uuid import uuid4
from archive_clients.bencode import encode
from archive_clients.jobs import ClientJobExecutor, JobExecutionError
from archive_clients.jobs import (
ClientJobExecutor,
JobExecutionError,
_resource_fingerprint,
)
from archive_clients.resources import normalize_resource
from archive_clients.state import ClientStore
from archive_control.v1 import control_pb2, job_pb2
@@ -90,6 +94,9 @@ class ClientJobHappyPathTests(unittest.TestCase):
definition.created_at.GetCurrentTime()
definition.transfer.requested_files.ranges.add(first=0, last=0)
definition.transfer.transfer_delta_files.ranges.add(first=0, last=0)
definition.transfer.source_fingerprint.CopyFrom(
_resource_fingerprint(resource, source_id)
)
source_store = ClientStore(root / "source.db")
target_store = ClientStore(root / "target.db")
@@ -98,7 +105,7 @@ class ClientJobHappyPathTests(unittest.TestCase):
source_qb = Mock()
source_qb.get_resource.return_value = resource
target_qb = Mock()
target_qb.get_resource.side_effect = [None, resource]
target_qb.get_resource.side_effect = [None, None, resource]
syncthing = CompleteSyncthing()
source = ClientJobExecutor(
client_id=source_id,
@@ -156,7 +163,10 @@ class ClientJobHappyPathTests(unittest.TestCase):
))
self.assertEqual(
[event.sequence for event in events],
[cursor_sequence + 1, cursor_sequence + 2],
list(range(
cursor_sequence + 1,
cursor_sequence + len(events) + 1,
)),
)
cursor_sequence = events[-1].sequence
cursor_revision = events[-1].job_revision
@@ -250,6 +260,54 @@ class ClientJobHappyPathTests(unittest.TestCase):
)
self.assertEqual(store.list_active_job_cursors(), [])
def test_active_source_cancellation_reports_durable_rollback(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
store = ClientStore(root / "client.db")
store.initialize()
definition = job_pb2.JobDefinition(
job_id=str(uuid4()),
idempotency_key=str(uuid4()),
operation=job_pb2.JOB_OPERATION_ARCHIVE,
resource_display_name="fixture",
transfer={
"source_client_id": "cache-1",
"target_client_id": "archive-1",
"route_id": "route-1",
},
)
definition.resource_id.info_hash_v1_hex = "a" * 40
definition.created_at.GetCurrentTime()
executor = ClientJobExecutor(
client_id="cache-1",
qbittorrent=Mock(),
store=store,
qb_root=root,
qb_api_root=Path("/downloads"),
route_path=lambda _: root / "route",
syncthing_transport=Mock(),
sparse_supported=True,
poll_interval=0,
)
executor.assign(control_pb2.AssignJobCommand(
job=definition,
expected_job_revision=1,
expected_last_event_sequence=0,
))
events = executor.cancel(control_pb2.CancelJobCommand(
job_id=definition.job_id,
expected_job_revision=2,
expected_last_event_sequence=1,
reason="test",
))
self.assertEqual(
events[-1].type,
control_pb2.JOB_EVENT_TYPE_ROLLBACK_SUCCEEDED,
)
self.assertEqual(
events[-1].state, job_pb2.JOB_STATE_ROLLING_BACK
)
if __name__ == "__main__":
unittest.main()