"""Tests para dav_get_collection. Smoke deterministas: monkeypatchean urllib.request.urlopen para capturar el Request (method REPORT, Depth, auth, cuerpo del query) y devolver un XML multistatus simulado con contenido inline. Cubren ambos perfiles (vcard / ical), el des-escapado de entidades XML, los sinonimos de content_type, el content_type invalido y el path de error HTTP. """ import base64 import sys import infra.dav_get_collection # noqa: F401 mod = sys.modules["infra.dav_get_collection"] dav_get_collection = mod.dav_get_collection # Multistatus de un addressbook-query: 2 vCards inline. El segundo contiene una # entidad XML (<) que debe des-escaparse a '<' en el campo data. _VCARD_XML = ( '' '' "/enmanuel/contacts/addressbook/ada.vcf" "HTTP/1.1 200 OK" '"etag-ada"' "BEGIN:VCARD\nFN:Ada\nUID:ada\nEND:VCARD\n" "" "/enmanuel/contacts/addressbook/alan.vcf" "HTTP/1.1 200 OK" '"etag-alan"' "BEGIN:VCARD\nFN:Alan <turing>\nUID:alan\nEND:VCARD\n" "" "" ) # Multistatus de un calendar-query: 1 VEVENT inline. _ICAL_XML = ( '' '' "/enmanuel/calendars/calendar/e1.ics" "HTTP/1.1 200 OK" '"etag-e1"' "BEGIN:VCALENDAR\nBEGIN:VEVENT\nSUMMARY:Cita\nUID:e1\nEND:VEVENT\nEND:VCALENDAR\n" "" "" ) class _FakeResp: def __init__(self, payload: str): self._payload = payload self.status = 207 def __enter__(self): return self def __exit__(self, *a): return False def read(self): return self._payload.encode("utf-8") def _capture(monkeypatch, payload: str): captured = {} def fake_urlopen(req, timeout=None, context=None): captured["url"] = req.full_url captured["method"] = req.get_method() captured["headers"] = {k.lower(): v for k, v in req.header_items()} captured["body"] = req.data.decode("utf-8") if req.data else "" return _FakeResp(payload) monkeypatch.setattr(mod.urllib.request, "urlopen", fake_urlopen) return captured def test_vcard_construye_report_addressbook_query(monkeypatch): cap = _capture(monkeypatch, _VCARD_XML) dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", content_type="vcard", ) assert cap["method"] == "REPORT" assert cap["headers"]["depth"] == "1" assert "addressbook-query" in cap["body"] assert "address-data" in cap["body"] assert "urn:ietf:params:xml:ns:carddav" in cap["body"] def test_ical_construye_report_calendar_query_con_filtro(monkeypatch): cap = _capture(monkeypatch, _ICAL_XML) dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/calendars/calendar/", content_type="ical", ) assert cap["method"] == "REPORT" assert "calendar-query" in cap["body"] assert 'comp-filter name="VCALENDAR"' in cap["body"] assert "urn:ietf:params:xml:ns:caldav" in cap["body"] def test_basic_auth_header_correcto(monkeypatch): cap = _capture(monkeypatch, _VCARD_XML) dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", ) expected = "Basic " + base64.b64encode(b"enmanuel:secret-pw").decode("ascii") assert cap["headers"]["authorization"] == expected def test_parsea_resources_con_data_inline(monkeypatch): _capture(monkeypatch, _VCARD_XML) res = dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", ) assert res["status"] == "ok" assert len(res["resources"]) == 2 by_href = {r["href"]: r for r in res["resources"]} ada = by_href["/enmanuel/contacts/addressbook/ada.vcf"] assert ada["etag"] == '"etag-ada"' assert "BEGIN:VCARD" in ada["data"] assert "FN:Ada" in ada["data"] def test_desescapa_entidades_xml_del_data(monkeypatch): _capture(monkeypatch, _VCARD_XML) res = dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", ) alan = next(r for r in res["resources"] if r["href"].endswith("alan.vcf")) # <turing> debe quedar des-escapado a . assert "FN:Alan " in alan["data"] def test_ical_parsea_calendar_data(monkeypatch): _capture(monkeypatch, _ICAL_XML) res = dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/calendars/calendar/", content_type="ical", ) assert res["status"] == "ok" assert len(res["resources"]) == 1 assert "BEGIN:VEVENT" in res["resources"][0]["data"] def test_acepta_sinonimos_de_content_type(monkeypatch): _capture(monkeypatch, _VCARD_XML) for alias in ("contacts", "carddav", "addressbook"): res = dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", content_type=alias, ) assert res["status"] == "ok" def test_content_type_invalido_devuelve_error(monkeypatch): _capture(monkeypatch, _VCARD_XML) res = dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", content_type="json", ) assert res["status"] == "error" assert res["http_status"] is None def test_httperror_devuelve_status_error(monkeypatch): def fake_urlopen(req, timeout=None, context=None): raise mod.urllib.error.HTTPError(req.full_url, 401, "Unauthorized", {}, None) monkeypatch.setattr(mod.urllib.request, "urlopen", fake_urlopen) res = dav_get_collection( "https://dav.example.com", "enmanuel", "secret-pw", "/enmanuel/contacts/addressbook/", ) assert res["status"] == "error" assert res["http_status"] == 401