gendesign/tradein-mvp/backend/tests/test_backfill_honest_status.py
bot-backend 0de22f4bc9
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 2m59s
Deploy Trade-In / build-backend (push) Successful in 2m1s
Deploy Trade-In / deploy (push) Successful in 2m14s
fix(tradein/scraper): диагноз бана перестаёт назначаться по умолчанию (#2764) (#2765)
2026-08-06 23:17:59 +00:00

84 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.

"""#2674 — detail-backfill с нулём обогащений перестаёт называться 'done'.
Все три backfill'а (avito/yandex/domclick) финализировались одним mark_done.
На проде 2026-08-06 это 78 прогонов из 158: avito 23/76 (включая 5 прогонов на
1500-1600 попыток без единого обогащения), yandex 31/52, domclick 24/30
(494 попытки → 0 обогащено, 63 блока, 431 fail — и все 30 'done').
Проверяем ровно ветвление mark_backfill_finished — БД замокана.
"""
from __future__ import annotations
import os
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from app.services import scrape_runs as runs_mod
def _finish(counters: dict[str, int], *, aborted: bool = False) -> tuple[str, str | None]:
"""Вызвать mark_backfill_finished с замоканными mark_* → (статус, причина)."""
calls: list[tuple[str, str | None]] = []
with (
patch.object(runs_mod, "mark_done", lambda *a, **k: calls.append(("done", None))),
patch.object(
runs_mod, "mark_failed", lambda db, rid, err, c: calls.append(("failed", err))
),
# **k — mark_banned принимает ещё и ban_kind (#2686/#2764); этот тест про
# ВЫБОР финализатора, диагноз проверяет test_2764_ban_kind_no_default.py.
patch.object(
runs_mod, "mark_banned", lambda db, rid, err, c, **k: calls.append(("banned", err))
),
):
runs_mod.mark_backfill_finished(
MagicMock(),
1,
counters,
source="domclick_detail_backfill",
aborted_by_blocks=aborted,
)
assert len(calls) == 1, f"ожидался ровно один финализатор, получено {calls}"
return calls[0]
@pytest.mark.parametrize(
("counters", "aborted", "expected"),
[
# Прод-факт domclick: 16 прогонов attempted=3 blocked=3 → брейкер оборвал.
({"attempted": 3, "enriched": 0, "blocked": 3, "failed": 0}, True, "banned"),
# Прод-факт domclick: 100 попыток, все fail, блоки не распознаны (до #2645).
({"attempted": 100, "enriched": 0, "blocked": 0, "failed": 100}, False, "failed"),
# Прод-факт avito: 1500 попыток, 1499 fail + 1 блок, ноль обогащений.
({"attempted": 1500, "enriched": 0, "blocked": 1, "failed": 1499}, False, "banned"),
# Прод-факт yandex: 31 прогон attempted=5 failed=5, ключа blocked нет вовсе.
({"attempted": 5, "enriched": 0, "failed": 5}, False, "failed"),
# Кандидатов не было — честная пустота, это успех.
({"attempted": 0, "enriched": 0, "blocked": 0, "failed": 0}, False, "done"),
# Частичный прогон: обогатили хоть что-то → успех.
({"attempted": 50, "enriched": 12, "blocked": 0, "failed": 38}, False, "done"),
# Блоки были, но прогон доработал и обогатил — не бан.
({"attempted": 50, "enriched": 12, "blocked": 2, "failed": 36}, False, "done"),
# Блок оборвал прогон, хотя часть успели обогатить — работа не доделана.
({"attempted": 50, "enriched": 12, "blocked": 5, "failed": 33}, True, "banned"),
# avito: 404-«снято с продажи» — тоже результат, а не пустой прогон.
({"attempted": 30, "enriched": 0, "gone": 30, "blocked": 0, "failed": 0}, False, "done"),
],
)
def test_status_matches_reality(counters: dict[str, Any], aborted: bool, expected: str) -> None:
status, _ = _finish(counters, aborted=aborted)
assert status == expected
def test_reason_carries_numbers_and_marker() -> None:
"""Причина в scrape_runs.error должна быть читаемой человеком, не пустой."""
status, reason = _finish({"attempted": 3, "enriched": 0, "blocked": 3}, aborted=True)
assert status == "banned"
assert reason is not None
assert "backfill-honest-status" in reason
assert "domclick_detail_backfill" in reason
assert "blocked=3" in reason and "из 3 попыток" in reason