tests: isolate the VIDEO database too (never open the real video_library.db)

conftest already redirected the MUSIC DB to a tmp path after test writes once
corrupted a user's music library (WSL/NTFS + WAL). The VIDEO side had the SAME
hazard but no guard: VideoDatabase()/get_video_db() with no path resolve from
VIDEO_DATABASE_PATH, defaulting to the real database/video_library.db, and its
enrichment threads WRITE — a default-path open during tests corrupted the real
video library (one table's btree pages; recovered via row-by-row salvage).

Fix: set VIDEO_DATABASE_PATH to the same throwaway tmp dir at conftest import,
before anything loads. Adds guard tests proving VideoDatabase()/get_video_db()
never resolve to database/video_library.db (mirrors the music guards).
This commit is contained in:
BoulderBadgeDad 2026-06-19 23:02:38 -07:00
parent 40149d09f7
commit 5d488bceb4
2 changed files with 33 additions and 4 deletions

View file

@ -16,6 +16,13 @@ endpoints and event handlers without importing the full web_server.py
# that call get_database() with no path → the real DB. Running those writers
# against the live DB over a WSL-mounted Windows drive corrupted a user's
# library. This guarantees it can't recur — tests get their own disposable DB.
#
# The VIDEO side has the SAME hazard: VideoDatabase()/get_video_db() with no
# path resolves from os.environ['VIDEO_DATABASE_PATH'] (see
# database/video_database.py) → the real database/video_library.db, and its
# enrichment threads WRITE. A blueprint/handler test that opened the default
# VideoDatabase corrupted the real video library once — same WSL/NTFS + WAL
# trap. So redirect VIDEO_DATABASE_PATH to /tmp here too, before any import.
import os as _os
import tempfile as _tempfile
import atexit as _atexit
@ -24,6 +31,7 @@ import shutil as _shutil
if not _os.environ.get('SOULSYNC_TEST_DB_READY'):
_TEST_DB_DIR = _tempfile.mkdtemp(prefix='soulsync-testdb-')
_os.environ['DATABASE_PATH'] = _os.path.join(_TEST_DB_DIR, 'test_music_library.db')
_os.environ['VIDEO_DATABASE_PATH'] = _os.path.join(_TEST_DB_DIR, 'test_video_library.db')
_os.environ['SOULSYNC_TEST_DB_READY'] = '1'
_atexit.register(lambda: _shutil.rmtree(_TEST_DB_DIR, ignore_errors=True))

View file

@ -1,8 +1,9 @@
"""Guard: the test suite must NEVER resolve the real database/music_library.db.
"""Guard: the test suite must NEVER resolve the real music OR video library DB.
Tests exercise modules that call get_database() with no path. If that resolves
to the live DB, test writes can corrupt a real library (this happened, over a
WSL-mounted Windows drive). conftest.py sets DATABASE_PATH to a temp file before
Tests exercise modules that call get_database()/get_video_db() with no path. If
that resolves to a live DB, test writes can corrupt a real library (this
happened to BOTH the music and the video library, over a WSL-mounted Windows
drive). conftest.py sets DATABASE_PATH + VIDEO_DATABASE_PATH to temp files before
anything imports; these tests prove it sticks for every default-path access.
"""
@ -30,3 +31,23 @@ def test_get_database_path_never_real():
from database.music_database import get_database
resolved = str(Path(get_database().database_path).resolve())
assert 'soulsync-testdb-' in resolved, resolved
# ── Video side — same hazard, same guarantee ──────────────────────────────
def test_video_database_path_env_is_isolated():
p = os.environ.get('VIDEO_DATABASE_PATH', '')
assert 'soulsync-testdb-' in p, f"VIDEO_DATABASE_PATH not isolated: {p!r}"
def test_videodatabase_default_path_never_real():
from database.video_database import VideoDatabase
resolved = str(Path(VideoDatabase().database_path).resolve())
assert 'soulsync-testdb-' in resolved, resolved
assert not resolved.replace('\\', '/').endswith('database/video_library.db'), resolved
def test_get_video_db_path_never_real():
from api.video import get_video_db
resolved = str(Path(get_video_db().database_path).resolve())
assert 'soulsync-testdb-' in resolved, resolved