gendesign/backend/tests/scrapers/test_pravo_gov66_client.py
bot-backend e2faab801c
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Successful in 1m30s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m21s
Deploy / deploy (push) Successful in 1m15s
fix(sf): pravo.gov66 law_type числовой код =1 Постановление (#1149)
Co-authored-by: bot-backend <bot-backend@gendsgn.local>
Co-committed-by: bot-backend <bot-backend@gendsgn.local>
2026-06-07 14:17:52 +00:00

171 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тесты 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 = """
<!DOCTYPE html>
<html>
<head><title>Поиск</title></head>
<body>
<div class="search-results">
<ul>
<li class="result-item">
<a href="/35496/">
Об изъятии земельных участков для государственных нужд
Свердловской области в целях реконструкции ул. Татищева
в г. Екатеринбурге
</a>
</li>
<li class="result-item">
<a href="/34812/">
О резервировании земельных участков для нужд Екатеринбург
</a>
</li>
<li class="result-item">
<!-- Ссылка на категорию, не документ — должна игнорироваться -->
<a href="/category/law/">Категория</a>
</li>
<li class="result-item">
<!-- Дубль doc_id 35496 — должен дедуплицироваться -->
<a href="/35496/">Дубль</a>
</li>
<li class="result-item">
<!-- Нечисловой id — не должен попасть -->
<a href="/some-slug/">Текстовый slug</a>
</li>
</ul>
</div>
</body>
</html>
"""
# ── Фикстура HTML страницы документа с обычным PDF ────────────────────────────
_DOC_HTML_WITH_PDF = """
<!DOCTYPE html>
<html>
<body>
<h1>Постановление № 509-ПП</h1>
<p>
Скачать документ:
<a href="/media/pravo/509-ПП_ab1234cd.pdf">Скачать PDF</a>
</p>
<p>
Подписанная версия:
<a href="/media/pravo/ecp/509-ПП.pdf">Скачать подписанный PDF</a>
</p>
</body>
</html>
"""
# ── Фикстура HTML страницы документа только с ecp PDF ─────────────────────────
_DOC_HTML_ECP_ONLY = """
<!DOCTYPE html>
<html>
<body>
<h1>Постановление № 312-ПП</h1>
<p>
<a href="/media/pravo/ecp/312-ПП.pdf">Подписанный PDF</a>
</p>
</body>
</html>
"""
# ── Фикстура HTML страницы документа без PDF ──────────────────────────────────
_DOC_HTML_NO_PDF = """
<!DOCTYPE html>
<html>
<body>
<h1>Постановление без PDF</h1>
<p>Текст постановления размещён ниже.</p>
</body>
</html>
"""
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 = "<html><body><a href='/category/'>Cat</a></body></html>"
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/")