PR #719 review raised a claim that these three fields fall through to
the "keep unchanged" catch-all in _merge_vcard_properties. The existing
elif branches for NICKNAME/BDAY/CATEGORIES already prevent that, but
the behaviour wasn't pinned by a test. Add a focused TestMergeVcardProperties
class that calls the merge helper directly and asserts:
- Existing NICKNAME/BDAY/CATEGORIES lines are overwritten by new values.
- When the existing vCard has none of these lines, update adds them.
- A URL update doesn't clobber unrelated ORG/NOTE/TEL properties.
If the primary update path ever regresses for these fields, these tests
will catch it immediately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Type-annotate _wrap_contact_field signature; drop stale "url" mention
from its docstring (url is handled by the list-coercion helper, not
this one).
- Split the shape-coercion helper so comma-splitting only applies to
categories: _as_str_list (no split) for org/nickname/url,
_split_categories (comma split) for CATEGORIES. Fixes the case where
organization="Smith, Jones & Associates" was mangled into a two-
component ORG.
- Share _normalize_contact_data between create and update so
_merge_vcard_properties only sees canonical keys; add URL handlers in
both update branches so the primary update path no longer drops URL
silently.
- Annotate the Contact(**kwargs) type:ignore with the reason
(pythonvCard4 typeshed doesn't accept **dict[str, Any]).
- Add tests/unit/client/test_contacts.py (pure unit, no HTTP) covering
the #716 round-trip, comma-in-org regression, invalid-bday warning,
tel/phone precedence, categories string-vs-list behaviour, and direct
_normalize_contact_data cases.
- Extend the MCP workflow test with an update-with-url step asserting
the URL handler in _merge_vcard_properties.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>