mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-10-02 22:06:00 +01:00
WordPress texturizes punctuation on output only, so a post stored as "The Stranger's Wife" renders as "The Stranger’s Wife". ABB's search matches the stored value and ANDs its terms, so one typographic character in the query empties the entire result set rather than merely ranking worse. Book metadata and mobile keyboards both hand us those characters. Map curly quotes, dashes and ellipses to ASCII before a query goes out, and on both sides of the relevance comparison, since scraped titles carry the rendered forms. Release titles are still stored and displayed exactly as ABB renders them; only matching normalizes. Also percent-encode the search query properly. The hand-rolled encoder only escaped double quotes and spaces, so a bare "&" started a new query parameter and silently truncated the search: "detective dan riley books 1 & 2 weatherley" reached ABB as "detective dan riley books 1" and returned six confident-looking results without the requested book among them. "%" and "+" were mangled too.
632 lines
22 KiB
Python
632 lines
22 KiB
Python
"""
|
||
Tests for AudiobookBay release source.
|
||
"""
|
||
|
||
from unittest.mock import patch
|
||
|
||
import pytest
|
||
|
||
from shelfmark.core.search_plan import ReleaseSearchPlan, ReleaseSearchVariant
|
||
from shelfmark.metadata_providers import BookMetadata
|
||
from shelfmark.release_sources.audiobookbay.source import (
|
||
AudiobookBaySource,
|
||
_generate_source_id,
|
||
_map_language,
|
||
_parse_bitrate_to_kbps,
|
||
_split_title_and_author,
|
||
)
|
||
|
||
|
||
class TestMapLanguage:
|
||
"""Tests for the _map_language function."""
|
||
|
||
def test_map_language_english(self):
|
||
"""Test mapping English language."""
|
||
assert _map_language("English") == "en"
|
||
assert _map_language("english") == "en"
|
||
assert _map_language("ENGLISH") == "en"
|
||
|
||
def test_map_language_spanish(self):
|
||
"""Test mapping Spanish language."""
|
||
assert _map_language("Spanish") == "es"
|
||
assert _map_language("spanish") == "es"
|
||
|
||
def test_map_language_french(self):
|
||
"""Test mapping French language."""
|
||
assert _map_language("French") == "fr"
|
||
assert _map_language("french") == "fr"
|
||
|
||
def test_map_language_german(self):
|
||
"""Test mapping German language."""
|
||
assert _map_language("German") == "de"
|
||
assert _map_language("german") == "de"
|
||
|
||
def test_map_language_unknown(self):
|
||
"""Test mapping unknown language returns lowercase."""
|
||
assert _map_language("UnknownLanguage") == "unknownlanguage"
|
||
assert _map_language("Klingon") == "klingon"
|
||
|
||
def test_map_language_empty(self):
|
||
"""Test empty language returns None."""
|
||
assert _map_language("") is None
|
||
assert _map_language(None) is None
|
||
|
||
def test_map_language_with_whitespace(self):
|
||
"""Test language with whitespace is trimmed."""
|
||
assert _map_language(" English ") == "en"
|
||
assert _map_language("\tFrench\n") == "fr"
|
||
|
||
|
||
class TestParseBitrate:
|
||
"""Tests for the _parse_bitrate_to_kbps function."""
|
||
|
||
def test_parse_bitrate_to_kbps(self):
|
||
"""Test normal bitrate parsing from ABB string values."""
|
||
assert _parse_bitrate_to_kbps("128 Kbps") == 128
|
||
assert _parse_bitrate_to_kbps("192kbps") == 192
|
||
|
||
def test_parse_bitrate_to_kbps_invalid(self):
|
||
"""Test invalid bitrate values return None."""
|
||
assert _parse_bitrate_to_kbps(None) is None
|
||
assert _parse_bitrate_to_kbps("") is None
|
||
assert _parse_bitrate_to_kbps("Unknown") is None
|
||
|
||
|
||
class TestGenerateSourceId:
|
||
"""Tests for the _generate_source_id function."""
|
||
|
||
def test_generate_source_id_consistent(self):
|
||
"""Test that same URL generates same ID."""
|
||
url = "https://audiobookbay.lu/abss/test-book/"
|
||
id1 = _generate_source_id(url)
|
||
id2 = _generate_source_id(url)
|
||
assert id1 == id2
|
||
assert len(id1) == 32 # MD5 hex digest length
|
||
|
||
def test_generate_source_id_different_urls(self):
|
||
"""Test that different URLs generate different IDs."""
|
||
url1 = "https://audiobookbay.lu/abss/book1/"
|
||
url2 = "https://audiobookbay.lu/abss/book2/"
|
||
id1 = _generate_source_id(url1)
|
||
id2 = _generate_source_id(url2)
|
||
assert id1 != id2
|
||
|
||
|
||
class TestAudiobookBaySource:
|
||
"""Tests for the AudiobookBaySource class."""
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def configure_hostname(self, monkeypatch):
|
||
"""Provide a default ABB hostname for source.search tests."""
|
||
import shelfmark.release_sources.audiobookbay.source as source_module
|
||
|
||
original_get = source_module.config.get
|
||
|
||
def mock_get(key, default=None):
|
||
if key == "ABB_HOSTNAME":
|
||
return "audiobookbay.lu"
|
||
return original_get(key, default)
|
||
|
||
monkeypatch.setattr(source_module.config, "get", mock_get)
|
||
|
||
def test_is_available_enabled(self, monkeypatch):
|
||
"""Test is_available when enabled."""
|
||
|
||
def mock_get(key, default=False):
|
||
if key == "ABB_ENABLED":
|
||
return True
|
||
if key == "ABB_HOSTNAME":
|
||
return "audiobookbay.lu"
|
||
return default
|
||
|
||
import shelfmark.release_sources.audiobookbay.source as source_module
|
||
|
||
monkeypatch.setattr(source_module.config, "get", mock_get)
|
||
|
||
source = AudiobookBaySource()
|
||
assert source.is_available() is True
|
||
|
||
def test_is_available_disabled(self, monkeypatch):
|
||
"""Test is_available when disabled."""
|
||
|
||
def mock_get(key, default=False):
|
||
if key == "ABB_ENABLED":
|
||
return False
|
||
return default
|
||
|
||
import shelfmark.release_sources.audiobookbay.source as source_module
|
||
|
||
monkeypatch.setattr(source_module.config, "get", mock_get)
|
||
|
||
source = AudiobookBaySource()
|
||
assert source.is_available() is False
|
||
|
||
def test_search_non_audiobook_content_type(self):
|
||
"""Test that non-audiobook content types return empty."""
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="ebook")
|
||
assert results == []
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_query_generation_manual(self, mock_search):
|
||
"""Test search with manual query."""
|
||
mock_search.return_value = []
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
manual_query="custom search query",
|
||
)
|
||
|
||
source.search(book, plan, content_type="audiobook")
|
||
|
||
mock_search.assert_called_once()
|
||
call_args = mock_search.call_args
|
||
assert call_args.kwargs["query"] == "custom search query"
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_query_generation_from_variants(self, mock_search):
|
||
"""Test search query generation from title variants with title-only retry."""
|
||
mock_search.return_value = []
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
source.search(book, plan, content_type="audiobook")
|
||
|
||
assert mock_search.call_count == 2
|
||
first_call = mock_search.call_args_list[0]
|
||
second_call = mock_search.call_args_list[1]
|
||
assert first_call.kwargs["query"] == "test book test author"
|
||
assert second_call.kwargs["query"] == "test book"
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_query_generation_from_title_only(self, mock_search):
|
||
"""Test search query generation when only title available."""
|
||
mock_search.return_value = []
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=[],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="",
|
||
title_variants=[],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
source.search(book, plan, content_type="audiobook")
|
||
|
||
mock_search.assert_called_once()
|
||
call_args = mock_search.call_args
|
||
assert call_args.kwargs["query"] == "test book"
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_empty_query(self, mock_search):
|
||
"""Test search with empty query returns empty."""
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="",
|
||
authors=[],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="",
|
||
title_variants=[],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
assert results == []
|
||
mock_search.assert_not_called()
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_relevance_filtering(self, mock_search):
|
||
"""Test that irrelevant results are filtered out."""
|
||
mock_search.return_value = [
|
||
{
|
||
"title": "Test Book by Test Author",
|
||
"link": "https://audiobookbay.lu/abss/test-book/",
|
||
"format": "M4B",
|
||
"size": "500 MB",
|
||
"language": "English",
|
||
},
|
||
{
|
||
"title": "Something Completely Different",
|
||
"link": "https://audiobookbay.lu/abss/unrelated/",
|
||
"format": "MP3",
|
||
"size": "1 GB",
|
||
"language": "English",
|
||
},
|
||
]
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
# Should filter out "Unrelated Book Title" as it doesn't contain query words
|
||
assert len(results) == 1
|
||
assert results[0].title == "Test Book by Test Author"
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_relevance_filtering_spans_typographic_punctuation(self, mock_search):
|
||
"""Test an ASCII query still matches the typographic title ABB renders."""
|
||
mock_search.return_value = [
|
||
{
|
||
"title": "The Stranger’s Wife — Anna‑Lou Weatherley",
|
||
"link": "https://audiobookbay.lu/abss/the-strangers-wife/",
|
||
"format": "M4B",
|
||
"size": "259 MB",
|
||
"language": "English",
|
||
},
|
||
]
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Stranger's",
|
||
authors=["Anna-Lou Weatherley"],
|
||
)
|
||
# Every query word carries punctuation, so the result survives only when
|
||
# both sides of the comparison are normalized.
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="",
|
||
title_variants=[ReleaseSearchVariant(title="Stranger's", author="")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
assert len(results) == 1
|
||
# The release keeps the title as ABB rendered it; only matching normalizes.
|
||
assert results[0].title == "The Stranger’s Wife — Anna‑Lou Weatherley"
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_result_mapping(self, mock_search):
|
||
"""Test conversion of scraper results to Release objects."""
|
||
mock_search.return_value = [
|
||
{
|
||
"title": "Test Book - Test Author",
|
||
"link": "https://audiobookbay.lu/abss/test-book/",
|
||
"format": "M4B",
|
||
"size": "500.00 MBs",
|
||
"language": "English",
|
||
"bitrate": "128 Kbps",
|
||
"posted_date": "01 Jan 2024",
|
||
"cover": "https://example.com/cover.jpg",
|
||
},
|
||
]
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
assert len(results) == 1
|
||
release = results[0]
|
||
assert release.source == "audiobookbay"
|
||
assert release.title == "Test Book"
|
||
assert release.format == "m4b"
|
||
assert release.language == "en"
|
||
assert release.size == "500.00 MBs"
|
||
assert release.download_url == "https://audiobookbay.lu/abss/test-book/"
|
||
assert release.info_url == "https://audiobookbay.lu/abss/test-book/"
|
||
assert release.protocol.value == "torrent"
|
||
assert release.indexer == "AudiobookBay"
|
||
assert release.content_type == "audiobook"
|
||
assert release.extra["preview"] == "https://example.com/cover.jpg"
|
||
assert release.extra["detail_url"] == "https://audiobookbay.lu/abss/test-book/"
|
||
assert release.extra["bitrate"] == "128 Kbps"
|
||
assert release.extra["bitrate_value"] == 128
|
||
assert release.extra["posted_date"] == "01 Jan 2024"
|
||
assert release.extra["title_raw"] == "Test Book - Test Author"
|
||
assert release.extra["author"] == "Test Author"
|
||
assert release.extra["language_raw"] == "English"
|
||
|
||
def test_split_title_and_author(self):
|
||
"""Test title/author parsing from ABB title patterns."""
|
||
assert _split_title_and_author("Book Title - Author Name") == ("Book Title", "Author Name")
|
||
assert _split_title_and_author("Book Title - Author Name - Narrator") == (
|
||
"Book Title - Author Name",
|
||
"Narrator",
|
||
)
|
||
assert _split_title_and_author("Book Title") == ("Book Title", None)
|
||
assert _split_title_and_author(" Book Title - Author Name ") == (
|
||
"Book Title",
|
||
"Author Name",
|
||
)
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_handles_scraper_exception(self, mock_search):
|
||
"""Test that scraper exceptions are handled gracefully."""
|
||
mock_search.side_effect = Exception("Scraper error")
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
assert results == []
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_handles_invalid_result(self, mock_search):
|
||
"""Test that invalid results are skipped."""
|
||
mock_search.return_value = [
|
||
{
|
||
"title": "Relevant Book",
|
||
"link": "https://audiobookbay.lu/abss/valid/",
|
||
"format": "M4B",
|
||
},
|
||
{
|
||
# Missing required fields
|
||
"title": "Relevant But Invalid",
|
||
},
|
||
]
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Relevant",
|
||
authors=["Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Author",
|
||
title_variants=[ReleaseSearchVariant(title="Relevant", author="Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
# Should only include valid result
|
||
assert len(results) == 1
|
||
assert results[0].title == "Relevant Book"
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_config_hostname(self, mock_search, monkeypatch):
|
||
"""Test that custom hostname is used from config."""
|
||
mock_search.return_value = []
|
||
|
||
def mock_get(key, default=None):
|
||
if key == "ABB_HOSTNAME":
|
||
return "audiobookbay.is"
|
||
if key == "ABB_PAGE_LIMIT":
|
||
return 3
|
||
return default
|
||
|
||
import shelfmark.release_sources.audiobookbay.source as source_module
|
||
|
||
monkeypatch.setattr(source_module.config, "get", mock_get)
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
source.search(book, plan, content_type="audiobook")
|
||
|
||
call_args = mock_search.call_args
|
||
assert call_args.kwargs["hostname"] == "audiobookbay.is"
|
||
assert call_args.kwargs["max_pages"] == 3
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_exact_phrase_setting_forwarded(self, mock_search, monkeypatch):
|
||
"""Test that exact phrase setting is forwarded to scraper search."""
|
||
mock_search.return_value = [
|
||
{
|
||
"title": "Test Book - Test Author",
|
||
"link": "https://audiobookbay.lu/abss/test-book/",
|
||
"cover": None,
|
||
"language": "English",
|
||
"format": "M4B",
|
||
"bitrate": "128 Kbps",
|
||
"size": "500 MB",
|
||
"posted_date": "01 Jan 2024",
|
||
}
|
||
]
|
||
|
||
def mock_get(key, default=None):
|
||
if key == "ABB_HOSTNAME":
|
||
return "audiobookbay.lu"
|
||
if key == "ABB_EXACT_PHRASE":
|
||
return True
|
||
return default
|
||
|
||
import shelfmark.release_sources.audiobookbay.source as source_module
|
||
|
||
monkeypatch.setattr(source_module.config, "get", mock_get)
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
source.search(book, plan, content_type="audiobook")
|
||
|
||
call_args = mock_search.call_args_list[0]
|
||
assert call_args.kwargs["exact_phrase"] is True
|
||
|
||
@patch("shelfmark.release_sources.audiobookbay.source.scraper.search_audiobookbay")
|
||
def test_search_falls_back_to_broad_when_exact_finds_no_results(self, mock_search, monkeypatch):
|
||
"""Test fallback to broad search when exact phrase search has no results."""
|
||
mock_search.side_effect = [
|
||
[],
|
||
[
|
||
{
|
||
"title": "Test Book - Test Author",
|
||
"link": "https://audiobookbay.lu/abss/test-book/",
|
||
"cover": None,
|
||
"language": "English",
|
||
"format": "M4B",
|
||
"bitrate": "128 Kbps",
|
||
"size": "500 MB",
|
||
"posted_date": "01 Jan 2024",
|
||
}
|
||
],
|
||
]
|
||
|
||
def mock_get(key, default=None):
|
||
if key == "ABB_HOSTNAME":
|
||
return "audiobookbay.lu"
|
||
if key == "ABB_EXACT_PHRASE":
|
||
return True
|
||
return default
|
||
|
||
import shelfmark.release_sources.audiobookbay.source as source_module
|
||
|
||
monkeypatch.setattr(source_module.config, "get", mock_get)
|
||
|
||
source = AudiobookBaySource()
|
||
book = BookMetadata(
|
||
provider="test",
|
||
provider_id="123",
|
||
title="Test Book",
|
||
authors=["Test Author"],
|
||
)
|
||
plan = ReleaseSearchPlan(
|
||
languages=["en"],
|
||
isbn_candidates=[],
|
||
author="Test Author",
|
||
title_variants=[ReleaseSearchVariant(title="Test Book", author="Test Author")],
|
||
grouped_title_variants=[],
|
||
)
|
||
|
||
results = source.search(book, plan, content_type="audiobook")
|
||
|
||
assert len(results) == 1
|
||
assert mock_search.call_count == 2
|
||
first_call = mock_search.call_args_list[0].kwargs
|
||
second_call = mock_search.call_args_list[1].kwargs
|
||
assert first_call["exact_phrase"] is True
|
||
assert second_call["exact_phrase"] is False
|
||
|
||
def test_get_column_config(self):
|
||
"""Test column configuration."""
|
||
source = AudiobookBaySource()
|
||
config = source.get_column_config()
|
||
|
||
assert config is not None
|
||
assert len(config.columns) == 4
|
||
column_keys = [col.key for col in config.columns]
|
||
assert "language" in column_keys
|
||
assert "format" in column_keys
|
||
assert "extra.bitrate" in column_keys
|
||
assert "size" in column_keys
|
||
assert "seeders" not in column_keys # ABB doesn't show seeders
|
||
assert config.supported_filters == ["format", "language"]
|
||
|
||
bitrate_col = next(col for col in config.columns if col.key == "extra.bitrate")
|
||
assert bitrate_col.sortable is True
|
||
assert bitrate_col.sort_key == "extra.bitrate_value"
|
||
|
||
size_col = next(col for col in config.columns if col.key == "size")
|
||
assert size_col.sortable is True
|
||
assert size_col.sort_key == "size_bytes"
|