gendesign/tradein-mvp/backend/tests/test_avito_detail_parse.py
Light1YT 312df8536a
All checks were successful
Deploy Trade-In / changes (push) Successful in 6s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 38s
Deploy Trade-In / build-backend (push) Successful in 48s
feat(tradein): infer repair_state from listing description text (#622) (#632)
2026-05-28 15:37:34 +00:00

223 lines
9.3 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.

"""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 = """
<html><body>
<div data-marker="item-view/item-id">№ 7986882804 · 18 мая в 13:40 · 1899 просмотров (+ 12 сегодня)</div>
<span itemprop="price" content="11990000">11 990 000 ₽</span>
<div data-marker="item-map-wrapper" data-map-lat="56.797713" data-map-lon="60.609135" data-location-id="654070"></div>
<div data-marker="item-view/item-params">
<ul>
<li>Количество комнат: 3</li>
<li>Общая площадь: 75 м²</li>
<li>Площадь кухни: 15 м²</li>
<li>Этаж: 20 из 26</li>
<li>Балкон или лоджия: лоджия</li>
<li>Ремонт: евро</li>
<li>Способ продажи: свободная</li>
<li>Условия продажи: возможна ипотека</li>
</ul>
<ul>
<li>Тип дома: монолитный</li>
<li>Этажей в доме: 26</li>
</ul>
</div>
<a data-marker="nd-jk-details-button" href="/catalog/houses/ekaterinburg/ul_postovskogo_17a/3171365">Дом</a>
<div data-marker="domoteka-entry-block">
<p data-marker="TeaserData.item">1 собственник</p>
<p data-marker="TeaserData.item">Последняя смена собственника 5 мая 2020</p>
<p data-marker="TeaserData.item">Не найдены ограничения и обременения</p>
<p data-marker="TeaserData.item">Совпадают площадь, адрес и этаж</p>
</div>
<div data-marker="item-view/item-description">Отличная квартира в новом ЖК. Чкаловская 11-15 мин пешком.</div>
</body></html>
"""
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 = """
<html><body>
<div data-marker="item-view/item-id">№ 1234567890</div>
<span itemprop="price" content="5000000">5 000 000 ₽</span>
<div data-marker="domoteka-entry-block">
<p data-marker="TeaserData.item">3 собственника или больше</p>
</div>
</body></html>
"""
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 = """
<html><body>
<div data-marker="item-view/item-id">№ 9999999999</div>
<span itemprop="price" content="3000000">3 000 000 ₽</span>
</body></html>
"""
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 = "<html><body><p>Нет айди</p></body></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 = """
<html><body>
<div data-marker="item-view/item-id">№ 1111111111</div>
<span itemprop="price" content="4000000">4 000 000 ₽</span>
<div data-marker="item-view/item-params">
<ul>
<li>Санузел: совмещенный</li>
<li>Окна: во двор и на улицу</li>
<li>Тип комнат: изолированные</li>
</ul>
</div>
</body></html>
"""
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 = """
<html><body>
<div data-marker="item-view/item-id">№ 2222222222</div>
<span itemprop="price" content="7000000">7 000 000 ₽</span>
<div data-marker="image-preview/item"><img src="https://img.avito.ru/img1.jpg"/></div>
<div data-marker="image-preview/item"><img src="https://img.avito.ru/img2.jpg"/></div>
</body></html>
"""
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 = """
<html><body>
<div data-marker="item-view/item-id">№ 3333333333</div>
<span itemprop="price" content="6000000">6 000 000 ₽</span>
<div data-marker="item-view/item-params">
<ul><li>Ремонт: евро</li></ul>
</div>
<div data-marker="item-view/item-description">Квартира без отделки, под чистовую</div>
</body></html>
"""
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 = """
<html><body>
<div data-marker="item-view/item-id">№ 4444444444</div>
<span itemprop="price" content="8000000">8 000 000 ₽</span>
<div data-marker="item-view/item-params">
<ul><li>Количество комнат: 2</li></ul>
</div>
<div data-marker="item-view/item-description">Сделан свежий евроремонт, заезжай и живи</div>
</body></html>
"""
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 = """
<html><body>
<div data-marker="item-view/item-id">№ 5555555555</div>
<span itemprop="price" content="9000000">9 000 000 ₽</span>
<div data-marker="item-view/item-description">Светлая квартира рядом с метро</div>
</body></html>
"""
result = parse_detail_html(html, "https://www.avito.ru/test_5555555555")
assert result.repair_state is None