Add search option to menu

This commit is contained in:
thePR0M3TH3AN
2025-07-03 15:56:28 -04:00
parent 3642489b40
commit 89574ef249
6 changed files with 86 additions and 15 deletions

View File

@@ -615,10 +615,11 @@ def display_menu(
Select an option:
1. Add Entry
2. Retrieve Entry
3. Modify an Existing Entry
4. 2FA Codes
5. Settings
6. Exit
3. Search Entries
4. Modify an Existing Entry
5. 2FA Codes
6. Settings
7. Exit
"""
display_fn = getattr(password_manager, "display_stats", None)
if callable(display_fn):
@@ -643,7 +644,7 @@ def display_menu(
print(colored(menu, "cyan"))
try:
choice = timed_input(
"Enter your choice (1-6): ", inactivity_timeout
"Enter your choice (1-7): ", inactivity_timeout
).strip()
except TimeoutError:
print(colored("Session timed out. Vault locked.", "yellow"))
@@ -654,7 +655,7 @@ def display_menu(
if not choice:
print(
colored(
"No input detected. Please enter a number between 1 and 6.",
"No input detected. Please enter a number between 1 and 7.",
"yellow",
)
)
@@ -682,14 +683,17 @@ def display_menu(
password_manager.handle_retrieve_entry()
elif choice == "3":
password_manager.update_activity()
password_manager.handle_modify_entry()
password_manager.handle_search_entries()
elif choice == "4":
password_manager.update_activity()
password_manager.handle_display_totp_codes()
password_manager.handle_modify_entry()
elif choice == "5":
password_manager.update_activity()
handle_settings(password_manager)
elif choice == "6":
password_manager.update_activity()
password_manager.handle_display_totp_codes()
elif choice == "7":
logging.info("Exiting the program.")
print(colored("Exiting the program.", "green"))
password_manager.nostr_client.close_client_pool()

View File

@@ -1303,6 +1303,35 @@ class PasswordManager:
logging.error(f"Error during modifying entry: {e}", exc_info=True)
print(colored(f"Error: Failed to modify entry: {e}", "red"))
def handle_search_entries(self) -> None:
"""Prompt for a query and display matching entries."""
try:
query = input("Enter search string: ").strip()
if not query:
print(colored("No search string provided.", "yellow"))
return
results = self.entry_manager.search_entries(query)
if not results:
print(colored("No matching entries found.", "yellow"))
return
print(colored("\n[+] Search Results:\n", "green"))
for entry in results:
index, website, username, url, blacklisted = entry
print(colored(f"Index: {index}", "cyan"))
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:
logging.error(f"Failed to search entries: {e}", exc_info=True)
print(colored(f"Error: Failed to search entries: {e}", "red"))
def delete_entry(self) -> None:
"""Deletes an entry from the password index."""
try:

View File

@@ -31,7 +31,7 @@ def test_auto_sync_triggers_post(monkeypatch):
called = True
monkeypatch.setattr(main, "handle_post_to_nostr", fake_post)
monkeypatch.setattr(main, "timed_input", lambda *_: "6")
monkeypatch.setattr(main, "timed_input", lambda *_: "7")
with pytest.raises(SystemExit):
main.display_menu(pm, sync_interval=0.1)

View File

@@ -52,7 +52,7 @@ def _make_pm(called, locked=None):
def test_empty_and_non_numeric_choice(monkeypatch, capsys):
called = {"add": False, "retrieve": False, "modify": False}
pm, _ = _make_pm(called)
inputs = iter(["", "abc", "6"])
inputs = iter(["", "abc", "7"])
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
with pytest.raises(SystemExit):
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):
called = {"add": False, "retrieve": False, "modify": False}
pm, _ = _make_pm(called)
inputs = iter(["9", "6"])
inputs = iter(["9", "7"])
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
with pytest.raises(SystemExit):
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):
called = {"add": False, "retrieve": False, "modify": False}
pm, _ = _make_pm(called)
inputs = iter(["1", "4", "3", "6"])
inputs = iter(["1", "4", "3", "7"])
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
monkeypatch.setattr("builtins.input", lambda *_: next(inputs))
with pytest.raises(SystemExit):
@@ -92,7 +92,7 @@ def test_inactivity_timeout_loop(monkeypatch, capsys):
pm, locked = _make_pm(called)
pm.last_activity = 0
monkeypatch.setattr(time, "time", lambda: 100.0)
monkeypatch.setattr(main, "timed_input", lambda *_: "6")
monkeypatch.setattr(main, "timed_input", lambda *_: "7")
with pytest.raises(SystemExit):
main.display_menu(pm, sync_interval=1000, inactivity_timeout=0.1)
out = capsys.readouterr().out

View File

@@ -36,7 +36,7 @@ def test_inactivity_triggers_lock(monkeypatch):
unlock_vault=unlock_vault,
)
monkeypatch.setattr(main, "timed_input", lambda *_: "6")
monkeypatch.setattr(main, "timed_input", lambda *_: "7")
with pytest.raises(SystemExit):
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,
)
responses = iter([TimeoutError(), "6"])
responses = iter([TimeoutError(), "7"])
def fake_input(*_args, **_kwargs):
val = next(responses)

View File

@@ -0,0 +1,38 @@
import time
from types import SimpleNamespace
from pathlib import Path
import sys
import pytest
sys.path.append(str(Path(__file__).resolve().parents[1]))
import main
def _make_pm(called):
pm = SimpleNamespace(
is_dirty=False,
last_update=time.time(),
last_activity=time.time(),
nostr_client=SimpleNamespace(close_client_pool=lambda: None),
handle_add_password=lambda: None,
handle_add_totp=lambda: None,
handle_retrieve_entry=lambda: None,
handle_search_entries=lambda: called.append("search"),
handle_modify_entry=lambda: None,
update_activity=lambda: None,
lock_vault=lambda: None,
unlock_vault=lambda: None,
)
return pm
def test_menu_search_option(monkeypatch):
called = []
pm = _make_pm(called)
inputs = iter(["3", "7"])
monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs))
monkeypatch.setattr("builtins.input", lambda *_: "query")
with pytest.raises(SystemExit):
main.display_menu(pm, sync_interval=1000, inactivity_timeout=1000)
assert called == ["search"]