fix: resolve per-torrent qb save paths
This commit is contained in:
+64
-3
@@ -4,11 +4,13 @@ import stat
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from dataclasses import replace
|
||||
from pathlib import Path, PurePosixPath
|
||||
from unittest.mock import Mock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
from archive_clients.bencode import encode
|
||||
from archive_clients.config import RootMapping
|
||||
from archive_clients.jobs import (
|
||||
ClientJobExecutor,
|
||||
JobExecutionError,
|
||||
@@ -246,6 +248,55 @@ class ClientJobHappyPathTests(unittest.TestCase):
|
||||
content=b"x" * 4096,
|
||||
)
|
||||
|
||||
def test_nested_qb_save_path_stages_from_mapped_subdirectory(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
self._run_transfer(
|
||||
Path(directory),
|
||||
job_pb2.JOB_OPERATION_UNARCHIVE,
|
||||
source_save_path="/downloads/Downloading",
|
||||
)
|
||||
|
||||
def test_qb_save_path_preflight_rejects_unmapped_path(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
executor = ClientJobExecutor(
|
||||
client_id="cache-1", qbittorrent=Mock(),
|
||||
store=ClientStore(root / "state.db"), qb_root=root,
|
||||
qb_api_root=PurePosixPath("/downloads"),
|
||||
route_path=lambda _: root, syncthing_transport=Mock(),
|
||||
sparse_supported=True,
|
||||
)
|
||||
resource = NormalizedResource(
|
||||
resource_pb2.ResourceSummary(), (), Mock(),
|
||||
save_path=PurePosixPath("/outside"),
|
||||
)
|
||||
with self.assertRaisesRegex(JobExecutionError, "outside"):
|
||||
executor._resource_root(resource)
|
||||
|
||||
def test_qb_save_path_override_uses_its_dedicated_local_mount(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
primary = root / "primary"
|
||||
override = root / "override"
|
||||
primary.mkdir()
|
||||
override.mkdir()
|
||||
executor = ClientJobExecutor(
|
||||
client_id="cache-1", qbittorrent=Mock(),
|
||||
store=ClientStore(root / "state.db"), qb_root=primary,
|
||||
qb_api_root=PurePosixPath("/downloads"),
|
||||
qb_roots=RootMapping(
|
||||
PurePosixPath("/downloads"), primary,
|
||||
((PurePosixPath("/downloads/slow"), override),),
|
||||
),
|
||||
route_path=lambda _: root, syncthing_transport=Mock(),
|
||||
sparse_supported=True,
|
||||
)
|
||||
resource = NormalizedResource(
|
||||
resource_pb2.ResourceSummary(), (), Mock(),
|
||||
save_path=PurePosixPath("/downloads/slow"),
|
||||
)
|
||||
self.assertEqual(executor._resource_root(resource), override)
|
||||
|
||||
def test_mount_boundary_requires_copy_space_even_with_same_device(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
@@ -390,6 +441,7 @@ class ClientJobHappyPathTests(unittest.TestCase):
|
||||
source_stage_free_bytes: int | None = None,
|
||||
content: bytes = b"archive-control-happy-path",
|
||||
syncthing: CompleteSyncthing | None = None,
|
||||
source_save_path: str = "/downloads",
|
||||
):
|
||||
source_root = root / "source"
|
||||
target_root = root / "target"
|
||||
@@ -397,7 +449,12 @@ class ClientJobHappyPathTests(unittest.TestCase):
|
||||
source_root.mkdir()
|
||||
target_root.mkdir()
|
||||
route_root.mkdir()
|
||||
(source_root / "fixture.bin").write_bytes(content)
|
||||
save_relative = PurePosixPath(source_save_path).relative_to(
|
||||
PurePosixPath("/downloads")
|
||||
)
|
||||
source_content_root = source_root.joinpath(*save_relative.parts)
|
||||
source_content_root.mkdir(parents=True, exist_ok=True)
|
||||
(source_content_root / "fixture.bin").write_bytes(content)
|
||||
info = {
|
||||
b"length": len(content),
|
||||
b"name": b"fixture.bin",
|
||||
@@ -411,6 +468,7 @@ class ClientJobHappyPathTests(unittest.TestCase):
|
||||
"hash": torrent_hash,
|
||||
"name": "fixture.bin",
|
||||
"state": "uploading",
|
||||
"save_path": source_save_path,
|
||||
},
|
||||
[{
|
||||
"index": 0,
|
||||
@@ -454,8 +512,11 @@ class ClientJobHappyPathTests(unittest.TestCase):
|
||||
source_qb = Mock()
|
||||
source_qb.get_resource.return_value = resource
|
||||
target_qb = Mock()
|
||||
target_resource = replace(
|
||||
resource, save_path=PurePosixPath("/downloads")
|
||||
)
|
||||
target_qb.get_resource.side_effect = [
|
||||
None, None, resource, resource,
|
||||
None, None, target_resource, target_resource,
|
||||
]
|
||||
syncthing = syncthing or CompleteSyncthing()
|
||||
source = ClientJobExecutor(
|
||||
|
||||
Reference in New Issue
Block a user