fix(#3386): curl_proxy_url — health=False и на отмене (BaseException); admin IMV через пул; тест без create=True
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
This commit is contained in:
parent
6f97995140
commit
33cad4f82e
4 changed files with 45 additions and 6 deletions
|
|
@ -1106,6 +1106,10 @@ async def scrape_avito_imv(
|
|||
has_balcony=has_balcony,
|
||||
has_loggia=has_loggia,
|
||||
config=RealScraperConfig(),
|
||||
# #3386: без provider'а curl_proxy_url считает use_pool=False (флаг AND
|
||||
# provider is not None) → env-прокси SCRAPER_PROXY_URL, мёртвый узел (#2613).
|
||||
# _kit_proxy_provider() отдаёт None при выключенных флагах — ship-dark цел.
|
||||
proxy_provider=_kit_proxy_provider(),
|
||||
)
|
||||
except IMVAddressNotFoundError as e:
|
||||
# Ожидаемое клиентское условие (адрес не в базе Avito), НЕ сбой — logger.warning
|
||||
|
|
|
|||
|
|
@ -158,9 +158,10 @@ def test_empty_pool_in_production_degrades_without_imv(
|
|||
async def _run() -> None:
|
||||
nonlocal result
|
||||
with (
|
||||
# create=True: на main символа в estimator нет, и без него тест краснел бы
|
||||
# AttributeError'ом («возможности нет»), а не неверным ЗНАЧЕНИЕМ.
|
||||
patch.object(estimator, "RealProxyProvider", _EmptyPoolProvider, create=True),
|
||||
# Без create=True: `RealProxyProvider` импортирован в estimator (estimator.py:83),
|
||||
# патч обязан бить по РЕАЛЬНОМУ атрибуту. С create=True переименование импорта
|
||||
# оставило бы тест зелёным против фантома.
|
||||
patch.object(estimator, "RealProxyProvider", _EmptyPoolProvider),
|
||||
patch("curl_cffi.requests.AsyncSession", _no_http),
|
||||
):
|
||||
result = await _call(_db_cache_miss(), address="ЕКБ, ул. Тургенева, 4")
|
||||
|
|
@ -210,7 +211,7 @@ def _run_with_pool(
|
|||
async def _run() -> None:
|
||||
nonlocal result
|
||||
with (
|
||||
patch.object(estimator, "RealProxyProvider", lambda: provider, create=True),
|
||||
patch.object(estimator, "RealProxyProvider", lambda: provider),
|
||||
patch.object(estimator, "save_imv_evaluation", return_value=1),
|
||||
patch("curl_cffi.requests.AsyncSession", lambda *a, **kw: session),
|
||||
# Транспорт нам не интересен — проверяем жизненный цикл lease вокруг него.
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ class-based провайдера (YandexValuationScraper):
|
|||
- флаг on + lease → fetch через lease.url, mark_health вызван, release в finally
|
||||
(в prod и dev одинаково — пул выдал лизу, отказа быть не должно);
|
||||
- исключение внутри блока → mark_health(ok=False) + release всё равно (lease не течёт);
|
||||
- отмена/прерывание (CancelledError/KeyboardInterrupt/SystemExit — BaseException, не
|
||||
Exception) внутри блока → тот же ok=False, исключение пролетает наружу;
|
||||
- acquire кинул → fallback env (dev) / NoProxyAvailableError (prod).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
|
@ -159,6 +162,30 @@ def test_plain_exception_does_not_call_mark_banned() -> None:
|
|||
assert spy.release_calls == [7]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"exc_type", [asyncio.CancelledError, KeyboardInterrupt, SystemExit], ids=lambda t: t.__name__
|
||||
)
|
||||
def test_base_exception_marks_fail_and_still_releases(exc_type: type[BaseException]) -> None:
|
||||
"""Отмена/прерывание внутри блока — тоже ok=False, а не «узел здоров».
|
||||
|
||||
`asyncio.CancelledError` наследует BaseException, а не Exception: при таймауте
|
||||
(`asyncio.wait_for` в `_with_budget` эстиматора) отмена приходит внутрь await'а
|
||||
ВНУТРИ блока. С `except Exception` она пролетала мимо, `ok` оставался True и
|
||||
в finally узел, из-за которого запрос завис до отмены, записывался ЗДОРОВЫМ.
|
||||
KeyboardInterrupt/SystemExit — тот же путь: health честный, но исключение
|
||||
обязано пролететь наружу (`raise` в блоке сохранён, не проглатываем).
|
||||
"""
|
||||
cfg = _FakeConfig(use_proxy_pool_curl=True)
|
||||
spy = _SpyProvider(_LEASE)
|
||||
with pytest.raises(exc_type):
|
||||
with curl_proxy_url(cfg, spy, "avito", env_fallback_url=None) as url:
|
||||
assert url == _LEASE.url
|
||||
raise exc_type()
|
||||
assert spy.mark_health_calls == [(7, False)]
|
||||
assert spy.release_calls == [7] # ровно один раз, lease не течёт
|
||||
assert spy.mark_banned_calls == [] # отмена ≠ бан площадки
|
||||
|
||||
|
||||
def test_acquire_raises_falls_back_to_env() -> None:
|
||||
cfg = _FakeConfig(use_proxy_pool_curl=True)
|
||||
spy = _SpyProvider(_LEASE, acquire_raises=True)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
(env_fallback_url) — curl-пути ходят ровно как сейчас, прод не меняется (легитимный
|
||||
dev/no-op путь — см. providers/cian/detail.py:118).
|
||||
- Флаг on + пул выдал lease → yield lease.url; на выходе mark_health(ok) + release(lease).
|
||||
ok=True если блок отработал без исключения, ok=False если внутри поднялось (бан/ошибка).
|
||||
ok=True если блок отработал без исключения, ok=False если внутри поднялось (бан/ошибка/
|
||||
отмена — ловим BaseException, иначе таймаут-cancel записывал бы зависший узел здоровым).
|
||||
- Флаг on + пул пуст/ошибка acquire + окружение НЕ "production" (dev/test) → fallback на
|
||||
env_fallback_url, НЕ падаем (легитимно для локальной разработки/тестов).
|
||||
- Флаг on + пул пуст/ошибка acquire + окружение "production" (#2616 шаг 1) → НЕ идём на
|
||||
|
|
@ -112,7 +113,13 @@ def curl_proxy_url(
|
|||
banned = False
|
||||
try:
|
||||
yield lease.url
|
||||
except Exception as exc:
|
||||
except BaseException as exc:
|
||||
# BaseException, а не Exception: `asyncio.CancelledError` (таймаут `wait_for` —
|
||||
# напр. `_with_budget` эстиматора — отменяет await ВНУТРИ блока) наследует
|
||||
# BaseException. С `except Exception` отмена пролетала мимо, ok оставался True,
|
||||
# и в finally узел, из-за которого запрос завис до отмены, писался ЗДОРОВЫМ.
|
||||
# `raise` сохранён — KeyboardInterrupt/SystemExit пролетают наружу как раньше,
|
||||
# меняется только честность health-сигнала.
|
||||
ok = False
|
||||
banned = isinstance(exc, ProxyBanError)
|
||||
raise
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue