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
97 lines
4.6 KiB
Python
97 lines
4.6 KiB
Python
"""#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()
|