mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-01 02:12:04 +00:00
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:
1 parent
c2d7306bb7
commit
b885573315
3 files changed
+40
-14
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in new issue
Block a user