gendesign/tradein-mvp/backend/tests/test_scraper_adapters_contracts.py
bot-backend d362b16d7c
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 2m39s
Deploy Trade-In / build-backend (push) Successful in 1m35s
Deploy Trade-In / deploy (push) Successful in 1m40s
fix(tradein/proxy): доводить сигнал бана площадки до пула (#2600 п.1) (#2653)
2026-08-05 11:37:35 +00:00

114 lines
4.5 KiB
Python
Raw Permalink 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)
# #2616 шаг 1: признак окружения присутствует и строка (проксирует settings.environment).
assert isinstance(config.environment, str)
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)
# #2164 sticky-session fix (2026-08): touch() heartbeat — продлевает lease для
# многочасовых browser-сессий (см. scraper_kit.browser_fetcher).
assert callable(provider.touch)
# #2600 п.1: mark_banned() — довести сигнал бана площадкой до пула.
assert callable(provider.mark_banned)
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