"""Тесты pravo_gov66_client — offline, без живой сети. Покрывают: парсинг search-HTML (doc_id/title/url), парсинг страницы документа (PDF-ссылка), fallback на ecp-вариант, отсутствие PDF. """ from __future__ import annotations from app.services.scrapers.pravo_gov66_client import ( _LAW_TYPE_POSTANOVLENIE, _extract_pdf_url, _parse_search_html, ) # ── Фикстура HTML страницы поиска ───────────────────────────────────────────── # Реальный фрагмент страницы https://www.pravo.gov66.ru/search/?q=изъятие+земельных+участков # Содержит ссылки вида /NNNNN/ с заголовком постановления. _SEARCH_HTML_FIXTURE = """ Поиск
""" # ── Фикстура HTML страницы документа с обычным PDF ──────────────────────────── _DOC_HTML_WITH_PDF = """

Постановление № 509-ПП

Скачать документ: Скачать PDF

Подписанная версия: Скачать подписанный PDF

""" # ── Фикстура HTML страницы документа только с ecp PDF ───────────────────────── _DOC_HTML_ECP_ONLY = """

Постановление № 312-ПП

Подписанный PDF

""" # ── Фикстура HTML страницы документа без PDF ────────────────────────────────── _DOC_HTML_NO_PDF = """

Постановление без PDF

Текст постановления размещён ниже.

""" class TestLawTypeConstant: def test_postanovlenie_is_numeric_string(self) -> None: """Числовой код — сервер игнорирует текстовые значения типа «Постановление».""" assert _LAW_TYPE_POSTANOVLENIE == "1" assert _LAW_TYPE_POSTANOVLENIE.isdigit() class TestParseSearchHtml: def test_extracts_doc_ids_and_titles(self) -> None: results = _parse_search_html(_SEARCH_HTML_FIXTURE) ids = [r["doc_id"] for r in results] assert "35496" in ids assert "34812" in ids def test_ignores_non_numeric_paths(self) -> None: results = _parse_search_html(_SEARCH_HTML_FIXTURE) ids = [r["doc_id"] for r in results] assert "some-slug" not in ids def test_deduplicates_doc_ids(self) -> None: results = _parse_search_html(_SEARCH_HTML_FIXTURE) ids = [r["doc_id"] for r in results] assert ids.count("35496") == 1 def test_ignores_category_links(self) -> None: results = _parse_search_html(_SEARCH_HTML_FIXTURE) # /category/law/ не числовой id — не попадает. ids = [r["doc_id"] for r in results] assert len(ids) == 2 def test_url_is_absolute(self) -> None: results = _parse_search_html(_SEARCH_HTML_FIXTURE) for r in results: assert r["url"].startswith("https://www.pravo.gov66.ru/") def test_title_extracted(self) -> None: results = _parse_search_html(_SEARCH_HTML_FIXTURE) doc = next(r for r in results if r["doc_id"] == "35496") assert "изъятии" in doc["title"].lower() def test_empty_html_returns_empty(self) -> None: assert _parse_search_html("") == [] def test_html_no_doc_links_returns_empty(self) -> None: html = "Cat" assert _parse_search_html(html) == [] class TestExtractPdfUrl: def test_prefers_non_ecp_pdf(self) -> None: url = _extract_pdf_url(_DOC_HTML_WITH_PDF) assert url is not None assert "ecp" not in url assert url.endswith(".pdf") def test_non_ecp_pdf_is_absolute(self) -> None: url = _extract_pdf_url(_DOC_HTML_WITH_PDF) assert url is not None assert url.startswith("https://www.pravo.gov66.ru/media/pravo/") def test_falls_back_to_ecp_when_no_regular_pdf(self) -> None: url = _extract_pdf_url(_DOC_HTML_ECP_ONLY) assert url is not None assert "ecp" in url assert url.endswith(".pdf") def test_returns_none_when_no_pdf(self) -> None: url = _extract_pdf_url(_DOC_HTML_NO_PDF) assert url is None def test_ecp_fallback_is_absolute(self) -> None: url = _extract_pdf_url(_DOC_HTML_ECP_ONLY) assert url is not None assert url.startswith("https://www.pravo.gov66.ru/")