gendesign/tradein-mvp/backend/tests/test_2860_yandex_sweep_run_status.py
bot-backend 939a8a1646
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
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 / backend-tests (pull_request) Successful in 4m57s
fix(#3197): no_proxy_stop=1 как у соседей, INFO на финале хендлера, докстринг фабрики
2026-09-06 06:27:40 +05:00

97 lines
4.6 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.

"""#2860: обход, не разрешивший ни одного дома, не должен числиться успешным.
Четырнадцать прогонов подряд (16.07-17.08.2026) стояли `status='done'` с пустым
`error_text` при счётчиках `processed 5, succeeded 0`. Исключения не было —
поэтому `mark_done`; но прогон не сделал ничего из заявленного, очередь за это
время выросла 351 → 397, а витрина `market.yandex_jk_enrichment` замерла на
34 строках с 15.07.
Счётчик при этом был честный. Не хватало вывода из него — ровно тот класс,
который эпик #2674 называет самым частым: «механизм исполняется, счётчик
честный, вывод из него никто не делает».
НЕ подгоняем succeeded: если дома не разрешаются, честный исход — назвать
прогон неуспешным, а не дотянуть счётчик до зелёного.
"""
from __future__ import annotations
import os
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
# Settings требует DATABASE_URL на импорте; соседние тесты хендлеров делают так же
# (см. test_scrape_skip_visibility.py) — коннекта не будет, нужен только парс.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
from app.services.product_handlers import _job_yandex_newbuilding_sweep
from app.tasks.yandex_newbuilding_sweep import YandexNewbuildingSweepResult
def _ctx() -> SimpleNamespace:
return SimpleNamespace(runs=MagicMock())
def _result(processed: int, succeeded: int) -> YandexNewbuildingSweepResult:
"""Настоящий результат задачи, а не MagicMock.
У MagicMock любой не заданный атрибут (`no_proxy_stop` из #3197) — truthy, то есть
добавление хендлеру новой ветки по полю результата уводило бы сюда ВСЕ три случая,
и тест краснел бы по причине, не имеющей отношения к #2860.
"""
return YandexNewbuildingSweepResult(processed=processed, succeeded=succeeded, total=425)
@pytest.mark.asyncio
async def test_zero_resolved_marks_run_failed() -> None:
"""processed > 0, succeeded == 0 → прогон неуспешен, причина названа."""
ctx = _ctx()
with patch(
"app.tasks.yandex_newbuilding_sweep.enrich_yandex_newbuilding_sweep",
AsyncMock(return_value=_result(5, 0)),
):
await _job_yandex_newbuilding_sweep(MagicMock(), 4156, {}, ctx)
ctx.runs.mark_done.assert_not_called()
ctx.runs.mark_failed.assert_called_once()
reason = ctx.runs.mark_failed.call_args[0][2]
assert "разрешено 0" in reason, reason
@pytest.mark.asyncio
async def test_partial_success_still_done() -> None:
"""Контроль: хотя бы один разрешённый дом — прогон по-прежнему успешен.
Зелёный с обеих сторон правки. Без него правка могла бы объявить неуспешным
любой неполный прогон — а `succeeded: 1 из 5` был нормой в июле, когда
механизм работал.
"""
ctx = _ctx()
with patch(
"app.tasks.yandex_newbuilding_sweep.enrich_yandex_newbuilding_sweep",
AsyncMock(return_value=_result(5, 1)),
):
await _job_yandex_newbuilding_sweep(MagicMock(), 1489, {}, ctx)
ctx.runs.mark_failed.assert_not_called()
ctx.runs.mark_done.assert_called_once()
@pytest.mark.asyncio
async def test_empty_queue_is_not_a_failure() -> None:
"""Контроль: обрабатывать было нечего (processed == 0) — это не отказ.
Иначе гейт краснел бы каждый раз, когда очередь разобрана до конца, то есть
ровно в успешном состоянии.
"""
ctx = _ctx()
with patch(
"app.tasks.yandex_newbuilding_sweep.enrich_yandex_newbuilding_sweep",
AsyncMock(return_value=_result(0, 0)),
):
await _job_yandex_newbuilding_sweep(MagicMock(), 9999, {}, ctx)
ctx.runs.mark_failed.assert_not_called()
ctx.runs.mark_done.assert_called_once()