gendesign/tradein-mvp/backend/tests/test_scraper_adapters_contracts.py
bot-backend 15361e6662
All checks were successful
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
feat(proxy-pool): P4 — browser dynamic pool-proxy (acquire→/fetch→relaunch) + orchestration wiring, ship-dark (#2164)
2026-07-02 22:18:51 +03:00

107 lines
3.9 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.

"""Проверка, что backend-адаптеры структурно удовлетворяют scraper_kit.contracts.
Двойная проверка:
1. Статическая — `_check_*` функции с аннотацией Protocol-типа. mypy проверит,
что каждый адаптер присваивается соответствующему Protocol'у (структурно).
2. Рантайм — `isinstance` против `@runtime_checkable` Protocol'ов (проверяет
наличие методов/атрибутов). Ловит рассинхрон имён без запуска mypy.
Границы контрактов зафиксированы для #2131 (шаг B); переключение скрапперов на
инжекцию — шаг C.
"""
from __future__ import annotations
from scraper_kit.contracts import (
EnrichmentJobs,
HouseMatcher,
ProxyProvider,
ScraperConfig,
SessionFactory,
)
from app.services.scraper_adapters import (
RealEnrichmentJobs,
RealMatcherAdapter,
RealProxyProvider,
RealScraperConfig,
RealSessionFactory,
)
def _check_matcher(m: HouseMatcher) -> None:
"""Статическая проверка: RealMatcherAdapter ⊑ HouseMatcher."""
def _check_config(c: ScraperConfig) -> None:
"""Статическая проверка: RealScraperConfig ⊑ ScraperConfig."""
def _check_session_factory(f: SessionFactory) -> None:
"""Статическая проверка: RealSessionFactory ⊑ SessionFactory."""
def _check_enrichment(j: EnrichmentJobs) -> None:
"""Статическая проверка: RealEnrichmentJobs ⊑ EnrichmentJobs."""
def _check_proxy_provider(p: ProxyProvider) -> None:
"""Статическая проверка: RealProxyProvider ⊑ ProxyProvider."""
def test_matcher_adapter_satisfies_protocol() -> None:
adapter = RealMatcherAdapter()
_check_matcher(adapter)
assert isinstance(adapter, HouseMatcher)
assert callable(adapter.match_or_create_house)
assert callable(adapter.upsert_listing_source)
def test_scraper_config_satisfies_protocol() -> None:
config = RealScraperConfig()
_check_config(config)
assert isinstance(config, ScraperConfig)
# Ключевые поля читаются без ошибок (проксируют в singleton settings).
assert isinstance(config.scraper_fetch_mode, str)
assert isinstance(config.browser_http_endpoint, str)
assert isinstance(config.avito_proxy_max_rotations, int)
assert isinstance(config.avito_serp_ekb_only, bool)
# #2163: новый флаг proxy-пула присутствует и bool.
assert isinstance(config.use_proxy_pool_curl, bool)
# #2164 P4: флаг browser-пула присутствует и bool.
assert isinstance(config.use_proxy_pool_browser, bool)
def test_proxy_provider_satisfies_protocol() -> None:
provider = RealProxyProvider()
_check_proxy_provider(provider)
assert isinstance(provider, ProxyProvider)
assert callable(provider.acquire)
assert callable(provider.release)
assert callable(provider.mark_health)
def test_session_factory_satisfies_protocol() -> None:
factory = RealSessionFactory()
_check_session_factory(factory)
assert isinstance(factory, SessionFactory)
def test_enrichment_jobs_satisfies_protocol() -> None:
jobs = RealEnrichmentJobs()
_check_enrichment(jobs)
assert isinstance(jobs, EnrichmentJobs)
assert callable(jobs.house_imv_backfill)
assert callable(jobs.house_dedup_merge)
assert callable(jobs.yandex_address_backfill)
def test_contracts_module_has_no_app_imports() -> None:
"""contracts.py не должен импортировать app.* (иначе circular / coupling)."""
import inspect
import scraper_kit.contracts as contracts_mod
source = inspect.getsource(contracts_mod)
assert "from app" not in source
assert "import app" not in source