"""Soft-block (HTTP 200 generic-витрина) detection на detail-странице (#1967 / #1950).
Под нагрузкой Avito иногда отдаёт HTTP 200 с ОБЩЕЙ витриной
(
Авито — Объявления на сайте Авито) вместо запрошенного объявления —
мягкий анти-бот deflect. У такой страницы нет ни firewall-маркеров, ни item-view →
parse_detail_html ронял ValueError → generic failed, IP НЕ ротировался, enriched
каскадно падал к 0. _is_detail_soft_block детектит её как блок → существующая
403/firewall reconnect-машинерия пересоздаёт эфемерную сессию (свежий exit-IP).
Зеркало fixture-стиля test_avito_detail_403_reconnect.py.
"""
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from app.core.config import settings
from app.services.scrapers import avito_detail as detail_mod
from app.services.scrapers.avito_detail import fetch_detail
from app.services.scrapers.avito_exceptions import AvitoBlockedError
_SENTINEL = object()
_SOFT_BLOCK_HTML = (
''
"Авито — Объявления на сайте Авито"
"...
"
)
def _resp(status_code: int, text: str = "") -> MagicMock:
r = MagicMock()
r.status_code = status_code
r.text = text
return r
def _enable_backconnect(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "scraper_proxy_url_env", "http://u:p@mproxy.site:14619")
@pytest.fixture(autouse=True)
def _patch_common() -> object:
"""asyncio.sleep → no-op; parse_detail_html → sentinel (нет нужды в реальном HTML)."""
with (
patch("app.services.scrapers.avito_detail.asyncio.sleep", AsyncMock()),
patch(
"app.services.scrapers.avito_detail.parse_detail_html", return_value=_SENTINEL
) as parse,
):
yield parse
@pytest.mark.asyncio
async def test_soft_block_200_reconnect_succeeds(monkeypatch: pytest.MonkeyPatch) -> None:
"""soft-block(HTTP 200) → 200: generic-витрина детектится как блок → reconnect."""
_enable_backconnect(monkeypatch)
caller = AsyncMock()
caller.get = AsyncMock(return_value=_resp(200, _SOFT_BLOCK_HTML))
retry1 = AsyncMock()
retry1.get = AsyncMock(return_value=_resp(200, "№ 123"
assert detail_mod._is_detail_soft_block(item_view) is False
assert detail_mod._is_detail_soft_block("