"""Tests for OpenAPI documentation completeness (Task 5.14).""" from __future__ import annotations import os import sys import pytest sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) def test_openapi_tags_configured(): """FastAPI app should have openapi_tags configured with descriptions.""" from app.main import app assert app.openapi_tags is not None assert len(app.openapi_tags) > 0 tag_names = {t["name"] for t in app.openapi_tags} # Core tags should be present assert "health" in tag_names assert "auth" in tag_names assert "users" in tag_names assert "contacts" in tag_names assert "system-settings" in tag_names def test_openapi_tags_have_descriptions(): """Each openapi_tag should have a description.""" from app.main import app for tag in app.openapi_tags: assert "description" in tag assert len(tag["description"]) > 0 def test_app_has_description(): """FastAPI app should have a description for OpenAPI docs.""" from app.main import app assert app.description is not None assert len(app.description) > 0 assert "LeoCRM" in app.description or "CRM" in app.description def test_all_api_routes_have_tags(): """All API routes should have at least one tag for OpenAPI grouping.""" from app.main import app untagged = [] for route in app.routes: if not hasattr(route, "methods") or not hasattr(route, "path"): continue if not route.path.startswith("/api/"): continue methods = route.methods - {"HEAD", "OPTIONS"} if route.methods else set() if not methods: continue tags = getattr(route, "tags", []) or [] if not tags: untagged.append(f"{route.path} [{','.join(sorted(methods))}]") assert len(untagged) == 0, f"Routes without tags: {untagged}" def test_key_routes_have_response_model(): """Key routes should have response_model set for OpenAPI schema generation.""" from app.main import app # Check that at least some routes have response_model routes_with_response_model = 0 for route in app.routes: if not hasattr(route, "methods") or not hasattr(route, "path"): continue if not route.path.startswith("/api/"): continue response_model = getattr(route, "response_model", None) if response_model is not None: routes_with_response_model += 1 assert routes_with_response_model >= 5, ( f"Expected at least 5 routes with response_model, got {routes_with_response_model}" ) def test_auth_login_has_auth_response_model(): """POST /api/v1/auth/login should have AuthResponse as response_model.""" from app.main import app from app.schemas.auth import AuthResponse for route in app.routes: if ( hasattr(route, "path") and route.path == "/api/v1/auth/login" and hasattr(route, "response_model") ): assert route.response_model == AuthResponse return pytest.fail("Route /api/v1/auth/login not found") def test_health_has_health_response_model(): """GET /api/v1/health should have HealthResponse as response_model.""" from app.main import app from app.schemas.common import HealthResponse for route in app.routes: if ( hasattr(route, "path") and route.path == "/api/v1/health" and hasattr(route, "response_model") ): assert route.response_model == HealthResponse return pytest.fail("Route /api/v1/health not found") def test_api_documentation_md_exists(): """docs/api-documentation.md should exist and have content.""" doc_path = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "docs", "api-documentation.md", ) assert os.path.isfile(doc_path), "docs/api-documentation.md not found" with open(doc_path) as f: content = f.read() assert "LeoCRM API Documentation" in content assert "auth" in content.lower() assert "contacts" in content.lower() assert "plugins" in content.lower()