feat(#27): Gate B [07/10]: FastAPI health endpoint + app factory (H-1)
- App factory pattern (FUND-8): create_app() produces fresh app per test
- Logging config (structured, no secrets)
- Config validation: SLAB_AES_KEY required, WOO_BASE_URL HTTPS-only
- GET /api/health returns 200 {status: ok, version, woo_reachable: false}
- SQLite + alembic skeleton: full DATA-MODEL schema, initial migration
- API contract tests via httpx TestClient: 5 cases all green
- test_returns_200, test_returns_ok_status, test_woo_reachable_false
- test_response_shape, test_with_test_client_lifespan (FUND-9/10)
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"""App factory and lifespan."""
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.api import health
|
||||
from app.config import Settings
|
||||
from app.db.engine import close_db_connection, init_db
|
||||
from app.logging_config import configure_logging
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Application lifespan: startup and shutdown hooks.
|
||||
|
||||
Handles database initialization, logging configuration,
|
||||
and cleanup on shutdown.
|
||||
"""
|
||||
configure_logging()
|
||||
logging.info("SlabUploader starting up")
|
||||
await init_db()
|
||||
yield
|
||||
logging.info("SlabUploader shutting down")
|
||||
await close_db_connection()
|
||||
|
||||
|
||||
def create_app(settings: Settings | None = None) -> FastAPI:
|
||||
"""Create a fresh FastAPI app instance.
|
||||
|
||||
Each call produces an independent app with its own database connection
|
||||
and cleared dependency overrides. This is the factory used by tests.
|
||||
|
||||
Args:
|
||||
settings: Application settings. Uses env vars if None.
|
||||
|
||||
Returns:
|
||||
Configured FastAPI application.
|
||||
"""
|
||||
if settings is None:
|
||||
settings = Settings()
|
||||
settings.validate()
|
||||
|
||||
app = FastAPI(title="SlabUploader", version="0.1.0", lifespan=lifespan)
|
||||
|
||||
app.include_router(health.router)
|
||||
|
||||
return app
|
||||
Reference in New Issue
Block a user