All checks were successful
Deploy Trade-In / build-browser (push) Successful in 2m13s
Deploy Trade-In / build-backend (push) Successful in 1m18s
Deploy Trade-In / deploy (push) Successful in 2m29s
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m32s
107 lines
3.9 KiB
Python
107 lines
3.9 KiB
Python
"""Проверка, что 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
|