diff --git a/scripts/generate_test_profile.py b/scripts/generate_test_profile.py index 5e73847a..104a6bab 100644 --- a/scripts/generate_test_profile.py +++ b/scripts/generate_test_profile.py @@ -31,12 +31,20 @@ if str(SRC_DIR) not in sys.path: import constants as consts -# Use a dedicated subdirectory for test profiles so regular data is not polluted -consts.APP_DIR = consts.APP_DIR / "tests" -consts.PARENT_SEED_FILE = consts.APP_DIR / "parent_seed.enc" -consts.SCRIPT_CHECKSUM_FILE = consts.APP_DIR / "seedpass_script_checksum.txt" +from constants import initialize_app -from constants import APP_DIR, initialize_app + +def test_app_dir() -> Path: + """Directory holding generated test profiles. + + Resolved on every call from ``constants.APP_DIR`` rather than captured at + import time, so a caller (or a test) that repoints ``constants.APP_DIR`` + is honored. Mutating the constant at import time instead made this module + read whichever directory happened to be configured when it was first + loaded -- in practice the developer's real ``~/.seedpass`` -- and appended + another ``tests`` segment on each reload. + """ + return Path(consts.APP_DIR) / "tests" from utils.key_derivation import derive_key_from_password, derive_index_key from seedpass.core.encryption import EncryptionManager from seedpass.core.vault import Vault @@ -59,7 +67,9 @@ def initialize_profile( ) -> tuple[str, EntryManager, Path, str, ConfigManager]: """Create or load a profile and return the seed phrase, manager, directory and fingerprint.""" initialize_app() - seed_txt = APP_DIR / f"{profile_name}_seed.txt" + app_dir = test_app_dir() + app_dir.mkdir(parents=True, exist_ok=True) + seed_txt = app_dir / f"{profile_name}_seed.txt" if seed_txt.exists(): seed_phrase = seed_txt.read_text().strip() else: @@ -71,13 +81,16 @@ def initialize_profile( seed_txt.write_text(seed_phrase) seed_txt.chmod(0o600) - fp_mgr = FingerprintManager(APP_DIR) - fingerprint = fp_mgr.add_fingerprint(seed_phrase) or generate_fingerprint( - seed_phrase - ) + fp_mgr = FingerprintManager(app_dir) + try: + fingerprint = fp_mgr.add_fingerprint(seed_phrase) + except ValueError: + # Re-running against an existing profile is the normal case for this + # script; add_fingerprint raises rather than returning None there. + fingerprint = None if fingerprint is None: - fingerprint = profile_name - profile_dir = APP_DIR / fingerprint + fingerprint = generate_fingerprint(seed_phrase) or profile_name + profile_dir = app_dir / fingerprint profile_dir.mkdir(parents=True, exist_ok=True) seed_key = derive_key_from_password(DEFAULT_PASSWORD, fingerprint) diff --git a/src/tests/test_generate_test_profile.py b/src/tests/test_generate_test_profile.py index 8b6da8cc..12ee4dc1 100644 --- a/src/tests/test_generate_test_profile.py +++ b/src/tests/test_generate_test_profile.py @@ -27,7 +27,14 @@ def test_initialize_profile_creates_directories(monkeypatch): seed, mgr, dir_path, fingerprint, cfg_mgr = gtp.initialize_profile("test") assert cfg_mgr is not None - assert constants.APP_DIR.exists() - assert (constants.APP_DIR / "test_seed.txt").exists() + # Generated profiles live under APP_DIR/tests so they never mix with + # real ones. The script used to achieve this by mutating + # constants.APP_DIR at import time, which leaked into every other + # importer in the process; it now derives the path per call. + test_dir = gtp.test_app_dir() + assert test_dir == constants.APP_DIR / "tests" + assert test_dir.exists() + assert (test_dir / "test_seed.txt").exists() + assert dir_path.parent == test_dir assert dir_path.exists() assert dir_path.name == fingerprint diff --git a/src/tests/test_profile_init_integration.py b/src/tests/test_profile_init_integration.py index 484447e8..e137a142 100644 --- a/src/tests/test_profile_init_integration.py +++ b/src/tests/test_profile_init_integration.py @@ -3,6 +3,7 @@ import importlib.util from pathlib import Path from tempfile import TemporaryDirectory +import constants from seedpass.core.manager import PasswordManager, EncryptionMode @@ -21,6 +22,11 @@ def test_initialize_profile_and_manager(monkeypatch): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) monkeypatch.setattr(Path, "home", lambda: tmp_path) + # constants.APP_DIR is computed from Path.home() when constants is + # first imported, which happens long before this monkeypatch. Without + # repointing it the script reads the developer's real ~/.seedpass and + # fails against whatever profile happens to live there. + monkeypatch.setattr(constants, "APP_DIR", tmp_path / ".seedpass") gtp = load_script()