gendesign/tradein-mvp/backend/tests/test_cian_views_parse.py
bot-backend bc5233de15
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
fix(tradein/cian): просмотры перестают теряться на int() по фразе (#2669) (#2705)
2026-08-06 06:46:18 +00:00

70 lines
3.4 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"""#2669: Cian отдаёт просмотры фразой — разбор обязан доставать из неё числа.
Замер на проде до правки: `listings.views_total` пуст у ВСЕХ 21 799 cian-строк
(0 из 1 571 detail-обогащённых), при том что у avito 12 350/12 352, у domklik
6 296/6 296, у yandex 1 146/1 210 — потеря ровно одна и только у Cian.
Корень: `_parse_views` звала `int()` по всей фразе
`"146 просмотров, 8 за сегодня"` → ValueError → None (except в самой функции).
Тесты гоняют настоящий сохранённый ответ (fixtures/cian_flat_330982715.html)
через настоящий `fetch_detail` + перебирают формы фразы.
"""
from __future__ import annotations
import os
from unittest.mock import AsyncMock, MagicMock
import pytest
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
from scraper_kit.providers.cian.detail import _parse_views, fetch_detail
_FIXTURE = os.path.join(os.path.dirname(__file__), "fixtures", "cian_flat_330982715.html")
# Дословно из фикстуры: "stats":{"totalViewsFormattedString":"146 просмотров, 8 за сегодня"}
_FIXTURE_PHRASE = "146 просмотров, 8 за сегодня"
def _fixture_html() -> str:
with open(_FIXTURE, encoding="utf-8") as fh:
return fh.read()
def test_fixture_still_carries_the_phrase() -> None:
"""Страховка: тесты ниже бессмысленны, если фикстура перестала содержать фразу."""
assert f'"totalViewsFormattedString":"{_FIXTURE_PHRASE}"' in _fixture_html()
@pytest.mark.parametrize(
("phrase", "expected"),
[
(_FIXTURE_PHRASE, (146, 8)), # форма из сохранённого ответа
("1 просмотр", (1, None)), # единственное число, хвоста нет
("2 просмотра", (2, None)),
("0 просмотров", (0, None)), # ноль — это 0, а не «нет данных»
("1 234 просмотра", (1234, None)), # разделитель тысяч — обычный пробел
("1\xa0234 просмотра", (1234, None)), # ... и NBSP
("12345 просмотров, 1\xa0234 за сегодня", (12345, 1234)), # narrow NBSP
("1 234", (1234, None)), # голое число (форма из старого докстринга)
("", (None, None)),
(None, (None, None)),
("просмотров нет", (None, None)), # без цифр — пусто, а не 0
],
)
def test_parse_views_forms(phrase: str | None, expected: tuple[int | None, int | None]) -> None:
assert _parse_views(phrase) == expected
async def test_fetch_detail_fills_views_from_real_saved_page() -> None:
"""Настоящая сохранённая страница → views_total/views_today непусты (#2669)."""
fetcher = MagicMock()
fetcher.fetch = AsyncMock(return_value=_fixture_html())
result = await fetch_detail("https://ekb.cian.ru/sale/flat/330982715/", browser_fetcher=fetcher)
assert result is not None
assert result.views_total == 146, "views_total снова теряется — колонка останется пустой"
assert result.views_today == 8