mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-05 10:11:12 +01:00
## Backport bug fixes from `NemesisHubris/litfinder` Forwards a curated set of bug fixes from [NemesisHubris/litfinder](https://github.com/NemesisHubris/litfinder) — a community fork of this project — that address open issues here. All commits preserve original authorship via `git cherry-pick`; this PR is a backport rather than original work. Each fix has been reviewed locally, lint/format-cleaned to match this repo's existing ruff config, and verified with the test suite. Rebrand strings, license switches, and features have been deliberately excluded. ### Upstream issues addressed - **#999** — Mirror URLs with query params no longer break search requests (strip query string/fragment in `normalize_http_url`) - **#956** — Apprise notifications now respect the configured proxy (proxy env vars injected before dispatch) - **#1025** — rTorrent: separate `RTORRENT_AUDIOBOOK_LABEL` setting, falls back to book label if unset - **#1010** — Stop button in Activity no longer makes the panel disappear (snapshot refresh on cancel) - **#1021** — Anna's Archive slow-download countdown now caps retries instead of looping forever - **#1040** — Empty destination directory cleaned up when write probe fails - **PR #1031** — Language detection from Anna's Archive distant path when listing metadata is missing ### Additional fixes (no open issue but clear bugs) - **fix: Python 2 `except` syntax across 27 files** — `except X, Y:` is a SyntaxError in Python 3 and prevents affected modules from importing at runtime. Mechanical sweep to `except (X, Y):`. - **fix(abb): info hash validation with magnet fallback** — adds SHA-1/SHA-256 hex validation on extracted info hashes; falls back to scanning the full page for a magnet link (e.g. posted in comments) when the table value is malformed. Also extends the exact-phrase fallback to manual queries and defaults the ABB listing language to `en` when missing, preventing valid results from being hidden by the language filter. Includes a small test-fixture fix (`test(abb): use valid hex info hashes in scraper test fixtures`) since the existing fixtures used non-hex placeholders that the new validation correctly rejects. - **fix: Anna's Archive title parser** — handles nested edition spans and filters `lgli` catalog descriptor entries (e.g. "Book/Online Audio") that were polluting search results. ### Deliberately not included - LitFinder rebranding (UI strings, Apprise app ID, logo). The `fix: three upstream bugs` commit (#999/#956/#1025) was cherry-picked with Apprise app-id, description, and logo-URL strings reverted from "LitFinder" back to "Shelfmark"; noted in the commit body. - Features from the LitFinder fork (multi-variant title search, multi-book flat-folder grouping, fuzzy text matching, "Leave in Place" output handler, admin display name, custom-source plugin system). These are larger behavior changes that each warrant their own focused review — happy to send any of them separately if of interest. - LitFinder-specific test environment and CI infrastructure. ### Verification - Backend: **1879 passed**, 96 skipped (1 preexisting failure on `seleniumbase`-dependent test in local venv; runs fine in the standard Docker image with the `browser` extra) - Lint, format, dead-code: all clean against this repo's existing ruff/vulture config - One follow-up cleanup commit (`style: ruff lint and format fixes for ported commits`) brings the cherry-picked code into compliance with this repo's ruff settings — no behavior changes there ### Etiquette / credit Per-commit authorship preserved by cherry-pick. The only edits to the original commits are: - `fix: three upstream bugs` — Apprise rebrand strings reverted to "Shelfmark" (noted in commit body, original author retained as `Co-Authored-By` via cherry-pick) - One follow-up `style:` commit for ruff config alignment Big thanks to [@NemesisHubris](https://github.com/NemesisHubris) for the original work in LitFinder; this PR exists to make sure these fixes reach Shelfmark's wider user base. Happy to revise scope, split into smaller PRs, or split off the Py2 cleanup separately if that's preferable. --------- Co-authored-by: NemesisHubris <155838970+NemesisHubris@users.noreply.github.com> Co-authored-by: CaliBrain <calibrain@l4n.xyz>
460 lines
19 KiB
Python
460 lines
19 KiB
Python
"""
|
|
Tests for AudiobookBay scraper functions.
|
|
"""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from shelfmark.release_sources.audiobookbay import scraper
|
|
|
|
# Mock HTML based on real ABB structure
|
|
SAMPLE_SEARCH_HTML = """
|
|
<html>
|
|
<body>
|
|
<div class="post">
|
|
<div class="postTitle"><h2><a href="/abss/test-book-title-by-author/" rel="bookmark">Test Book Title - Test Author</a></h2></div>
|
|
<div class="postInfo">Category: Genre <br>Language: English<span style="margin-left:100px;">Keywords: Test Keywords </span><br></div>
|
|
<div class="postContent">
|
|
<div class="center">
|
|
<p class="center">Shared by:<a href="/member/users/index?&mode=userinfo&username=testuser">testuser</a></p>
|
|
<p class="center"><a href="https://audiobookbay.lu/abss/test-book-title-by-author/"><img src="https://example.com/cover.jpg" alt="Test Cover" width="250"></a></p>
|
|
</div>
|
|
<p style="text-align:center;">Posted: 01 Jan 2024<br>Format: <span style="color:#a00;">M4B</span> / Bitrate: <span style="color:#a00;">128 Kbps</span><br>File Size: <span style="color:#00f;">500.00</span> MBs</p>
|
|
</div>
|
|
<div class="postMeta">
|
|
<span class="postLink"><a href="https://audiobookbay.lu/abss/test-book-title-by-author/">Audiobook Details</a></span>
|
|
<span class="postComments"><a href="/dload-now?ll=test" rel="nofollow">Direct Download</a></span>
|
|
</div>
|
|
</div>
|
|
<div class="post">
|
|
<div class="postTitle"><h2><a href="/abss/another-test-book/" rel="bookmark">Another Test Book - Another Author</a></h2></div>
|
|
<div class="postInfo">Category: Fiction <br>Language: Spanish<span style="margin-left:100px;">Keywords: Test </span><br></div>
|
|
<div class="postContent">
|
|
<div class="center">
|
|
<p class="center">Shared by:<a href="/member/users/index?&mode=userinfo&username=user2">user2</a></p>
|
|
<p class="center"><a href="https://audiobookbay.lu/abss/another-test-book/"><img src="https://example.com/cover2.jpg" alt="Cover 2" width="250"></a></p>
|
|
</div>
|
|
<p style="text-align:center;">Posted: 15 Nov 2023<br>Format: <span style="color:#a00;">MP3</span> / Bitrate: <span style="color:#a00;">256 Kbps</span><br>File Size: <span style="color:#00f;">1.01</span> GBs</p>
|
|
</div>
|
|
<div class="postMeta">
|
|
<span class="postLink"><a href="https://audiobookbay.lu/abss/another-test-book/">Audiobook Details</a></span>
|
|
<span class="postComments"><a href="/dload-now?ll=test2" rel="nofollow">Direct Download</a></span>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
EMPTY_SEARCH_HTML = """
|
|
<html>
|
|
<body>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Mock HTML for detail page with info hash and trackers
|
|
SAMPLE_DETAIL_HTML = """
|
|
<html>
|
|
<body>
|
|
<table>
|
|
<tr>
|
|
<td>Info Hash</td>
|
|
<td>ABC123DEF456789012345678901234567890ABCD</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Tracker 1</td>
|
|
<td>udp://tracker.openbittorrent.com:80</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Tracker 2</td>
|
|
<td>http://tracker.example.com:8080</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Other Info</td>
|
|
<td>Some other data</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
DETAIL_HTML_NO_TRACKERS = """
|
|
<html>
|
|
<body>
|
|
<table>
|
|
<tr>
|
|
<td>Info Hash</td>
|
|
<td>ABC123DEF456789012345678901234567890ABCD</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
|
|
class TestSearchAudiobookbay:
|
|
"""Tests for the search_audiobookbay function."""
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_success(self, mock_config_get, mock_html_get):
|
|
"""Test successful search with results."""
|
|
mock_config_get.return_value = 1.0 # rate_limit_delay
|
|
mock_html_get.return_value = (
|
|
SAMPLE_SEARCH_HTML,
|
|
"https://audiobookbay.lu/page/1/?s=test+query&cat=undefined%2Cundefined",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test query", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 2
|
|
assert results[0]["title"] == "Test Book Title - Test Author"
|
|
assert results[0]["link"] == "https://audiobookbay.lu/abss/test-book-title-by-author/"
|
|
assert results[0]["language"] == "English"
|
|
assert results[0]["format"] == "M4B"
|
|
assert results[0]["bitrate"] == "128 Kbps"
|
|
assert results[0]["size"] == "500.00 MB"
|
|
assert results[0]["posted_date"] == "01 Jan 2024"
|
|
assert results[0]["cover"] == "https://example.com/cover.jpg"
|
|
|
|
assert results[1]["title"] == "Another Test Book - Another Author"
|
|
assert results[1]["language"] == "Spanish"
|
|
assert results[1]["format"] == "MP3"
|
|
assert results[1]["size"] == "1.01 GB"
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_pagination(self, mock_config_get, mock_html_get):
|
|
"""Test pagination through multiple pages."""
|
|
mock_config_get.return_value = 0.0 # No delay for faster tests
|
|
mock_html_get.side_effect = [
|
|
(EMPTY_SEARCH_HTML, "https://audiobookbay.lu/"), # Session bootstrap
|
|
(
|
|
SAMPLE_SEARCH_HTML,
|
|
"https://audiobookbay.lu/page/1/?s=test&cat=undefined%2Cundefined",
|
|
),
|
|
(EMPTY_SEARCH_HTML, "https://audiobookbay.lu/page/2/?s=test&cat=undefined%2Cundefined"),
|
|
]
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=2, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 2 # Only from first page
|
|
assert mock_html_get.call_count == 3
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_page_one_uses_root_search_endpoint(
|
|
self, mock_config_get, mock_html_get
|
|
):
|
|
"""Test page 1 search uses ABB root endpoint instead of /page/1/."""
|
|
mock_config_get.return_value = 0.0
|
|
mock_html_get.return_value = (
|
|
SAMPLE_SEARCH_HTML,
|
|
"https://audiobookbay.lu/?s=test",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 2
|
|
requested_url = mock_html_get.call_args.args[0]
|
|
assert requested_url.startswith("https://audiobookbay.lu/?s=test")
|
|
assert "/page/1/" not in requested_url
|
|
assert "cat=undefined%2Cundefined" in requested_url
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_bootstraps_and_reuses_session(
|
|
self, mock_config_get, mock_html_get
|
|
):
|
|
"""Test ABB search initializes and reuses a request session for cookie continuity."""
|
|
mock_config_get.return_value = 0.0
|
|
mock_html_get.side_effect = [
|
|
("", "https://audiobookbay.lu/"), # Bootstrap attempt
|
|
(SAMPLE_SEARCH_HTML, "https://audiobookbay.lu/?s=test&cat=undefined%2Cundefined"),
|
|
]
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 2
|
|
assert mock_html_get.call_count == 2
|
|
bootstrap_call = mock_html_get.call_args_list[0]
|
|
search_call = mock_html_get.call_args_list[1]
|
|
assert bootstrap_call.args[0] == "https://audiobookbay.lu/"
|
|
assert search_call.args[0].startswith("https://audiobookbay.lu/?s=test")
|
|
assert bootstrap_call.kwargs["session"] is not None
|
|
assert search_call.kwargs["session"] is bootstrap_call.kwargs["session"]
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_empty(self, mock_config_get, mock_html_get):
|
|
"""Test search with no results."""
|
|
mock_config_get.return_value = 1.0
|
|
mock_html_get.return_value = (
|
|
EMPTY_SEARCH_HTML,
|
|
"https://audiobookbay.lu/page/1/?s=test&cat=undefined%2Cundefined",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 0
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_error_non_200(self, mock_config_get, mock_html_get):
|
|
"""Test error handling for non-200 status code."""
|
|
mock_config_get.return_value = 1.0
|
|
mock_html_get.return_value = (
|
|
"",
|
|
"https://audiobookbay.lu/page/1/?s=test&cat=undefined%2Cundefined",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 0
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_redirect_to_homepage(self, mock_config_get, mock_html_get):
|
|
"""Test handling redirect to homepage (blocked/invalid search)."""
|
|
mock_config_get.return_value = 1.0
|
|
mock_html_get.return_value = (
|
|
EMPTY_SEARCH_HTML,
|
|
"https://audiobookbay.lu", # Redirected to homepage
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 0
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_request_exception(self, mock_config_get, mock_html_get):
|
|
"""Test handling request exceptions."""
|
|
mock_config_get.return_value = 1.0
|
|
mock_html_get.return_value = (
|
|
"",
|
|
"https://audiobookbay.lu/page/1/?s=test&cat=undefined%2Cundefined",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 0
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_relative_link(self, mock_config_get, mock_html_get):
|
|
"""Test handling relative links in results."""
|
|
mock_config_get.return_value = 1.0
|
|
|
|
html_with_relative_link = """
|
|
<div class="post">
|
|
<div class="postTitle"><h2><a href="/abss/relative-link/">Test Book</a></h2></div>
|
|
<div class="postInfo">Language: English</div>
|
|
<div class="postContent">
|
|
<p style="text-align:center;">Posted: 01 Jan 2024<br>Format: M4B<br>File Size: 100 MBs</p>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
mock_html_get.return_value = (
|
|
html_with_relative_link,
|
|
"https://audiobookbay.lu/page/1/?s=test&cat=undefined%2Cundefined",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 1
|
|
assert results[0]["link"] == "https://audiobookbay.lu/abss/relative-link/"
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_protocol_relative_links(self, mock_config_get, mock_html_get):
|
|
"""Test protocol-relative links are normalized without duplicating hostname."""
|
|
mock_config_get.return_value = 0.0
|
|
|
|
html_with_protocol_relative_links = """
|
|
<div class="post">
|
|
<div class="postTitle"><h2><a href="//audiobookbay.lu/abss/protocol-relative/">Protocol Relative Book</a></h2></div>
|
|
<div class="postInfo">Language: English</div>
|
|
<div class="postContent">
|
|
<div class="center">
|
|
<img src="//audiobookbay.lu/wp-content/uploads/cover.jpg" alt="Cover" />
|
|
</div>
|
|
<p style="text-align:center;">Posted: 01 Jan 2024<br>Format: M4B<br>File Size: 100 MBs</p>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
mock_html_get.return_value = (
|
|
html_with_protocol_relative_links,
|
|
"https://audiobookbay.lu/?s=test",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 1
|
|
assert results[0]["link"] == "https://audiobookbay.lu/abss/protocol-relative/"
|
|
assert results[0]["cover"] == "https://audiobookbay.lu/wp-content/uploads/cover.jpg"
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_exact_phrase_query(self, mock_config_get, mock_html_get):
|
|
"""Test exact phrase wrapping and encoding in search URL."""
|
|
mock_config_get.return_value = 0.0
|
|
mock_html_get.return_value = (
|
|
SAMPLE_SEARCH_HTML,
|
|
"https://audiobookbay.lu/page/1/?s=%22test+query%22",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay(
|
|
"test query",
|
|
max_pages=1,
|
|
hostname="audiobookbay.lu",
|
|
exact_phrase=True,
|
|
)
|
|
|
|
assert len(results) == 2
|
|
requested_url = mock_html_get.call_args.args[0]
|
|
assert "s=%22test+query%22" in requested_url
|
|
assert "cat=undefined%2Cundefined" in requested_url
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.config.get")
|
|
def test_search_audiobookbay_always_uses_legacy_category_query(
|
|
self, mock_config_get, mock_html_get
|
|
):
|
|
"""Test ABB search always includes legacy category query and does not fallback."""
|
|
mock_config_get.return_value = 0.0
|
|
mock_html_get.return_value = (
|
|
"",
|
|
"https://audiobookbay.lu/?s=test&cat=undefined%2Cundefined",
|
|
)
|
|
|
|
results = scraper.search_audiobookbay("test", max_pages=1, hostname="audiobookbay.lu")
|
|
|
|
assert len(results) == 0
|
|
assert mock_html_get.call_count >= 2
|
|
search_urls = [
|
|
call.args[0] for call in mock_html_get.call_args_list if "?s=test" in call.args[0]
|
|
]
|
|
assert search_urls
|
|
assert all(
|
|
url == "https://audiobookbay.lu/?s=test&cat=undefined%2Cundefined"
|
|
for url in search_urls
|
|
)
|
|
|
|
|
|
class TestExtractMagnetLink:
|
|
"""Tests for the extract_magnet_link function."""
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_success(self, mock_html_get):
|
|
"""Test successful magnet link extraction."""
|
|
mock_html_get.side_effect = [
|
|
("", "https://audiobookbay.lu/"), # Bootstrap attempt
|
|
SAMPLE_DETAIL_HTML,
|
|
]
|
|
|
|
magnet_link = scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert magnet_link is not None
|
|
assert magnet_link.startswith("magnet:?xt=urn:btih:")
|
|
assert "ABC123DEF456789012345678901234567890ABCD" in magnet_link
|
|
assert "udp%3A//tracker.openbittorrent.com%3A80" in magnet_link
|
|
assert "http%3A//tracker.example.com%3A8080" in magnet_link
|
|
assert mock_html_get.call_count == 2
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_reuses_bootstrap_session(self, mock_html_get):
|
|
"""Test detail page fetch reuses the bootstrap session for ABB cookies."""
|
|
mock_html_get.side_effect = [
|
|
("", "https://audiobookbay.lu/"),
|
|
SAMPLE_DETAIL_HTML,
|
|
]
|
|
|
|
scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert mock_html_get.call_count == 2
|
|
bootstrap_call = mock_html_get.call_args_list[0]
|
|
detail_call = mock_html_get.call_args_list[1]
|
|
assert bootstrap_call.args[0] == "https://audiobookbay.lu/"
|
|
assert detail_call.args[0] == "https://audiobookbay.lu/abss/test-book/"
|
|
assert detail_call.kwargs["session"] is bootstrap_call.kwargs["session"]
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_fallback(self, mock_html_get):
|
|
"""Test fallback to default trackers when none found."""
|
|
mock_html_get.return_value = DETAIL_HTML_NO_TRACKERS
|
|
|
|
magnet_link = scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert magnet_link is not None
|
|
assert magnet_link.startswith("magnet:?xt=urn:btih:")
|
|
assert "ABC123DEF456789012345678901234567890ABCD" in magnet_link
|
|
# Should contain default trackers
|
|
assert "udp%3A//tracker.openbittorrent.com%3A80" in magnet_link
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_no_info_hash(self, mock_html_get):
|
|
"""Test handling missing info hash."""
|
|
mock_html_get.return_value = "<html><body></body></html>"
|
|
|
|
magnet_link = scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert magnet_link is None
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_non_200(self, mock_html_get):
|
|
"""Test handling non-200 status code."""
|
|
mock_html_get.return_value = ""
|
|
|
|
magnet_link = scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert magnet_link is None
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_request_exception(self, mock_html_get):
|
|
"""Test handling request exceptions."""
|
|
mock_html_get.return_value = ""
|
|
|
|
magnet_link = scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert magnet_link is None
|
|
|
|
@patch("shelfmark.release_sources.audiobookbay.scraper.downloader.html_get_page")
|
|
def test_extract_magnet_link_cleans_info_hash(self, mock_html_get):
|
|
"""Test that info hash whitespace is cleaned."""
|
|
html_with_whitespace = """
|
|
<html>
|
|
<body>
|
|
<table>
|
|
<tr>
|
|
<td>Info Hash</td>
|
|
<td>ABC 123 DEF 456 789 012 345 678 901 234 567 890 ABC D</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
mock_html_get.return_value = html_with_whitespace
|
|
|
|
magnet_link = scraper.extract_magnet_link(
|
|
"https://audiobookbay.lu/abss/test-book/", hostname="audiobookbay.lu"
|
|
)
|
|
|
|
assert magnet_link is not None
|
|
# Info hash should be cleaned (no spaces, uppercase)
|
|
assert "ABC123DEF456" in magnet_link
|