From 04e7441d78616659b0b121ccad04efa090341801 Mon Sep 17 00:00:00 2001 From: red person Date: Wed, 3 Jun 2026 08:11:21 +0300 Subject: [PATCH] Skip invalid contacts CLI rows (#1569) --- scripts/odysseus-contacts | 8 ++++++-- tests/test_contacts_cli_rows.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/test_contacts_cli_rows.py diff --git a/scripts/odysseus-contacts b/scripts/odysseus-contacts index e9197e1..3607192 100755 --- a/scripts/odysseus-contacts +++ b/scripts/odysseus-contacts @@ -60,13 +60,17 @@ def fail(msg: str, code: int = 1) -> None: sys.exit(code) +def _contact_rows(contacts): + return [c for c in contacts or [] if isinstance(c, dict)] + + # ─── list ──────────────────────────────────────────────────────────── def cmd_list(args) -> None: cfg = _get_carddav_config() if not cfg["url"]: fail("CardDAV not configured. Set carddav_url/username/password in the web UI.") - contacts = _fetch_contacts(force=args.refresh) + contacts = _contact_rows(_fetch_contacts(force=args.refresh)) emit(contacts, args) @@ -77,7 +81,7 @@ def cmd_search(args) -> None: if not cfg["url"]: fail("CardDAV not configured.") q = args.query.lower() - contacts = _fetch_contacts() + contacts = _contact_rows(_fetch_contacts()) matches = [ c for c in contacts if q in (c.get("name") or "").lower() or q in (c.get("email") or "").lower() diff --git a/tests/test_contacts_cli_rows.py b/tests/test_contacts_cli_rows.py new file mode 100644 index 0000000..bd257e7 --- /dev/null +++ b/tests/test_contacts_cli_rows.py @@ -0,0 +1,33 @@ +import importlib.machinery +import importlib.util +import sys +import types +from pathlib import Path +from unittest.mock import MagicMock + + +ROOT = Path(__file__).resolve().parents[1] + + +def _load_cli(monkeypatch): + routes = types.ModuleType("routes.contacts_routes") + routes._get_carddav_config = MagicMock() + routes._fetch_contacts = MagicMock() + routes._create_contact = MagicMock() + monkeypatch.setitem(sys.modules, "routes.contacts_routes", routes) + path = ROOT / "scripts" / "odysseus-contacts" + loader = importlib.machinery.SourceFileLoader("odysseus_contacts_cli", str(path)) + spec = importlib.util.spec_from_loader(loader.name, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + + +def test_contact_rows_skips_invalid_rows(monkeypatch): + cli = _load_cli(monkeypatch) + + assert cli._contact_rows([ + {"name": "Ada", "email": "ada@example.test"}, + "bad-row", + None, + ]) == [{"name": "Ada", "email": "ada@example.test"}]