Fix torrent post-import category follow-ups and clear the lint backlog (#1154)

rTorrent set_category and remove now uppercase the info hash, which
reaches us lowercase while rTorrent's XML-RPC lookups are case
sensitive. remove() had this bug before #1148, making
PROWLARR_TORRENT_ACTION=remove a silent no-op for rTorrent.

Transmission set_category appends the post-import label instead of
replacing the whole label list. The unsupported-client path in
post_process_cleanup logs at debug instead of warning, so Real-Debrid
and AllDebrid users stop seeing a warning on every successful import.

The Real-Debrid and AllDebrid clients now follow the conventions used by
the other clients (_raise_runtime_error helpers, narrow error tuples,
ClassVar, Path.open), and register_client is generic over a TypeVar
bound to DownloadClient so decorated classes keep their concrete type.
make fix and the lint, format and typecheck targets all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
CaliBrain
2026-07-31 00:17:28 -04:00
committed by GitHub
co-authored by Claude Opus 5
parent 10554444a9
commit 21f2b6b95c
10 changed files with 220 additions and 122 deletions
+7 -5
View File
@@ -33,8 +33,9 @@ def test_set_category_updates_custom1_label():
client = RTorrentClient.__new__(RTorrentClient)
client._rpc = MagicMock()
# rTorrent lookups are case sensitive and info hashes reach us lowercase.
assert client.set_category("abc123", "imported") is True
client._rpc.d.custom1.set.assert_called_once_with("abc123", "imported")
client._rpc.d.custom1.set.assert_called_once_with("ABC123", "imported")
class TestRTorrentClientIsConfigured:
@@ -779,8 +780,9 @@ class TestRTorrentClientRemove:
result = client.remove("abc123def456", delete_files=False)
assert result is True
mock_rpc.d.stop.assert_called_once_with("abc123def456")
mock_rpc.d.erase.assert_called_once_with("abc123def456")
# rTorrent lookups are case sensitive and info hashes reach us lowercase.
mock_rpc.d.stop.assert_called_once_with("ABC123DEF456")
mock_rpc.d.erase.assert_called_once_with("ABC123DEF456")
def test_remove_with_files(self, monkeypatch):
"""Test torrent removal with file deletion."""
@@ -813,8 +815,8 @@ class TestRTorrentClientRemove:
result = client.remove("abc123def456", delete_files=True)
assert result is True
mock_rpc.d.delete_tied.assert_called_once_with("abc123def456")
mock_rpc.d.erase.assert_called_once_with("abc123def456")
mock_rpc.d.delete_tied.assert_called_once_with("ABC123DEF456")
mock_rpc.d.erase.assert_called_once_with("ABC123DEF456")
def test_remove_failure(self, monkeypatch):
"""Test failed torrent removal."""
+2
View File
@@ -776,6 +776,8 @@ class TestMamLanguageCoverage:
}
for tag, expected in cases.items():
assert _extract_mam_language(f"Book [{tag} / M4B]") == expected, tag
class _MultiIndexerClient:
"""Torznab client where each indexer entry returns its own result set.
+32 -1
View File
@@ -68,11 +68,27 @@ def create_mock_transmission_rpc_module():
return mock_module
def test_set_category_replaces_labels():
def _transmission_client_with_labels(labels):
from shelfmark.download.clients.transmission import TransmissionClient
client = TransmissionClient.__new__(TransmissionClient)
client._client = MagicMock()
client._client.get_torrent.return_value = types.SimpleNamespace(labels=labels)
return client
def test_set_category_appends_to_existing_labels():
client = _transmission_client_with_labels(["books", "seeding"])
assert client.set_category("abc123", "imported") is True
client._client.change_torrent.assert_called_once_with(
ids="abc123",
labels=["books", "seeding", "imported"],
)
def test_set_category_adds_label_when_torrent_has_none():
client = _transmission_client_with_labels([])
assert client.set_category("abc123", "imported") is True
client._client.change_torrent.assert_called_once_with(
@@ -81,6 +97,21 @@ def test_set_category_replaces_labels():
)
def test_set_category_is_a_noop_when_label_already_present():
client = _transmission_client_with_labels(["books", "imported"])
assert client.set_category("abc123", "imported") is True
client._client.change_torrent.assert_not_called()
def test_set_category_does_not_clobber_labels_when_lookup_fails():
client = _transmission_client_with_labels([])
client._client.get_torrent.side_effect = ValueError("boom")
assert client.set_category("abc123", "imported") is False
client._client.change_torrent.assert_not_called()
class TestTransmissionClientIsConfigured:
"""Tests for TransmissionClient.is_configured()."""