From 9fbe8e2717ba63d805058288a4e6da8384b3280c Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sun, 31 May 2026 21:07:46 +0300 Subject: [PATCH] fixup(tradein): merge main (#901 avito browser branch) + real SERP-routing test (#915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Branch was cut from pre-#901 main, so its avito.py lacked the _browser branch in _fetch_serp_html → pipeline's scraper._browser was a no-op (reviewer was right). Merge current main to bring #901's avito.py in; replace the monkeypatch SERP test with a real _fetch_serp_html call proving browser routing. Refs #883, #901 --- .../tests/test_pipeline_browser_routing.py | 59 +++++-------------- 1 file changed, 14 insertions(+), 45 deletions(-) diff --git a/tradein-mvp/backend/tests/test_pipeline_browser_routing.py b/tradein-mvp/backend/tests/test_pipeline_browser_routing.py index 0bb8a636..3777a290 100644 --- a/tradein-mvp/backend/tests/test_pipeline_browser_routing.py +++ b/tradein-mvp/backend/tests/test_pipeline_browser_routing.py @@ -368,57 +368,26 @@ async def test_fetch_detail_curl_mode_no_browser_fetcher() -> None: assert result.item_id == "99887766" -# ── 6. SERP routing: browser-mode — _cffi never touched (refutes #916 concern) ─ +# ── 6. SERP routing: real _fetch_serp_html routes through browser ──────────── @pytest.mark.asyncio -async def test_fetch_serp_html_uses_browser_when_browser_set() -> None: - """SERP routing: with _browser set and _cffi=None, fetch_around routes SERP - through _browser and never dereferences _cffi (no AssertionError). - - Refutes #916 reviewer concern that _cffi assert always fires in browser-mode. - The pipeline sets scraper._browser before calling fetch_around; _fetch_serp_html - is monkey-patched at method level to verify _browser is present and _cffi is - absent at call time — proving the routing contract holds end-to-end without - a network call. - """ - from unittest.mock import AsyncMock, patch +async def test_fetch_serp_html_routes_through_browser() -> None: + """With _browser set and _cffi=None, the REAL _fetch_serp_html returns the + browser HTML and never dereferences _cffi (no AssertionError). Proves #915 + SERP routing on top of #901's browser branch.""" + from unittest.mock import AsyncMock from app.services.scrapers.avito import AvitoScraper scraper = AvitoScraper() - assert scraper._cffi is None # browser-mode: curl session never created + assert scraper._cffi is None + scraper._browser = AsyncMock() + scraper._browser.fetch = AsyncMock(return_value="real serp listing") - mock_browser = AsyncMock() - # Non-firewall SERP HTML: avoid AvitoBlockedError (_FIREWALL_MARKERS not present) - serp_html = "
ok serp result page
" - mock_browser.fetch = AsyncMock(return_value=serp_html) - scraper._browser = mock_browser + html = await scraper._fetch_serp_html( + "https://www.avito.ru/ekaterinburg/kvartiry/prodam", page=1 + ) - browser_calls: list[str] = [] - - async def _patched_fetch_serp(url: str, page: int) -> str | None: - # Core assertion: _browser is set, _cffi is still None in browser-mode - assert scraper._browser is not None, "_browser must be set in browser-mode" - assert scraper._cffi is None, "_cffi must remain None in browser-mode (no AssertionError)" - browser_calls.append(url) - # Delegate to the mock browser (as production code does via browser_fetcher.fetch) - return await scraper._browser.fetch(url) - - # Patch at method level — exercises fetch_around → _fetch_serp_html pathway - scraper._fetch_serp_html = _patched_fetch_serp # type: ignore[method-assign] - - # Patch _parse_html so 0-cards doesn't obscure the SERP-routing assertion - with patch.object(scraper, "_parse_html", return_value=[]): - try: - await scraper.fetch_around(56.84, 60.60, 1000, pages=1) - except Exception: - # AvitoContentBlockedError on 0-card page=1 is expected — irrelevant to routing - pass - - # _browser.fetch was called → SERP routed through browser, not _cffi - assert ( - len(browser_calls) >= 1 - ), "browser.fetch was never called — SERP not routed through browser" - mock_browser.fetch.assert_awaited() - # Reaching here without AssertionError proves _cffi=None did not crash the SERP path + assert html == "real serp listing" + scraper._browser.fetch.assert_awaited_once()