Merge pull request 'fix(tradein/domclick): бан сайдкара наследуется от httpx-ошибки и уезжал в транспортные сбои (#3283)' (#3287) from fix/3283b-sidecar-ban-is-httpx-subclass into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 4m5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m6s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 4m5s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m6s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
This commit is contained in:
commit
189ae63e53
2 changed files with 71 additions and 4 deletions
|
|
@ -91,7 +91,11 @@ from dataclasses import dataclass, field
|
|||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import httpx
|
||||
from scraper_kit.browser_fetcher import BrowserFetcher, ban_kind_from_status
|
||||
from scraper_kit.browser_fetcher import (
|
||||
BrowserFetcher,
|
||||
SidecarBanPageError,
|
||||
ban_kind_from_status,
|
||||
)
|
||||
from scraper_kit.domclick_exceptions import DomClickBlockedError, DomClickParseError
|
||||
from scraper_kit.providers.domclick.detail import fetch_detail, save_detail_enrichment
|
||||
from scraper_kit.proxy_errors import NoProxyAvailableError
|
||||
|
|
@ -221,10 +225,17 @@ def _is_transport_failure(exc: BaseException) -> bool:
|
|||
* ветка `except Exception` — таймаут / 5xx сайдкара / транспорт, обёрнутый
|
||||
`raise ... from exc`. Исходное исключение остаётся в `__cause__`.
|
||||
|
||||
Смотрим именно на третий случай: httpx-ошибка в цепочке причин. Отсутствие
|
||||
статуса признаком служить не может — им как раз отличается генуинный блок.
|
||||
ПОРЯДОК ПРОВЕРОК ЗДЕСЬ НЕСЛУЧАЕН. `SidecarBanPageError` объявлен как
|
||||
`class SidecarBanPageError(httpx.HTTPStatusError)`, то есть ПО ТИПУ он
|
||||
httpx-ошибка — и проверка на httpx, стоящая первой, объявляла генуинный
|
||||
отказ площадки транспортным сбоем. Поймано на проде: прогон 5406 записал
|
||||
`http=401, kind=platform` под заголовком «не блок площадки». Поэтому бан
|
||||
сайдкара отсекается ДО общей httpx-ветки, а не после.
|
||||
"""
|
||||
return any(isinstance(c, httpx.HTTPError) for c in _iter_causes(exc))
|
||||
causes = _iter_causes(exc)
|
||||
if any(isinstance(c, SidecarBanPageError) for c in causes):
|
||||
return False
|
||||
return any(isinstance(c, httpx.HTTPError) for c in causes)
|
||||
|
||||
|
||||
def _ban_kind_of_block(exc: DomClickBlockedError) -> str:
|
||||
|
|
|
|||
|
|
@ -202,3 +202,59 @@ async def test_success_resets_the_soft_streak() -> None:
|
|||
result, _ = await _run(seq, n=18, max_consecutive_soft_failures=4)
|
||||
assert result.attempted == 18
|
||||
assert result.enriched == 9
|
||||
|
||||
|
||||
# ── SidecarBanPageError — httpx-ошибка ПО ТИПУ, но генуинный отказ по смыслу ──
|
||||
# Поймано на проде уже ПОСЛЕ первой правки: прогон 5406 записал отказ площадки
|
||||
# (http=401, kind=platform) под заголовком «не блок площадки». Причина —
|
||||
# `class SidecarBanPageError(httpx.HTTPStatusError)`: проверка на httpx, стоящая
|
||||
# первой, забирала генуинный бан себе. Тесты ниже закрепляют порядок проверок.
|
||||
|
||||
|
||||
def _sidecar_ban_page() -> DomClickBlockedError:
|
||||
"""Форма ровно как в fetch_detail: DomClickBlockedError ← SidecarBanPageError."""
|
||||
from scraper_kit.browser_fetcher import SidecarBanPageError
|
||||
|
||||
ban = SidecarBanPageError(
|
||||
"tradein-browser[domclick]: статический отказ площадки",
|
||||
request=httpx.Request("POST", "http://tradein-browser:3000/fetch"),
|
||||
response=httpx.Response(500),
|
||||
upstream_status=401,
|
||||
)
|
||||
exc = DomClickBlockedError("DomClick detail: sidecar detected platform refusal", status=401)
|
||||
exc.__cause__ = ban
|
||||
return exc
|
||||
|
||||
|
||||
def test_sidecar_ban_page_is_a_subclass_of_httpx_error() -> None:
|
||||
"""Предпосылка теста ниже — зафиксировать её, а не подразумевать.
|
||||
|
||||
Если иерархия однажды изменится, тест скажет об этом прямо, а не молча
|
||||
перестанет проверять то, ради чего написан.
|
||||
"""
|
||||
from scraper_kit.browser_fetcher import SidecarBanPageError
|
||||
|
||||
assert issubclass(SidecarBanPageError, httpx.HTTPError)
|
||||
|
||||
|
||||
async def test_sidecar_ban_page_counts_as_block_not_transport() -> None:
|
||||
"""Отказ, опознанный сайдкаром по маркерам тела, — блок площадки."""
|
||||
result, _ = await _run([_sidecar_ban_page()] * 10, n=10, max_consecutive_blocks=3)
|
||||
assert result.blocked == 3
|
||||
assert result.failed == 0
|
||||
assert result.attempted == 3
|
||||
|
||||
|
||||
async def test_sidecar_ban_page_aborts_the_run() -> None:
|
||||
"""Три отказа площадки подряд обязаны рвать прогон — ровно как до #3283."""
|
||||
_, runs = await _run([_sidecar_ban_page()] * 10, n=10, max_consecutive_blocks=3)
|
||||
runs.mark_backfill_finished.assert_called_once()
|
||||
assert runs.mark_backfill_finished.call_args.kwargs["aborted_by_blocks"] is True
|
||||
|
||||
|
||||
async def test_plain_sidecar_500_still_counts_as_transport() -> None:
|
||||
"""Обычная 500 от сайдкара остаётся сбоем: правка не должна отменить #3283."""
|
||||
result, _ = await _run([_sidecar_500()] * 5 + [None] * 5, n=10)
|
||||
assert result.blocked == 0
|
||||
assert result.failed == 5
|
||||
assert result.enriched == 5
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue