130 lines
4.1 KiB
Python
130 lines
4.1 KiB
Python
"""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()
|