M5: enable SQLite foreign_keys pragma for DB-level FK enforcement
This commit is contained in:
@@ -28,6 +28,7 @@ def _get_engine(database_url: str) -> Engine:
|
||||
def _enable_sqlite_wal(dbapi_connection, _connection_record):
|
||||
cursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA journal_mode=WAL")
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
return engine
|
||||
|
||||
@@ -403,3 +403,62 @@ def test_modbus_reading_restrict_prevents_device_deletion(tmp_path: Path):
|
||||
session.commit()
|
||||
|
||||
engine.dispose()
|
||||
|
||||
|
||||
def test_modbus_reading_restrict_enforced_via_app_engine(tmp_path: Path):
|
||||
"""FK RESTRICT is enforced by the *default* app engine (db.py) without any manual pragma.
|
||||
|
||||
After enabling ``PRAGMA foreign_keys=ON`` in the db.py connect event, the app
|
||||
engine must enforce ON DELETE RESTRICT on modbus_reading.device_id without
|
||||
callers having to set the pragma themselves. This test uses the real app engine
|
||||
obtained via ``app.db.get_engine`` (routed through the lru_cache, just like the
|
||||
running app) so it exercises the exact production code path.
|
||||
"""
|
||||
import sqlalchemy.exc
|
||||
|
||||
from app.db import _get_engine, reset_db_caches
|
||||
|
||||
db_path = tmp_path / "app_engine_restrict_test.db"
|
||||
db_url = f"sqlite:///{db_path}"
|
||||
|
||||
alembic_cfg = _make_app_alembic_config(db_url)
|
||||
command.upgrade(alembic_cfg, "head")
|
||||
|
||||
# Obtain engine through the same lru_cache path that production uses.
|
||||
# Clear caches first so we get a fresh engine pointing at our tmp DB.
|
||||
reset_db_caches()
|
||||
engine = _get_engine(db_url)
|
||||
|
||||
try:
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
with Session(engine) as session:
|
||||
device = ModbusDevice(
|
||||
friendly_name="App-Engine RESTRICT Test",
|
||||
host="10.0.0.2",
|
||||
profile="sdm120",
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
session.add(device)
|
||||
session.flush()
|
||||
|
||||
reading = ModbusReading(
|
||||
device_id=device.id,
|
||||
recorded_at=now,
|
||||
payload={"voltage": 230.0},
|
||||
)
|
||||
session.add(reading)
|
||||
session.commit()
|
||||
device_id = device.id
|
||||
|
||||
# Without manual pragma, the app engine (db.py) must still enforce RESTRICT.
|
||||
with pytest.raises(sqlalchemy.exc.IntegrityError):
|
||||
with Session(engine) as session:
|
||||
session.execute(
|
||||
text("DELETE FROM modbus_device WHERE id = :did"),
|
||||
{"did": device_id},
|
||||
)
|
||||
session.commit()
|
||||
finally:
|
||||
reset_db_caches()
|
||||
engine.dispose()
|
||||
|
||||
Reference in New Issue
Block a user