Files
fast-next-template/backend/tests/e2e/test_api_contracts.py
Felipe Cardoso 0ea428b718 Refactor tests for improved readability and fixture consistency
- Reformatted headers in E2E tests to improve readability and ensure consistent style.
- Updated confidential client fixture to use bcrypt for secret hashing, enhancing security and testing backward compatibility with legacy SHA-256 hashes.
- Added new test cases for PKCE verification, rejecting insecure 'plain' methods, and improved error handling.
- Refined session workflows and user agent handling in E2E tests for session management.
- Consolidated schema operation tests and fixed minor formatting inconsistencies.
2025-11-26 00:13:53 +01:00

194 lines
7.5 KiB
Python

"""
API Contract Tests using Schemathesis.
These tests demonstrate Schemathesis contract testing capabilities.
Schemathesis auto-generates test cases from OpenAPI schema and validates
that responses match documented schemas.
Usage:
make test-e2e-schema # Run schema tests only
make test-e2e # Run all E2E tests
Note: Schemathesis v4.x API - filtering is done via include/exclude methods.
"""
import pytest
try:
from hypothesis import settings
from schemathesis import openapi
SCHEMATHESIS_AVAILABLE = True
except ImportError:
SCHEMATHESIS_AVAILABLE = False
# Skip all tests in this module if schemathesis is not installed
pytestmark = [
pytest.mark.e2e,
pytest.mark.schemathesis,
pytest.mark.skipif(
not SCHEMATHESIS_AVAILABLE,
reason="schemathesis not installed - run: make install-e2e",
),
]
if SCHEMATHESIS_AVAILABLE:
from app.main import app
# Load schema from the FastAPI app using schemathesis.openapi (v4.x API)
schema = openapi.from_asgi("/api/v1/openapi.json", app=app)
# =========================================================================
# Public Endpoints (No Auth Required)
# =========================================================================
# Test root endpoint
root_schema = schema.include(path="/")
@root_schema.parametrize()
@settings(max_examples=5)
def test_root_endpoint_schema(case):
"""Root endpoint schema compliance."""
response = case.call()
assert response.status_code < 500, f"Server error: {response.text}"
# Test health endpoint
health_schema = schema.include(path="/health")
@health_schema.parametrize()
@settings(max_examples=3)
def test_health_endpoint_schema(case):
"""Health endpoint schema compliance."""
response = case.call()
# Health check may return 200 or 503 depending on DB
assert response.status_code < 500 or response.status_code == 503
# Test auth registration endpoint
auth_register_schema = schema.include(path="/api/v1/auth/register")
@auth_register_schema.parametrize()
@settings(max_examples=10)
def test_register_endpoint_validates_input(case):
"""Registration endpoint input validation."""
response = case.call()
# 200/201 (success), 400/422 (validation), 409 (conflict)
assert response.status_code < 500, f"Server error: {response.text}"
# Note: Login and refresh endpoints require database, so they're tested
# in test_database_workflows.py instead of here. Schemathesis tests run
# without the testcontainers database fixtures.
# =========================================================================
# Protected Endpoints - Manual tests for auth requirements
# (Schemathesis parametrize tests all methods, manual tests are clearer)
# =========================================================================
class TestProtectedEndpointsRequireAuth:
"""Test that protected endpoints return proper auth errors."""
def test_users_me_requires_auth(self):
"""Users/me GET endpoint requires authentication."""
from starlette.testclient import TestClient
with TestClient(app) as client:
response = client.get("/api/v1/users/me")
assert response.status_code == 401
def test_sessions_me_requires_auth(self):
"""Sessions/me GET endpoint requires authentication."""
from starlette.testclient import TestClient
with TestClient(app) as client:
response = client.get("/api/v1/sessions/me")
assert response.status_code == 401
def test_organizations_me_requires_auth(self):
"""Organizations/me GET endpoint requires authentication."""
from starlette.testclient import TestClient
with TestClient(app) as client:
response = client.get("/api/v1/organizations/me")
assert response.status_code == 401
def test_admin_users_requires_auth(self):
"""Admin users GET endpoint requires authentication."""
from starlette.testclient import TestClient
with TestClient(app) as client:
response = client.get("/api/v1/admin/users")
assert response.status_code == 401
def test_admin_stats_requires_auth(self):
"""Admin stats GET endpoint requires authentication."""
from starlette.testclient import TestClient
with TestClient(app) as client:
response = client.get("/api/v1/admin/stats")
assert response.status_code == 401
def test_admin_organizations_requires_auth(self):
"""Admin organizations GET endpoint requires authentication."""
from starlette.testclient import TestClient
with TestClient(app) as client:
response = client.get("/api/v1/admin/organizations")
assert response.status_code == 401
# =========================================================================
# Schema Validation Tests
# =========================================================================
class TestSchemaValidation:
"""Manual validation tests for schema structure."""
def test_schema_loaded_successfully(self):
"""Verify schema was loaded from the app."""
ops = list(schema.get_all_operations())
assert len(ops) > 0, "No operations found in schema"
def test_multiple_endpoints_documented(self):
"""Verify multiple endpoints are documented in schema."""
ops = list(schema.get_all_operations())
assert len(ops) >= 10, f"Only {len(ops)} operations found"
def test_schema_has_auth_operations(self):
"""Verify auth-related operations exist."""
auth_ops = list(schema.include(path_regex=r".*auth.*").get_all_operations())
assert len(auth_ops) > 0, "No auth operations found"
def test_schema_has_user_operations(self):
"""Verify user-related operations exist."""
user_ops = list(
schema.include(path_regex=r".*users.*").get_all_operations()
)
assert len(user_ops) > 0, "No user operations found"
def test_schema_has_organization_operations(self):
"""Verify organization-related operations exist."""
org_ops = list(
schema.include(path_regex=r".*organizations.*").get_all_operations()
)
assert len(org_ops) > 0, "No organization operations found"
def test_schema_has_admin_operations(self):
"""Verify admin-related operations exist."""
admin_ops = list(
schema.include(path_regex=r".*admin.*").get_all_operations()
)
assert len(admin_ops) > 0, "No admin operations found"
def test_schema_has_session_operations(self):
"""Verify session-related operations exist."""
session_ops = list(
schema.include(path_regex=r".*sessions.*").get_all_operations()
)
assert len(session_ops) > 0, "No session operations found"
def test_total_endpoint_count(self):
"""Verify expected number of endpoints are documented."""
ops = list(schema.get_all_operations())
# We expect at least 40+ endpoints in this comprehensive API
assert len(ops) >= 40, f"Only {len(ops)} operations found, expected 40+"