소스 검색

Startup: create the HMS notified-faults table (#3502)

- init_db() did not import the hms_notified_fault model, so
  create_all never made its table: every start logged "no such
  table: hms_notified_faults" and HMS notifications were sent again
  after each restart, as before #3486
- a test now starts init_db() in a clean process on an empty
  database and checks that every model's table exists; the suite
  imports all models itself, which is why this went unnoticed
maziggy 1 일 전
부모
커밋
c691d2e905
2개의 변경된 파일과 67개의 추가작업 그리고 0개의 파일을 삭제
  1. 1 0
      backend/app/core/database.py
  2. 66 0
      backend/tests/integration/test_startup_creates_every_table.py

+ 1 - 0
backend/app/core/database.py

@@ -632,6 +632,7 @@ async def init_db():
         finance,
         github_backup,
         group,
+        hms_notified_fault,
         kprofile_note,
         library,
         local_preset,

+ 66 - 0
backend/tests/integration/test_startup_creates_every_table.py

@@ -0,0 +1,66 @@
+"""Startup creates the table of every model (#3502).
+
+init_db() imports the model modules so create_all knows their tables. A module
+missing from that list (and from models/__init__) gets no table on a real
+start, while the test suite, which imports every model itself, never notices:
+hms_notified_faults (#3486) was never created that way. So this starts a clean
+Python process, runs init_db() against an empty SQLite database and checks the
+tables against the ones the model files declare.
+"""
+
+import os
+import re
+import sqlite3
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+REPO = Path(__file__).resolve().parents[3]
+MODELS = REPO / "backend" / "app" / "models"
+
+
+def _declared_tables() -> set[str]:
+    tables = set()
+    for path in MODELS.glob("*.py"):
+        source = path.read_text()
+        tables.update(re.findall(r'__tablename__\s*=\s*"(\w+)"', source))
+        tables.update(re.findall(r'Table\(\s*"(\w+)"', source))
+    return tables
+
+
+@pytest.mark.integration
+@pytest.mark.sqlite_only  # inspects the SQLite file the started process creates
+def test_a_fresh_start_creates_every_model_table(tmp_path):
+    db_path = tmp_path / "bambuddy.db"
+    env = {
+        **os.environ,
+        "DATA_DIR": str(tmp_path),
+        "LOG_DIR": str(tmp_path / "logs"),
+        "DATABASE_URL": f"sqlite+aiosqlite:///{db_path}",
+    }
+    result = subprocess.run(
+        [
+            sys.executable,
+            "-c",
+            "import asyncio\n"
+            "from backend.app.core.database import init_db, engine\n"
+            "async def main():\n"
+            "    await init_db()\n"
+            "    await engine.dispose()\n"
+            "asyncio.run(main())\n",
+        ],
+        cwd=REPO,
+        env=env,
+        capture_output=True,
+        text=True,
+        timeout=300,
+    )
+    assert result.returncode == 0, result.stderr[-3000:]
+
+    with sqlite3.connect(db_path) as db:
+        created = {row[0] for row in db.execute("SELECT name FROM sqlite_master WHERE type = 'table'")}
+    declared = _declared_tables()
+    assert "hms_notified_faults" in declared
+    assert declared - created == set()