import requests
from shelfmark.core.cache import get_metadata_cache
from shelfmark.metadata_providers import MetadataSearchOptions, SearchType
from shelfmark.metadata_providers.databazeknih import (
DatabazeKnihProvider,
_id_from_link,
_names_match,
_valid_isbn,
)
# Shortened copies of real pages from https://www.databazeknih.cz
BOOK_HTML = r"""
Krev elfů
O knize Krev elfů
První část ságy o Zaklínači.
Geralt, Ciri, Yennefer a další.
"""
MORE_INFO_HTML = """
Originální názevKrew elfów, 1994
Počet stran308
Jazyk vydáníčeský
ISBN80-85951-05-3
"""
SEARCH_HTML = """
Krev elfů
1998,
Andrzej Sapkowski
Zaklínač
1998,
Jason Dark (p)
"""
AUTHOR_SEARCH_HTML = """
Andrzej Sapkowski
Andrzej Nowak
"""
AUTHOR_BOOKS_HTML = """
Krev elfů
2011 (1. vydání 1994)
Až vyjde nová kniha autora
"""
class _DbkResponse:
def __init__(self, text):
self.text = text
def raise_for_status(self):
return None
class _DbkSession:
"""Serves canned HTML per URL substring, first match wins."""
def __init__(self, pages):
self.pages = pages
self.urls = []
self.headers = {}
@property
def calls(self):
return len(self.urls)
def get(self, url, **kwargs):
self.urls.append(url)
for fragment, html in self.pages.items():
if fragment in url:
return _DbkResponse(html)
raise requests.HTTPError(f"unexpected URL: {url}")
class _FlakyDbkSession(_DbkSession):
def get(self, url, **kwargs):
if not self.urls:
self.urls.append(url)
raise requests.Timeout
return super().get(url, **kwargs)
def _provider(session):
provider = DatabazeKnihProvider()
provider.session = session
return provider
class TestDatabazeKnihSearch:
def test_search_parses_results(self):
get_metadata_cache().clear()
provider = _provider(_DbkSession({"/vyhledavani/knihy": SEARCH_HTML}))
books = provider.search(MetadataSearchOptions(query="krev elfů"))
assert [b.provider_id for b in books] == ["30482", "40113"]
book = books[0]
assert book.provider == "databazeknih"
assert book.title == "Krev elfů"
assert book.authors == ["Andrzej Sapkowski"]
assert book.publish_year == 1998
assert book.cover_url.startswith("https://www.databazeknih.cz/img/books/30_/30482/")
assert book.source_url == "https://www.databazeknih.cz/prehled-knihy/kniha-30482"
assert {f.label: f.value for f in book.display_fields} == {"Rating": "91%"}
# Pseudonym marker removed, missing cover stays empty
assert books[1].authors == ["Jason Dark"]
assert books[1].cover_url is None
def test_search_passes_page_and_language(self):
get_metadata_cache().clear()
session = _DbkSession({"/vyhledavani/knihy": SEARCH_HTML})
provider = _provider(session)
provider.search(MetadataSearchOptions(query="krev", page=2, language="sk"))
assert "page=2" in session.urls[0]
assert "lang=sk" in session.urls[0]
def test_search_redirected_to_single_book(self):
get_metadata_cache().clear()
provider = _provider(_DbkSession({"/vyhledavani/knihy": BOOK_HTML}))
books = provider.search(MetadataSearchOptions(query="krev elfů"))
assert [b.provider_id for b in books] == ["30482"]
assert books[0].series_name == "Zaklínač: sága"
def test_title_and_author_filters_on_author(self):
get_metadata_cache().clear()
provider = _provider(_DbkSession({"/vyhledavani/knihy": SEARCH_HTML}))
books = provider.search(
MetadataSearchOptions(query="", fields={"title": "zaklínač", "author": "jason dark"})
)
assert [b.provider_id for b in books] == ["40113"]
def test_author_only_lists_author_books(self):
get_metadata_cache().clear()
session = _DbkSession(
{"in=authors": AUTHOR_SEARCH_HTML, "/vydane-knihy/": AUTHOR_BOOKS_HTML}
)
provider = _provider(session)
books = provider.search(
MetadataSearchOptions(query="", fields={"author": "Sapkowski Andrzej"})
)
assert session.urls[1] == "https://www.databazeknih.cz/vydane-knihy/autor-269"
assert [(b.provider_id, b.title, b.publish_year) for b in books] == [
("433", "Krev elfů", 1994),
("30500", "Magické střípky", 2002),
]
assert books[0].authors == ["Sapkowski Andrzej"]
def test_unknown_author_returns_nothing(self):
get_metadata_cache().clear()
session = _DbkSession({"in=authors": AUTHOR_SEARCH_HTML})
provider = _provider(session)
books = provider.search(
MetadataSearchOptions(query="Terry Pratchett", search_type=SearchType.AUTHOR)
)
assert books == []
assert session.calls == 1
def test_search_does_not_cache_request_failures(self):
get_metadata_cache().clear()
session = _FlakyDbkSession({"/vyhledavani/knihy": SEARCH_HTML})
provider = _provider(session)
options = MetadataSearchOptions(query="krev elfů")
assert provider.search(options) == []
books = provider.search(options)
assert session.calls == 2
assert [book.provider_id for book in books] == ["30482", "40113"]
class TestDatabazeKnihGetBook:
def test_get_book_parses_details(self):
get_metadata_cache().clear()
provider = _provider(
_DbkSession({"/prehled-knihy/": BOOK_HTML, "/book-detail-more-info/": MORE_INFO_HTML})
)
book = provider.get_book("30482")
assert book is not None
assert book.title == "Krev elfů"
assert book.authors == ["Andrzej Sapkowski"]
assert book.isbn_10 == "8085951053"
assert book.isbn_13 is None
assert book.publisher == "Leonardo"
assert book.publish_year == 1998
assert book.language == "cs"
assert book.genres == ["Literatura světová", "Romány", "Fantasy"]
assert book.cover_url.endswith("/30482/bmid_krev-elfu-Jqe-30482.jpg")
assert book.source_url.endswith("/prehled-knihy/zaklinac-saga-krev-elfu-30482")
# The whole description from the page, not the shortened JSON-LD one
assert book.description.startswith("První část ságy o Zaklínači.\nGeralt")
assert (book.series_id, book.series_name, book.series_position) == (
"7063",
"Zaklínač: sága",
1.0,
)
fields = {f.label: f.value for f in book.display_fields}
assert fields == {
"Rating": "4.6",
"Ratings": "5,790",
"Pages": "308",
"Original title": "Krew elfów, 1994",
}
def test_get_book_rejects_non_book_page(self):
get_metadata_cache().clear()
provider = _provider(_DbkSession({"/prehled-knihy/": SEARCH_HTML}))
assert provider.get_book("12345") is None
def test_isbn_search_resolves_redirected_book(self):
get_metadata_cache().clear()
session = _DbkSession(
{
"/search?": BOOK_HTML,
"/prehled-knihy/": BOOK_HTML,
"/book-detail-more-info/": MORE_INFO_HTML,
}
)
provider = _provider(session)
books = provider.search(
MetadataSearchOptions(query="80-85951-05-3", search_type=SearchType.ISBN)
)
assert "q=8085951053" in session.urls[0]
assert [b.provider_id for b in books] == ["30482"]
assert books[0].isbn_10 == "8085951053"
def test_isbn_search_ignores_invalid_isbn(self):
session = _DbkSession({})
provider = _provider(session)
assert provider.search_by_isbn("2024") is None
assert session.calls == 0
class TestHelpers:
def test_id_from_link(self):
assert _id_from_link("/prehled-knihy/krev-elfu-433") == "433"
assert _id_from_link("/serie/zaklinac-saga---7063?lang=cz") == "7063"
assert _id_from_link("autori/andrzej-sapkowski-269") == "269"
assert _id_from_link("/prehled-knihy/") == ""
def test_names_match_ignores_accents_and_order(self):
assert _names_match("Karel Čapek", "capek karel")
assert _names_match("Stanisław Lem", "Stanislaw Lem")
assert not _names_match("Andrzej Nowak", "Andrzej Sapkowski")
def test_valid_isbn(self):
assert _valid_isbn("978-80-257-1234-5") == "9788025712345"
assert _valid_isbn("80-85951-05-x") == "808595105X"
assert _valid_isbn("2024") is None