from pathlib import Path from tempfile import TemporaryDirectory from types import SimpleNamespace import pytest from helpers import create_vault, TEST_SEED, TEST_PASSWORD import sys sys.path.append(str(Path(__file__).resolve().parents[1])) from seedpass.core.entry_management import EntryManager from seedpass.core.backup import BackupManager from seedpass.core.manager import PasswordManager, EncryptionMode from seedpass.core.entry_types import EntryType from seedpass.core.config_manager import ConfigManager def test_handle_list_entries(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) cfg_mgr = ConfigManager(vault, tmp_path) backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) pm = PasswordManager.__new__(PasswordManager) pm.encryption_mode = EncryptionMode.SEED_ONLY pm.encryption_manager = enc_mgr pm.vault = vault pm.entry_manager = entry_mgr pm.backup_manager = backup_mgr pm.nostr_client = SimpleNamespace() pm.fingerprint_dir = tmp_path pm.secret_mode_enabled = False pm.secret_mode_enabled = False entry_mgr.add_totp("Example", TEST_SEED) entry_mgr.add_entry("example.com", 12) entry_mgr.add_key_value("API entry", "api", "abc123") entry_mgr.add_managed_account("acct", TEST_SEED) inputs = iter(["1", ""]) # list all, then exit monkeypatch.setattr("builtins.input", lambda *_: next(inputs)) pm.handle_list_entries() out = capsys.readouterr().out assert "Example" in out assert "example.com" in out assert "API" in out assert "acct" in out def test_list_entries_show_details(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) cfg_mgr = ConfigManager(vault, tmp_path) backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) pm = PasswordManager.__new__(PasswordManager) pm.encryption_mode = EncryptionMode.SEED_ONLY pm.encryption_manager = enc_mgr pm.vault = vault pm.entry_manager = entry_mgr pm.backup_manager = backup_mgr pm.parent_seed = TEST_SEED pm.nostr_client = SimpleNamespace() pm.fingerprint_dir = tmp_path pm.secret_mode_enabled = False entry_mgr.add_totp("Example", TEST_SEED) entry_mgr.add_key_value("API entry", "api", "val") entry_mgr.add_managed_account("acct", TEST_SEED) monkeypatch.setattr(pm.entry_manager, "get_totp_code", lambda *a, **k: "123456") monkeypatch.setattr( pm.entry_manager, "get_totp_time_remaining", lambda *a, **k: 1 ) monkeypatch.setattr("seedpass.core.manager.time.sleep", lambda *a, **k: None) monkeypatch.setattr( "seedpass.core.manager.timed_input", lambda *a, **k: "b", ) inputs = iter(["1", "0"]) monkeypatch.setattr("builtins.input", lambda *_: next(inputs)) pm.handle_list_entries() out = capsys.readouterr().out assert "Label: Example" in out assert "Period: 30s" in out assert "API" in out assert "acct" in out def test_show_entry_details_by_index(monkeypatch): """Ensure entry details screen triggers expected calls.""" with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) cfg_mgr = ConfigManager(vault, tmp_path) backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) pm = PasswordManager.__new__(PasswordManager) pm.encryption_mode = EncryptionMode.SEED_ONLY pm.encryption_manager = enc_mgr pm.vault = vault pm.entry_manager = entry_mgr pm.backup_manager = backup_mgr pm.nostr_client = SimpleNamespace() pm.fingerprint_dir = tmp_path pm.secret_mode_enabled = False index = entry_mgr.add_entry("example.com", 12) header_calls = [] monkeypatch.setattr( "seedpass.core.manager.clear_header_with_notification", lambda *a, **k: header_calls.append(True), ) call_order = [] monkeypatch.setattr( pm, "display_entry_details", lambda *a, **k: call_order.append("display"), ) monkeypatch.setattr( pm, "_entry_actions_menu", lambda *a, **k: call_order.append("actions"), ) monkeypatch.setattr("seedpass.core.manager.pause", lambda *a, **k: None) monkeypatch.setattr( "seedpass.core.manager.confirm_action", lambda *a, **k: False ) pm.password_generator = SimpleNamespace(generate_password=lambda l, i: "pw123") monkeypatch.setattr(pm, "notify", lambda *a, **k: None) pm.show_entry_details_by_index(index) assert len(header_calls) == 1 assert call_order == ["display", "actions"] def _setup_manager(tmp_path): vault, enc_mgr = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) cfg_mgr = ConfigManager(vault, tmp_path) backup_mgr = BackupManager(tmp_path, cfg_mgr) entry_mgr = EntryManager(vault, backup_mgr) pm = PasswordManager.__new__(PasswordManager) pm.encryption_mode = EncryptionMode.SEED_ONLY pm.encryption_manager = enc_mgr pm.vault = vault pm.entry_manager = entry_mgr pm.backup_manager = backup_mgr pm.parent_seed = TEST_SEED pm.nostr_client = SimpleNamespace() pm.fingerprint_dir = tmp_path pm.secret_mode_enabled = False return pm, entry_mgr def _detail_common(monkeypatch, pm): monkeypatch.setattr( "seedpass.core.manager.clear_header_with_notification", lambda *a, **k: None, ) monkeypatch.setattr("seedpass.core.manager.pause", lambda *a, **k: None) monkeypatch.setattr("builtins.input", lambda *a, **k: "") monkeypatch.setattr("seedpass.core.manager.confirm_action", lambda *a, **k: False) monkeypatch.setattr("seedpass.core.manager.timed_input", lambda *a, **k: "b") monkeypatch.setattr("seedpass.core.manager.time.sleep", lambda *a, **k: None) monkeypatch.setattr(pm, "notify", lambda *a, **k: None) pm.password_generator = SimpleNamespace(generate_password=lambda l, i: "pw123") called = [] monkeypatch.setattr(pm, "_entry_actions_menu", lambda *a, **k: called.append(True)) return called def test_show_seed_entry_details(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) idx = entry_mgr.add_seed("seed", TEST_SEED, words_num=12) called = _detail_common(monkeypatch, pm) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert "Type: Seed Phrase" in out assert "Label: seed" in out assert "Words: 12" in out assert f"Derivation Index: {idx}" in out assert called == [True] def test_show_ssh_entry_details(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) idx = entry_mgr.add_ssh_key("ssh", TEST_SEED) data = entry_mgr._load_index(force_reload=True) data["entries"][str(idx)]["public_key_label"] = "server" data["entries"][str(idx)]["fingerprint"] = "abc123" entry_mgr._save_index(data) called = _detail_common(monkeypatch, pm) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert "Type: SSH Key" in out assert "Label: ssh" in out assert f"Derivation Index: {idx}" in out assert "server" in out assert "abc123" in out assert called == [True] def test_show_pgp_entry_details(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) idx = entry_mgr.add_pgp_key("pgp", TEST_SEED, user_id="test") _k, fp = entry_mgr.get_pgp_key(idx, TEST_SEED) called = _detail_common(monkeypatch, pm) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert "Type: PGP Key" in out assert "Label: pgp" in out assert "Key Type: ed25519" in out assert "User ID: test" in out assert f"Derivation Index: {idx}" in out assert fp in out assert called == [True] def test_show_nostr_entry_details(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) idx = entry_mgr.add_nostr_key("nostr", TEST_SEED) called = _detail_common(monkeypatch, pm) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert "Type: Nostr Key" in out assert "Label: nostr" in out assert f"Derivation Index: {idx}" in out assert called == [True] def test_show_managed_account_entry_details(monkeypatch, capsys): with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) idx = entry_mgr.add_managed_account("acct", TEST_SEED) fp = entry_mgr.retrieve_entry(idx).get("fingerprint") called = _detail_common(monkeypatch, pm) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert "Type: Managed Account" in out assert "Label: acct" in out assert f"Derivation Index: {idx}" in out assert "Words: 12" in out assert fp in out assert called == [True] @pytest.mark.parametrize( "entry_type", [ "password", "seed", "ssh", "pgp", "nostr", "totp", "key_value", "managed_account", ], ) def test_show_entry_details_sensitive(monkeypatch, capsys, entry_type): """Ensure sensitive details are displayed for each entry type.""" with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) pm.password_generator = SimpleNamespace(generate_password=lambda l, i: "pw123") monkeypatch.setattr( "seedpass.core.manager.confirm_action", lambda *a, **k: True ) monkeypatch.setattr( "seedpass.core.manager.copy_to_clipboard", lambda *a, **k: None ) monkeypatch.setattr("seedpass.core.manager.timed_input", lambda *a, **k: "b") monkeypatch.setattr("seedpass.core.manager.time.sleep", lambda *a, **k: None) monkeypatch.setattr( "seedpass.core.manager.TotpManager.print_qr_code", lambda *a, **k: None ) monkeypatch.setattr( "seedpass.core.manager.clear_header_with_notification", lambda *a, **k: None, ) monkeypatch.setattr("seedpass.core.manager.pause", lambda *a, **k: None) input_val = "r" if entry_type == "managed_account" else "" monkeypatch.setattr("builtins.input", lambda *a, **k: input_val) called = [] monkeypatch.setattr( pm, "_entry_actions_menu", lambda *a, **k: called.append(True) ) if entry_type == "password": idx = entry_mgr.add_entry("example", 8) expected = "pw123" elif entry_type == "seed": idx = entry_mgr.add_seed("seed", TEST_SEED, words_num=12) expected = entry_mgr.get_seed_phrase(idx, TEST_SEED) elif entry_type == "ssh": idx = entry_mgr.add_ssh_key("ssh", TEST_SEED) priv, pub = entry_mgr.get_ssh_key_pair(idx, TEST_SEED) expected = priv extra = pub elif entry_type == "pgp": idx = entry_mgr.add_pgp_key("pgp", TEST_SEED, user_id="test") priv, fp = entry_mgr.get_pgp_key(idx, TEST_SEED) expected = priv extra = fp elif entry_type == "nostr": idx = entry_mgr.add_nostr_key("nostr", TEST_SEED) _npub, nsec = entry_mgr.get_nostr_key_pair(idx, TEST_SEED) expected = nsec elif entry_type == "totp": entry_mgr.add_totp("Example", TEST_SEED) idx = 0 monkeypatch.setattr( pm.entry_manager, "get_totp_code", lambda *a, **k: "123456" ) monkeypatch.setattr( pm.entry_manager, "get_totp_time_remaining", lambda *a, **k: 1 ) expected = "123456" elif entry_type == "key_value": idx = entry_mgr.add_key_value("API entry", "api", "abc") expected = "abc" else: # managed_account idx = entry_mgr.add_managed_account("acct", TEST_SEED) expected = entry_mgr.get_managed_account_seed(idx, TEST_SEED) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert expected in out if entry_type in {"ssh", "pgp"}: assert extra in out assert called == [True] @pytest.mark.parametrize( "entry_type", [EntryType.PASSWORD, EntryType.TOTP, EntryType.KEY_VALUE] ) def test_show_entry_details_with_enum_type(monkeypatch, capsys, entry_type): """Entries storing an EntryType enum should display correctly.""" with TemporaryDirectory() as tmpdir: tmp_path = Path(tmpdir) pm, entry_mgr = _setup_manager(tmp_path) if entry_type == EntryType.PASSWORD: idx = entry_mgr.add_entry("example.com", 8) expect = "example.com" elif entry_type == EntryType.TOTP: entry_mgr.add_totp("Example", TEST_SEED) idx = 0 monkeypatch.setattr( pm.entry_manager, "get_totp_code", lambda *a, **k: "123456" ) monkeypatch.setattr( pm.entry_manager, "get_totp_time_remaining", lambda *a, **k: 1 ) expect = "Label: Example" else: # KEY_VALUE idx = entry_mgr.add_key_value("API entry", "api", "abc") expect = "API entry" data = entry_mgr._load_index(force_reload=True) data["entries"][str(idx)]["type"] = entry_type entry_mgr._save_index(data) called = _detail_common(monkeypatch, pm) pm.show_entry_details_by_index(idx) out = capsys.readouterr().out assert expect in out assert called == [True]