// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package caldavtests import ( "encoding/xml" "net/http/httptest" "strings" "testing" ics "github.com/arran4/golang-ical" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Multistatus represents a WebDAV multistatus response (RFC 4918 ยง13) type Multistatus struct { XMLName xml.Name `xml:"DAV: multistatus"` Responses []Response `xml:"response"` } // Response represents a single response within a multistatus type Response struct { Href string `xml:"href"` Propstat []Propstat `xml:"propstat"` } // Propstat groups a set of properties with a status type Propstat struct { Prop Prop `xml:"prop"` Status string `xml:"status"` } // Prop holds the actual property values returned by PROPFIND/REPORT. type Prop struct { // Standard DAV properties DisplayName string `xml:"displayname,omitempty"` ResourceType RawXML `xml:"resourcetype,omitempty"` GetETag string `xml:"getetag,omitempty"` GetCTag string `xml:"http://calendarserver.org/ns/ getctag,omitempty"` // CalDAV properties CalendarData string `xml:"urn:ietf:params:xml:ns:caldav calendar-data,omitempty"` CalendarHomeSet RawXML `xml:"urn:ietf:params:xml:ns:caldav calendar-home-set,omitempty"` SupportedComponents RawXML `xml:"urn:ietf:params:xml:ns:caldav supported-calendar-component-set,omitempty"` CalendarDescription string `xml:"urn:ietf:params:xml:ns:caldav calendar-description,omitempty"` // Principal properties CurrentUserPrincipal RawXML `xml:"current-user-principal,omitempty"` // ACL properties CurrentUserPrivilegeSet RawXML `xml:"current-user-privilege-set,omitempty"` // Catch-all for unexpected properties InnerXML string `xml:",innerxml"` } // RawXML captures raw XML content for properties we want to inspect flexibly type RawXML struct { InnerXML string `xml:",innerxml"` } // parseMultistatus parses a WebDAV multistatus XML response body. func parseMultistatus(t *testing.T, rec *httptest.ResponseRecorder) Multistatus { t.Helper() var ms Multistatus err := xml.Unmarshal(rec.Body.Bytes(), &ms) require.NoError(t, err, "Failed to parse multistatus XML. Body:\n%s", rec.Body.String()) return ms } // getSuccessfulProp returns the Prop from the first propstat with a 200 status. func getSuccessfulProp(t *testing.T, r Response) Prop { t.Helper() for _, ps := range r.Propstat { if strings.Contains(ps.Status, "200") { return ps.Prop } } t.Fatalf("No successful (200) propstat found in response for href %s", r.Href) return Prop{} // unreachable } // parseICalFromResponse parses iCalendar data from a response body. func parseICalFromResponse(t *testing.T, rec *httptest.ResponseRecorder) *ics.Calendar { t.Helper() cal, err := ics.ParseCalendar(strings.NewReader(rec.Body.String())) require.NoError(t, err, "Failed to parse iCalendar. Body:\n%s", rec.Body.String()) return cal } // parseICalFromString parses iCalendar data from a string (e.g., calendar-data property). func parseICalFromString(t *testing.T, data string) *ics.Calendar { t.Helper() cal, err := ics.ParseCalendar(strings.NewReader(data)) require.NoError(t, err, "Failed to parse iCalendar data:\n%s", data) return cal } // getVTodo extracts the first VTODO component from a calendar. func getVTodo(t *testing.T, cal *ics.Calendar) *ics.VTodo { t.Helper() for _, comp := range cal.Components { if vtodo, ok := comp.(*ics.VTodo); ok { return vtodo } } t.Fatal("No VTODO component found in calendar") return nil // unreachable } // getVTodoProperty extracts a property value from a VTODO. func getVTodoProperty(vtodo *ics.VTodo, prop ics.ComponentProperty) string { p := vtodo.GetProperty(prop) if p == nil { return "" } return p.Value } // assertResponseStatus asserts the HTTP status code. func assertResponseStatus(t *testing.T, rec *httptest.ResponseRecorder, expectedStatus int) { //nolint:unparam t.Helper() assert.Equal(t, expectedStatus, rec.Code, "Response body:\n%s", rec.Body.String()) }