"""Tests for the lean ČSFD title-bridge scraper (`src.core.csfd`).""" import hashlib from unittest.mock import patch import pytest from src.core import csfd from src.core.csfd import ( CsfdError, CsfdFilm, _extract_csfd_id, _is_film_href, _parse_origin, _parse_original_title, _solve_anubis_pow, clean_filename_to_query, fetch_film, find_csfd_url, search_films, ) # A trimmed ČSFD film page: Czech title in JSON-LD, USA origin, and a # ``.film-names`` list whose USA-flagged entry is the original title. FILM_HTML = """

Vykoupení z věznice Shawshank

USA 1994 142 min
""" # A Czech film: ČSFD omits the Czech title from ``.film-names`` (it equals the #

) and lists only a foreign festival title — the original title must fall # back to the

, not "Cosy Dens". CZECH_FILM_HTML = """

Pelíšky

Česko 1999
""" # The real search page groups hits into
blocks; a.film-title-name also # matches series episodes, so search_films must scope to "Filmy" and drop the # two-segment episode hrefs. SEARCH_HTML = """

Filmy

Vykoupení z věznice Shawshank Film o filmu

Seriály

Arrow - Vykoupení
""" class TestCleanFilenameToQuery: def test_strips_release_tags_and_keeps_year(self): assert ( clean_filename_to_query("Matrix.1999.1080p.BluRay.x264-GROUP.mkv") == "Matrix 1999" ) def test_handles_spaces_and_parens_year(self): assert ( clean_filename_to_query("Forrest Gump (1994) 2160p HDR.mkv") == "Forrest Gump 1994" ) def test_no_year_no_markers(self): assert clean_filename_to_query("Amelie.mkv") == "Amelie" def test_underscores_and_resolution(self): assert clean_filename_to_query("Sam_doma_720p.mkv") == "Sam doma" def test_falls_back_to_stem_when_starting_with_marker(self): assert clean_filename_to_query("1080p.mkv") == "1080p" class TestExtractCsfdId: def test_extract_from_various_urls(self): assert ( _extract_csfd_id("https://www.csfd.cz/film/2294-vykoupeni/prehled/") == 2294 ) assert _extract_csfd_id("/film/999/") == 999 def test_none_when_absent(self): assert _extract_csfd_id("https://www.csfd.cz/") is None class TestAnubisPoW: def test_solve_difficulty_one_gives_one_leading_zero_nibble(self): hash_hex, nonce, _ = _solve_anubis_pow("abc123", difficulty=1) assert hash_hex[0] == "0" assert hashlib.sha256(f"abc123{nonce}".encode()).hexdigest() == hash_hex def test_solve_difficulty_two_gives_leading_zero_byte(self): hash_hex, _, _ = _solve_anubis_pow("seed", difficulty=2) assert hash_hex[:2] == "00" class TestParseOrigin: def test_country_and_year_from_bullet_format(self): from bs4 import BeautifulSoup soup = BeautifulSoup(FILM_HTML, "html.parser") assert _parse_origin(soup) == ("USA", "1994") def test_first_country_of_coproduction(self): from bs4 import BeautifulSoup html = ( '
Francie / Německo ' "2001 120 min
" ) soup = BeautifulSoup(html, "html.parser") assert _parse_origin(soup) == ("Francie", "2001") class TestParseOriginalTitle: def test_picks_name_matching_origin_country(self): from bs4 import BeautifulSoup soup = BeautifulSoup(FILM_HTML, "html.parser") assert _parse_original_title(soup, "USA") == "The Shawshank Redemption" def test_none_without_country_match_so_caller_uses_czech_title(self): # A Czech film lists only foreign/festival alt-titles here — none match # the origin country, so we must NOT return one of them. from bs4 import BeautifulSoup html = ( '' ) soup = BeautifulSoup(html, "html.parser") assert _parse_original_title(soup, "Česko") is None def test_cesko_matches_ceskoslovensko_flag(self): from bs4 import BeautifulSoup html = ( '" ) soup = BeautifulSoup(html, "html.parser") assert _parse_original_title(soup, "Česko") == "Ostře sledované vlaky" def test_none_when_no_names(self): from bs4 import BeautifulSoup soup = BeautifulSoup("
x
", "html.parser") assert _parse_original_title(soup, "USA") is None class TestFetchFilm: def test_parses_czech_and_original_title_and_year(self): class _Resp: text = FILM_HTML with patch("src.core.csfd._get_page", return_value=_Resp()): film = fetch_film("https://www.csfd.cz/film/2294-x/") assert film.czech_title == "Vykoupení z věznice Shawshank" assert film.original_title == "The Shawshank Redemption" assert film.year == "1994" assert film.csfd_id == 2294 assert film.tmdb_query == ("The Shawshank Redemption", "1994") def test_czech_film_original_title_falls_back_to_czech(self): class _Resp: text = CZECH_FILM_HTML with patch("src.core.csfd._get_page", return_value=_Resp()): film = fetch_film("https://www.csfd.cz/film/15387-pelisky/") assert film.czech_title == "Pelíšky" assert film.original_title == "Pelíšky" assert film.year == "1999" assert film.tmdb_query == ("Pelíšky", "1999") def test_wraps_network_error_in_csfd_error(self): import requests with ( patch( "src.core.csfd._get_page", side_effect=requests.RequestException("boom") ), pytest.raises(CsfdError), ): fetch_film("https://www.csfd.cz/film/1/") class TestIsFilmHref: def test_accepts_single_segment_film(self): assert _is_film_href("/film/2294-vykoupeni/prehled/") is True assert _is_film_href("/film/2294-vykoupeni/") is True def test_rejects_series_episode(self): assert _is_film_href("/film/318290-arrow/620181-vykoupeni/prehled/") is False def test_rejects_non_film(self): assert _is_film_href("/tvurce/123-nekdo/") is False class TestSearchFilms: def test_scopes_to_films_section_and_drops_episodes(self): class _Resp: text = SEARCH_HTML with patch("src.core.csfd._get_page", return_value=_Resp()): films = search_films("shawshank", limit=5) # the Seriály-section episode link is excluded assert [f.csfd_id for f in films] == [2294, 248803] assert films[0].url == ( "https://www.csfd.cz/film/2294-vykoupeni-z-veznice-shawshank/prehled/" ) def test_empty_query_returns_empty(self): assert search_films(" ") == [] class TestFindCsfdUrl: def test_returns_first_hit_url(self): hit = CsfdFilm(url="https://www.csfd.cz/film/2294-x/", csfd_id=2294) with patch("src.core.csfd.search_films", return_value=[hit]): assert find_csfd_url("shawshank 1994") == "https://www.csfd.cz/film/2294-x/" def test_retries_without_trailing_year_when_first_query_misses(self): hit = CsfdFilm( url="https://www.csfd.cz/film/221006-vratne-lahve/", csfd_id=221006 ) calls: list[str] = [] def fake_search(query, limit=1, session=None): calls.append(query) return [] if query.endswith("2006") else [hit] with patch("src.core.csfd.search_films", side_effect=fake_search): assert find_csfd_url("Vratne lahve 2006") == hit.url assert calls == ["Vratne lahve 2006", "Vratne lahve"] def test_none_for_blank_query(self): assert find_csfd_url(" ") is None def test_none_when_no_results(self): with patch("src.core.csfd.search_films", return_value=[]): assert find_csfd_url("nic") is None class TestDependencies: def test_dependencies_available_in_test_env(self): assert csfd.dependencies_available() is True