160 lines
6.5 KiB
Python
160 lines
6.5 KiB
Python
"""Тесты 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]
|