gendesign/tradein-mvp/backend/tests/test_scraper_adapters_contracts.py
lekss361 0937da4260
All checks were successful
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 8s
Deploy Trade-In / test (push) Successful in 1m26s
Deploy Trade-In / build-backend (push) Successful in 1m18s
Deploy Trade-In / deploy (push) Successful in 1m22s
feat(proxy-pool): P3 — kit curl-paths acquire/release from pool behind USE_PROXY_POOL_CURL, ship-dark (#2163)
2026-07-02 18:56:36 +00:00

105 lines
3.8 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)
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