"""Smoke тесты для avito_detail parser.
Загружает minimal HTML fixture и проверяет selector + conversion logic
без сетевых запросов.
"""
import pytest
from app.services.scrapers.avito_detail import DetailEnrichment, parse_detail_html
MINIMAL_HTML = """
№ 7986882804 · 18 мая в 13:40 · 1899 просмотров (+ 12 сегодня)
11 990 000 ₽
- Количество комнат: 3
- Общая площадь: 75 м²
- Площадь кухни: 15 м²
- Этаж: 20 из 26
- Балкон или лоджия: лоджия
- Ремонт: евро
- Способ продажи: свободная
- Условия продажи: возможна ипотека
- Тип дома: монолитный
- Этажей в доме: 26
Дом
1 собственник
Последняя смена собственника 5 мая 2020
Не найдены ограничения и обременения
Совпадают площадь, адрес и этаж
Отличная квартира в новом ЖК. Чкаловская 11-15 мин пешком.
"""
SOURCE_URL = "https://www.avito.ru/ekaterinburg/kvartiry/test_7986882804"
def test_minimal_parse() -> None:
result = parse_detail_html(MINIMAL_HTML, SOURCE_URL)
assert isinstance(result, DetailEnrichment)
assert result.item_id == "7986882804"
assert result.price_rub == 11990000
assert result.rooms == 3
assert result.area_m2 == 75.0
assert result.kitchen_area_m2 == 15.0
assert result.floor == 20
assert result.total_floors == 26
assert result.balcony_loggia == "loggia"
assert result.repair_state == "good" # «евро» → good (normalized enum, #621)
assert result.sale_type == "free"
assert result.mortgage_available is True
assert result.house_type == "monolith"
assert result.total_floors_house == 26
assert result.lat == 56.797713
assert result.lon == 60.609135
assert result.avito_location_id == 654070
assert result.house_catalog_url is not None and "3171365" in result.house_catalog_url
assert result.owners_count == 1
assert result.owners_at_least is False
assert result.last_owner_change_date is not None and result.last_owner_change_date.year == 2020
assert result.encumbrances_clean is True
assert result.registry_match is True
assert result.views_total == 1899
assert result.views_today == 12
def test_metro_extraction() -> None:
result = parse_detail_html(MINIMAL_HTML, SOURCE_URL)
# "Чкаловская 11-15 мин пешком" → metro_stations
assert len(result.metro_stations) >= 1
station = result.metro_stations[0]
assert station["name"] == "Чкаловская"
assert station["min_from"] == 11
assert station["min_to"] == 15
assert station["mode"] == "walk"
def test_owners_at_least_false() -> None:
"""Один собственник (без 'или больше') → owners_at_least=False."""
result = parse_detail_html(MINIMAL_HTML, SOURCE_URL)
assert result.owners_count == 1
assert result.owners_at_least is False
def test_owners_at_least_true() -> None:
"""'3 собственника или больше' → owners_at_least=True."""
html = """
№ 1234567890
5 000 000 ₽
3 собственника или больше
"""
result = parse_detail_html(html, "https://www.avito.ru/test_1234567890")
assert result.owners_count == 3
assert result.owners_at_least is True
def test_missing_domoteka_all_none() -> None:
"""Если нет domoteka-блока — все 5 полей None."""
html = """
№ 9999999999
3 000 000 ₽
"""
result = parse_detail_html(html, "https://www.avito.ru/test_9999999999")
assert result.owners_count is None
assert result.owners_at_least is None
assert result.last_owner_change_date is None
assert result.encumbrances_clean is None
assert result.registry_match is None
def test_invalid_html_raises_value_error() -> None:
"""HTML без item_id → ValueError."""
html = "Нет айди
"
with pytest.raises(ValueError, match="Cannot extract item_id"):
parse_detail_html(html, "https://www.avito.ru/test")
def test_house_catalog_url_full() -> None:
"""house_catalog_url должен быть absolute URL."""
result = parse_detail_html(MINIMAL_HTML, SOURCE_URL)
assert result.house_catalog_url is not None
assert result.house_catalog_url.startswith("https://www.avito.ru")
def test_bathroom_type_combined() -> None:
html = """
№ 1111111111
4 000 000 ₽
- Санузел: совмещенный
- Окна: во двор и на улицу
- Тип комнат: изолированные
"""
result = parse_detail_html(html, "https://www.avito.ru/test_1111111111")
assert result.bathroom_type == "combined"
assert result.windows_view == "both"
assert result.room_layout == "isolated"
def test_photo_urls_extracted() -> None:
html = """
№ 2222222222
7 000 000 ₽
"""
result = parse_detail_html(html, "https://www.avito.ru/test_2222222222")
assert len(result.photo_urls) == 2
assert result.photo_urls[0] == "https://img.avito.ru/img1.jpg"
def test_source_url_preserved() -> None:
result = parse_detail_html(MINIMAL_HTML, SOURCE_URL)
assert result.source_url == SOURCE_URL
# ── repair_state fallback из описания (#622) ──────────────────────────────────
def test_repair_state_structured_wins_over_description() -> None:
"""Структурное «Ремонт: евро» (good) в приоритете даже при сигнале в тексте."""
html = """
№ 3333333333
6 000 000 ₽
Квартира без отделки, под чистовую
"""
result = parse_detail_html(html, "https://www.avito.ru/test_3333333333")
assert result.repair_state == "good" # структурное поле, не needs_repair из текста
def test_repair_state_inferred_from_description_when_structured_absent() -> None:
"""Нет блока «Ремонт» → инференс из описания (#622)."""
html = """
№ 4444444444
8 000 000 ₽
Сделан свежий евроремонт, заезжай и живи
"""
result = parse_detail_html(html, "https://www.avito.ru/test_4444444444")
assert result.repair_state == "good"
def test_repair_state_none_when_no_signal() -> None:
"""Нет структурного поля и нет сигнала в тексте → None (не фабрикуем)."""
html = """
№ 5555555555
9 000 000 ₽
Светлая квартира рядом с метро
"""
result = parse_detail_html(html, "https://www.avito.ru/test_5555555555")
assert result.repair_state is None