diff --git a/tests/conftest.py b/tests/conftest.py index 2ee99421..75c3de68 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,29 @@ Creates a minimal Flask+SocketIO app that replicates the relevant endpoints and event handlers without importing the full web_server.py (which would try to initialize Spotify, Soulseek, Plex, etc.).""" +# ── TEST-DATABASE ISOLATION — MUST run before any other import ───────────── +# Force every MusicDatabase()/get_database() call in the suite onto a throwaway +# temp database so a test can NEVER open or write the real +# database/music_library.db. MusicDatabase resolves its default path from +# os.environ['DATABASE_PATH'] (see database/music_database.py); setting it here, +# at conftest import (before any test module loads), redirects ALL default-path +# DB access to /tmp. +# +# Why this is non-negotiable: tests exercise modules (e.g. album_mbid_cache) +# 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. +import os as _os +import tempfile as _tempfile +import atexit as _atexit +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['SOULSYNC_TEST_DB_READY'] = '1' + _atexit.register(lambda: _shutil.rmtree(_TEST_DB_DIR, ignore_errors=True)) + import copy import pytest import threading diff --git a/tests/test_db_isolation_guard.py b/tests/test_db_isolation_guard.py new file mode 100644 index 00000000..a03e2c32 --- /dev/null +++ b/tests/test_db_isolation_guard.py @@ -0,0 +1,32 @@ +"""Guard: the test suite must NEVER resolve the real database/music_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 +anything imports; these tests prove it sticks for every default-path access. +""" + +from __future__ import annotations + +import os +from pathlib import Path + +_REAL = os.path.join('database', 'music_library.db') + + +def test_database_path_env_is_isolated(): + p = os.environ.get('DATABASE_PATH', '') + assert 'soulsync-testdb-' in p, f"DATABASE_PATH not isolated: {p!r}" + + +def test_musicdatabase_default_path_never_real(): + from database.music_database import MusicDatabase + resolved = str(Path(MusicDatabase().database_path).resolve()) + assert 'soulsync-testdb-' in resolved, resolved + assert not resolved.replace('\\', '/').endswith('database/music_library.db'), resolved + + +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