Skip to content

Commit bdd4478

Browse files
committed
Fix backup restore tests
1 parent b718253 commit bdd4478

2 files changed

Lines changed: 42 additions & 6 deletions

File tree

backend/app/services/backup_restore.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,45 @@ def _run_alembic_migrations() -> None:
5252
upgrade(alembic_cfg, "head")
5353

5454

55+
def _stamp_alembic_head_if_fresh() -> None:
56+
"""Stamp alembic_version at the current head when the version table is absent
57+
or empty.
58+
59+
After ``SQLModel.metadata.create_all()`` creates tables directly (bypassing
60+
alembic), alembic would otherwise try to re-run the initial migration and fail
61+
with *table already exists*.
62+
63+
When ``alembic_version`` already has a row (e.g. an old backup was restored),
64+
stamping is skipped so that ``alembic upgrade head`` can apply pending
65+
migrations on top of whatever revision the backup was at.
66+
"""
67+
from alembic.config import Config
68+
from alembic import command
69+
from alembic.script import ScriptDirectory
70+
71+
import sqlite3
72+
73+
db_path = _extract_db_path(settings.database_url)
74+
conn = sqlite3.connect(db_path)
75+
try:
76+
cursor = conn.execute(
77+
"SELECT name FROM sqlite_master WHERE type='table' AND name='alembic_version'"
78+
)
79+
table_exists = cursor.fetchone() is not None
80+
if table_exists:
81+
row_count = conn.execute("SELECT COUNT(*) FROM alembic_version").fetchone()[0]
82+
if row_count > 0:
83+
return
84+
finally:
85+
conn.close()
86+
87+
alembic_cfg = Config(os.path.join(os.path.dirname(__file__), "..", "..", "alembic.ini"))
88+
script = ScriptDirectory.from_config(alembic_cfg)
89+
head = script.get_current_head()
90+
if head:
91+
command.stamp(alembic_cfg, head)
92+
93+
5594
def _recreate_engine() -> None:
5695
"""Replace the global SQLAlchemy engine with a fresh one.
5796
@@ -67,6 +106,7 @@ def _recreate_engine() -> None:
67106
connect_args={"check_same_thread": False},
68107
)
69108
SQLModel.metadata.create_all(new_engine)
109+
_stamp_alembic_head_if_fresh()
70110
_run_alembic_migrations()
71111
db_mod.engine = new_engine
72112

backend/tests/test_backup_restore.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,14 +546,13 @@ def test_restore_backup_single_file_too_large(valid_backup_zip: bytes, tmp_db_pa
546546

547547
# ── _recreate_engine ──────────────────────────────────────────────────────────
548548

549-
def test_recreate_engine(monkeypatch: MonkeyPatch) -> None:
549+
def test_recreate_engine(monkeypatch: MonkeyPatch, tmp_path: Path) -> None:
550550
"""_recreate_engine should replace app.database.engine."""
551551
import app.database as db_mod
552552

553553
original_engine = db_mod.engine
554+
tmp_db = str(tmp_path / "test.db")
554555
try:
555-
# Use a temp DB path
556-
tmp_db = "/tmp/test_recreate_engine.db"
557556
conn = sqlite3.connect(tmp_db)
558557
conn.execute("CREATE TABLE IF NOT EXISTS t (id INT)")
559558
conn.commit()
@@ -563,10 +562,7 @@ def test_recreate_engine(monkeypatch: MonkeyPatch) -> None:
563562
br._recreate_engine()
564563
new_engine = db_mod.engine
565564
assert new_engine is not original_engine
566-
567-
os.remove(tmp_db)
568565
finally:
569-
# Dispose the newly created engine before restoring the original
570566
if db_mod.engine is not original_engine:
571567
db_mod.engine.dispose()
572568
db_mod.engine = original_engine

0 commit comments

Comments
 (0)