mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-07-31 06:46:39 -04:00
## Summary Fixes Prowlarr torrent downloads that fail with `Could not determine torrent hash from URL` when the result has no magnet link and no infohash (e.g. MyAnonaMouse), where fetching the .torrent from Prowlarr's proxy download link is the only path. Two problems compounded here: 1. **Every add attempt fetched the download link twice.** `find_existing()` prefetched the .torrent to compute a dedup hash, discarded the result, and `add_download()` fetched the same URL again seconds later. Private tracker links behind Prowlarr's proxy can be slow, rate-limited, or effectively single-use, so the second hit could fail even when the link itself was valid — which is why the reporter's manual fetch of the same URL succeeded. 2. **The real failure reason was invisible.** When the fetch failed (e.g. Prowlarr returning HTTP 500 because the tracker rejected the request — see the 2026-07-07 MAM report on #476, which turned out to be a MAM IP-settings problem), the reason was logged at DEBUG only and the user saw the misleading generic hash error. ## What changed - `extract_torrent_info()` now reuses a recent successful fetch of the same URL (short-TTL in-memory cache, successes only), so one add attempt hits the tracker download link exactly once across `find_existing()` + `add_download()`. All four torrent clients (qBittorrent, Deluge, Transmission, rTorrent) share this path and benefit. Failures are never cached, so retries refetch. - `TorrentInfo` gains a `fetch_error` field. qBittorrent and rTorrent append it to the hash error (`... (torrent file fetch failed: 500 Server Error ...)`), Deluge to its "Failed to fetch torrent file" error. The enriched message still contains the exact substring the #1109 expired-link refresh hook matches on, so the refresh-and-retry path keeps working. - Torrent fetch failures are logged at WARNING instead of DEBUG, so non-debug logs show the cause. ## Validation - `uv run pytest tests/prowlarr tests/download -q` — 498 passed - `uv run pytest tests/newznab tests/audiobookbay -q` — 147 passed - `uv run ruff check` / `ruff format --check` on all changed files - New tests: fetch-cache reuse, failure-not-cached + reason capture, expected-hash fallback on failed/hashless fetches, TTL expiry, magnet-redirect reuse, and the enriched qBittorrent error message. Fixes #1111
97 lines
2.9 KiB
Python
97 lines
2.9 KiB
Python
"""
|
|
Pytest configuration and shared fixtures.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
# Set environment variables BEFORE importing the application
|
|
# These override the defaults that try to use system paths like /var/log
|
|
_temp_base = tempfile.mkdtemp(prefix="cwabd_test_")
|
|
|
|
# LOG_ROOT is the base - LOG_DIR is computed as LOG_ROOT / "shelfmark"
|
|
# So we set LOG_ROOT to our temp directory to get LOG_DIR = _temp_base/shelfmark
|
|
os.environ["LOG_ROOT"] = _temp_base
|
|
os.environ["CONFIG_DIR"] = os.path.join(_temp_base, "config")
|
|
os.environ["INGEST_DIR"] = os.path.join(_temp_base, "ingest")
|
|
os.environ["TMP_DIR"] = os.path.join(_temp_base, "tmp")
|
|
|
|
# Create the directories that will be used
|
|
os.makedirs(os.path.join(_temp_base, "shelfmark"), exist_ok=True) # LOG_DIR
|
|
os.makedirs(os.path.join(_temp_base, "config"), exist_ok=True)
|
|
os.makedirs(os.path.join(_temp_base, "ingest"), exist_ok=True)
|
|
os.makedirs(os.path.join(_temp_base, "tmp"), exist_ok=True)
|
|
|
|
# Add the project root to Python path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_torrent_fetch_cache():
|
|
"""Keep the shared torrent fetch cache from leaking between tests."""
|
|
from shelfmark.download.clients.torrent_utils import clear_torrent_fetch_cache
|
|
|
|
clear_torrent_fetch_cache()
|
|
yield
|
|
clear_torrent_fetch_cache()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_prowlarr_result():
|
|
"""Sample Prowlarr API search result."""
|
|
return {
|
|
"guid": "abc123-guid",
|
|
"title": "The Great Book by Author Name.epub",
|
|
"indexer": "MyIndexer",
|
|
"protocol": "torrent",
|
|
"size": 5242880, # 5 MB
|
|
"downloadUrl": "magnet:?xt=urn:btih:abc123",
|
|
"infoUrl": "https://example.com/book/123",
|
|
"seeders": 10,
|
|
"leechers": 2,
|
|
"publishDate": "2024-01-15T12:00:00Z",
|
|
"categories": [{"id": 7020, "name": "Books/EBook"}],
|
|
"indexerId": 1,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_nzb_result():
|
|
"""Sample Prowlarr API NZB result."""
|
|
return {
|
|
"guid": "nzb456-guid",
|
|
"title": "Another Book [PDF] by Writer",
|
|
"indexer": "NZBIndexer",
|
|
"protocol": "usenet",
|
|
"size": 10485760, # 10 MB
|
|
"downloadUrl": "https://example.com/download.nzb",
|
|
"infoUrl": "https://example.com/nzb/456",
|
|
"grabs": 50,
|
|
"publishDate": "2024-02-20T10:30:00Z",
|
|
"categories": [{"id": 7020, "name": "Books/EBook"}],
|
|
"indexerId": 2,
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_config(monkeypatch):
|
|
"""Fixture to mock config values."""
|
|
config_values = {}
|
|
|
|
def mock_get(key, default=""):
|
|
return config_values.get(key, default)
|
|
|
|
def set_config(key, value):
|
|
config_values[key] = value
|
|
|
|
# Create a mock config module
|
|
class MockConfig:
|
|
get = staticmethod(mock_get)
|
|
set = staticmethod(set_config)
|
|
_values = config_values
|
|
|
|
return MockConfig
|