From f6f4300f736a310d2f497e34fbb47dbee9c11f2f Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Fri, 20 Mar 2026 21:09:33 +0200 Subject: [PATCH] kin: BATON-ARCH-013-backend_dev --- tests/test_arch_013.py | 84 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/test_arch_013.py diff --git a/tests/test_arch_013.py b/tests/test_arch_013.py new file mode 100644 index 0000000..b690937 --- /dev/null +++ b/tests/test_arch_013.py @@ -0,0 +1,84 @@ +""" +Tests for BATON-ARCH-013: Keep-alive mechanism / health endpoint. + +Acceptance criteria: +1. GET /health returns HTTP 200 OK. +2. Response body contains JSON with {"status": "ok"}. +3. Endpoint does not require authorization (no token, no secret header needed). +4. Keep-alive loop is started when APP_URL is set, and NOT started when APP_URL is unset. +""" +from __future__ import annotations + +import os + +os.environ.setdefault("BOT_TOKEN", "test-bot-token") +os.environ.setdefault("CHAT_ID", "-1001234567890") +os.environ.setdefault("WEBHOOK_SECRET", "test-webhook-secret") +os.environ.setdefault("WEBHOOK_URL", "https://example.com/api/webhook/telegram") +os.environ.setdefault("FRONTEND_ORIGIN", "http://localhost:3000") + +from unittest.mock import AsyncMock, patch + +import pytest + +from tests.conftest import make_app_client, temp_db + + +# --------------------------------------------------------------------------- +# Criterion 1 & 2 & 3 — GET /health → 200 OK, {"status": "ok"}, no auth +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_health_returns_200_ok(): + """GET /health должен вернуть HTTP 200 без какого-либо заголовка авторизации.""" + async with make_app_client() as client: + response = await client.get("/health") + + assert response.status_code == 200 + + +@pytest.mark.asyncio +async def test_health_returns_status_ok(): + """GET /health должен вернуть JSON содержащий {"status": "ok"}.""" + async with make_app_client() as client: + response = await client.get("/health") + + data = response.json() + assert data.get("status") == "ok" + + +# --------------------------------------------------------------------------- +# Criterion 4 — keep-alive task lifecycle +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_keepalive_started_when_app_url_set(): + """Keep-alive задача должна стартовать при наличии APP_URL.""" + from backend.main import app + + with temp_db(): + with patch("backend.telegram.set_webhook", new_callable=AsyncMock): + with patch("backend.config.APP_URL", "https://example.com"): + with patch("backend.main._keep_alive_loop", new_callable=AsyncMock) as mock_loop: + async with app.router.lifespan_context(app): + pass + + # asyncio.create_task вызывается с корутиной _keep_alive_loop — проверяем что она была вызвана + assert mock_loop.called + + +@pytest.mark.asyncio +async def test_keepalive_not_started_when_app_url_unset(): + """Keep-alive задача НЕ должна стартовать при отсутствии APP_URL.""" + from backend.main import app + + with temp_db(): + with patch("backend.telegram.set_webhook", new_callable=AsyncMock): + with patch("backend.config.APP_URL", None): + with patch("backend.main._keep_alive_loop", new_callable=AsyncMock) as mock_loop: + async with app.router.lifespan_context(app): + pass + + assert not mock_loop.called