feat(calendar): Kalender anlegen, umbenennen, loeschen -- vom Client aus
Bisher konnte ein CalDAV-Client Termine verwalten, aber keine Kalender:
MKCALENDAR -> 405 anlegen
PROPPATCH -> 501 umbenennen, umfaerben
DELETE -> 404 loeschen
go-webdav deckt davon nur MKCOL ab, und selbst da verwarf unser Backend
die vom Client gewaehlte Adresse. Alle drei Verben faengt der Handler
jetzt vorher ab (collections.go).
Der Kern steckt aber nicht in den Verben, sondern in der Adresse: Bei
MKCALENDAR sucht *der Client* aus, wo der Kalender liegen soll
("…/calendars/max/arbeit/"). Unsere Kalender lagen bisher zwingend unter
ihrer UUID -- der Client haette angelegt und auf seine eigene Adresse
sofort ein 404 bekommen.
Deshalb Migration 23 mit einer Spalte path_segment: gesetzt = die
gewaehlte Adresse, NULL = es gilt weiterhin die UUID. Bestehende
Kalender bleiben damit unter genau der Adresse erreichbar, unter der die
eingerichteten Geraete sie kennen -- ein Wechsel haette jedes Telefon
dazu gebracht, seine Termine fuer geloescht zu halten. Aufgeloest wird
beides in einer Abfrage (GetCalendarByRef), die gewaehlte Adresse hat
Vorrang.
PROPPATCH antwortet mit einem Status je Eigenschaft, auch wenn alles
geklappt hat: Es kann teilweise gelingen, und ein nacktes 200
verschwiege, welcher Teil angekommen ist. Was wir nicht aendern koennen,
bekommt 403 statt stillem Schlucken -- ein Client, der seine Aenderung
fuer angekommen haelt, ist schlimmer dran als einer mit klarer Absage.
Ebenso eine unlesbare Farbe: absagen statt raten.
DELETE auf eine Sammlung loescht den Kalender mitsamt allen Terminen --
das ist in WebDAV seine Bedeutung.
CalDAV-Selbsttest von 45 auf 62 Pruefungen: anlegen, unter der
gewaehlten Adresse erreichbar, Farbe aus dem MKCALENDAR uebernommen,
Termin darin ablegen und abrufen, zweites Anlegen auf dieselbe Adresse
405, umbenennen und umfaerben mit Status je Eigenschaft, loeschen, danach
404 -- und dasselbe noch einmal ueber MKCOL, das Verb, das DAVx5 nimmt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
820ffdde6a
commit
f71ab710e1
13 files changed
+551
-53
No files matched your search
@@ -345,6 +345,79 @@ try:
|
||||
st, _, _ = dav("GET", alt)
|
||||
check("unter der alten Adresse weg (404)", st == 404, st)
|
||||
|
||||
print("\n── Kalender verwalten (vom Client aus) ──")
|
||||
EIGEN = f"/caldav/calendars/{USER}/selbsttest-arbeit/"
|
||||
mk = ('<?xml version="1.0" encoding="utf-8"?>'
|
||||
'<c:mkcalendar xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav" '
|
||||
'xmlns:i="http://apple.com/ns/ical/"><d:set><d:prop>'
|
||||
'<d:displayname>Arbeit</d:displayname>'
|
||||
'<i:calendar-color>#FF6D00FF</i:calendar-color>'
|
||||
'</d:prop></d:set></c:mkcalendar>')
|
||||
st, _, _ = dav("MKCALENDAR", EIGEN, mk, {"Content-Type": "application/xml"})
|
||||
check("MKCALENDAR legt an (201)", st == 201, st)
|
||||
|
||||
# Der Kern der Sache: Der Client sucht die Adresse aus, und unter genau
|
||||
# dieser Adresse muss der Kalender danach auch erreichbar sein.
|
||||
st, _, x = propfind(EIGEN, "<d:displayname/><i:calendar-color/>")
|
||||
check("unter der gewählten Adresse erreichbar", st == 207 and "Arbeit" in x, st)
|
||||
check("Farbe aus dem MKCALENDAR übernommen",
|
||||
wert(x, "calendar-color") == "#FF6D00FF", wert(x, "calendar-color"))
|
||||
|
||||
_, cals_jetzt = api("GET", "/api/v1/calendars")
|
||||
eigen = [c for c in cals_jetzt or [] if c["name"] == "Arbeit"]
|
||||
check("taucht in der Weboberfläche auf", len(eigen) == 1, [c["name"] for c in cals_jetzt or []])
|
||||
|
||||
st, _, _ = dav("PUT", EIGEN + "mk1.ics",
|
||||
ICS.format(uid="mk1", start="20261201T090000Z",
|
||||
end="20261201T100000Z", summary="Im eigenen Kalender"),
|
||||
{"Content-Type": "text/calendar"})
|
||||
check("Termin darin anlegen", st == 201, st)
|
||||
st, _, body = dav("GET", EIGEN + "mk1.ics")
|
||||
check("und wieder abrufen", st == 200 and "Im eigenen Kalender" in body, st)
|
||||
|
||||
st, _, _ = dav("MKCALENDAR", EIGEN, mk, {"Content-Type": "application/xml"})
|
||||
check("zweites MKCALENDAR auf dieselbe Adresse: 405", st == 405, st)
|
||||
|
||||
pp = ('<?xml version="1.0" encoding="utf-8"?>'
|
||||
'<d:propertyupdate xmlns:d="DAV:" xmlns:i="http://apple.com/ns/ical/">'
|
||||
'<d:set><d:prop><d:displayname>Arbeit neu</d:displayname>'
|
||||
'<i:calendar-color>#34A853FF</i:calendar-color></d:prop></d:set>'
|
||||
'<d:set><d:prop><d:getetag>x</d:getetag></d:prop></d:set>'
|
||||
'</d:propertyupdate>')
|
||||
st, _, x = dav("PROPPATCH", EIGEN, pp, {"Content-Type": "application/xml"})
|
||||
check("PROPPATCH antwortet mit 207", st == 207, st)
|
||||
# PROPPATCH kann teilweise gelingen. Deshalb ein Status je Eigenschaft –
|
||||
# ein nacktes 200 verschwiege, welcher Teil angekommen ist.
|
||||
check("Name und Farbe mit 200",
|
||||
prop_status(x, "displayname") == 200 and prop_status(x, "calendar-color") == 200, x)
|
||||
check("unveränderliche Eigenschaft mit 403", prop_status(x, "getetag") == 403, x)
|
||||
|
||||
_, _, x = propfind(EIGEN, "<d:displayname/><i:calendar-color/>")
|
||||
check("Umbenennen ist angekommen", "Arbeit neu" in x, x[:200])
|
||||
check("Umfärben ist angekommen", wert(x, "calendar-color") == "#34A853FF",
|
||||
wert(x, "calendar-color"))
|
||||
|
||||
st, _, _ = dav("DELETE", EIGEN)
|
||||
check("DELETE löscht den Kalender (204)", st == 204, st)
|
||||
st, _, _ = propfind(EIGEN, "<d:displayname/>")
|
||||
check("danach 404", st == 404, st)
|
||||
_, cals_jetzt = api("GET", "/api/v1/calendars")
|
||||
check("auch in der Weboberfläche weg",
|
||||
all(c["name"] != "Arbeit neu" for c in cals_jetzt or []),
|
||||
[c["name"] for c in cals_jetzt or []])
|
||||
|
||||
# DAVx5 nimmt nicht MKCALENDAR, sondern den erweiterten MKCOL.
|
||||
MKCOL_PFAD = f"/caldav/calendars/{USER}/selbsttest-mkcol/"
|
||||
mkcol = ('<?xml version="1.0" encoding="utf-8"?>'
|
||||
'<d:mkcol xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">'
|
||||
'<d:set><d:prop><d:resourcetype><d:collection/><c:calendar/></d:resourcetype>'
|
||||
'<d:displayname>Per MKCOL</d:displayname></d:prop></d:set></d:mkcol>')
|
||||
st, _, _ = dav("MKCOL", MKCOL_PFAD, mkcol, {"Content-Type": "application/xml"})
|
||||
check("MKCOL legt ebenfalls an (201)", st == 201, st)
|
||||
st, _, x = propfind(MKCOL_PFAD, "<d:displayname/>")
|
||||
check("auch dieser unter seiner Adresse erreichbar", st == 207 and "Per MKCOL" in x, st)
|
||||
dav("DELETE", MKCOL_PFAD)
|
||||
|
||||
print("\n── Fremde Kalender ──")
|
||||
st, _, _ = dav("PROPFIND", "/caldav/calendars/gibtsnicht/",
|
||||
headers={"Depth": "0", "Content-Type": "application/xml"})
|
||||
|
||||
Reference in new issue
Block a user