gendesign/tradein-mvp/backend/tests/test_admin_scrape_avito_proxy_provider.py
bot-backend a95c38bdcc
All checks were successful
CI / changes (pull_request) Successful in 7s
CI Trade-In / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 2m40s
fix(tradein/scraper): подключить браузерный путь Авито к пулу прокси
2026-08-02 17:31:00 +03:00

104 lines
4 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.

"""#2616: POST /api/v1/admin/scrape (source=avito) прокидывает proxy_provider в AvitoScraper.
Root cause (issue #2613/#2616): `AvitoScraper(config, delay_provider=get_scraper_delay)` в
`scrape_around` не передавал `proxy_provider=proxy_provider`, хотя переменная уже вычислена
в scope и передаётся в CianScraper/YandexRealtyScraper на соседних строках. Браузерный путь
Avito (SCRAPER_FETCH_MODE=browser) в результате всегда шёл через мёртвый `BROWSER_PROXY_AVITO`
env-прокси вместо пула.
Offline: db/AvitoScraper/save_listings мокаются, NO live network/DB.
"""
from __future__ import annotations
import os
from typing import Any, ClassVar
from unittest.mock import MagicMock, patch
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
@pytest.fixture
def client() -> TestClient:
from app.api.v1 import admin as admin_module
from app.core.db import get_db
app = FastAPI()
app.include_router(admin_module.router, prefix="/api/v1/admin")
def fake_db() -> Any:
yield MagicMock()
app.dependency_overrides[get_db] = fake_db
return TestClient(app)
class _RecordingScraper:
"""Recording async-context-manager: пишет init-kwargs в общий список, fetch_around
возвращает пустой список лотов (save_listings ниже мокнут, до БД не доходим)."""
calls: ClassVar[list[dict[str, Any]]] = []
def __init__(self, config: Any, **kwargs: Any) -> None:
type(self).calls.append({"config": config, **kwargs})
async def __aenter__(self) -> _RecordingScraper:
return self
async def __aexit__(self, *_args: object) -> None:
return None
async def fetch_around(self, *_args: Any, **_kwargs: Any) -> list[Any]:
return []
def test_scrape_avito_passes_proxy_provider(client: TestClient) -> None:
"""source=['avito'] → AvitoScraper(config, ..., proxy_provider=<sentinel>).
Falsification: если admin.py перестанет передавать proxy_provider= в AvitoScraper(...),
calls[-1] не будет содержать ключ 'proxy_provider' со значением sentinel (либо будет
None) — assert падает на VALUE (или KeyError на .get None-fallback), не на TypeError
(recorder принимает **kwargs, сигнатуру не проверяет).
"""
sentinel = object()
_RecordingScraper.calls = []
with (
patch("app.api.v1.admin.AvitoScraper", _RecordingScraper),
patch("app.api.v1.admin._kit_proxy_provider", return_value=sentinel),
patch("app.api.v1.admin.save_listings", return_value=(0, 0)),
):
r = client.post(
"/api/v1/admin/scrape",
json={"lat": 56.8332, "lon": 60.5944, "radius_m": 1000, "sources": ["avito"]},
)
assert r.status_code == 200
assert len(_RecordingScraper.calls) == 1
assert _RecordingScraper.calls[0].get("proxy_provider") is sentinel
def test_scrape_avito_no_pool_default_none(client: TestClient) -> None:
"""_kit_proxy_provider() → None (pool off, ship-dark default) → AvitoScraper получает
proxy_provider=None — поведение прежнее.
"""
_RecordingScraper.calls = []
with (
patch("app.api.v1.admin.AvitoScraper", _RecordingScraper),
patch("app.api.v1.admin._kit_proxy_provider", return_value=None),
patch("app.api.v1.admin.save_listings", return_value=(0, 0)),
):
r = client.post(
"/api/v1/admin/scrape",
json={"lat": 56.8332, "lon": 60.5944, "radius_m": 1000, "sources": ["avito"]},
)
assert r.status_code == 200
assert len(_RecordingScraper.calls) == 1
assert _RecordingScraper.calls[0].get("proxy_provider") is None