package caldav import ( "context" "fmt" "io" "io/ioutil" "net/http/httptest" "strings" "testing" "time" "github.com/emersion/go-ical" ) var propFindSupportedCalendarComponentRequest = ` ` var testPropFindSupportedCalendarComponentCases = map[*Calendar][]string{ &Calendar{Path: "/user/calendars/cal"}: []string{"VEVENT"}, &Calendar{Path: "/user/calendars/cal", SupportedComponentSet: []string{"VTODO"}}: []string{"VTODO"}, &Calendar{Path: "/user/calendars/cal", SupportedComponentSet: []string{"VEVENT", "VTODO"}}: []string{"VEVENT", "VTODO"}, } func TestPropFindSupportedCalendarComponent(t *testing.T) { for calendar, expected := range testPropFindSupportedCalendarComponentCases { req := httptest.NewRequest("PROPFIND", calendar.Path, nil) req.Body = io.NopCloser(strings.NewReader(propFindSupportedCalendarComponentRequest)) req.Header.Set("Content-Type", "application/xml") w := httptest.NewRecorder() handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}} handler.ServeHTTP(w, req) res := w.Result() defer res.Body.Close() data, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } resp := string(data) for _, comp := range expected { // Would be nicer to do a proper XML-decoding here, but this is probably good enough for now. if !strings.Contains(resp, comp) { t.Errorf("Expected component: %v not found in response:\n%v", comp, resp) } } } } var propFindUserPrincipal = ` ` func TestPropFindRoot(t *testing.T) { req := httptest.NewRequest("PROPFIND", "/", strings.NewReader(propFindUserPrincipal)) req.Header.Set("Content-Type", "application/xml") w := httptest.NewRecorder() calendar := &Calendar{} handler := Handler{Backend: testBackend{calendars: []Calendar{*calendar}}} handler.ServeHTTP(w, req) res := w.Result() defer res.Body.Close() data, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } resp := string(data) if !strings.Contains(resp, `/user/`) { t.Errorf("No user-principal returned when doing a PROPFIND against root, response:\n%s", resp) } } var reportCalendarData = ` %s ` func TestMultiCalendarBackend(t *testing.T) { calendarB := Calendar{Path: "/user/calendars/b", SupportedComponentSet: []string{"VTODO"}} calendars := []Calendar{ Calendar{Path: "/user/calendars/a"}, calendarB, } eventSummary := "This is a todo" event := ical.NewEvent() event.Name = ical.CompToDo event.Props.SetText(ical.PropUID, "46bbf47a-1861-41a3-ae06-8d8268c6d41e") event.Props.SetDateTime(ical.PropDateTimeStamp, time.Now()) event.Props.SetText(ical.PropSummary, eventSummary) cal := ical.NewCalendar() cal.Props.SetText(ical.PropVersion, "2.0") cal.Props.SetText(ical.PropProductID, "-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN") cal.Children = []*ical.Component{ event.Component, } object := CalendarObject{ Path: "/user/calendars/b/test.ics", Data: cal, } req := httptest.NewRequest("PROPFIND", "/user/calendars/", strings.NewReader(propFindUserPrincipal)) req.Header.Set("Content-Type", "application/xml") w := httptest.NewRecorder() handler := Handler{Backend: testBackend{ calendars: calendars, objectMap: map[string][]CalendarObject{ calendarB.Path: []CalendarObject{object}, }, }} handler.ServeHTTP(w, req) res := w.Result() defer res.Body.Close() data, err := ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } resp := string(data) for _, calendar := range calendars { if !strings.Contains(resp, fmt.Sprintf(`%s`, calendar.Path)) { t.Errorf("Calendar: %v not returned in PROPFIND, response:\n%s", calendar, resp) } } // Now do a PROPFIND for the last calendar req = httptest.NewRequest("PROPFIND", calendarB.Path, strings.NewReader(propFindSupportedCalendarComponentRequest)) req.Header.Set("Content-Type", "application/xml") w = httptest.NewRecorder() handler.ServeHTTP(w, req) res = w.Result() defer res.Body.Close() data, err = ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } resp = string(data) if !strings.Contains(resp, "VTODO") { t.Errorf("Expected component: VTODO not found in response:\n%v", resp) } if !strings.Contains(resp, object.Path) { t.Errorf("Expected calendar object: %v not found in response:\n%v", object, resp) } // Now do a REPORT to get the actual data for the event req = httptest.NewRequest("REPORT", calendarB.Path, strings.NewReader(fmt.Sprintf(reportCalendarData, object.Path))) req.Header.Set("Content-Type", "application/xml") w = httptest.NewRecorder() handler.ServeHTTP(w, req) res = w.Result() defer res.Body.Close() data, err = ioutil.ReadAll(res.Body) if err != nil { t.Error(err) } resp = string(data) if !strings.Contains(resp, fmt.Sprintf("SUMMARY:%s", eventSummary)) { t.Errorf("ICAL content not properly returned in response:\n%v", resp) } } type testBackend struct { calendars []Calendar objectMap map[string][]CalendarObject } func (t testBackend) ListCalendars(ctx context.Context) ([]Calendar, error) { return t.calendars, nil } func (t testBackend) GetCalendar(ctx context.Context, path string) (*Calendar, error) { for _, cal := range t.calendars { if cal.Path == path { return &cal, nil } } return nil, fmt.Errorf("Calendar for path: %s not found", path) } func (t testBackend) CalendarHomeSetPath(ctx context.Context) (string, error) { return "/user/calendars/", nil } func (t testBackend) CurrentUserPrincipal(ctx context.Context) (string, error) { return "/user/", nil } func (t testBackend) DeleteCalendarObject(ctx context.Context, path string) error { return nil } func (t testBackend) GetCalendarObject(ctx context.Context, path string, req *CalendarCompRequest) (*CalendarObject, error) { for _, objs := range t.objectMap { for _, obj := range objs { if obj.Path == path { return &obj, nil } } } return nil, fmt.Errorf("Couldn't find calendar object at: %s", path) } func (t testBackend) PutCalendarObject(ctx context.Context, path string, calendar *ical.Calendar, opts *PutCalendarObjectOptions) (string, error) { return "", nil } func (t testBackend) ListCalendarObjects(ctx context.Context, path string, req *CalendarCompRequest) ([]CalendarObject, error) { return t.objectMap[path], nil } func (t testBackend) QueryCalendarObjects(ctx context.Context, query *CalendarQuery) ([]CalendarObject, error) { return nil, nil }