fix: convert BDAY datetime.date to string before Pydantic validation
pythonvCard4 parses vCard BDAY fields into datetime.date objects, but the Contact model expects Optional[str]. This caused a validation error that crashed the entire contact list. Convert at the client layer (consistent with the calendar client pattern) with a defensive check at the server mapping layer. Closes #672 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
97ae750916
commit
da380a38c6
@@ -2,6 +2,7 @@
|
||||
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import date
|
||||
|
||||
from pythonvCard4.vcard import Contact
|
||||
|
||||
@@ -272,7 +273,9 @@ class ContactsClient(BaseNextcloudClient):
|
||||
"contact": {
|
||||
"fullname": contact.fn,
|
||||
"nickname": contact.nickname,
|
||||
"birthday": contact.bday,
|
||||
"birthday": contact.bday.isoformat()
|
||||
if isinstance(contact.bday, date)
|
||||
else contact.bday,
|
||||
"email": contact.email,
|
||||
"tel": contact.tel,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from datetime import date
|
||||
from typing import Any
|
||||
|
||||
from mcp.server.fastmcp import Context, FastMCP
|
||||
@@ -83,7 +84,9 @@ def _raw_contact_to_model(raw: dict) -> Contact:
|
||||
uid=raw["vcard_id"],
|
||||
fn=contact_info.get("fullname", ""),
|
||||
etag=raw.get("getetag"),
|
||||
birthday=contact_info.get("birthday"),
|
||||
birthday=contact_info["birthday"].isoformat()
|
||||
if isinstance(contact_info.get("birthday"), date)
|
||||
else contact_info.get("birthday"),
|
||||
emails=emails,
|
||||
phones=phones,
|
||||
custom_fields=custom_fields,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Unit tests for Pydantic response models."""
|
||||
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from nextcloud_mcp_server.models.contacts import (
|
||||
@@ -310,6 +312,46 @@ def test_contact_mapping_preserves_email_birthday_nickname():
|
||||
assert contact.custom_fields["nickname"] == "JD"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_contact_mapping_birthday_datetime_date_object():
|
||||
"""Test that a datetime.date birthday is converted to ISO string.
|
||||
|
||||
Regression test for GH #672: pythonvCard4 returns datetime.date objects
|
||||
for BDAY fields, which caused Pydantic validation errors.
|
||||
"""
|
||||
raw_contact = {
|
||||
"vcard_id": "bday-date-1",
|
||||
"contact": {
|
||||
"fullname": "Date Object",
|
||||
"birthday": date(1990, 5, 15),
|
||||
},
|
||||
}
|
||||
|
||||
contact = _map_contact(raw_contact)
|
||||
|
||||
assert contact.birthday == "1990-05-15"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_contact_mapping_birthday_apple_unknown_year():
|
||||
"""Test Apple/iOS unknown-year birthday convention (year 1604).
|
||||
|
||||
Apple contacts use BDAY;VALUE=DATE:16040808 when the birth year is unknown.
|
||||
pythonvCard4 parses this as datetime.date(1604, 8, 8).
|
||||
"""
|
||||
raw_contact = {
|
||||
"vcard_id": "bday-apple-1",
|
||||
"contact": {
|
||||
"fullname": "Apple Contact",
|
||||
"birthday": date(1604, 8, 8),
|
||||
},
|
||||
}
|
||||
|
||||
contact = _map_contact(raw_contact)
|
||||
|
||||
assert contact.birthday == "1604-08-08"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_contact_mapping_multiple_emails():
|
||||
"""Test that multiple emails are mapped correctly."""
|
||||
|
||||
Reference in New Issue
Block a user