diff --git a/src/password_manager/entry_management.py b/src/password_manager/entry_management.py index f2e871c..b779580 100644 --- a/src/password_manager/entry_management.py +++ b/src/password_manager/entry_management.py @@ -920,6 +920,7 @@ class EntryManager: filter_kind: str | None = None, *, include_archived: bool = False, + verbose: bool = True, ) -> List[Tuple[int, str, Optional[str], Optional[str], bool]]: """List entries in the index with optional sorting and filtering. @@ -932,7 +933,8 @@ class EntryManager: if not entries_data: logger.info("No entries found.") - print(colored("No entries found.", "yellow")) + if verbose: + print(colored("No entries found.", "yellow")) return [] def sort_key(item: Tuple[str, Dict[str, Any]]): @@ -987,51 +989,59 @@ class EntryManager: ) logger.debug(f"Total entries found: {len(entries)}") - for idx, entry in filtered_items: - etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) - print(colored(f"Index: {idx}", "cyan")) - if etype == EntryType.TOTP.value: - print(colored(" Type: TOTP", "cyan")) - print(colored(f" Label: {entry.get('label', '')}", "cyan")) - print(colored(f" Derivation Index: {entry.get('index')}", "cyan")) - print( - colored( - f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}", - "cyan", + if verbose: + for idx, entry in filtered_items: + etype = entry.get( + "type", entry.get("kind", EntryType.PASSWORD.value) + ) + print(colored(f"Index: {idx}", "cyan")) + if etype == EntryType.TOTP.value: + print(colored(" Type: TOTP", "cyan")) + print(colored(f" Label: {entry.get('label', '')}", "cyan")) + print( + colored(f" Derivation Index: {entry.get('index')}", "cyan") ) - ) - elif etype == EntryType.PASSWORD.value: - print( - colored( - f" Label: {entry.get('label', entry.get('website', ''))}", - "cyan", + print( + colored( + f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}", + "cyan", + ) ) - ) - print( - colored(f" Username: {entry.get('username') or 'N/A'}", "cyan") - ) - print(colored(f" URL: {entry.get('url') or 'N/A'}", "cyan")) - print( - colored( - f" Archived: {'Yes' if entry.get('archived', entry.get('blacklisted', False)) else 'No'}", - "cyan", + elif etype == EntryType.PASSWORD.value: + print( + colored( + f" Label: {entry.get('label', entry.get('website', ''))}", + "cyan", + ) ) - ) - else: - print(colored(f" Label: {entry.get('label', '')}", "cyan")) - print( - colored( - f" Derivation Index: {entry.get('index', idx)}", - "cyan", + print( + colored( + f" Username: {entry.get('username') or 'N/A'}", "cyan" + ) ) - ) - print("-" * 40) + print(colored(f" URL: {entry.get('url') or 'N/A'}", "cyan")) + print( + colored( + f" Archived: {'Yes' if entry.get('archived', entry.get('blacklisted', False)) else 'No'}", + "cyan", + ) + ) + else: + print(colored(f" Label: {entry.get('label', '')}", "cyan")) + print( + colored( + f" Derivation Index: {entry.get('index', idx)}", + "cyan", + ) + ) + print("-" * 40) return entries except Exception as e: logger.error(f"Failed to list entries: {e}", exc_info=True) - print(colored(f"Error: Failed to list entries: {e}", "red")) + if verbose: + print(colored(f"Error: Failed to list entries: {e}", "red")) return [] def search_entries( diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 8b8ca8a..517ac4a 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -3240,7 +3240,9 @@ class PasswordManager: def handle_view_archived_entries(self) -> None: """Display archived entries and optionally view or restore them.""" try: - archived = self.entry_manager.list_entries(include_archived=True) + archived = self.entry_manager.list_entries( + include_archived=True, verbose=False + ) archived = [e for e in archived if e[4]] if not archived: self.notify("No archived entries found.", level="WARNING") @@ -3286,7 +3288,7 @@ class PasswordManager: self.last_update = time.time() pause() archived = self.entry_manager.list_entries( - include_archived=True + include_archived=True, verbose=False ) archived = [e for e in archived if e[4]] if not archived: diff --git a/src/tests/test_archive_restore.py b/src/tests/test_archive_restore.py index b182866..8332fd5 100644 --- a/src/tests/test_archive_restore.py +++ b/src/tests/test_archive_restore.py @@ -152,3 +152,41 @@ def test_view_archived_entries_removed_after_restore(monkeypatch, capsys): note = pm.notifications.get_nowait() assert note.level == "WARNING" assert note.message == "No archived entries found." + + +def test_archived_entries_menu_hides_active(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.is_dirty = False + pm.notifications = queue.Queue() + + archived_idx = entry_mgr.add_entry("archived.com", 8) + active_idx = entry_mgr.add_entry("active.com", 8) + + # Archive only the first entry + monkeypatch.setattr("builtins.input", lambda *_: str(archived_idx)) + pm.handle_archive_entry() + assert entry_mgr.retrieve_entry(archived_idx)["archived"] is True + assert entry_mgr.retrieve_entry(active_idx)["archived"] is False + + # View archived entries and immediately exit + inputs = iter([""]) + monkeypatch.setattr("builtins.input", lambda *_: next(inputs)) + pm.handle_view_archived_entries() + out = capsys.readouterr().out + assert "archived.com" in out + assert "active.com" not in out