mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 23:38:49 +00:00
Merge pull request #271 from PR0M3TH3AN/codex/add--list-entries--item-to-main-menu
Add list entries feature
This commit is contained in:
22
src/main.py
22
src/main.py
@@ -701,10 +701,11 @@ def display_menu(
|
|||||||
1. Add Entry
|
1. Add Entry
|
||||||
2. Retrieve Entry
|
2. Retrieve Entry
|
||||||
3. Search Entries
|
3. Search Entries
|
||||||
4. Modify an Existing Entry
|
4. List Entries
|
||||||
5. 2FA Codes
|
5. Modify an Existing Entry
|
||||||
6. Settings
|
6. 2FA Codes
|
||||||
7. Exit
|
7. Settings
|
||||||
|
8. Exit
|
||||||
"""
|
"""
|
||||||
display_fn = getattr(password_manager, "display_stats", None)
|
display_fn = getattr(password_manager, "display_stats", None)
|
||||||
if callable(display_fn):
|
if callable(display_fn):
|
||||||
@@ -729,7 +730,7 @@ def display_menu(
|
|||||||
print(colored(menu, "cyan"))
|
print(colored(menu, "cyan"))
|
||||||
try:
|
try:
|
||||||
choice = timed_input(
|
choice = timed_input(
|
||||||
"Enter your choice (1-7): ", inactivity_timeout
|
"Enter your choice (1-8): ", inactivity_timeout
|
||||||
).strip()
|
).strip()
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
print(colored("Session timed out. Vault locked.", "yellow"))
|
print(colored("Session timed out. Vault locked.", "yellow"))
|
||||||
@@ -740,7 +741,7 @@ def display_menu(
|
|||||||
if not choice:
|
if not choice:
|
||||||
print(
|
print(
|
||||||
colored(
|
colored(
|
||||||
"No input detected. Please enter a number between 1 and 7.",
|
"No input detected. Please enter a number between 1 and 8.",
|
||||||
"yellow",
|
"yellow",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -787,14 +788,17 @@ def display_menu(
|
|||||||
password_manager.handle_search_entries()
|
password_manager.handle_search_entries()
|
||||||
elif choice == "4":
|
elif choice == "4":
|
||||||
password_manager.update_activity()
|
password_manager.update_activity()
|
||||||
password_manager.handle_modify_entry()
|
password_manager.handle_list_entries()
|
||||||
elif choice == "5":
|
elif choice == "5":
|
||||||
password_manager.update_activity()
|
password_manager.update_activity()
|
||||||
password_manager.handle_display_totp_codes()
|
password_manager.handle_modify_entry()
|
||||||
elif choice == "6":
|
elif choice == "6":
|
||||||
password_manager.update_activity()
|
password_manager.update_activity()
|
||||||
handle_settings(password_manager)
|
password_manager.handle_display_totp_codes()
|
||||||
elif choice == "7":
|
elif choice == "7":
|
||||||
|
password_manager.update_activity()
|
||||||
|
handle_settings(password_manager)
|
||||||
|
elif choice == "8":
|
||||||
logging.info("Exiting the program.")
|
logging.info("Exiting the program.")
|
||||||
print(colored("Exiting the program.", "green"))
|
print(colored("Exiting the program.", "green"))
|
||||||
password_manager.nostr_client.close_client_pool()
|
password_manager.nostr_client.close_client_pool()
|
||||||
|
@@ -841,3 +841,39 @@ class EntryManager:
|
|||||||
logger.error(f"Failed to list all entries: {e}", exc_info=True)
|
logger.error(f"Failed to list all entries: {e}", exc_info=True)
|
||||||
print(colored(f"Error: Failed to list all entries: {e}", "red"))
|
print(colored(f"Error: Failed to list all entries: {e}", "red"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def get_entry_summaries(
|
||||||
|
self, filter_kind: str | None = None
|
||||||
|
) -> list[tuple[int, str]]:
|
||||||
|
"""Return a list of entry index and display labels."""
|
||||||
|
try:
|
||||||
|
data = self.vault.load_index()
|
||||||
|
entries_data = data.get("entries", {})
|
||||||
|
|
||||||
|
summaries: list[tuple[int, str]] = []
|
||||||
|
for idx_str, entry in entries_data.items():
|
||||||
|
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
|
||||||
|
if filter_kind and etype != filter_kind:
|
||||||
|
continue
|
||||||
|
if etype == EntryType.PASSWORD.value:
|
||||||
|
label = entry.get("website", "")
|
||||||
|
elif etype == EntryType.TOTP.value:
|
||||||
|
label = entry.get("label", "")
|
||||||
|
elif etype == EntryType.SSH.value:
|
||||||
|
label = "SSH Key"
|
||||||
|
elif etype == EntryType.SEED.value:
|
||||||
|
label = "Seed Phrase"
|
||||||
|
elif etype == EntryType.NOSTR.value:
|
||||||
|
label = entry.get("label", "Nostr Key")
|
||||||
|
elif etype == EntryType.PGP.value:
|
||||||
|
label = "PGP Key"
|
||||||
|
else:
|
||||||
|
label = etype
|
||||||
|
summaries.append((int(idx_str), label))
|
||||||
|
|
||||||
|
summaries.sort(key=lambda x: x[0])
|
||||||
|
return summaries
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get entry summaries: {e}", exc_info=True)
|
||||||
|
print(colored(f"Error: Failed to get entry summaries: {e}", "red"))
|
||||||
|
return []
|
||||||
|
@@ -1752,93 +1752,126 @@ class PasswordManager:
|
|||||||
|
|
||||||
print(colored("\n[+] Search Results:\n", "green"))
|
print(colored("\n[+] Search Results:\n", "green"))
|
||||||
for match in results:
|
for match in results:
|
||||||
index, website, username, url, blacklisted = match
|
self.display_entry_details(match[0])
|
||||||
entry = self.entry_manager.retrieve_entry(index)
|
|
||||||
if not entry:
|
|
||||||
continue
|
|
||||||
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
|
|
||||||
print(colored(f"Index: {index}", "cyan"))
|
|
||||||
if etype == EntryType.TOTP.value:
|
|
||||||
print(colored(f" Label: {entry.get('label', website)}", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Derivation Index: {entry.get('index', index)}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}",
|
|
||||||
"cyan",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
notes = entry.get("notes", "")
|
|
||||||
if notes:
|
|
||||||
print(colored(f" Notes: {notes}", "cyan"))
|
|
||||||
elif etype == EntryType.SEED.value:
|
|
||||||
print(colored(" Type: Seed Phrase", "cyan"))
|
|
||||||
print(colored(f" Words: {entry.get('words', 24)}", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Derivation Index: {entry.get('index', index)}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
notes = entry.get("notes", "")
|
|
||||||
if notes:
|
|
||||||
print(colored(f" Notes: {notes}", "cyan"))
|
|
||||||
elif etype == EntryType.SSH.value:
|
|
||||||
print(colored(" Type: SSH Key", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Derivation Index: {entry.get('index', index)}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
notes = entry.get("notes", "")
|
|
||||||
if notes:
|
|
||||||
print(colored(f" Notes: {notes}", "cyan"))
|
|
||||||
elif etype == EntryType.PGP.value:
|
|
||||||
print(colored(" Type: PGP Key", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
uid = entry.get("user_id", "")
|
|
||||||
if uid:
|
|
||||||
print(colored(f" User ID: {uid}", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Derivation Index: {entry.get('index', index)}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
notes = entry.get("notes", "")
|
|
||||||
if notes:
|
|
||||||
print(colored(f" Notes: {notes}", "cyan"))
|
|
||||||
elif etype == EntryType.NOSTR.value:
|
|
||||||
print(colored(" Type: Nostr Key", "cyan"))
|
|
||||||
print(colored(f" Label: {entry.get('label', '')}", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Derivation Index: {entry.get('index', index)}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
notes = entry.get("notes", "")
|
|
||||||
if notes:
|
|
||||||
print(colored(f" Notes: {notes}", "cyan"))
|
|
||||||
else:
|
|
||||||
print(colored(f" Website: {website}", "cyan"))
|
|
||||||
print(colored(f" Username: {username or 'N/A'}", "cyan"))
|
|
||||||
print(colored(f" URL: {url or 'N/A'}", "cyan"))
|
|
||||||
print(
|
|
||||||
colored(
|
|
||||||
f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Failed to search entries: {e}", exc_info=True)
|
logging.error(f"Failed to search entries: {e}", exc_info=True)
|
||||||
print(colored(f"Error: Failed to search entries: {e}", "red"))
|
print(colored(f"Error: Failed to search entries: {e}", "red"))
|
||||||
|
|
||||||
|
def display_entry_details(self, index: int) -> None:
|
||||||
|
"""Print detailed information for a single entry."""
|
||||||
|
entry = self.entry_manager.retrieve_entry(index)
|
||||||
|
if not entry:
|
||||||
|
return
|
||||||
|
|
||||||
|
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
|
||||||
|
print(colored(f"Index: {index}", "cyan"))
|
||||||
|
if etype == EntryType.TOTP.value:
|
||||||
|
print(colored(f" Label: {entry.get('label', '')}", "cyan"))
|
||||||
|
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
|
||||||
|
print(
|
||||||
|
colored(
|
||||||
|
f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}",
|
||||||
|
"cyan",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
notes = entry.get("notes", "")
|
||||||
|
if notes:
|
||||||
|
print(colored(f" Notes: {notes}", "cyan"))
|
||||||
|
elif etype == EntryType.SEED.value:
|
||||||
|
print(colored(" Type: Seed Phrase", "cyan"))
|
||||||
|
print(colored(f" Words: {entry.get('words', 24)}", "cyan"))
|
||||||
|
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
|
||||||
|
notes = entry.get("notes", "")
|
||||||
|
if notes:
|
||||||
|
print(colored(f" Notes: {notes}", "cyan"))
|
||||||
|
elif etype == EntryType.SSH.value:
|
||||||
|
print(colored(" Type: SSH Key", "cyan"))
|
||||||
|
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
|
||||||
|
notes = entry.get("notes", "")
|
||||||
|
if notes:
|
||||||
|
print(colored(f" Notes: {notes}", "cyan"))
|
||||||
|
elif etype == EntryType.PGP.value:
|
||||||
|
print(colored(" Type: PGP Key", "cyan"))
|
||||||
|
print(colored(f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan"))
|
||||||
|
uid = entry.get("user_id", "")
|
||||||
|
if uid:
|
||||||
|
print(colored(f" User ID: {uid}", "cyan"))
|
||||||
|
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
|
||||||
|
notes = entry.get("notes", "")
|
||||||
|
if notes:
|
||||||
|
print(colored(f" Notes: {notes}", "cyan"))
|
||||||
|
elif etype == EntryType.NOSTR.value:
|
||||||
|
print(colored(" Type: Nostr Key", "cyan"))
|
||||||
|
print(colored(f" Label: {entry.get('label', '')}", "cyan"))
|
||||||
|
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
|
||||||
|
notes = entry.get("notes", "")
|
||||||
|
if notes:
|
||||||
|
print(colored(f" Notes: {notes}", "cyan"))
|
||||||
|
else:
|
||||||
|
website = entry.get("website", "")
|
||||||
|
username = entry.get("username", "")
|
||||||
|
url = entry.get("url", "")
|
||||||
|
blacklisted = entry.get("blacklisted", False)
|
||||||
|
print(colored(f" Website: {website}", "cyan"))
|
||||||
|
print(colored(f" Username: {username or 'N/A'}", "cyan"))
|
||||||
|
print(colored(f" URL: {url or 'N/A'}", "cyan"))
|
||||||
|
print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan"))
|
||||||
|
print("-" * 40)
|
||||||
|
|
||||||
|
def handle_list_entries(self) -> None:
|
||||||
|
"""List entries and optionally show details."""
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
print("\nList Entries:")
|
||||||
|
print("1. All")
|
||||||
|
print("2. Passwords")
|
||||||
|
print("3. 2FA (TOTP)")
|
||||||
|
print("4. SSH Key")
|
||||||
|
print("5. Seed Phrase")
|
||||||
|
print("6. Nostr Key Pair")
|
||||||
|
print("7. PGP")
|
||||||
|
print("8. Back")
|
||||||
|
choice = input("Select entry type: ").strip()
|
||||||
|
if choice == "1":
|
||||||
|
filter_kind = None
|
||||||
|
elif choice == "2":
|
||||||
|
filter_kind = EntryType.PASSWORD.value
|
||||||
|
elif choice == "3":
|
||||||
|
filter_kind = EntryType.TOTP.value
|
||||||
|
elif choice == "4":
|
||||||
|
filter_kind = EntryType.SSH.value
|
||||||
|
elif choice == "5":
|
||||||
|
filter_kind = EntryType.SEED.value
|
||||||
|
elif choice == "6":
|
||||||
|
filter_kind = EntryType.NOSTR.value
|
||||||
|
elif choice == "7":
|
||||||
|
filter_kind = EntryType.PGP.value
|
||||||
|
elif choice == "8":
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print(colored("Invalid choice.", "red"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
summaries = self.entry_manager.get_entry_summaries(filter_kind)
|
||||||
|
if not summaries:
|
||||||
|
continue
|
||||||
|
print(colored("\n[+] Entries:\n", "green"))
|
||||||
|
for idx, label in summaries:
|
||||||
|
print(colored(f"{idx}. {label}", "cyan"))
|
||||||
|
idx_input = input(
|
||||||
|
"Enter index to view details or press Enter to return: "
|
||||||
|
).strip()
|
||||||
|
if not idx_input:
|
||||||
|
return
|
||||||
|
if not idx_input.isdigit():
|
||||||
|
print(colored("Invalid index.", "red"))
|
||||||
|
continue
|
||||||
|
self.display_entry_details(int(idx_input))
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to list entries: {e}", exc_info=True)
|
||||||
|
print(colored(f"Error: Failed to list entries: {e}", "red"))
|
||||||
|
|
||||||
def delete_entry(self) -> None:
|
def delete_entry(self) -> None:
|
||||||
"""Deletes an entry from the password index."""
|
"""Deletes an entry from the password index."""
|
||||||
try:
|
try:
|
||||||
|
@@ -31,7 +31,7 @@ def test_auto_sync_triggers_post(monkeypatch):
|
|||||||
called = True
|
called = True
|
||||||
|
|
||||||
monkeypatch.setattr(main, "handle_post_to_nostr", fake_post)
|
monkeypatch.setattr(main, "handle_post_to_nostr", fake_post)
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: "7")
|
monkeypatch.setattr(main, "timed_input", lambda *_: "8")
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main.display_menu(pm, sync_interval=0.1)
|
main.display_menu(pm, sync_interval=0.1)
|
||||||
|
@@ -52,7 +52,7 @@ def _make_pm(called, locked=None):
|
|||||||
def test_empty_and_non_numeric_choice(monkeypatch, capsys):
|
def test_empty_and_non_numeric_choice(monkeypatch, capsys):
|
||||||
called = {"add": False, "retrieve": False, "modify": False}
|
called = {"add": False, "retrieve": False, "modify": False}
|
||||||
pm, _ = _make_pm(called)
|
pm, _ = _make_pm(called)
|
||||||
inputs = iter(["", "abc", "7"])
|
inputs = iter(["", "abc", "8"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main.display_menu(pm, sync_interval=1000, inactivity_timeout=1000)
|
main.display_menu(pm, sync_interval=1000, inactivity_timeout=1000)
|
||||||
@@ -65,7 +65,7 @@ def test_empty_and_non_numeric_choice(monkeypatch, capsys):
|
|||||||
def test_out_of_range_menu(monkeypatch, capsys):
|
def test_out_of_range_menu(monkeypatch, capsys):
|
||||||
called = {"add": False, "retrieve": False, "modify": False}
|
called = {"add": False, "retrieve": False, "modify": False}
|
||||||
pm, _ = _make_pm(called)
|
pm, _ = _make_pm(called)
|
||||||
inputs = iter(["9", "7"])
|
inputs = iter(["9", "8"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main.display_menu(pm, sync_interval=1000, inactivity_timeout=1000)
|
main.display_menu(pm, sync_interval=1000, inactivity_timeout=1000)
|
||||||
@@ -77,7 +77,7 @@ def test_out_of_range_menu(monkeypatch, capsys):
|
|||||||
def test_invalid_add_entry_submenu(monkeypatch, capsys):
|
def test_invalid_add_entry_submenu(monkeypatch, capsys):
|
||||||
called = {"add": False, "retrieve": False, "modify": False}
|
called = {"add": False, "retrieve": False, "modify": False}
|
||||||
pm, _ = _make_pm(called)
|
pm, _ = _make_pm(called)
|
||||||
inputs = iter(["1", "8", "7", "7"])
|
inputs = iter(["1", "8", "7", "8"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
|
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
@@ -92,7 +92,7 @@ def test_inactivity_timeout_loop(monkeypatch, capsys):
|
|||||||
pm, locked = _make_pm(called)
|
pm, locked = _make_pm(called)
|
||||||
pm.last_activity = 0
|
pm.last_activity = 0
|
||||||
monkeypatch.setattr(time, "time", lambda: 100.0)
|
monkeypatch.setattr(time, "time", lambda: 100.0)
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: "7")
|
monkeypatch.setattr(main, "timed_input", lambda *_: "8")
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main.display_menu(pm, sync_interval=1000, inactivity_timeout=0.1)
|
main.display_menu(pm, sync_interval=1000, inactivity_timeout=0.1)
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
|
@@ -36,7 +36,7 @@ def test_inactivity_triggers_lock(monkeypatch):
|
|||||||
unlock_vault=unlock_vault,
|
unlock_vault=unlock_vault,
|
||||||
)
|
)
|
||||||
|
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: "7")
|
monkeypatch.setattr(main, "timed_input", lambda *_: "8")
|
||||||
|
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main.display_menu(pm, sync_interval=1000, inactivity_timeout=0.1)
|
main.display_menu(pm, sync_interval=1000, inactivity_timeout=0.1)
|
||||||
@@ -72,7 +72,7 @@ def test_input_timeout_triggers_lock(monkeypatch):
|
|||||||
unlock_vault=unlock_vault,
|
unlock_vault=unlock_vault,
|
||||||
)
|
)
|
||||||
|
|
||||||
responses = iter([TimeoutError(), "7"])
|
responses = iter([TimeoutError(), "8"])
|
||||||
|
|
||||||
def fake_input(*_args, **_kwargs):
|
def fake_input(*_args, **_kwargs):
|
||||||
val = next(responses)
|
val = next(responses)
|
||||||
|
43
src/tests/test_manager_list_entries.py
Normal file
43
src/tests/test_manager_list_entries.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
from helpers import create_vault, TEST_SEED, TEST_PASSWORD
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
|
from password_manager.entry_management import EntryManager
|
||||||
|
from password_manager.backup import BackupManager
|
||||||
|
from password_manager.manager import PasswordManager, EncryptionMode
|
||||||
|
from password_manager.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
|
||||||
|
|
||||||
|
entry_mgr.add_totp("Example", TEST_SEED)
|
||||||
|
entry_mgr.add_entry("example.com", 12)
|
||||||
|
|
||||||
|
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
|
@@ -30,7 +30,7 @@ def _make_pm(calls):
|
|||||||
def test_menu_totp_option(monkeypatch):
|
def test_menu_totp_option(monkeypatch):
|
||||||
calls = []
|
calls = []
|
||||||
pm = _make_pm(calls)
|
pm = _make_pm(calls)
|
||||||
inputs = iter(["5", "7"])
|
inputs = iter(["6", "8"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
monkeypatch.setattr(main, "handle_settings", lambda *_: None)
|
monkeypatch.setattr(main, "handle_settings", lambda *_: None)
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
@@ -41,7 +41,7 @@ def test_menu_totp_option(monkeypatch):
|
|||||||
def test_menu_settings_option(monkeypatch):
|
def test_menu_settings_option(monkeypatch):
|
||||||
calls = []
|
calls = []
|
||||||
pm = _make_pm(calls)
|
pm = _make_pm(calls)
|
||||||
inputs = iter(["6", "7"])
|
inputs = iter(["7", "8"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
monkeypatch.setattr(main, "handle_settings", lambda *_: calls.append("settings"))
|
monkeypatch.setattr(main, "handle_settings", lambda *_: calls.append("settings"))
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
@@ -30,7 +30,7 @@ def _make_pm(called):
|
|||||||
def test_menu_search_option(monkeypatch):
|
def test_menu_search_option(monkeypatch):
|
||||||
called = []
|
called = []
|
||||||
pm = _make_pm(called)
|
pm = _make_pm(called)
|
||||||
inputs = iter(["3", "7"])
|
inputs = iter(["3", "8"])
|
||||||
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
|
||||||
monkeypatch.setattr("builtins.input", lambda *_: "query")
|
monkeypatch.setattr("builtins.input", lambda *_: "query")
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
|
Reference in New Issue
Block a user