no-mistakes(review): Add startup self-heal guard for show_in_form column
This commit is contained in:
+13
@@ -9,6 +9,7 @@ from pathlib import Path
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.database import Base, async_session_factory, engine
|
||||
@@ -18,12 +19,24 @@ from app.services.seed import seed_categories, seed_units, seed_users
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def ensure_legacy_schema(conn) -> None:
|
||||
"""Add columns from alembic migrations that legacy create_all databases lack."""
|
||||
result = await conn.execute(text("PRAGMA table_info(categories)"))
|
||||
columns = {row[1] for row in result}
|
||||
if "show_in_form" not in columns:
|
||||
await conn.execute(
|
||||
text("ALTER TABLE categories ADD COLUMN show_in_form BOOLEAN NOT NULL DEFAULT 1")
|
||||
)
|
||||
logger.info("Added missing categories.show_in_form column (legacy database)")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Initialise database and seed data on startup."""
|
||||
logger.info("Starting Denya OneCare …")
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
await ensure_legacy_schema(conn)
|
||||
async with async_session_factory() as session:
|
||||
await seed_users(session)
|
||||
await session.commit()
|
||||
|
||||
@@ -138,6 +138,41 @@ async def test_seed_syncs_show_in_form_on_existing_rows(client):
|
||||
assert len(created) >= 1
|
||||
|
||||
|
||||
async def test_legacy_db_self_heals_show_in_form_column():
|
||||
"""A pre-Sprint-A categories table (no show_in_form) gets the column on startup."""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.core.database import engine
|
||||
from app.main import ensure_legacy_schema
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.execute(text("DROP TABLE IF EXISTS tickets"))
|
||||
await conn.execute(text("DROP TABLE IF EXISTS categories"))
|
||||
await conn.execute(
|
||||
text(
|
||||
"CREATE TABLE categories ("
|
||||
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
|
||||
"type VARCHAR(20) NOT NULL, "
|
||||
"name VARCHAR(100) NOT NULL, "
|
||||
"parent_id INTEGER, "
|
||||
"sla_urgency VARCHAR(10))"
|
||||
)
|
||||
)
|
||||
await ensure_legacy_schema(conn)
|
||||
result = await conn.execute(text("PRAGMA table_info(categories)"))
|
||||
assert "show_in_form" in {row[1] for row in result}
|
||||
|
||||
# Idempotent on the next startup
|
||||
async with engine.begin() as conn:
|
||||
await ensure_legacy_schema(conn)
|
||||
result = await conn.execute(text("PRAGMA table_info(categories)"))
|
||||
assert "show_in_form" in {row[1] for row in result}
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.execute(text("DROP TABLE IF EXISTS categories"))
|
||||
await conn.execute(text("DROP TABLE IF EXISTS tickets"))
|
||||
|
||||
|
||||
# ── Unit hierarchy ────────────────────────────────────────────────────
|
||||
async def test_units_building_filter(client):
|
||||
"""GET /api/tickets/units?building= filters to one building."""
|
||||
|
||||
Reference in New Issue
Block a user