diff --git a/src/password_manager/entry_management.py b/src/password_manager/entry_management.py index 713d3e3..098ede5 100644 --- a/src/password_manager/entry_management.py +++ b/src/password_manager/entry_management.py @@ -385,8 +385,10 @@ class EntryManager: colored(f"Error: Failed to modify entry at index {index}: {e}", "red") ) - def list_entries(self) -> List[Tuple[int, str, Optional[str], Optional[str], bool]]: - """List all entries in the index.""" + def list_entries( + self, sort_by: str = "index", filter_kind: str | None = None + ) -> List[Tuple[int, str, Optional[str], Optional[str], bool]]: + """List entries in the index with optional sorting and filtering.""" try: data = self.vault.load_index() entries_data = data.get("entries", {}) @@ -396,17 +398,36 @@ class EntryManager: print(colored("No entries found.", "yellow")) return [] - entries = [] - for idx, entry in sorted(entries_data.items(), key=lambda x: int(x[0])): + def sort_key(item: Tuple[str, Dict[str, Any]]): + idx_str, entry = item + if sort_by == "index": + return int(idx_str) + if sort_by == "website": + return entry.get("website", "").lower() + if sort_by == "username": + return entry.get("username", "").lower() + raise ValueError("sort_by must be 'index', 'website', or 'username'") + + sorted_items = sorted(entries_data.items(), key=sort_key) + + filtered_items: List[Tuple[int, Dict[str, Any]]] = [] + for idx_str, entry in sorted_items: + if ( + filter_kind is not None + and entry.get("type", EntryType.PASSWORD.value) != filter_kind + ): + continue + filtered_items.append((int(idx_str), entry)) + + entries: List[Tuple[int, str, Optional[str], Optional[str], bool]] = [] + for idx, entry in filtered_items: etype = entry.get("type", EntryType.PASSWORD.value) if etype == EntryType.TOTP.value: - entries.append( - (int(idx), entry.get("label", ""), None, None, False) - ) + entries.append((idx, entry.get("label", ""), None, None, False)) else: entries.append( ( - int(idx), + idx, entry.get("website", ""), entry.get("username", ""), entry.get("url", ""), @@ -415,7 +436,7 @@ class EntryManager: ) logger.debug(f"Total entries found: {len(entries)}") - for idx, entry in sorted(entries_data.items(), key=lambda x: int(x[0])): + for idx, entry in filtered_items: etype = entry.get("type", EntryType.PASSWORD.value) print(colored(f"Index: {idx}", "cyan")) if etype == EntryType.TOTP.value: @@ -592,12 +613,12 @@ class EntryManager: ) ) - def list_all_entries(self) -> None: - """ - Displays all entries in a formatted manner. - """ + def list_all_entries( + self, sort_by: str = "index", filter_kind: str | None = None + ) -> None: + """Display all entries using :meth:`list_entries`.""" try: - entries = self.list_entries() + entries = self.list_entries(sort_by=sort_by, filter_kind=filter_kind) if not entries: print(colored("No entries to display.", "yellow")) return diff --git a/src/tests/test_list_entries_sort_filter.py b/src/tests/test_list_entries_sort_filter.py new file mode 100644 index 0000000..f56d3ef --- /dev/null +++ b/src/tests/test_list_entries_sort_filter.py @@ -0,0 +1,55 @@ +import sys +from pathlib import Path +from tempfile import TemporaryDirectory + +from helpers import create_vault, TEST_SEED, TEST_PASSWORD + +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.config_manager import ConfigManager +from password_manager.entry_types import EntryType + + +def setup_entry_manager(tmp_path: Path) -> EntryManager: + vault, _ = create_vault(tmp_path, TEST_SEED, TEST_PASSWORD) + cfg_mgr = ConfigManager(vault, tmp_path) + backup_mgr = BackupManager(tmp_path, cfg_mgr) + return EntryManager(vault, backup_mgr) + + +def test_sort_by_website(): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + em = setup_entry_manager(tmp_path) + idx0 = em.add_entry("b.com", 8, "user1") + idx1 = em.add_entry("A.com", 8, "user2") + result = em.list_entries(sort_by="website") + assert result == [ + (idx1, "A.com", "user2", "", False), + (idx0, "b.com", "user1", "", False), + ] + + +def test_sort_by_username(): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + em = setup_entry_manager(tmp_path) + idx0 = em.add_entry("alpha.com", 8, "Charlie") + idx1 = em.add_entry("beta.com", 8, "alice") + result = em.list_entries(sort_by="username") + assert result == [ + (idx1, "beta.com", "alice", "", False), + (idx0, "alpha.com", "Charlie", "", False), + ] + + +def test_filter_by_type(): + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + em = setup_entry_manager(tmp_path) + em.add_entry("site", 8, "user") + em.add_totp("Example", TEST_SEED) + result = em.list_entries(filter_kind=EntryType.TOTP.value) + assert result == [(1, "Example", None, None, False)]