caldav: implement filter function for queries
This is not yet complete (see TODOs in code), but basic filtering of a list of CaledarObjects works. Includes test data from the RFC, which allows to use the RFCs examples as test cases.
This commit is contained in:
committed by
Simon Ser
parent
06ecb0e64c
commit
13fa812f94
268
caldav/match_test.go
Normal file
268
caldav/match_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-ical"
|
||||
)
|
||||
|
||||
var dateFormat = "20060102T150405Z"
|
||||
|
||||
func toDate(t *testing.T, date string) time.Time {
|
||||
res, err := time.ParseInLocation(dateFormat, date, time.UTC)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Test data taken from https://datatracker.ietf.org/doc/html/rfc4791#appendix-B
|
||||
// TODO add missing data
|
||||
func TestFilter(t *testing.T) {
|
||||
newCO := func(str string) CalendarObject {
|
||||
cal, err := ical.NewDecoder(strings.NewReader(str)).Decode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return CalendarObject{
|
||||
Data: cal,
|
||||
}
|
||||
}
|
||||
|
||||
event1 := newCO(`BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||
BEGIN:VTIMEZONE
|
||||
LAST-MODIFIED:20040110T032845Z
|
||||
TZID:US/Eastern
|
||||
BEGIN:DAYLIGHT
|
||||
DTSTART:20000404T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||
TZNAME:EDT
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
DTSTART:20001026T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||
TZNAME:EST
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20060206T001102Z
|
||||
DTSTART;TZID=US/Eastern:20060102T100000
|
||||
DURATION:PT1H
|
||||
SUMMARY:Event #1
|
||||
Description:Go Steelers!
|
||||
UID:74855313FA803DA593CD579A@example.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR`)
|
||||
|
||||
event2 := newCO(`BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||
BEGIN:VTIMEZONE
|
||||
LAST-MODIFIED:20040110T032845Z
|
||||
TZID:US/Eastern
|
||||
BEGIN:DAYLIGHT
|
||||
DTSTART:20000404T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||
TZNAME:EDT
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
DTSTART:20001026T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||
TZNAME:EST
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20060206T001121Z
|
||||
DTSTART;TZID=US/Eastern:20060102T120000
|
||||
DURATION:PT1H
|
||||
RRULE:FREQ=DAILY;COUNT=5
|
||||
SUMMARY:Event #2
|
||||
UID:00959BC664CA650E933C892C@example.com
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
DTSTAMP:20060206T001121Z
|
||||
DTSTART;TZID=US/Eastern:20060104T140000
|
||||
DURATION:PT1H
|
||||
RECURRENCE-ID;TZID=US/Eastern:20060104T120000
|
||||
SUMMARY:Event #2 bis
|
||||
UID:00959BC664CA650E933C892C@example.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR`)
|
||||
|
||||
event3 := newCO(`BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||
BEGIN:VTIMEZONE
|
||||
LAST-MODIFIED:20040110T032845Z
|
||||
TZID:US/Eastern
|
||||
BEGIN:DAYLIGHT
|
||||
DTSTART:20000404T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
|
||||
TZNAME:EDT
|
||||
TZOFFSETFROM:-0500
|
||||
TZOFFSETTO:-0400
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
DTSTART:20001026T020000
|
||||
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||
TZNAME:EST
|
||||
TZOFFSETFROM:-0400
|
||||
TZOFFSETTO:-0500
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
BEGIN:VEVENT
|
||||
ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:cyrus@example.com
|
||||
ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:lisa@example.com
|
||||
DTSTAMP:20060206T001220Z
|
||||
DTSTART;TZID=US/Eastern:20060104T100000
|
||||
DURATION:PT1H
|
||||
LAST-MODIFIED:20060206T001330Z
|
||||
ORGANIZER:mailto:cyrus@example.com
|
||||
SEQUENCE:1
|
||||
STATUS:TENTATIVE
|
||||
SUMMARY:Event #3
|
||||
UID:DC6C50A017428C5216A2F1CD@example.com
|
||||
END:VEVENT
|
||||
END:VCALENDAR`)
|
||||
|
||||
todo1 := newCO(`BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:-//Example Corp.//CalDAV Client//EN
|
||||
BEGIN:VTODO
|
||||
DTSTAMP:20060205T235335Z
|
||||
DUE;VALUE=DATE:20060104
|
||||
STATUS:NEEDS-ACTION
|
||||
SUMMARY:Task #1
|
||||
UID:DDDEEB7915FA61233B861457@example.com
|
||||
BEGIN:VALARM
|
||||
ACTION:AUDIO
|
||||
TRIGGER;RELATED=START:-PT10M
|
||||
END:VALARM
|
||||
END:VTODO
|
||||
END:VCALENDAR`)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
query *CalendarQuery
|
||||
addrs []CalendarObject
|
||||
want []CalendarObject
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "nil-query",
|
||||
query: nil,
|
||||
addrs: []CalendarObject{event1, event2, event3, todo1},
|
||||
want: []CalendarObject{event1, event2, event3, todo1},
|
||||
},
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-7.8.8
|
||||
name: "events only",
|
||||
query: &CalendarQuery{
|
||||
CompFilter: CompFilter{
|
||||
Name: "VCALENDAR",
|
||||
Comps: []CompFilter{
|
||||
CompFilter{
|
||||
Name: "VEVENT",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
addrs: []CalendarObject{event1, event2, event3, todo1},
|
||||
want: []CalendarObject{event1, event2, event3},
|
||||
},
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-7.8.1
|
||||
name: "events in time range",
|
||||
query: &CalendarQuery{
|
||||
CompFilter: CompFilter{
|
||||
Name: "VCALENDAR",
|
||||
Comps: []CompFilter{
|
||||
CompFilter{
|
||||
Name: "VEVENT",
|
||||
Start: toDate(t, "20060104T000000Z"),
|
||||
End: toDate(t, "20060105T000000Z"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
addrs: []CalendarObject{event1, event2, event3, todo1},
|
||||
want: []CalendarObject{event2, event3},
|
||||
},
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-7.8.6
|
||||
name: "events by UID",
|
||||
query: &CalendarQuery{
|
||||
CompFilter: CompFilter{
|
||||
Name: "VCALENDAR",
|
||||
Comps: []CompFilter{
|
||||
CompFilter{
|
||||
Name: "VEVENT",
|
||||
Props: []PropFilter{{
|
||||
Name: "UID",
|
||||
TextMatch: &TextMatch{
|
||||
Text: "DC6C50A017428C5216A2F1CD@example.com",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
addrs: []CalendarObject{event1, event2, event3, todo1},
|
||||
want: []CalendarObject{event3},
|
||||
},
|
||||
{
|
||||
// https://datatracker.ietf.org/doc/html/rfc4791#section-7.8.6
|
||||
name: "events by description substring",
|
||||
query: &CalendarQuery{
|
||||
CompFilter: CompFilter{
|
||||
Name: "VCALENDAR",
|
||||
Comps: []CompFilter{
|
||||
CompFilter{
|
||||
Name: "VEVENT",
|
||||
Props: []PropFilter{{
|
||||
Name: "Description",
|
||||
TextMatch: &TextMatch{
|
||||
Text: "Steelers",
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
addrs: []CalendarObject{event1, event2, event3, todo1},
|
||||
want: []CalendarObject{event1},
|
||||
},
|
||||
// TODO add more examples
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := Filter(tc.query, tc.addrs)
|
||||
switch {
|
||||
case err != nil && tc.err == nil:
|
||||
t.Fatalf("unexpected error: %+v", err)
|
||||
case err != nil && tc.err != nil:
|
||||
if got, want := err.Error(), tc.err.Error(); got != want {
|
||||
t.Fatalf("invalid error:\ngot= %q\nwant=%q", got, want)
|
||||
}
|
||||
case err == nil && tc.err != nil:
|
||||
t.Fatalf("expected an error:\ngot= %+v\nwant=%+v", err, tc.err)
|
||||
case err == nil && tc.err == nil:
|
||||
if got, want := got, tc.want; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("invalid filter values:\ngot= %+v\nwant=%+v", got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user