4a0f0e9dc0
Completa el CRUD del grupo dav (put/get/list/get-collection/delete). HTTP DELETE con Basic auth, If-Match opcional para borrado condicional, maneja 404 como idempotente. Solo stdlib. 7 tests deterministas (monkeypatch urlopen). Probado contra Xandikos real durante la limpieza del ciclo de sync OSINT. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
3.0 KiB
Python
97 lines
3.0 KiB
Python
"""Tests para dav_delete_resource.
|
|
|
|
Smoke deterministas: monkeypatchean urllib.request.urlopen para capturar el
|
|
Request (method DELETE, auth, URL, headers If-Match) y simular respuestas.
|
|
"""
|
|
|
|
import base64
|
|
import sys
|
|
|
|
import infra.dav_delete_resource # noqa: F401
|
|
|
|
mod = sys.modules["infra.dav_delete_resource"]
|
|
dav_delete_resource = mod.dav_delete_resource
|
|
|
|
|
|
class _FakeResp:
|
|
def __init__(self, status=204):
|
|
self.status = status
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *a):
|
|
return False
|
|
|
|
|
|
def _capture(monkeypatch, status=204):
|
|
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()}
|
|
return _FakeResp(status)
|
|
|
|
monkeypatch.setattr(mod.urllib.request, "urlopen", fake_urlopen)
|
|
return captured
|
|
|
|
|
|
def test_construye_request_delete_con_auth(monkeypatch):
|
|
cap = _capture(monkeypatch)
|
|
res = dav_delete_resource(
|
|
"https://dav.example.com", "enmanuel", "secret-pw",
|
|
"/enmanuel/contacts/addressbook/ada.vcf",
|
|
)
|
|
assert res["status"] == "ok"
|
|
assert cap["method"] == "DELETE"
|
|
expected = "Basic " + base64.b64encode(b"enmanuel:secret-pw").decode("ascii")
|
|
assert cap["headers"]["authorization"] == expected
|
|
|
|
|
|
def test_resource_path_relativo_se_resuelve_con_base_url(monkeypatch):
|
|
cap = _capture(monkeypatch)
|
|
dav_delete_resource(
|
|
"https://dav.example.com", "u", "p",
|
|
"/enmanuel/contacts/addressbook/ada.vcf",
|
|
)
|
|
assert cap["url"] == "https://dav.example.com/enmanuel/contacts/addressbook/ada.vcf"
|
|
|
|
|
|
def test_resource_path_absoluto_se_respeta(monkeypatch):
|
|
cap = _capture(monkeypatch)
|
|
abs_url = "https://otra.example.com/path/x.vcf"
|
|
dav_delete_resource("https://dav.example.com", "u", "p", abs_url)
|
|
assert cap["url"] == abs_url
|
|
|
|
|
|
def test_if_match_se_envia_cuando_hay_etag(monkeypatch):
|
|
cap = _capture(monkeypatch)
|
|
dav_delete_resource(
|
|
"https://dav.example.com", "u", "p", "/x.vcf", etag='"abc123"',
|
|
)
|
|
assert cap["headers"]["if-match"] == '"abc123"'
|
|
|
|
|
|
def test_sin_etag_no_envia_if_match(monkeypatch):
|
|
cap = _capture(monkeypatch)
|
|
dav_delete_resource("https://dav.example.com", "u", "p", "/x.vcf")
|
|
assert "if-match" not in cap["headers"]
|
|
|
|
|
|
def test_204_devuelve_ok(monkeypatch):
|
|
_capture(monkeypatch, status=204)
|
|
res = dav_delete_resource("https://dav.example.com", "u", "p", "/x.vcf")
|
|
assert res["status"] == "ok"
|
|
assert res["http_status"] == 204
|
|
|
|
|
|
def test_404_devuelve_status_error(monkeypatch):
|
|
def fake_urlopen(req, timeout=None, context=None):
|
|
raise mod.urllib.error.HTTPError(req.full_url, 404, "Not Found", {}, None)
|
|
|
|
monkeypatch.setattr(mod.urllib.request, "urlopen", fake_urlopen)
|
|
res = dav_delete_resource("https://dav.example.com", "u", "p", "/x.vcf")
|
|
assert res["status"] == "error"
|
|
assert res["http_status"] == 404
|