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"""

Zaklínač: sága série

1. díl

Zaklínač série

3. díl

Krev elfů

Fantasy
1998 , Leonardo

O knize Krev elfů

První část ságy o Zaklínači. Geralt, Ciri, Yennefer a další.

""" MORE_INFO_HTML = """
Originální název
Krew elfów, 1994
Počet stran
308
Jazyk vydání
český
ISBN
80-85951-05-3
""" SEARCH_HTML = """

Obálka knihy Krev elfů 91%Krev elfů
1998, Andrzej Sapkowski

47%Zaklínač
1998, Jason Dark (p)

""" AUTHOR_SEARCH_HTML = """ Andrzej Sapkowski Andrzej Nowak """ AUTHOR_BOOKS_HTML = """

Krev elfů 2011 (1. vydání 1994)

Magické střípky 2002 (1. vydání)

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