Перейти к содержанию

Тестирование

Фреймворк: pytest + pytest-asyncio (asyncio_mode = auto) + respx (моки HTTP). Дев-зависимости в requirements-dev.txt.

Запуск

Тесты смонтированы в контейнер как volume (./tests:/app/tests:ro), поэтому правки на хосте видны сразу — docker cp не нужен.

# все тесты (моки, без сети)
docker exec rb-backend pytest
# или через compose (одноразовый run)
docker compose -f compose.rb.yml run --rm rb-backend pytest

# конкретный файл
docker exec rb-backend pytest tests/test_moysklad_webhooks.py

# покрытие
docker exec rb-backend pip install pytest-cov
docker exec rb-backend pytest --cov=src --cov-report=term-missing

Маркеры

Маркер Назначение
(по умолчанию) unit/router тесты, всё замокано
-m integration реальные МойСклад / Google Sheets (нужны креды)
docker exec rb-backend pytest -m integration

Интеграционные тесты

Только read-only проверки (например, Stocks.stocks_dict). Не выполняют запись в таблицы. Требуют валидный OMF_MS_TOKEN.

Структура тестов

Файл Покрытие
conftest.py fixtures: client (TestClient), mock_sheets_all (dict по модулям), mock_moysklad_stock, mock_email, client_live
test_base.py chain_get, get_uuid, get_doc_type, format_sum + реальные meta МойСклад
test_moysklad_webhooks.py роутинг internal-webhook по примерам вебхуков (CREATE/UPDATE/DELETE, mixed, unknown, пустые/битые тела)
test_routers.py все 5 rb-эндпоинтов через TestClient
test_status.py логика статусов (update_state_by_stock, update_state_in_regular, routing)
test_bot.py prepare_tg_msg (экранирование MarkdownV2)
test_mail.py prepare_email_msg / prepare_email_link
test_const.py константы (Kuda, excluded_product_paths, колонки)
test_stocks.py Stocks, is_stock_enough
test_celery_stub.py заглушка @app.task / .delay
test_integration.py реальные вызовы (маркер integration)

Принципы мокирования

Внешние вызовы (МойСклад, Google Sheets, Telegram, SMTP) патчатся в точке использования (point-of-use), потому что модули делают from x import Y — локальные алиасы, и патч модуля-источника их не перехватит.

# правильно — патчим локальную ссылку в модуле, где вызывается
with patch("src.endpoints.replace_buyer.handle_create_purchaseorder") as h:
    client.post("/vendor_endpoint/rb/internal-webhook", json=body)
    h.assert_called_once()

# Google Sheets: mock_sheets_all["status"] / ["endpoints"] / ["regular"] / ["main"]
status_sheets, status_sheet = mock_sheets_all["status"]

Текущий статус

110 passed, 0 failed.

Известные дыры покрытия (15–28%): regular.py, regular_buy.py, table_from_doc.py, approve.py — тяжёлые сетевые обработчики. Покрыты только точкой входа (мокаются в router-тестах). Для полного покрытия нужны VCR-записи ответов МойСклад.