gendesign/backend/tests/services/scrapers/test_page_reservation_parser.py
lekss361 59f2628e0b
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m41s
Deploy / build-worker (push) Successful in 3m25s
Deploy / deploy (push) Successful in 1m25s
feat(sf): ПАГЕ-парсер изъятия/резервирования → land_reservation (#1118)
Co-authored-by: lekss361 <lekss361@gendsgn.local>
Co-committed-by: lekss361 <lekss361@gendsgn.local>
2026-06-07 10:15:03 +00:00

160 lines
6.5 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.

"""Тесты page_reservation_parser (#1091, #1062) — парсер постановлений об изъятии/резервировании."""
from __future__ import annotations
from datetime import date
import pytest
from app.services.scrapers.page_reservation_parser import (
ReservationRecord,
extract_reservations,
)
# ── Фикстура-текст с типичным содержимым постановления ────────────────────────
_FULL_TEXT = """
ПОСТАНОВЛЕНИЕ
Правительства Свердловской области
от 12.03.2024 № 509-ПП
Об изъятии земельных участков для государственных нужд в целях реконструкции улицы Татищева
в городе Екатеринбурге.
В соответствии со статьёй 49 Земельного кодекса Российской Федерации Правительство
Свердловской области ПОСТАНОВЛЯЕТ:
1. Изъять для государственных нужд следующие земельные участки:
66:41:0101001:123 — площадь 450 кв.м;
66:41:0101001:456 — площадь 300 кв.м;
66:41:0101002:789 — площадь 680 кв.м.
2. Министерству по управлению государственным имуществом Свердловской области обеспечить
государственную регистрацию права собственности.
Губернатор Свердловской области
"""
_REZERV_TEXT = """
ПОСТАНОВЛЕНИЕ
Правительства Свердловской области
от 05 июня 2024 г.
№ 210-ПП
О резервировании земельных участков для строительства дороги.
Зарезервировать земельные участки:
66:41:0203010:11
66:41:0203010:22
в целях строительства объекта дорожной инфраструктуры.
"""
_GARBAGE_TEXT = "Это текст без кад-номеров и без постановлений."
_BAD_DATE_TEXT = """
№ 99-ПП от 99.99.9999
Изъять земельный участок 66:41:0101001:500.
"""
# ── Тесты extract_reservations ────────────────────────────────────────────────
def test_extracts_cad_nums() -> None:
"""Все три КН извлекаются из постановления об изъятии."""
records = extract_reservations(_FULL_TEXT)
cad_nums = {r.cad_num for r in records}
assert cad_nums == {"66:41:0101001:123", "66:41:0101001:456", "66:41:0101002:789"}
def test_deduplicates_cad_nums() -> None:
"""Дублирующиеся КН в документе схлопываются в одну запись."""
text_with_dup = _FULL_TEXT + "\n66:41:0101001:123 повторно упомянут."
records = extract_reservations(text_with_dup)
cad_nums = [r.cad_num for r in records]
assert cad_nums.count("66:41:0101001:123") == 1
def test_kind_izyatie() -> None:
"""Определяет 'изъятие' из текста с «Изъять»."""
records = extract_reservations(_FULL_TEXT)
assert all(r.reservation_kind == "изъятие" for r in records)
def test_kind_rezervirovanie() -> None:
"""Определяет 'резервирование' из текста с «резервирование»."""
records = extract_reservations(_REZERV_TEXT)
assert all(r.reservation_kind == "резервирование" for r in records)
def test_act_number_parsed() -> None:
"""Номер постановления 509-ПП корректно извлекается."""
records = extract_reservations(_FULL_TEXT)
assert records[0].act_number == "509-ПП"
def test_act_date_numeric() -> None:
"""Дата «от 12.03.2024» парсится как date(2024, 3, 12)."""
records = extract_reservations(_FULL_TEXT)
assert records[0].act_date == date(2024, 3, 12)
def test_act_date_word_month() -> None:
"""Дата «05 июня 2024 г.» парсится как date(2024, 6, 5)."""
records = extract_reservations(_REZERV_TEXT)
assert records[0].act_date == date(2024, 6, 5)
def test_purpose_extracted() -> None:
"""Поле purpose содержит осмысленный текст (best-effort)."""
records = extract_reservations(_FULL_TEXT)
# Хотя бы у одной записи purpose не None.
assert any(r.purpose is not None for r in records)
def test_raw_excerpt_first_record() -> None:
"""raw_excerpt есть у первой записи (±120 символов)."""
records = extract_reservations(_FULL_TEXT)
first = records[0]
assert first.raw_excerpt is not None
assert "66:41:0101001:123" in (first.raw_excerpt or "")
def test_garbage_text_returns_empty() -> None:
"""Мусорный текст без КН → пустой список."""
records = extract_reservations(_GARBAGE_TEXT)
assert records == []
def test_empty_string_returns_empty() -> None:
"""Пустая строка → пустой список."""
assert extract_reservations("") == []
def test_bad_date_does_not_raise() -> None:
"""Нечитаемая дата (99.99.9999) → act_date=None, не падает."""
records = extract_reservations(_BAD_DATE_TEXT)
assert len(records) == 1
assert records[0].act_date is None
assert records[0].cad_num == "66:41:0101001:500"
def test_default_kind_applied() -> None:
"""Если нет маркеров изъятия/резервирования — берётся default_kind."""
text = "Земельный участок 66:41:0101001:777 подлежит ограничению."
records = extract_reservations(text, default_kind="резервирование")
assert records[0].reservation_kind == "резервирование"
def test_record_is_frozen() -> None:
"""ReservationRecord — frozen dataclass."""
r = ReservationRecord(
cad_num="66:41:0101001:1",
reservation_kind="изъятие",
act_number="509-ПП",
act_date=None,
purpose=None,
raw_excerpt=None,
)
with pytest.raises((AttributeError, TypeError)):
r.cad_num = "other" # type: ignore[misc]