CalAnonSync/src/calanonsync/caldav_test.go

467 lines
11 KiB
Go

package main
import (
"encoding/xml"
"strings"
"testing"
"time"
)
func TestParseICal(t *testing.T) {
t.Run("Complex", func(t *testing.T) {
i, err := ParseICal(strings.NewReader(icsWithTZ))
if err != nil {
t.Fatalf("Parse failed: %s", err)
}
if i.Summary() != "WithTZ" {
t.Error("Summary is wrong.")
}
if i.UID() != "e726899a5ca4c9e478842669bac9688144bb95d9" {
t.Error("UID is wrong")
}
if i.Start().Format(ICAL_TIME) != "20180411T170000" {
t.Error("Start time wrong")
}
if i.End().Format(ICAL_TIME) != "20180411T210000" {
t.Error("End time wrong")
}
// No "Z" at the end --> local time zone.
if i.Start().Location() != time.Local {
t.Error("Timezone is wrong")
}
})
t.Run("Simple", func(t *testing.T) {
i, err := ParseICal(strings.NewReader(icsSimple))
if err != nil {
t.Fatalf("Parse failed: %s", err)
}
if i.Summary() != "Anon" {
t.Error("Summary is wrong.")
}
if i.Start().Format(ICAL_TIME) != "20180308T113000" {
t.Error("Start time wrong")
}
if i.End().Format(ICAL_TIME) != "20180308T130000" {
t.Error("End time wrong")
}
// "Z" at the end --> UTC.
if i.Start().Location() != time.UTC {
t.Error("Start time should be UTC")
}
})
t.Run("First event only", func(t *testing.T) {
i, err := ParseICal(strings.NewReader(icsMultiEvents))
if err != nil {
t.Fatalf("Parse failed: %s", err)
}
if i.Summary() != "First" {
t.Error("Summary is wrong.")
}
if i.UID() != "123" {
t.Error("UID is wrong.")
}
// Check that our sub slice is correct:
if i.eventData[0] != "UID:123" {
t.Error("First line is wrong")
}
if i.eventData[len(i.eventData)-1] != "LAST-MODIFIED:20180401T182238Z" {
t.Error("Last line is wrong")
}
})
t.Run("Whole day", func(t *testing.T) {
i, err := ParseICal(strings.NewReader(icsWithDateOnly))
if err != nil {
t.Fatalf("Parse failed: %s", err)
}
start := i.Start()
end := i.End()
expectedStart := time.Date(2018, 4, 7, 0, 0, 0, 0, time.Local)
expectedEnd := time.Date(2018, 4, 9, 0, 0, 0, 0, time.Local)
if !start.Equal(expectedStart) {
t.Errorf("Unexpected start time (%s != %s)", start, expectedStart)
}
if !end.Equal(expectedEnd) {
t.Errorf("Unexpected end time (%s != %s)", end, expectedEnd)
}
})
}
func TestICal_Update(t *testing.T) {
newStart := time.Date(2019, 10, 05, 10, 25, 0, 0, time.UTC)
newEnd := time.Date(2019, 10, 05, 10, 55, 30, 0, time.UTC)
t.Run("Values updated", func(t *testing.T) {
ical, _ := ParseICal(strings.NewReader(icsSimple))
ical.Update(newStart, newEnd, false)
if ical.Start() != newStart {
t.Error("Start not updated correctly")
}
if ical.End() != newEnd {
t.Error("End not updated correctly")
}
if ical.getTimeField("DTSTAMP").Sub(time.Now()) > 5*time.Second {
t.Error("Timestamp doesn't seem to be updated")
}
})
t.Run("Other values untouched", func(t *testing.T) {
// Use a ICal structure with dummy/unknown fields but also without
// DTSTAMP or LAST-MODIFIED, so we can do a string comparison against
// known values.
ical, _ := ParseICal(strings.NewReader(`BEGIN:VCALENDAR
PRODID:-//some//thing//EN
VERSION:2.0
UNKNOWN:FIELD
SOME;MORE:STUFF
BEGIN:VEVENT
UID:WHATEVERID
SUMMARY:Summary
CLASS:PUBLIC
DTSTART:20180308T113000Z
DTEND:20180308T130000Z
CREATED:20180401T182238Z
END:VEVENT
EVEN;MORE:STUFF
END:VCALENDAR`))
ical.Update(newStart, newEnd, false)
if ical.String() != `BEGIN:VCALENDAR
PRODID:-//some//thing//EN
VERSION:2.0
UNKNOWN:FIELD
SOME;MORE:STUFF
BEGIN:VEVENT
UID:WHATEVERID
SUMMARY:Summary
CLASS:PUBLIC
DTSTART:20191005T102500Z
DTEND:20191005T105530Z
CREATED:20180401T182238Z
END:VEVENT
EVEN;MORE:STUFF
END:VCALENDAR` {
t.Error("The resulting ICS seems wrong.")
}
})
t.Run("Values wholeDay", func(t *testing.T) {
newStart := time.Date(2019, 1, 2, 0, 0, 0, 0, time.Local)
newEnd := time.Date(2019, 1, 3, 0, 0, 0, 0, time.Local)
ical, _ := ParseICal(strings.NewReader(icsSimple))
ical.Update(newStart, newEnd, true)
if ical.Start() != newStart {
t.Error("Start not updated correctly")
}
if ical.End() != newEnd {
t.Error("End not updated correctly")
}
if ical.getTimeField("DTSTAMP").Sub(time.Now()) > 5*time.Second {
t.Error("Timestamp doesn't seem to be updated")
}
})
}
func TestXMLUnmarshal(t *testing.T) {
response := `<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:cal="urn:ietf:params:xml:ns:caldav" xmlns:cs="http://calendarserver.org/ns/"><d:response><d:href>/calendar/</d:href><d:propstat><d:prop><cal:calendar-data/></d:prop><d:status>HTTP/1.1 404 Not Found</d:status></d:propstat></d:response><d:response><d:href>/calendar/f10545b91c12a44ef7ed2ee3aa250df4-z2QSIWY.ics</d:href><d:propstat><d:prop><cal:calendar-data>BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//some//NONSGML Calendar//EN
X-WR-Timezone: Europe/London
BEGIN:VEVENT
DTSTART:20180404T080000Z
DTEND:20180404T081500Z
DTSTAMP:20180404T070805Z
SEQUENCE:1522825685
SUMMARY:Test
UID:f10545b91c12a44ef7ed2ee3aa250df4-z2QSIWY
END:VEVENT
END:VCALENDAR</cal:calendar-data></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat></d:response></d:multistatus>`
ms := &MultiStatus{}
err := xml.Unmarshal([]byte(response), ms)
if err != nil {
t.Fatal("Decoding should have worked")
}
if len(ms.Response) != 2 {
t.Fatal("Invalid number of results.")
}
if ms.Response[0].PropStat.Prop.CalendarData.Valid() {
t.Error("CalendarData 0 should not be valid")
}
if !ms.Response[1].PropStat.Prop.CalendarData.Valid() {
t.Error("CalendarData 1 should be valid")
}
}
const icsWithTZ = `BEGIN:VCALENDAR
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VTIMEZONE
TZID:/freeassociation.sourceforge.net/Europe/Berlin
X-LIC-LOCATION:Europe/Berlin
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19160429T230000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19160430T220000Z;BYMONTH=4;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19161007T010000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19160930T230000Z;BYMONTH=10;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19170416T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19180415T010000Z;BYMONTH=4;BYDAY=3MO
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19170917T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19180916T010000Z;BYMONTH=9;BYDAY=3MO
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19400402T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19400401T010000Z;BYMONTH=4;BYDAY=1MO
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19421105T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19421102T010000Z;BYMONTH=11;BYDAY=1MO
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19430326T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19430329T010000Z;BYMONTH=3;BYDAY=-1MO
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19431001T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19441002T010000Z;BYMONTH=10;BYDAY=1MO
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19440402T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19450402T000000Z;BYMONTH=4;BYDAY=1MO
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEMT
DTSTART:19450524T020000
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
RRULE:FREQ=YEARLY;UNTIL=19450523T230000Z;BYMONTH=5;BYDAY=-2TH
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19450924T030000
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19450924T020000Z;BYMONTH=9;BYDAY=-1MO
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19451118T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19451118T020000Z;BYMONTH=11;BYDAY=3SU
END:STANDARD
BEGIN:STANDARD
TZNAME:CET
DTSTART:19460102T000000
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19451231T220000Z;BYMONTH=1;BYDAY=1TU
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19460408T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19460414T010000Z;BYMONTH=4;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19461001T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19461007T010000Z;BYMONTH=10;BYDAY=1MO
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19470401T030000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19470406T010000Z;BYMONTH=4;BYDAY=1SU
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEMT
DTSTART:19470513T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0300
RRULE:FREQ=YEARLY;UNTIL=19470511T000000Z;BYMONTH=5;BYDAY=2SU
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19470624T030000
TZOFFSETFROM:+0300
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19470629T020000Z;BYMONTH=6;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19471007T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19491002T020000Z;BYMONTH=10;BYDAY=1SU
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19480415T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19480418T010000Z;BYMONTH=4;BYDAY=3SU
END:DAYLIGHT
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19490408T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19490410T010000Z;BYMONTH=4;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19800102T000000
TZOFFSETFROM:+0100
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19791231T220000Z;BYMONTH=1;BYDAY=1TU
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19800401T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;UNTIL=19800406T010000Z;BYMONTH=4;BYDAY=1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19800930T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;UNTIL=19950924T010000Z;BYMONTH=9;BYDAY=-1SU
END:STANDARD
BEGIN:DAYLIGHT
TZNAME:CEST
DTSTART:19810325T020000
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
DTSTART:19961028T030000
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
UID:e726899a5ca4c9e478842669bac9688144bb95d9
DTSTAMP:20180312T092638Z
DTSTART;TZID=/freeassociation.sourceforge.net/Europe/Berlin:20180411T170000
DTEND;TZID=/freeassociation.sourceforge.net/Europe/Berlin:20180411T210000
SEQUENCE:2
SUMMARY:WithTZ
TRANSP:OPAQUE
CLASS:PUBLIC
CREATED:20180313T074119Z
LAST-MODIFIED:20180313T074119Z
END:VEVENT
END:VCALENDAR`
const icsWithDateOnly = `BEGIN:VCALENDAR
PRODID:+//IDN bitfire.at//DAVdroid/1.10.1.1-gplay ical4j/2.x
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20180308T192428Z
UID:b3cf0d84-b746-420c-8642-3b107ddfd834
SUMMARY:WholeDay
DTSTART;VALUE=DATE:20180407
DTEND;VALUE=DATE:20180409
STATUS:CONFIRMED
TRANSP:TRANSPARENT
CLASS:PUBLIC
END:VEVENT
END:VCALENDAR`
const icsSimple = `BEGIN:VCALENDAR
PRODID:-//aksdb//calanonsync//EN
VERSION:2.0
BEGIN:VEVENT
UID:0A6768CB8B086C58F2942757C6E515DF
SUMMARY:Anon
CLASS:PUBLIC
DTSTART:20180308T113000Z
DTEND:20180308T130000Z
CREATED:20180401T182238Z
DTSTAMP:20180401T182238Z
LAST-MODIFIED:20180401T182238Z
END:VEVENT
END:VCALENDAR`
const icsMultiEvents = `BEGIN:VCALENDAR
PRODID:-//some//thing//EN
VERSION:2.0
BEGIN:VEVENT
UID:123
SUMMARY:First
CLASS:PUBLIC
DTSTART:20180308T113000Z
DTEND:20180308T130000Z
CREATED:20180401T182238Z
DTSTAMP:20180401T182238Z
LAST-MODIFIED:20180401T182238Z
END:VEVENT
BEGIN:VEVENT
UID:456
SUMMARY:Second
CLASS:PUBLIC
DTSTART:20180309T113000Z
DTEND:20180309T130000Z
CREATED:20180401T182238Z
DTSTAMP:20180401T182238Z
LAST-MODIFIED:20180401T182239Z
END:VEVENT
END:VCALENDAR`