""" Tests for BATON-FIX-016: VAPID public key — убедиться, что ключ не вшит как пустая строка в frontend-коде и читается через API. Acceptance criteria: 1. В frontend-коде нет хардкода пустой строки в качестве VAPID key в -теге. 2. frontend читает ключ через API /api/vapid-public-key (_fetchVapidPublicKey). 3. GET /api/vapid-public-key возвращает HTTP 200. 4. GET /api/vapid-public-key возвращает JSON с полем vapid_public_key. 5. При наличии конфигурации VAPID_PUBLIC_KEY — ответ содержит непустое значение. """ from __future__ import annotations import os import re from pathlib import Path from unittest.mock import patch 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") os.environ.setdefault("ADMIN_TOKEN", "test-admin-token") os.environ.setdefault("ADMIN_CHAT_ID", "5694335584") import pytest from tests.conftest import make_app_client PROJECT_ROOT = Path(__file__).parent.parent FRONTEND_DIR = PROJECT_ROOT / "frontend" INDEX_HTML = FRONTEND_DIR / "index.html" APP_JS = FRONTEND_DIR / "app.js" _TEST_VAPID_PUBLIC_KEY = "BFakeVapidPublicKeyForTestingPurposesOnlyBase64UrlEncoded" # --------------------------------------------------------------------------- # Criterion 1 — AST: no hardcoded empty VAPID key in tag (index.html) # --------------------------------------------------------------------------- def test_index_html_has_no_vapid_meta_tag_with_empty_content() -> None: """index.html не должен содержать -тег с application-server-key и пустым content.""" content = INDEX_HTML.read_text(encoding="utf-8") match = re.search( r']*(?:application-server-key|vapid)[^>]*content\s*=\s*["\']["\']', content, re.IGNORECASE, ) assert match is None, ( f"index.html содержит -тег с пустым VAPID ключом: {match.group(0)!r}" ) def test_index_html_has_no_hardcoded_application_server_key_attribute() -> None: """index.html не должен содержать атрибут application-server-key вообще.""" content = INDEX_HTML.read_text(encoding="utf-8") assert "application-server-key" not in content.lower(), ( "index.html содержит атрибут 'application-server-key' — " "VAPID ключ не должен быть вшит в HTML" ) # --------------------------------------------------------------------------- # Criterion 2 — AST: frontend reads key through API (app.js) # --------------------------------------------------------------------------- def test_app_js_contains_fetch_vapid_public_key_function() -> None: """app.js должен содержать функцию _fetchVapidPublicKey.""" content = APP_JS.read_text(encoding="utf-8") assert "_fetchVapidPublicKey" in content, ( "app.js не содержит функцию _fetchVapidPublicKey — " "чтение VAPID ключа через API не реализовано" ) def test_app_js_fetch_vapid_calls_api_endpoint() -> None: """_fetchVapidPublicKey в app.js должна обращаться к /api/vapid-public-key.""" content = APP_JS.read_text(encoding="utf-8") assert "/api/vapid-public-key" in content, ( "app.js не содержит URL '/api/vapid-public-key' — VAPID ключ не читается через API" ) def test_app_js_init_push_subscription_has_null_guard() -> None: """_initPushSubscription в app.js должна содержать guard против null ключа.""" content = APP_JS.read_text(encoding="utf-8") assert re.search(r"if\s*\(\s*!\s*vapidPublicKey\s*\)", content), ( "app.js: _initPushSubscription не содержит guard 'if (!vapidPublicKey)' — " "подписка может быть создана без ключа" ) def test_app_js_init_chains_fetch_vapid_then_init_subscription() -> None: """_init() в app.js должна вызывать _fetchVapidPublicKey().then(_initPushSubscription).""" content = APP_JS.read_text(encoding="utf-8") assert re.search( r"_fetchVapidPublicKey\(\)\s*\.\s*then\s*\(\s*_initPushSubscription\s*\)", content, ), ( "app.js: _init() не содержит цепочку _fetchVapidPublicKey().then(_initPushSubscription)" ) def test_app_js_has_no_empty_string_hardcoded_as_application_server_key() -> None: """app.js не должен содержать хардкода пустой строки для applicationServerKey.""" content = APP_JS.read_text(encoding="utf-8") match = re.search(r"applicationServerKey\s*[=:]\s*[\"']{2}", content) assert match is None, ( f"app.js содержит хардкод пустой строки для applicationServerKey: {match.group(0)!r}" ) # --------------------------------------------------------------------------- # Criterion 3 — HTTP: GET /api/vapid-public-key returns 200 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_vapid_public_key_endpoint_returns_200() -> None: """GET /api/vapid-public-key должен вернуть HTTP 200.""" async with make_app_client() as client: response = await client.get("/api/vapid-public-key") assert response.status_code == 200, ( f"GET /api/vapid-public-key вернул {response.status_code}, ожидался 200" ) # --------------------------------------------------------------------------- # Criterion 4 — HTTP: response JSON contains vapid_public_key field # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_vapid_public_key_endpoint_returns_json_with_field() -> None: """GET /api/vapid-public-key должен вернуть JSON с полем vapid_public_key.""" async with make_app_client() as client: response = await client.get("/api/vapid-public-key") data = response.json() assert "vapid_public_key" in data, ( f"Ответ /api/vapid-public-key не содержит поле 'vapid_public_key': {data!r}" ) # --------------------------------------------------------------------------- # Criterion 5 — HTTP: non-empty vapid_public_key when env var is configured # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_vapid_public_key_endpoint_returns_configured_value() -> None: """GET /api/vapid-public-key возвращает непустой ключ, когда VAPID_PUBLIC_KEY задан.""" with patch("backend.config.VAPID_PUBLIC_KEY", _TEST_VAPID_PUBLIC_KEY): async with make_app_client() as client: response = await client.get("/api/vapid-public-key") data = response.json() assert data.get("vapid_public_key") == _TEST_VAPID_PUBLIC_KEY, ( f"vapid_public_key должен быть '{_TEST_VAPID_PUBLIC_KEY}', " f"получили: {data.get('vapid_public_key')!r}" )