fix(tradein): сайдкар отдаёт подтверждённый бан площадки кодом 403, не 500
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m31s
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m31s
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
Единственный код 500 означал и «площадка забанила», и «сайдкар упал»: разбор каждого инцидента начинался с ложного следа — в лог провайдера и в houses.imv_error_reason уезжала httpx-преамбула «Server error '500 Internal Server Error' for url 'http://tradein-browser:3000/fetch'», то есть текст ошибки называл гонца, а не виновника. - browser/server.py: BanPageDetectedError → 403 (доступ ограничен площадкой); 451 — про юридическую блокировку, это не она. Своих 403 сайдкар не отдаёт (400/422/503), код однозначен. classify_browser_probe не задета: у неё любой status >= 400 → "sidecar". Тело не меняется — ban_page/status на месте. - scraper_kit/browser_fetcher.py: текст SidecarBanPageError теперь свой — «площадка отдала бан-страницу (upstream 403, ответ сайдкара 403): …». Распознавание остаётся по ТЕЛУ и code-agnostic: tradein-browser — отдельный образ со своим деплоем, версии штатно расходятся на часы, и гейт по коду в этот час уводил бы отказ площадки в инфра-ветку. - Тесты: 403 → SidecarBanPageError; старый 500 + ban_page → он же; чистый 500 без ban_page → прежний инфра-диагноз; текст ошибки без «500»/«Server error». Refs #3288 п.4
This commit is contained in:
parent
357c4348ac
commit
1200af58f5
4 changed files with 86 additions and 10 deletions
|
|
@ -175,9 +175,10 @@ def test_ban_kind_values_fit_scrape_runs_check() -> None:
|
|||
# 'platform', то есть ротация IP не запустилась бы вовсе.
|
||||
|
||||
|
||||
def _sidecar_response(body: Any) -> httpx.Response:
|
||||
def _sidecar_response(body: Any, status: int = 403) -> httpx.Response:
|
||||
"""Ответ сайдкара. 403 — код подтверждённого бана с #3288 п.4 (был 500)."""
|
||||
return httpx.Response(
|
||||
500, json=body, request=httpx.Request("POST", "http://tradein-browser:3000/fetch")
|
||||
status, json=body, request=httpx.Request("POST", "http://tradein-browser:3000/fetch")
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -190,6 +191,41 @@ def test_ban_page_body_raises_typed_error_with_upstream_status() -> None:
|
|||
assert excinfo.value.upstream_status == 401
|
||||
|
||||
|
||||
def test_ban_page_legacy_500_still_recognised() -> None:
|
||||
"""Сайдкар старого образа отдаёт тот же ban_page с кодом 500 — диагноз тот же.
|
||||
|
||||
tradein-browser собирается ОТДЕЛЬНЫМ образом и деплоится своим шагом, так что
|
||||
после выката #3288 клиент часами работает со старым сайдкаром. Гейта по коду
|
||||
нет намеренно: он бы в этот час уводил отказ площадки в инфра-ветку.
|
||||
"""
|
||||
resp = _sidecar_response(
|
||||
{"error": "BanPageDetectedError: статический отказ", "ban_page": True, "status": 401},
|
||||
status=500,
|
||||
)
|
||||
with pytest.raises(SidecarBanPageError) as excinfo:
|
||||
_raise_for_sidecar_status(resp)
|
||||
assert excinfo.value.upstream_status == 401
|
||||
|
||||
|
||||
def test_ban_page_message_names_platform_not_sidecar_500() -> None:
|
||||
"""Текст ошибки называет виновника: отказ ПЛОЩАДКИ, а не падение нашего сайдкара.
|
||||
|
||||
До #3288 п.4 сюда уезжала httpx-преамбула «Server error '500 Internal Server
|
||||
Error' for url 'http://tradein-browser:3000/fetch'» — и ровно она попадала в
|
||||
лог провайдера и в imv_error_reason, отправляя разбор по ложному следу.
|
||||
"""
|
||||
resp = _sidecar_response(
|
||||
{"error": "BanPageDetectedError: доступ ограничен", "ban_page": True, "status": 403}
|
||||
)
|
||||
with pytest.raises(SidecarBanPageError) as excinfo:
|
||||
_raise_for_sidecar_status(resp)
|
||||
message = str(excinfo.value)
|
||||
assert "500" not in message
|
||||
assert "Server error" not in message
|
||||
assert "бан-страницу" in message
|
||||
assert "upstream 403" in message
|
||||
|
||||
|
||||
def test_ban_page_error_is_httpx_status_error() -> None:
|
||||
"""Подкласс — иначе retry-политика fetch() и ловля у прочих поставщиков сломались бы."""
|
||||
resp = _sidecar_response({"error": "BanPageDetectedError: x", "ban_page": True, "status": 403})
|
||||
|
|
@ -198,7 +234,8 @@ def test_ban_page_error_is_httpx_status_error() -> None:
|
|||
|
||||
|
||||
def test_plain_500_stays_plain_status_error() -> None:
|
||||
resp = _sidecar_response({"error": "Error: Page.goto: NS_ERROR_PROXY_BAD_GATEWAY"})
|
||||
"""Настоящий сбой сайдкара остаётся 500-кой БЕЗ ban_page → инфра-диагноз, как раньше."""
|
||||
resp = _sidecar_response({"error": "Error: Page.goto: NS_ERROR_PROXY_BAD_GATEWAY"}, status=500)
|
||||
with pytest.raises(httpx.HTTPStatusError) as excinfo:
|
||||
_raise_for_sidecar_status(resp)
|
||||
assert not isinstance(excinfo.value, SidecarBanPageError)
|
||||
|
|
@ -230,7 +267,8 @@ def test_non_json_error_body_does_not_break() -> None:
|
|||
|
||||
def test_success_response_raises_nothing() -> None:
|
||||
resp = httpx.Response(
|
||||
200, json={"html": "<html/>", "status": 200},
|
||||
200,
|
||||
json={"html": "<html/>", "status": 200},
|
||||
request=httpx.Request("POST", "http://tradein-browser:3000/fetch"),
|
||||
)
|
||||
_raise_for_sidecar_status(resp)
|
||||
|
|
|
|||
|
|
@ -1466,10 +1466,23 @@ async def fetch_handler(request: web.Request) -> web.Response:
|
|||
# транспорта, и только за первым стоит report_ban на клиенте.
|
||||
# До этой правки оба случая приезжали одинаковой 500-кой, клиент
|
||||
# различить их не мог и настоящий отказ площадки переставал
|
||||
# ротировать узел (регрессия #3237). Код ответа НЕ меняем: на 500
|
||||
# завязана classify_browser_probe, признак несёт тело.
|
||||
# ротировать узел (регрессия #3237).
|
||||
#
|
||||
# #3288 п.4: код ответа теперь РАЗНЫЙ. 500 означало и «площадка
|
||||
# забанила», и «сайдкар упал», поэтому разбор каждого инцидента
|
||||
# начинался с ложного следа («Server error 500 for url
|
||||
# tradein-browser:3000/fetch» в логе провайдера). 403 —
|
||||
# «в доступе отказано», ровно то, что произошло НА ПЛОЩАДКЕ;
|
||||
# 451 семантически про юридическую блокировку, это не она.
|
||||
# Своих 403 сайдкар не отдаёт (auth/валидация — 400/422/503),
|
||||
# так что код однозначен. classify_browser_probe не задет: у неё
|
||||
# любой status >= 400 → "sidecar", 403 и 500 там неразличимы.
|
||||
# Признак ban_page в теле ОСТАЁТСЯ — клиент опознаёт бан по нему,
|
||||
# а не по коду (сайдкар и backend — разные образы, версии едут
|
||||
# врозь).
|
||||
error_body["ban_page"] = True
|
||||
error_body["status"] = _last_response_status.get(provider)
|
||||
return web.json_response(error_body, status=403)
|
||||
return web.json_response(error_body, status=500)
|
||||
|
||||
# Аддитивно (#3196): ключ "html" на месте и не изменился — клиент, читающий
|
||||
|
|
|
|||
|
|
@ -354,16 +354,20 @@ def _fetch_handler_error_body(
|
|||
|
||||
|
||||
def test_fetch_handler_marks_ban_page_in_error_body(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Бан-страница несёт ban_page + апстрим-статус.
|
||||
"""Бан-страница несёт ban_page + апстрим-статус И ОТЛИЧИМЫЙ КОД 403.
|
||||
|
||||
Без этого признака клиент видит обычную 500-ку, уводит отказ в транспортную
|
||||
Без признака в теле клиент видит обычную 500-ку, уводит отказ в транспортную
|
||||
ветку (report_ban там не зовётся) и перестаёт ротировать отказавший узел —
|
||||
регрессия, которую #3237 внёс, а #3239 чинит.
|
||||
|
||||
Код 403 (#3288 п.4): один и тот же 500 означал «площадка забанила» и
|
||||
«сайдкар упал», и разбор инцидента начинался с ложного следа. Тело не
|
||||
меняется — клиент опознаёт бан по нему (образы деплоятся врозь).
|
||||
"""
|
||||
status, body = _fetch_handler_error_body(
|
||||
monkeypatch, server.BanPageDetectedError("статический отказ площадки"), 401
|
||||
)
|
||||
assert status == 500 # код НЕ меняем: на него завязана classify_browser_probe
|
||||
assert status == 403
|
||||
assert body["ban_page"] is True
|
||||
assert body["status"] == 401
|
||||
assert "BanPageDetectedError" in body["error"]
|
||||
|
|
|
|||
|
|
@ -164,6 +164,16 @@ def _sidecar_ban_page_status(resp: httpx.Response) -> tuple[bool, int | None]:
|
|||
Тело — ``{"error": ..., "ban_page": true, "status": <int|null>}``. Сайдкар
|
||||
старой сборки ключей не отдаёт → (False, None), поведение как до правки.
|
||||
``bool`` отсекаем явно: он подтип ``int`` и JSON ``true`` уехал бы статусом.
|
||||
|
||||
Признак — ТЕЛО, а не код ответа, и проверка намеренно остаётся
|
||||
code-agnostic: с #3288 сайдкар отвечает на бан 403, до него отвечал 500, а
|
||||
``tradein-browser`` — ОТДЕЛЬНЫЙ образ (``gendesign-tradein-browser``,
|
||||
docker-compose.prod.yml) со своим деплоем, т.е. версии сайдкара и backend
|
||||
штатно разъезжаются на несколько часов. Оба кода дают одинаковый диагноз,
|
||||
гейта по коду тут нет и заводить его не нужно: он ничего не добавит (свой
|
||||
``ban_page`` умеет писать только наш сайдкар), а в час рассинхрона снова
|
||||
уводил бы отказ площадки в инфра-ветку. Снимать «совместимость» поэтому
|
||||
нечего — 500-ветка исчезнет сама, когда сайдкар перестанет её отдавать.
|
||||
"""
|
||||
try:
|
||||
body = resp.json()
|
||||
|
|
@ -203,8 +213,19 @@ def _raise_for_sidecar_status(resp: httpx.Response) -> None:
|
|||
# #3239: тип несёт диагноз наверх — подстрокой в тексте его искать
|
||||
# нельзя, detail обрезан до 300 символов и формулировка отказа
|
||||
# менялась дважды за месяц.
|
||||
#
|
||||
# #3288 п.4: текст СВОЙ, без httpx-преамбулы `{exc}`. Она печатала
|
||||
# «Server error '500 Internal Server Error' for url
|
||||
# 'http://tradein-browser:3000/fetch'», и этой строкой отказ ПЛОЩАДКИ
|
||||
# уезжал в лог провайдера и в imv_error_reason как падение НАШЕГО
|
||||
# сайдкара — разбор инцидента каждый раз начинался с ложного следа
|
||||
# («текст ошибки называет гонца»). Код ответа сайдкара оставляем в
|
||||
# тексте отдельным числом: он отличает 403 нового сайдкара от 500
|
||||
# старого, но больше ничего не значит.
|
||||
raise SidecarBanPageError(
|
||||
f"{exc} | tradein-browser: {detail or 'ban page'}",
|
||||
f"tradein-browser: площадка отдала бан-страницу "
|
||||
f"(upstream {upstream_status if upstream_status is not None else '?'}, "
|
||||
f"ответ сайдкара {resp.status_code}): {detail or 'ban page'}",
|
||||
request=exc.request,
|
||||
response=exc.response,
|
||||
upstream_status=upstream_status,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue