fix: make the test-profile generator hermetic and re-runnable

test_profile_init_integration failed on any machine with a generated test
profile already present. Two causes, both real bugs rather than test noise:

- scripts/generate_test_profile.py mutated constants.APP_DIR at import time
  to append "tests". The value was captured when constants was first
  imported -- long before a test can monkeypatch Path.home -- so the script
  read the developer's real ~/.seedpass/tests, and a reload appended another
  "tests" segment. The mutation also leaked into every other importer of
  constants in the same process. The tests directory is now derived per call
  from constants.APP_DIR instead.

- initialize_profile assumed add_fingerprint returns None for an existing
  profile, which is what its docstring still promises; it actually raises
  ValueError. Re-running the generator against an existing profile therefore
  crashed. It now treats "already exists" as the ordinary case.

test_profile_init_integration also repoints constants.APP_DIR so it no
longer depends on the developer's home directory, and
test_generate_test_profile asserts against the derived tests directory
rather than the mutated global.

Verified hermetic: the test passes repeatedly and touches nothing under the
real ~/.seedpass. Full Python suite: 1178 passed, 16 skipped, 0 failed.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Fable 5 committed 2026-08-14 21:16:24 -04:00
1 parent c2d7306bb7
commit b885573315
3 files changed
+40 -14

No files matched your search

+25 -12
View File
@@ -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)
+9 -2
View File
@@ -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
@@ -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()