Files
shelfmark/tests/conftest.py
T
splitsec2andCaliBrain f837506017 Merge commit from fork
A signed-in user picks the release fields of POST /api/releases/download, and a record
id with the "oceanofpdf:" prefix reached the provider without handles() or is_enabled().
The provider then fetched source_url, POSTed to the form action found on that page and
fetched the meta-refresh target, with no check on any of them.

The provider now refuses to run when it is switched off, and the book page, the form
action and every POST redirect hop must be on the configured mirror (same scheme, host
and port). The signed file link may sit on another host but must be http(s).

network.ensure_public_destination refuses a host that resolves to a loopback, private,
link-local or otherwise non-global address (IPv4-mapped IPv6 judged by its IPv4 address).
Hosts the operator configured as mirrors and names that do not resolve pass as before.
html_get_page and download_url call it on their first URL and check each redirect hop
through a response hook. The image cache uses the shared address check too.

Co-authored-by: CaliBrain <calibrain@l4n.xyz>
2026-10-21 02:06:31 -04:00

115 lines
3.5 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(autouse=True)
def _public_destination_dns(monkeypatch):
"""Resolve every host to a public address for the destination guard.
The guard looks hosts up before a request. Tests use made-up and real hostnames and
must not depend on the DNS of the machine running them (a filtering resolver answers
``::`` for some mirrors), so the guard's resolver is stubbed. Tests of the guard
itself replace it again.
"""
from shelfmark.download import network
monkeypatch.setattr(
network,
"_system_getaddrinfo",
lambda *_args, **_kwargs: [(2, 1, 6, "", ("93.184.216.34", 0))],
)
@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