no-mistakes(review): Add startup self-heal guard for show_in_form column

This commit is contained in:
root
2026-08-02 12:36:10 +00:00
parent ae17005932
commit db6826cb49
2 changed files with 48 additions and 0 deletions
+13
View File
@@ -9,6 +9,7 @@ from pathlib import Path
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from sqlalchemy import text
from app.core.config import settings from app.core.config import settings
from app.core.database import Base, async_session_factory, engine 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__) 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 @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
"""Initialise database and seed data on startup.""" """Initialise database and seed data on startup."""
logger.info("Starting Denya OneCare …") logger.info("Starting Denya OneCare …")
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
await ensure_legacy_schema(conn)
async with async_session_factory() as session: async with async_session_factory() as session:
await seed_users(session) await seed_users(session)
await session.commit() await session.commit()
+35
View File
@@ -138,6 +138,41 @@ async def test_seed_syncs_show_in_form_on_existing_rows(client):
assert len(created) >= 1 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 ──────────────────────────────────────────────────── # ── Unit hierarchy ────────────────────────────────────────────────────
async def test_units_building_filter(client): async def test_units_building_filter(client):
"""GET /api/tickets/units?building= filters to one building.""" """GET /api/tickets/units?building= filters to one building."""