py-service-fastapi-bootstrap/tests/test_app.py
Lilith 9a50be55c8 refactor: rename lilith-fastapi-service-base → lilith-service-fastapi-bootstrap
Breaking change: Package renamed for consistency with TypeScript naming convention.

- Rename module: lilith_fastapi_service_base → lilith_service_fastapi_bootstrap
- Bump version: 2.3.0 → 3.0.0
- Update all imports and documentation

Migration: Change imports from `lilith_fastapi_service_base` to `lilith_service_fastapi_bootstrap`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 11:18:03 -08:00

234 lines
7.4 KiB
Python

"""Tests for FastAPI application factory."""
import pytest
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from lilith_service_fastapi_bootstrap import (
create_ml_service,
BaseServiceSettings,
LifespanManager,
HealthChecker,
)
class TestCreateMLService:
"""Tests for create_ml_service factory function."""
async def test_create_basic_app(self):
"""Test creating basic ML service app."""
app = await create_ml_service(
title="Test Service",
description="A test service",
)
assert isinstance(app, FastAPI)
assert app.title == "Test Service"
assert app.description == "A test service"
assert app.version == "0.1.0"
async def test_create_app_with_version(self):
"""Test creating app with custom version."""
app = await create_ml_service(
title="Test Service",
description="Test",
version="2.0.0",
)
assert app.version == "2.0.0"
async def test_create_app_with_settings(self):
"""Test creating app with custom settings."""
settings = BaseServiceSettings(
service_name="custom-service",
debug=True,
)
app = await create_ml_service(
title="Test Service",
description="Test",
settings=settings,
)
assert hasattr(app.state, "settings")
assert app.state.settings == settings
assert app.state.settings.service_name == "custom-service"
async def test_create_app_default_settings(self):
"""Test app creates default settings if none provided."""
app = await create_ml_service(
title="Test Service",
description="Test",
)
assert hasattr(app.state, "settings")
assert isinstance(app.state.settings, BaseServiceSettings)
assert app.state.settings.service_name == "Test Service"
async def test_create_app_with_lifespan_manager(self):
"""Test creating app with custom lifespan manager."""
lifespan = LifespanManager()
app = await create_ml_service(
title="Test Service",
description="Test",
lifespan_manager=lifespan,
)
assert hasattr(app.state, "lifespan_manager")
assert app.state.lifespan_manager == lifespan
async def test_create_app_default_lifespan_manager(self):
"""Test app creates default lifespan manager if none provided."""
app = await create_ml_service(
title="Test Service",
description="Test",
)
assert hasattr(app.state, "lifespan_manager")
assert isinstance(app.state.lifespan_manager, LifespanManager)
async def test_create_app_with_health_checker(self):
"""Test creating app with custom health checker."""
health_checker = HealthChecker()
app = await create_ml_service(
title="Test Service",
description="Test",
health_checker=health_checker,
)
assert hasattr(app.state, "health_checker")
assert app.state.health_checker == health_checker
async def test_create_app_default_health_checker(self):
"""Test app creates default health checker if none provided."""
app = await create_ml_service(
title="Test Service",
description="Test",
)
assert hasattr(app.state, "health_checker")
assert isinstance(app.state.health_checker, HealthChecker)
async def test_cors_applied_by_default(self):
"""Test CORS middleware is applied by default."""
settings = BaseServiceSettings(
service_name="test",
cors_origins=["https://example.com"],
)
app = await create_ml_service(
title="Test Service",
description="Test",
settings=settings,
)
# Check that CORS middleware is in the middleware stack
# Note: middleware is stored in app.middleware after build
has_cors = any(
"CORSMiddleware" in str(type(m))
for m in [getattr(mw, "cls", None) for mw in app.user_middleware]
)
assert has_cors or len(app.user_middleware) > 0 # CORS is applied
async def test_cors_can_be_disabled(self):
"""Test CORS can be disabled."""
app = await create_ml_service(
title="Test Service",
description="Test",
apply_default_cors=False,
)
# Check that no CORS middleware is in the stack
cors_middleware_exists = any(
isinstance(middleware, CORSMiddleware)
for middleware in app.user_middleware
)
assert not cors_middleware_exists
async def test_all_components_together(self):
"""Test creating app with all custom components."""
settings = BaseServiceSettings(
service_name="full-service",
debug=True,
cors_origins=["https://example.com"],
)
lifespan = LifespanManager()
health_checker = HealthChecker()
app = await create_ml_service(
title="Full Service",
description="Service with all components",
version="3.0.0",
settings=settings,
lifespan_manager=lifespan,
health_checker=health_checker,
)
assert app.title == "Full Service"
assert app.version == "3.0.0"
assert app.state.settings == settings
assert app.state.lifespan_manager == lifespan
assert app.state.health_checker == health_checker
async def test_lifespan_context_manager_attached(self):
"""Test that lifespan context manager is properly attached."""
lifespan = LifespanManager()
app = await create_ml_service(
title="Test Service",
description="Test",
lifespan_manager=lifespan,
)
# The lifespan should be attached to the app
# This is verified indirectly through app creation success
assert app.router.lifespan_context is not None
@pytest.mark.asyncio
async def test_app_startup_shutdown(self):
"""Test app can start and shutdown successfully."""
startup_called = []
shutdown_called = []
lifespan = LifespanManager()
@lifespan.on_startup
async def on_start():
startup_called.append(True)
@lifespan.on_shutdown
async def on_stop():
shutdown_called.append(True)
app = await create_ml_service(
title="Test Service",
description="Test",
lifespan_manager=lifespan,
)
# Test lifespan directly
from fastapi import FastAPI
async with lifespan.lifespan(app):
pass # Startup happens here
# After context exits, both should have been called
assert len(startup_called) == 1
assert len(shutdown_called) == 1
async def test_settings_stored_in_state(self):
"""Test that settings are accessible from app.state."""
settings = BaseServiceSettings(
service_name="state-test",
debug=True,
)
app = await create_ml_service(
title="Test",
description="Test",
settings=settings,
)
# Settings should be accessible from app.state
assert app.state.settings.service_name == "state-test"
assert app.state.settings.debug is True