Phase 5.14: API documentation - OpenAPI tags, response models, examples, docs
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user