mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-07 06:48:52 +00:00
Merge pull request #710 from PR0M3TH3AN/codex/add-entry-type-to-search-results
Include entry type in search results
This commit is contained in:
@@ -59,6 +59,7 @@ SeedPass now uses the `portalocker` library for cross-platform file locking. No
|
||||
- **Quick Unlock:** Optionally skip the password prompt after verifying once.
|
||||
- **Secret Mode:** When enabled, newly generated and retrieved passwords are copied to your clipboard and automatically cleared after a delay.
|
||||
- **Tagging Support:** Organize entries with optional tags and find them quickly via search.
|
||||
- **Typed Search Results:** Results now display each entry's type for quicker identification.
|
||||
- **Manual Vault Export/Import:** Create encrypted backups or restore them using the CLI or API.
|
||||
- **Parent Seed Backup:** Securely save an encrypted copy of the master seed.
|
||||
- **Manual Vault Locking:** Instantly clear keys from memory when needed.
|
||||
@@ -235,6 +236,7 @@ seedpass import --file "~/seedpass_backup.json"
|
||||
seedpass search "github"
|
||||
seedpass search --tags "work,personal"
|
||||
seedpass get "github"
|
||||
# Search results show the entry type, e.g. "1: Password - GitHub"
|
||||
# Retrieve a TOTP entry
|
||||
seedpass entry get "email"
|
||||
# The code is printed and copied to your clipboard
|
||||
|
@@ -136,7 +136,7 @@ Run or stop the local HTTP API.
|
||||
### `entry` Commands
|
||||
|
||||
- **`seedpass entry list`** – List entries in the vault, optionally sorted or filtered.
|
||||
- **`seedpass entry search <query>`** – Search across labels, usernames, URLs and notes.
|
||||
- **`seedpass entry search <query>`** – Search across labels, usernames, URLs and notes. Results show the entry type before each label.
|
||||
- **`seedpass entry get <query>`** – Retrieve the password or TOTP code for one matching entry, depending on the entry's type.
|
||||
- **`seedpass entry add <label>`** – Create a new password entry. Use `--length` and flags like `--no-special`, `--special-mode safe`, or `--exclude-ambiguous` to override the global policy.
|
||||
- **`seedpass entry add-totp <label>`** – Create a TOTP entry. Use `--secret` to import an existing secret or `--index` to derive from the seed.
|
||||
|
@@ -70,6 +70,7 @@ maintainable while enabling a consistent experience on multiple platforms.
|
||||
- **Quick Unlock:** Optionally skip the password prompt after verifying once. Startup delay is unaffected.
|
||||
- **Secret Mode:** Copy retrieved passwords directly to your clipboard and automatically clear it after a delay.
|
||||
- **Tagging Support:** Organize entries with optional tags and find them quickly via search.
|
||||
- **Typed Search Results:** Searches display each entry's type for easier scanning.
|
||||
- **Manual Vault Export/Import:** Create encrypted backups or restore them using the CLI or API.
|
||||
- **Parent Seed Backup:** Securely save an encrypted copy of the master seed.
|
||||
- **Manual Vault Locking:** Instantly clear keys from memory when needed.
|
||||
@@ -219,6 +220,7 @@ seedpass vault import --file "~/seedpass_backup.json"
|
||||
seedpass search "github"
|
||||
seedpass search --tags "work,personal"
|
||||
seedpass get "github"
|
||||
# Search results show the entry type, e.g. "1: Password - GitHub"
|
||||
# Retrieve a TOTP entry
|
||||
seedpass entry get "email"
|
||||
# The code is printed and copied to your clipboard
|
||||
|
27
src/main.py
27
src/main.py
@@ -342,31 +342,28 @@ def handle_display_stats(password_manager: PasswordManager) -> None:
|
||||
|
||||
def print_matches(
|
||||
password_manager: PasswordManager,
|
||||
matches: list[tuple[int, str, str | None, str | None, bool]],
|
||||
matches: list[tuple[int, str, str | None, str | None, bool, EntryType]],
|
||||
) -> None:
|
||||
"""Print a list of search matches."""
|
||||
print(colored("\n[+] Matches:\n", "green"))
|
||||
for entry in matches:
|
||||
idx, website, username, url, blacklisted = entry
|
||||
idx, website, username, url, blacklisted, etype = entry
|
||||
data = password_manager.entry_manager.retrieve_entry(idx)
|
||||
etype = (
|
||||
data.get("type", data.get("kind", EntryType.PASSWORD.value))
|
||||
if data
|
||||
else EntryType.PASSWORD.value
|
||||
)
|
||||
print(color_text(f"Index: {idx}", "index"))
|
||||
if etype == EntryType.TOTP.value:
|
||||
print(color_text(f" Label: {data.get('label', website)}", "index"))
|
||||
print(color_text(f" Derivation Index: {data.get('index', idx)}", "index"))
|
||||
elif etype == EntryType.SEED.value:
|
||||
if etype == EntryType.TOTP:
|
||||
label = data.get("label", website) if data else website
|
||||
deriv = data.get("index", idx) if data else idx
|
||||
print(color_text(f" Label: {label}", "index"))
|
||||
print(color_text(f" Derivation Index: {deriv}", "index"))
|
||||
elif etype == EntryType.SEED:
|
||||
print(color_text(" Type: Seed Phrase", "index"))
|
||||
elif etype == EntryType.SSH.value:
|
||||
elif etype == EntryType.SSH:
|
||||
print(color_text(" Type: SSH Key", "index"))
|
||||
elif etype == EntryType.PGP.value:
|
||||
elif etype == EntryType.PGP:
|
||||
print(color_text(" Type: PGP Key", "index"))
|
||||
elif etype == EntryType.NOSTR.value:
|
||||
elif etype == EntryType.NOSTR:
|
||||
print(color_text(" Type: Nostr Key", "index"))
|
||||
elif etype == EntryType.KEY_VALUE.value:
|
||||
elif etype == EntryType.KEY_VALUE:
|
||||
print(color_text(" Type: Key/Value", "index"))
|
||||
else:
|
||||
if website:
|
||||
|
@@ -86,8 +86,9 @@ def search_entry(query: str, authorization: str | None = Header(None)) -> List[A
|
||||
"username": username,
|
||||
"url": url,
|
||||
"archived": archived,
|
||||
"type": etype.value,
|
||||
}
|
||||
for idx, label, username, url, archived in results
|
||||
for idx, label, username, url, archived, etype in results
|
||||
]
|
||||
|
||||
|
||||
|
@@ -161,8 +161,8 @@ def entry_search(
|
||||
if not results:
|
||||
typer.echo("No matching entries found")
|
||||
return
|
||||
for idx, label, username, url, _arch in results:
|
||||
line = f"{idx}: {label}"
|
||||
for idx, label, username, url, _arch, etype in results:
|
||||
line = f"{idx}: {etype.value.replace('_', ' ').title()} - {label}"
|
||||
if username:
|
||||
line += f" ({username})"
|
||||
if url:
|
||||
@@ -180,8 +180,8 @@ def entry_get(ctx: typer.Context, query: str) -> None:
|
||||
raise typer.Exit(code=1)
|
||||
if len(matches) > 1:
|
||||
typer.echo("Matches:")
|
||||
for idx, label, username, _url, _arch in matches:
|
||||
name = f"{idx}: {label}"
|
||||
for idx, label, username, _url, _arch, etype in matches:
|
||||
name = f"{idx}: {etype.value.replace('_', ' ').title()} - {label}"
|
||||
if username:
|
||||
name += f" ({username})"
|
||||
typer.echo(name)
|
||||
|
@@ -17,6 +17,7 @@ from pydantic import BaseModel
|
||||
|
||||
from .manager import PasswordManager
|
||||
from .pubsub import bus
|
||||
from .entry_types import EntryType
|
||||
|
||||
|
||||
class VaultExportRequest(BaseModel):
|
||||
@@ -274,7 +275,7 @@ class EntryService:
|
||||
|
||||
def search_entries(
|
||||
self, query: str, kinds: list[str] | None = None
|
||||
) -> list[tuple[int, str, str | None, str | None, bool]]:
|
||||
) -> list[tuple[int, str, str | None, str | None, bool, EntryType]]:
|
||||
"""Search entries optionally filtering by ``kinds``.
|
||||
|
||||
Parameters
|
||||
|
@@ -1210,8 +1210,12 @@ class EntryManager:
|
||||
|
||||
def search_entries(
|
||||
self, query: str, kinds: List[str] | None = None
|
||||
) -> List[Tuple[int, str, Optional[str], Optional[str], bool]]:
|
||||
"""Return entries matching ``query`` across whitelisted metadata fields."""
|
||||
) -> List[Tuple[int, str, Optional[str], Optional[str], bool, EntryType]]:
|
||||
"""Return entries matching ``query`` across whitelisted metadata fields.
|
||||
|
||||
Each match is represented as ``(index, label, username, url, archived, etype)``
|
||||
where ``etype`` is the :class:`EntryType` of the entry.
|
||||
"""
|
||||
|
||||
data = self._load_index()
|
||||
entries_data = data.get("entries", {})
|
||||
@@ -1220,19 +1224,23 @@ class EntryManager:
|
||||
return []
|
||||
|
||||
query_lower = query.lower()
|
||||
results: List[Tuple[int, str, Optional[str], Optional[str], bool]] = []
|
||||
results: List[
|
||||
Tuple[int, str, Optional[str], Optional[str], bool, EntryType]
|
||||
] = []
|
||||
|
||||
for idx, entry in sorted(entries_data.items(), key=lambda x: int(x[0])):
|
||||
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
|
||||
etype = EntryType(
|
||||
entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
|
||||
)
|
||||
|
||||
if kinds is not None and etype not in kinds:
|
||||
if kinds is not None and etype.value not in kinds:
|
||||
continue
|
||||
|
||||
label = entry.get("label", entry.get("website", ""))
|
||||
username = (
|
||||
entry.get("username", "") if etype == EntryType.PASSWORD.value else None
|
||||
entry.get("username", "") if etype == EntryType.PASSWORD else None
|
||||
)
|
||||
url = entry.get("url", "") if etype == EntryType.PASSWORD.value else None
|
||||
url = entry.get("url", "") if etype == EntryType.PASSWORD else None
|
||||
tags = entry.get("tags", [])
|
||||
archived = entry.get("archived", entry.get("blacklisted", False))
|
||||
|
||||
@@ -1249,6 +1257,7 @@ class EntryManager:
|
||||
username if username is not None else None,
|
||||
url if url is not None else None,
|
||||
archived,
|
||||
etype,
|
||||
)
|
||||
)
|
||||
|
||||
|
@@ -3379,11 +3379,12 @@ class PasswordManager:
|
||||
child_fingerprint=child_fp,
|
||||
)
|
||||
print(colored("\n[+] Search Results:\n", "green"))
|
||||
for idx, label, username, _url, _b in results:
|
||||
for idx, label, username, _url, _b, etype in results:
|
||||
display_label = label
|
||||
if username:
|
||||
display_label += f" ({username})"
|
||||
print(colored(f"{idx}. {display_label}", "cyan"))
|
||||
type_name = etype.value.replace("_", " ").title()
|
||||
print(colored(f"{idx}. {type_name} - {display_label}", "cyan"))
|
||||
|
||||
idx_input = input(
|
||||
"Enter index to view details or press Enter to go back: "
|
||||
|
@@ -351,7 +351,7 @@ class SearchDialog(toga.Window):
|
||||
query = self.query_input.value or ""
|
||||
results = self.main.entries.search_entries(query)
|
||||
self.main.entry_source.clear()
|
||||
for idx, label, username, url, _arch in results:
|
||||
for idx, label, username, url, _arch, _etype in results:
|
||||
self.main.entry_source.append(
|
||||
{
|
||||
"id": idx,
|
||||
|
@@ -8,13 +8,16 @@ from fastapi.testclient import TestClient
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from seedpass import api
|
||||
from seedpass.core.entry_types import EntryType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(monkeypatch):
|
||||
dummy = SimpleNamespace(
|
||||
entry_manager=SimpleNamespace(
|
||||
search_entries=lambda q: [(1, "Site", "user", "url", False)],
|
||||
search_entries=lambda q: [
|
||||
(1, "Site", "user", "url", False, EntryType.PASSWORD)
|
||||
],
|
||||
retrieve_entry=lambda i: {"label": "Site"},
|
||||
add_entry=lambda *a, **k: 1,
|
||||
modify_entry=lambda *a, **k: None,
|
||||
|
@@ -9,6 +9,7 @@ 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.config_manager import ConfigManager
|
||||
from seedpass.core.entry_types import EntryType
|
||||
|
||||
|
||||
def setup_entry_mgr(tmp_path: Path) -> EntryManager:
|
||||
@@ -26,7 +27,9 @@ def test_archive_nonpassword_list_search():
|
||||
idx = em.search_entries("Example")[0][0]
|
||||
|
||||
assert em.list_entries() == [(idx, "Example", None, None, False)]
|
||||
assert em.search_entries("Example") == [(idx, "Example", None, None, False)]
|
||||
assert em.search_entries("Example") == [
|
||||
(idx, "Example", None, None, False, EntryType.TOTP)
|
||||
]
|
||||
|
||||
em.archive_entry(idx)
|
||||
assert em.retrieve_entry(idx)["archived"] is True
|
||||
@@ -34,9 +37,13 @@ def test_archive_nonpassword_list_search():
|
||||
assert em.list_entries(include_archived=True) == [
|
||||
(idx, "Example", None, None, True)
|
||||
]
|
||||
assert em.search_entries("Example") == [(idx, "Example", None, None, True)]
|
||||
assert em.search_entries("Example") == [
|
||||
(idx, "Example", None, None, True, EntryType.TOTP)
|
||||
]
|
||||
|
||||
em.restore_entry(idx)
|
||||
assert em.retrieve_entry(idx)["archived"] is False
|
||||
assert em.list_entries() == [(idx, "Example", None, None, False)]
|
||||
assert em.search_entries("Example") == [(idx, "Example", None, None, False)]
|
||||
assert em.search_entries("Example") == [
|
||||
(idx, "Example", None, None, False, EntryType.TOTP)
|
||||
]
|
||||
|
@@ -14,6 +14,7 @@ from seedpass.core.entry_management import EntryManager
|
||||
from seedpass.core.backup import BackupManager
|
||||
from seedpass.core.config_manager import ConfigManager
|
||||
from seedpass.core.manager import PasswordManager, EncryptionMode
|
||||
from seedpass.core.entry_types import EntryType
|
||||
|
||||
|
||||
def setup_entry_mgr(tmp_path: Path) -> EntryManager:
|
||||
@@ -31,7 +32,7 @@ def test_archive_restore_affects_listing_and_search():
|
||||
|
||||
assert em.list_entries() == [(idx, "example.com", "alice", "", False)]
|
||||
assert em.search_entries("example") == [
|
||||
(idx, "example.com", "alice", "", False)
|
||||
(idx, "example.com", "alice", "", False, EntryType.PASSWORD)
|
||||
]
|
||||
|
||||
em.archive_entry(idx)
|
||||
@@ -40,13 +41,15 @@ def test_archive_restore_affects_listing_and_search():
|
||||
assert em.list_entries(include_archived=True) == [
|
||||
(idx, "example.com", "alice", "", True)
|
||||
]
|
||||
assert em.search_entries("example") == [(idx, "example.com", "alice", "", True)]
|
||||
assert em.search_entries("example") == [
|
||||
(idx, "example.com", "alice", "", True, EntryType.PASSWORD)
|
||||
]
|
||||
|
||||
em.restore_entry(idx)
|
||||
assert em.retrieve_entry(idx)["archived"] is False
|
||||
assert em.list_entries() == [(idx, "example.com", "alice", "", False)]
|
||||
assert em.search_entries("example") == [
|
||||
(idx, "example.com", "alice", "", False)
|
||||
(idx, "example.com", "alice", "", False, EntryType.PASSWORD)
|
||||
]
|
||||
|
||||
|
||||
|
@@ -5,6 +5,7 @@ from typer.testing import CliRunner
|
||||
|
||||
from seedpass import cli
|
||||
from seedpass.cli import app
|
||||
from seedpass.core.entry_types import EntryType
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
@@ -34,7 +35,7 @@ def test_cli_entry_add_search_sync(monkeypatch):
|
||||
|
||||
def search_entries(q, kinds=None):
|
||||
calls["search"] = (q, kinds)
|
||||
return [(1, "Label", None, None, False)]
|
||||
return [(1, "Label", None, None, False, EntryType.PASSWORD)]
|
||||
|
||||
def start_background_vault_sync():
|
||||
calls["sync"] = True
|
||||
|
@@ -17,7 +17,9 @@ class DummyPM:
|
||||
list_entries=lambda sort_by="index", filter_kind=None, include_archived=False: [
|
||||
(1, "Label", "user", "url", False)
|
||||
],
|
||||
search_entries=lambda q, kinds=None: [(1, "GitHub", "user", "", False)],
|
||||
search_entries=lambda q, kinds=None: [
|
||||
(1, "GitHub", "user", "", False, EntryType.PASSWORD)
|
||||
],
|
||||
retrieve_entry=lambda idx: {"type": EntryType.PASSWORD.value, "length": 8},
|
||||
get_totp_code=lambda idx, seed: "123456",
|
||||
add_entry=lambda label, length, username, url, **kwargs: 1,
|
||||
|
@@ -27,7 +27,7 @@ def make_pm(search_results, entry=None, totp_code="123456"):
|
||||
|
||||
|
||||
def test_search_command(monkeypatch, capsys):
|
||||
pm = make_pm([(0, "Example", "user", "", False)])
|
||||
pm = make_pm([(0, "Example", "user", "", False, EntryType.PASSWORD)])
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
monkeypatch.setattr(main, "initialize_app", lambda: None)
|
||||
@@ -40,7 +40,7 @@ def test_search_command(monkeypatch, capsys):
|
||||
|
||||
def test_get_command(monkeypatch, capsys):
|
||||
entry = {"type": EntryType.PASSWORD.value, "length": 8}
|
||||
pm = make_pm([(0, "Example", "user", "", False)], entry=entry)
|
||||
pm = make_pm([(0, "Example", "user", "", False, EntryType.PASSWORD)], entry=entry)
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
monkeypatch.setattr(main, "initialize_app", lambda: None)
|
||||
@@ -53,7 +53,7 @@ def test_get_command(monkeypatch, capsys):
|
||||
|
||||
def test_totp_command(monkeypatch, capsys):
|
||||
entry = {"type": EntryType.TOTP.value, "period": 30, "index": 0}
|
||||
pm = make_pm([(0, "Example", None, None, False)], entry=entry)
|
||||
pm = make_pm([(0, "Example", None, None, False, EntryType.TOTP)], entry=entry)
|
||||
called = {}
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
@@ -83,7 +83,10 @@ def test_search_command_no_results(monkeypatch, capsys):
|
||||
|
||||
|
||||
def test_get_command_multiple_matches(monkeypatch, capsys):
|
||||
matches = [(0, "Example", "user", "", False), (1, "Ex2", "bob", "", False)]
|
||||
matches = [
|
||||
(0, "Example", "user", "", False, EntryType.PASSWORD),
|
||||
(1, "Ex2", "bob", "", False, EntryType.PASSWORD),
|
||||
]
|
||||
pm = make_pm(matches)
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
@@ -97,7 +100,7 @@ def test_get_command_multiple_matches(monkeypatch, capsys):
|
||||
|
||||
def test_get_command_wrong_type(monkeypatch, capsys):
|
||||
entry = {"type": EntryType.TOTP.value}
|
||||
pm = make_pm([(0, "Example", "user", "", False)], entry=entry)
|
||||
pm = make_pm([(0, "Example", None, None, False, EntryType.TOTP)], entry=entry)
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
monkeypatch.setattr(main, "initialize_app", lambda: None)
|
||||
@@ -109,7 +112,10 @@ def test_get_command_wrong_type(monkeypatch, capsys):
|
||||
|
||||
|
||||
def test_totp_command_multiple_matches(monkeypatch, capsys):
|
||||
matches = [(0, "GH", None, None, False), (1, "Git", None, None, False)]
|
||||
matches = [
|
||||
(0, "GH", None, None, False, EntryType.TOTP),
|
||||
(1, "Git", None, None, False, EntryType.TOTP),
|
||||
]
|
||||
pm = make_pm(matches)
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
@@ -123,7 +129,7 @@ def test_totp_command_multiple_matches(monkeypatch, capsys):
|
||||
|
||||
def test_totp_command_wrong_type(monkeypatch, capsys):
|
||||
entry = {"type": EntryType.PASSWORD.value, "length": 8}
|
||||
pm = make_pm([(0, "Example", "user", "", False)], entry=entry)
|
||||
pm = make_pm([(0, "Example", "user", "", False, EntryType.PASSWORD)], entry=entry)
|
||||
monkeypatch.setattr(main, "PasswordManager", lambda *a, **k: pm)
|
||||
monkeypatch.setattr(main, "configure_logging", lambda: None)
|
||||
monkeypatch.setattr(main, "initialize_app", lambda: None)
|
||||
|
@@ -2,6 +2,7 @@ import types
|
||||
from types import SimpleNamespace
|
||||
|
||||
from seedpass.core.api import VaultService, EntryService, SyncService, UnlockRequest
|
||||
from seedpass.core.entry_types import EntryType
|
||||
|
||||
|
||||
def test_vault_service_unlock():
|
||||
@@ -27,7 +28,7 @@ def test_entry_service_add_entry_and_search():
|
||||
|
||||
def search_entries(q, kinds=None):
|
||||
called["search"] = (q, kinds)
|
||||
return [(5, "Example", username, url, False)]
|
||||
return [(5, "Example", username, url, False, EntryType.PASSWORD)]
|
||||
|
||||
def start_background_vault_sync():
|
||||
called["sync"] = True
|
||||
@@ -47,7 +48,7 @@ def test_entry_service_add_entry_and_search():
|
||||
assert called.get("sync") is True
|
||||
|
||||
results = service.search_entries("ex", kinds=["password"])
|
||||
assert results == [(5, "Example", username, url, False)]
|
||||
assert results == [(5, "Example", username, url, False, EntryType.PASSWORD)]
|
||||
assert called["search"] == ("ex", ["password"])
|
||||
|
||||
|
||||
|
@@ -46,6 +46,6 @@ def test_search_entries_prompt_for_details(monkeypatch, capsys):
|
||||
|
||||
pm.handle_search_entries()
|
||||
out = capsys.readouterr().out
|
||||
assert "0. Example" in out
|
||||
assert "0. Totp - Example" in out
|
||||
assert "Label: Example" in out
|
||||
assert "Period: 30s" in out
|
||||
|
@@ -28,7 +28,7 @@ def test_search_by_website():
|
||||
entry_mgr.add_entry("Other.com", 8, "bob")
|
||||
|
||||
result = entry_mgr.search_entries("example")
|
||||
assert result == [(idx0, "Example.com", "alice", "", False)]
|
||||
assert result == [(idx0, "Example.com", "alice", "", False, EntryType.PASSWORD)]
|
||||
|
||||
|
||||
def test_search_by_username():
|
||||
@@ -40,7 +40,7 @@ def test_search_by_username():
|
||||
idx1 = entry_mgr.add_entry("Test.com", 8, "Bob")
|
||||
|
||||
result = entry_mgr.search_entries("bob")
|
||||
assert result == [(idx1, "Test.com", "Bob", "", False)]
|
||||
assert result == [(idx1, "Test.com", "Bob", "", False, EntryType.PASSWORD)]
|
||||
|
||||
|
||||
def test_search_by_url():
|
||||
@@ -52,7 +52,9 @@ def test_search_by_url():
|
||||
entry_mgr.add_entry("Other", 8)
|
||||
|
||||
result = entry_mgr.search_entries("login")
|
||||
assert result == [(idx, "Example", "", "https://ex.com/login", False)]
|
||||
assert result == [
|
||||
(idx, "Example", "", "https://ex.com/login", False, EntryType.PASSWORD)
|
||||
]
|
||||
|
||||
|
||||
def test_search_by_notes_and_totp():
|
||||
@@ -117,7 +119,7 @@ def test_search_by_tag_password():
|
||||
idx = entry_mgr.add_entry("TaggedSite", 8, tags=["work"])
|
||||
|
||||
result = entry_mgr.search_entries("work")
|
||||
assert result == [(idx, "TaggedSite", "", "", False)]
|
||||
assert result == [(idx, "TaggedSite", "", "", False, EntryType.PASSWORD)]
|
||||
|
||||
|
||||
def test_search_by_tag_totp():
|
||||
@@ -129,7 +131,7 @@ def test_search_by_tag_totp():
|
||||
idx = entry_mgr.search_entries("OTPAccount")[0][0]
|
||||
|
||||
result = entry_mgr.search_entries("mfa")
|
||||
assert result == [(idx, "OTPAccount", None, None, False)]
|
||||
assert result == [(idx, "OTPAccount", None, None, False, EntryType.TOTP)]
|
||||
|
||||
|
||||
def test_search_with_kind_filter():
|
||||
@@ -147,4 +149,4 @@ def test_search_with_kind_filter():
|
||||
assert {r[0] for r in all_results} == {idx_pw, idx_totp}
|
||||
|
||||
only_pw = entry_mgr.search_entries("", kinds=[EntryType.PASSWORD.value])
|
||||
assert only_pw == [(idx_pw, "Site", "", "", False)]
|
||||
assert only_pw == [(idx_pw, "Site", "", "", False, EntryType.PASSWORD)]
|
||||
|
@@ -9,6 +9,7 @@ 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.config_manager import ConfigManager
|
||||
from seedpass.core.entry_types import EntryType
|
||||
|
||||
|
||||
def setup_entry_manager(tmp_path: Path) -> EntryManager:
|
||||
@@ -29,7 +30,7 @@ def test_tags_persist_on_new_entry():
|
||||
entry_mgr = setup_entry_manager(tmp_path)
|
||||
|
||||
result = entry_mgr.search_entries("work")
|
||||
assert result == [(idx, "Site", "", "", False)]
|
||||
assert result == [(idx, "Site", "", "", False, EntryType.PASSWORD)]
|
||||
|
||||
|
||||
def test_tags_persist_after_modify():
|
||||
@@ -41,9 +42,11 @@ def test_tags_persist_after_modify():
|
||||
entry_mgr.modify_entry(idx, tags=["personal"])
|
||||
|
||||
# Ensure tag searchable before reload
|
||||
assert entry_mgr.search_entries("personal") == [(idx, "Site", "", "", False)]
|
||||
assert entry_mgr.search_entries("personal") == [
|
||||
(idx, "Site", "", "", False, EntryType.PASSWORD)
|
||||
]
|
||||
|
||||
# Reinitialize to simulate application restart
|
||||
entry_mgr = setup_entry_manager(tmp_path)
|
||||
result = entry_mgr.search_entries("personal")
|
||||
assert result == [(idx, "Site", "", "", False)]
|
||||
assert result == [(idx, "Site", "", "", False, EntryType.PASSWORD)]
|
||||
|
@@ -34,19 +34,21 @@ def test_entry_list(monkeypatch):
|
||||
def test_entry_search(monkeypatch):
|
||||
pm = SimpleNamespace(
|
||||
entry_manager=SimpleNamespace(
|
||||
search_entries=lambda q, kinds=None: [(1, "L", None, None, False)]
|
||||
search_entries=lambda q, kinds=None: [
|
||||
(1, "L", None, None, False, EntryType.PASSWORD)
|
||||
]
|
||||
),
|
||||
select_fingerprint=lambda fp: None,
|
||||
)
|
||||
monkeypatch.setattr(cli, "PasswordManager", lambda: pm)
|
||||
result = runner.invoke(app, ["entry", "search", "l"])
|
||||
assert result.exit_code == 0
|
||||
assert "1: L" in result.stdout
|
||||
assert "Password - L" in result.stdout
|
||||
|
||||
|
||||
def test_entry_get_password(monkeypatch):
|
||||
def search(q, kinds=None):
|
||||
return [(2, "Example", "", "", False)]
|
||||
return [(2, "Example", "", "", False, EntryType.PASSWORD)]
|
||||
|
||||
entry = {"type": EntryType.PASSWORD.value, "length": 8}
|
||||
pm = SimpleNamespace(
|
||||
|
Reference in New Issue
Block a user