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:
parent
40149d09f7
commit
5d488bceb4
2 changed files with 33 additions and 4 deletions
|
|
@ -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
|
# 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
|
# 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.
|
# 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 os as _os
|
||||||
import tempfile as _tempfile
|
import tempfile as _tempfile
|
||||||
import atexit as _atexit
|
import atexit as _atexit
|
||||||
|
|
@ -24,6 +31,7 @@ import shutil as _shutil
|
||||||
if not _os.environ.get('SOULSYNC_TEST_DB_READY'):
|
if not _os.environ.get('SOULSYNC_TEST_DB_READY'):
|
||||||
_TEST_DB_DIR = _tempfile.mkdtemp(prefix='soulsync-testdb-')
|
_TEST_DB_DIR = _tempfile.mkdtemp(prefix='soulsync-testdb-')
|
||||||
_os.environ['DATABASE_PATH'] = _os.path.join(_TEST_DB_DIR, 'test_music_library.db')
|
_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'
|
_os.environ['SOULSYNC_TEST_DB_READY'] = '1'
|
||||||
_atexit.register(lambda: _shutil.rmtree(_TEST_DB_DIR, ignore_errors=True))
|
_atexit.register(lambda: _shutil.rmtree(_TEST_DB_DIR, ignore_errors=True))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
Tests exercise modules that call get_database()/get_video_db() with no path. If
|
||||||
to the live DB, test writes can corrupt a real library (this happened, over a
|
that resolves to a live DB, test writes can corrupt a real library (this
|
||||||
WSL-mounted Windows drive). conftest.py sets DATABASE_PATH to a temp file before
|
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.
|
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
|
from database.music_database import get_database
|
||||||
resolved = str(Path(get_database().database_path).resolve())
|
resolved = str(Path(get_database().database_path).resolve())
|
||||||
assert 'soulsync-testdb-' in resolved, resolved
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue