From 5d488bceb413efded54d5f73e265c9ed9b103c4b Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Fri, 19 Jun 2026 23:02:38 -0700 Subject: [PATCH] tests: isolate the VIDEO database too (never open the real video_library.db) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- tests/conftest.py | 8 ++++++++ tests/test_db_isolation_guard.py | 29 +++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 288f439b..84166e84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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)) diff --git a/tests/test_db_isolation_guard.py b/tests/test_db_isolation_guard.py index a03e2c32..b865ad28 100644 --- a/tests/test_db_isolation_guard.py +++ b/tests/test_db_isolation_guard.py @@ -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