Files
2025-11-28 03:58:59 +03:00

158 lines
5.8 KiB
Python

import json
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
from fastapi import FastAPI, status
from app.config import settings
from app.services.payment_service import PaymentService
# ensure backup directory exists before importing the unified app to avoid side effects during module import
_backup_dir = Path("data/backups")
_backup_dir.mkdir(parents=True, exist_ok=True)
from app.webserver.unified_app import create_unified_app
@pytest.mark.anyio
async def test_unified_app_health_reports_features(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
bot = AsyncMock()
dispatcher = SimpleNamespace(feed_update=AsyncMock())
payment_service = AsyncMock(spec=PaymentService)
miniapp_static_dir = tmp_path / "miniapp"
miniapp_static_dir.mkdir()
monkeypatch.setattr(settings, "WEB_API_ENABLED", True, raising=False)
monkeypatch.setattr(settings, "TRIBUTE_ENABLED", True, raising=False)
monkeypatch.setattr(settings, "WEBHOOK_URL", "https://hooks.example.com", raising=False)
monkeypatch.setattr(settings, "WEBHOOK_PATH", "/telegram-webhook", raising=False)
monkeypatch.setattr(settings, "WEBHOOK_SECRET_TOKEN", "super-secret", raising=False)
monkeypatch.setattr(settings, "BOT_RUN_MODE", "webhook", raising=False)
monkeypatch.setattr(settings, "WEBHOOK_MAX_QUEUE_SIZE", 8, raising=False)
monkeypatch.setattr(settings, "WEBHOOK_WORKERS", 1, raising=False)
monkeypatch.setattr(settings, "WEBHOOK_ENQUEUE_TIMEOUT", 0.0, raising=False)
monkeypatch.setattr(settings, "WEBHOOK_WORKER_SHUTDOWN_TIMEOUT", 1.0, raising=False)
monkeypatch.setattr(settings, "MINIAPP_STATIC_PATH", str(miniapp_static_dir), raising=False)
app = create_unified_app(
bot,
dispatcher, # type: ignore[arg-type]
payment_service,
enable_telegram_webhook=True,
)
health_route = next(
route
for route in app.routes
if getattr(route, "endpoint", None) and getattr(route.endpoint, "__name__", "") == "unified_health"
)
assert getattr(health_route, "path", None) == "/health/unified"
await app.router.startup()
try:
response = await health_route.endpoint() # type: ignore[func-returns-value]
finally:
await app.router.shutdown()
payload = json.loads(response.body.decode("utf-8")) # type: ignore[attr-defined]
assert payload["status"] == "ok"
assert payload["web_api_enabled"] is True
assert payload["bot_run_mode"] == "webhook"
assert payload["telegram_webhook"]["enabled"] is True
assert payload["telegram_webhook"]["running"] is True
assert payload["telegram_webhook"]["path"] == "/telegram-webhook"
assert payload["telegram_webhook"]["secret_configured"] is True
assert payload["payment_webhooks"]["enabled"] is True
assert payload["payment_webhooks"]["providers"]["tribute"] is True
assert payload["miniapp_static"]["mounted"] is True
assert payload["miniapp_static"]["path"].endswith("miniapp")
def _build_unified_app(monkeypatch: pytest.MonkeyPatch, docs_enabled: bool) -> FastAPI:
bot = AsyncMock()
dispatcher = SimpleNamespace(feed_update=AsyncMock())
payment_service = AsyncMock(spec=PaymentService)
monkeypatch.setattr(settings, "WEB_API_ENABLED", False, raising=False)
monkeypatch.setattr(settings, "WEB_API_DOCS_ENABLED", docs_enabled, raising=False)
monkeypatch.setattr(settings, "MINIAPP_STATIC_PATH", "miniapp", raising=False)
return create_unified_app(
bot,
dispatcher, # type: ignore[arg-type]
payment_service,
enable_telegram_webhook=False,
)
@pytest.mark.anyio
async def test_unified_app_health_path_without_admin(monkeypatch: pytest.MonkeyPatch) -> None:
bot = AsyncMock()
dispatcher = SimpleNamespace(feed_update=AsyncMock())
payment_service = AsyncMock(spec=PaymentService)
monkeypatch.setattr(settings, "WEB_API_ENABLED", False, raising=False)
monkeypatch.setattr(settings, "MINIAPP_STATIC_PATH", "miniapp", raising=False)
app = create_unified_app(
bot,
dispatcher, # type: ignore[arg-type]
payment_service,
enable_telegram_webhook=False,
)
health_route = next(
route
for route in app.routes
if getattr(route, "endpoint", None) and getattr(route.endpoint, "__name__", "") == "unified_health"
)
assert getattr(health_route, "path", None) == "/health"
def test_unified_app_docs_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
app = _build_unified_app(monkeypatch, docs_enabled=False)
assert app.docs_url is None
assert app.redoc_url is None
assert app.openapi_url is None
registered_paths = {getattr(route, "path", None) for route in app.routes}
assert "/doc" not in registered_paths
@pytest.mark.anyio
async def test_unified_app_docs_enabled_with_alias(monkeypatch: pytest.MonkeyPatch) -> None:
app = _build_unified_app(monkeypatch, docs_enabled=True)
assert app.docs_url == "/docs"
assert app.openapi_url == "/openapi.json"
alias_route = next(
(route for route in app.routes if getattr(route, "path", None) == "/doc"),
None,
)
assert alias_route is not None
assert getattr(alias_route, "include_in_schema", True) is False
redoc_route = next(
(route for route in app.routes if getattr(route, "path", None) == "/redoc"),
None,
)
assert redoc_route is not None
assert getattr(redoc_route, "include_in_schema", True) is False
response = await alias_route.endpoint() # type: ignore[func-returns-value]
assert response.status_code == status.HTTP_307_TEMPORARY_REDIRECT
assert response.headers["location"] == "/docs"
redoc_response = await redoc_route.endpoint() # type: ignore[func-returns-value]
assert b"ReDoc" in redoc_response.body # type: ignore[attr-defined]