mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-05-08 21:08:04 -05:00
test(caldav): add VTODO field round-trip tests (RFC 5545 §3.6.2)
Adds comprehensive round-trip tests for VTODO properties: - Core fields: SUMMARY, DESCRIPTION, DUE, DTSTART, PRIORITY, COMPLETED, STATUS, CATEGORIES - VALARM: absolute, relative-to-start, relative-to-end triggers - COLOR via X-APPLE-CALENDAR-COLOR - RRULE: DAILY, WEEKLY, MONTHLY frequencies - Priority mapping: all 9 CalDAV levels including lossy mappings - DURATION and DTSTART+DUE interaction
This commit is contained in:
395
pkg/caldavtests/vtodo_roundtrip_test.go
Normal file
395
pkg/caldavtests/vtodo_roundtrip_test.go
Normal file
@@ -0,0 +1,395 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldavtests
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ics "github.com/arran4/golang-ical"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestVTodoRoundTrip(t *testing.T) {
|
||||
// Helper: PUT a VTODO, GET it back, parse the VTODO
|
||||
putAndGet := func(t *testing.T, uid, path string, vtodoBody string) *ics.VTodo {
|
||||
t.Helper()
|
||||
e := setupTestEnv(t)
|
||||
|
||||
rec := caldavPUT(t, e, path, vtodoBody)
|
||||
require.True(t, rec.Code >= 200 && rec.Code < 300,
|
||||
"PUT failed with status %d. Body:\n%s", rec.Code, rec.Body.String())
|
||||
|
||||
rec2 := caldavGET(t, e, path)
|
||||
require.Equal(t, 200, rec2.Code, "GET failed. Body:\n%s", rec2.Body.String())
|
||||
|
||||
cal := parseICalFromResponse(t, rec2)
|
||||
return getVTodo(t, cal)
|
||||
}
|
||||
|
||||
t.Run("SUMMARY round-trips", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.1.12 (rfc5545.txt line 5179)
|
||||
vtodo := NewVTodo("rt-summary", "My Task Summary").Build()
|
||||
result := putAndGet(t, "rt-summary", "/dav/projects/36/rt-summary.ics", vtodo)
|
||||
assert.Equal(t, "My Task Summary", getVTodoProperty(result, ics.ComponentPropertySummary))
|
||||
})
|
||||
|
||||
t.Run("DESCRIPTION round-trips", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.1.5 (rfc5545.txt line 4688)
|
||||
vtodo := NewVTodo("rt-desc", "Desc Test").
|
||||
Description("This is a detailed description").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-desc", "/dav/projects/36/rt-desc.ics", vtodo)
|
||||
desc := getVTodoProperty(result, ics.ComponentPropertyDescription)
|
||||
assert.Contains(t, desc, "This is a detailed description")
|
||||
})
|
||||
|
||||
t.Run("DESCRIPTION with newlines round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-desc-nl", "Desc Newline Test").
|
||||
Description("Line 1\\nLine 2\\nLine 3").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-desc-nl", "/dav/projects/36/rt-desc-nl.ics", vtodo)
|
||||
desc := getVTodoProperty(result, ics.ComponentPropertyDescription)
|
||||
// Should preserve the newline structure
|
||||
assert.True(t, strings.Contains(desc, "Line 1") && strings.Contains(desc, "Line 2"),
|
||||
"Description should preserve multi-line content. Got: %s", desc)
|
||||
})
|
||||
|
||||
t.Run("DUE round-trips", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.2.3 (rfc5545.txt line 5353)
|
||||
due := time.Date(2024, 6, 15, 14, 30, 0, 0, time.UTC)
|
||||
vtodo := NewVTodo("rt-due", "Due Test").Due(due).Build()
|
||||
result := putAndGet(t, "rt-due", "/dav/projects/36/rt-due.ics", vtodo)
|
||||
|
||||
dueStr := getVTodoProperty(result, ics.ComponentPropertyDue)
|
||||
assert.Contains(t, dueStr, "20240615",
|
||||
"DUE date should be preserved. Got: %s", dueStr)
|
||||
})
|
||||
|
||||
t.Run("DTSTART round-trips", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.2.4 (rfc5545.txt line 5415)
|
||||
start := time.Date(2024, 5, 1, 9, 0, 0, 0, time.UTC)
|
||||
vtodo := NewVTodo("rt-dtstart", "Start Test").DtStart(start).Build()
|
||||
result := putAndGet(t, "rt-dtstart", "/dav/projects/36/rt-dtstart.ics", vtodo)
|
||||
|
||||
startStr := getVTodoProperty(result, ics.ComponentPropertyDtStart)
|
||||
assert.Contains(t, startStr, "20240501",
|
||||
"DTSTART should be preserved. Got: %s", startStr)
|
||||
})
|
||||
|
||||
t.Run("PRIORITY round-trips", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.1.9 (rfc5545.txt line 4956)
|
||||
// CalDAV priority 1 = Vikunja priority 5 (highest)
|
||||
// The round-trip may map through Vikunja's priority system
|
||||
// Vikunja maps: CalDAV 1→Vikunja 5→CalDAV 1
|
||||
vtodo := NewVTodo("rt-priority-1", "Priority 1 Test").Priority(1).Build()
|
||||
result := putAndGet(t, "rt-priority-1", "/dav/projects/36/rt-priority-1.ics", vtodo)
|
||||
assert.Equal(t, "1", getVTodoProperty(result, ics.ComponentPropertyPriority),
|
||||
"Priority 1 (highest) should round-trip")
|
||||
})
|
||||
|
||||
t.Run("PRIORITY 5 round-trips as 5", func(t *testing.T) {
|
||||
// CalDAV 5 = Vikunja 2 (medium) → CalDAV 5
|
||||
vtodo := NewVTodo("rt-priority-5", "Priority 5 Test").Priority(5).Build()
|
||||
result := putAndGet(t, "rt-priority-5", "/dav/projects/36/rt-priority-5.ics", vtodo)
|
||||
assert.Equal(t, "5", getVTodoProperty(result, ics.ComponentPropertyPriority),
|
||||
"Priority 5 (medium) should round-trip")
|
||||
})
|
||||
|
||||
t.Run("PRIORITY 9 round-trips as 9", func(t *testing.T) {
|
||||
// CalDAV 9 = Vikunja 1 (low) → CalDAV 9
|
||||
vtodo := NewVTodo("rt-priority-9", "Priority 9 Test").Priority(9).Build()
|
||||
result := putAndGet(t, "rt-priority-9", "/dav/projects/36/rt-priority-9.ics", vtodo)
|
||||
assert.Equal(t, "9", getVTodoProperty(result, ics.ComponentPropertyPriority),
|
||||
"Priority 9 (lowest) should round-trip")
|
||||
})
|
||||
|
||||
t.Run("PRIORITY 0 (unset) round-trips", func(t *testing.T) {
|
||||
// Priority 0 means unset — should not appear in output
|
||||
vtodo := NewVTodo("rt-priority-0", "No Priority Test").Build()
|
||||
result := putAndGet(t, "rt-priority-0", "/dav/projects/36/rt-priority-0.ics", vtodo)
|
||||
priorityStr := getVTodoProperty(result, ics.ComponentPropertyPriority)
|
||||
assert.True(t, priorityStr == "" || priorityStr == "0",
|
||||
"Priority 0 (unset) should round-trip as empty or 0. Got: %s", priorityStr)
|
||||
})
|
||||
|
||||
t.Run("COMPLETED and STATUS:COMPLETED round-trip", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.2.1 (rfc5545.txt line 5240)
|
||||
completed := time.Date(2024, 3, 15, 10, 0, 0, 0, time.UTC)
|
||||
vtodo := NewVTodo("rt-completed", "Completed Test").
|
||||
Completed(completed).
|
||||
Status("COMPLETED").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-completed", "/dav/projects/36/rt-completed.ics", vtodo)
|
||||
|
||||
compStr := getVTodoProperty(result, ics.ComponentPropertyCompleted)
|
||||
assert.Contains(t, compStr, "20240315",
|
||||
"COMPLETED date should be preserved. Got: %s", compStr)
|
||||
|
||||
statusStr := getVTodoProperty(result, ics.ComponentPropertyStatus)
|
||||
assert.Equal(t, "COMPLETED", statusStr,
|
||||
"STATUS should be COMPLETED when task is done")
|
||||
})
|
||||
|
||||
t.Run("CATEGORIES round-trip", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.1.2 (rfc5545.txt line 4520)
|
||||
vtodo := NewVTodo("rt-categories", "Categories Test").
|
||||
Categories("work", "urgent", "bug").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-categories", "/dav/projects/36/rt-categories.ics", vtodo)
|
||||
|
||||
catProp := result.GetProperty(ics.ComponentPropertyCategories)
|
||||
require.NotNil(t, catProp, "CATEGORIES property should be present")
|
||||
|
||||
catStr := catProp.Value
|
||||
assert.Contains(t, catStr, "work", "Should contain 'work' category")
|
||||
assert.Contains(t, catStr, "urgent", "Should contain 'urgent' category")
|
||||
assert.Contains(t, catStr, "bug", "Should contain 'bug' category")
|
||||
})
|
||||
|
||||
t.Run("Single CATEGORY round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-cat-single", "Single Category").
|
||||
Categories("solo-label").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-cat-single", "/dav/projects/36/rt-cat-single.ics", vtodo)
|
||||
|
||||
catProp := result.GetProperty(ics.ComponentPropertyCategories)
|
||||
require.NotNil(t, catProp, "CATEGORIES property should be present")
|
||||
assert.Contains(t, catProp.Value, "solo-label")
|
||||
})
|
||||
|
||||
t.Run("VALARM with absolute trigger round-trips", func(t *testing.T) {
|
||||
// RFC 5545 §3.8.6 (rfc5545.txt line 7352)
|
||||
alarmTime := time.Date(2024, 6, 15, 8, 0, 0, 0, time.UTC)
|
||||
vtodo := NewVTodo("rt-alarm-abs", "Alarm Absolute Test").
|
||||
Due(time.Date(2024, 6, 15, 12, 0, 0, 0, time.UTC)).
|
||||
AlarmAbsolute(alarmTime).
|
||||
Build()
|
||||
result := putAndGet(t, "rt-alarm-abs", "/dav/projects/36/rt-alarm-abs.ics", vtodo)
|
||||
|
||||
// Check that the VTODO contains a VALARM
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
assert.Contains(t, body, "BEGIN:VALARM", "Should contain a VALARM component")
|
||||
assert.Contains(t, body, "TRIGGER", "VALARM should have a TRIGGER")
|
||||
})
|
||||
|
||||
t.Run("VALARM with relative-to-start trigger round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-alarm-rel-start", "Alarm Relative Start").
|
||||
DtStart(time.Date(2024, 6, 1, 9, 0, 0, 0, time.UTC)).
|
||||
AlarmRelativeStart("-PT15M").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-alarm-rel-start", "/dav/projects/36/rt-alarm-rel-start.ics", vtodo)
|
||||
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
assert.Contains(t, body, "BEGIN:VALARM", "Should contain a VALARM component")
|
||||
assert.Contains(t, body, "TRIGGER", "VALARM should have a TRIGGER")
|
||||
})
|
||||
|
||||
t.Run("VALARM with relative-to-end trigger round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-alarm-rel-end", "Alarm Relative End").
|
||||
Due(time.Date(2024, 6, 15, 12, 0, 0, 0, time.UTC)).
|
||||
AlarmRelativeEnd("-PT30M").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-alarm-rel-end", "/dav/projects/36/rt-alarm-rel-end.ics", vtodo)
|
||||
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
assert.Contains(t, body, "BEGIN:VALARM", "Should contain a VALARM component")
|
||||
})
|
||||
|
||||
t.Run("COLOR via X-APPLE-CALENDAR-COLOR round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-color", "Color Test").
|
||||
Color("#ff0000FF").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-color", "/dav/projects/36/rt-color.ics", vtodo)
|
||||
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
// Vikunja should preserve the color in at least one of the color properties
|
||||
colorFound := strings.Contains(body, "ff0000") ||
|
||||
strings.Contains(body, "FF0000") ||
|
||||
strings.Contains(body, "#ff0000")
|
||||
assert.True(t, colorFound,
|
||||
"Color should be preserved in some form. Got:\n%s", body)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVTodoRRuleRoundTrip(t *testing.T) {
|
||||
// RFC 5545 §3.8.5.3 (rfc5545.txt line 6794)
|
||||
|
||||
putAndGet := func(t *testing.T, uid, path string, vtodoBody string) *ics.VTodo {
|
||||
t.Helper()
|
||||
e := setupTestEnv(t)
|
||||
rec := caldavPUT(t, e, path, vtodoBody)
|
||||
require.True(t, rec.Code >= 200 && rec.Code < 300,
|
||||
"PUT failed with %d", rec.Code)
|
||||
rec2 := caldavGET(t, e, path)
|
||||
require.Equal(t, 200, rec2.Code)
|
||||
cal := parseICalFromResponse(t, rec2)
|
||||
return getVTodo(t, cal)
|
||||
}
|
||||
|
||||
t.Run("RRULE FREQ=DAILY round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-rrule-daily", "Daily Repeat").
|
||||
Due(time.Date(2024, 6, 1, 9, 0, 0, 0, time.UTC)).
|
||||
Rrule("FREQ=DAILY;INTERVAL=1").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-rrule-daily", "/dav/projects/36/rt-rrule-daily.ics", vtodo)
|
||||
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
assert.Contains(t, body, "RRULE", "Should contain RRULE")
|
||||
assert.Contains(t, body, "DAILY", "Should contain DAILY frequency")
|
||||
})
|
||||
|
||||
t.Run("RRULE FREQ=WEEKLY round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-rrule-weekly", "Weekly Repeat").
|
||||
Due(time.Date(2024, 6, 1, 9, 0, 0, 0, time.UTC)).
|
||||
Rrule("FREQ=WEEKLY;INTERVAL=2").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-rrule-weekly", "/dav/projects/36/rt-rrule-weekly.ics", vtodo)
|
||||
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
assert.Contains(t, body, "RRULE", "Should contain RRULE")
|
||||
assert.Contains(t, body, "WEEKLY", "Should contain WEEKLY frequency")
|
||||
})
|
||||
|
||||
t.Run("RRULE FREQ=MONTHLY round-trips", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-rrule-monthly", "Monthly Repeat").
|
||||
Due(time.Date(2024, 6, 15, 9, 0, 0, 0, time.UTC)).
|
||||
Rrule("FREQ=MONTHLY;BYMONTHDAY=15").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-rrule-monthly", "/dav/projects/36/rt-rrule-monthly.ics", vtodo)
|
||||
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
assert.Contains(t, body, "RRULE", "Should contain RRULE")
|
||||
assert.Contains(t, body, "MONTHLY", "Should contain MONTHLY frequency")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVTodoPriorityMapping(t *testing.T) {
|
||||
// RFC 5545 §3.8.1.9 (rfc5545.txt line 4956):
|
||||
// "A value of 0 specifies an undefined priority. A value of 1
|
||||
// is the highest priority. A value of 9 is the lowest priority."
|
||||
//
|
||||
// Vikunja mapping (pkg/caldav/priority.go):
|
||||
// CalDAV 1 → Vikunja 5 → CalDAV 1 (DO NOW)
|
||||
// CalDAV 2 → Vikunja 4 → CalDAV 2 (Urgent)
|
||||
// CalDAV 3 → Vikunja 3 → CalDAV 3 (High)
|
||||
// CalDAV 4 → Vikunja 3 → CalDAV 3 (maps to High, LOSSY)
|
||||
// CalDAV 5 → Vikunja 2 → CalDAV 5 (Medium)
|
||||
// CalDAV 6-8 → Vikunja 1 → CalDAV 9 (maps to Low, LOSSY)
|
||||
// CalDAV 9 → Vikunja 1 → CalDAV 9 (Low)
|
||||
|
||||
putAndGetPriority := func(t *testing.T, uid string, inputPriority int) string {
|
||||
t.Helper()
|
||||
e := setupTestEnv(t)
|
||||
vtodo := NewVTodo(uid, "Priority "+uid).Priority(inputPriority).Build()
|
||||
path := "/dav/projects/36/" + uid + ".ics"
|
||||
rec := caldavPUT(t, e, path, vtodo)
|
||||
require.True(t, rec.Code >= 200 && rec.Code < 300)
|
||||
rec2 := caldavGET(t, e, path)
|
||||
require.Equal(t, 200, rec2.Code)
|
||||
cal := parseICalFromResponse(t, rec2)
|
||||
return getVTodoProperty(getVTodo(t, cal), ics.ComponentPropertyPriority)
|
||||
}
|
||||
|
||||
// Lossless round-trips
|
||||
t.Run("Priority 1 (highest) round-trips losslessly", func(t *testing.T) {
|
||||
assert.Equal(t, "1", putAndGetPriority(t, "p1", 1))
|
||||
})
|
||||
t.Run("Priority 2 round-trips losslessly", func(t *testing.T) {
|
||||
assert.Equal(t, "2", putAndGetPriority(t, "p2", 2))
|
||||
})
|
||||
t.Run("Priority 3 round-trips losslessly", func(t *testing.T) {
|
||||
assert.Equal(t, "3", putAndGetPriority(t, "p3", 3))
|
||||
})
|
||||
t.Run("Priority 5 round-trips losslessly", func(t *testing.T) {
|
||||
assert.Equal(t, "5", putAndGetPriority(t, "p5", 5))
|
||||
})
|
||||
t.Run("Priority 9 (lowest) round-trips losslessly", func(t *testing.T) {
|
||||
assert.Equal(t, "9", putAndGetPriority(t, "p9", 9))
|
||||
})
|
||||
|
||||
// Lossy mappings (document the behavior)
|
||||
t.Run("Priority 4 maps to 3 (lossy)", func(t *testing.T) {
|
||||
result := putAndGetPriority(t, "p4", 4)
|
||||
assert.Equal(t, "3", result,
|
||||
"CalDAV priority 4 maps to Vikunja 3 (High), which exports as CalDAV 3")
|
||||
})
|
||||
t.Run("Priority 6 maps to 9 (lossy)", func(t *testing.T) {
|
||||
result := putAndGetPriority(t, "p6", 6)
|
||||
assert.Equal(t, "9", result,
|
||||
"CalDAV priority 6 maps to Vikunja 1 (Low), which exports as CalDAV 9")
|
||||
})
|
||||
t.Run("Priority 7 maps to 9 (lossy)", func(t *testing.T) {
|
||||
result := putAndGetPriority(t, "p7", 7)
|
||||
assert.Equal(t, "9", result)
|
||||
})
|
||||
t.Run("Priority 8 maps to 9 (lossy)", func(t *testing.T) {
|
||||
result := putAndGetPriority(t, "p8", 8)
|
||||
assert.Equal(t, "9", result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVTodoDurationRoundTrip(t *testing.T) {
|
||||
// RFC 5545 §3.8.2.5 (rfc5545.txt line 5495):
|
||||
// "In a VTODO calendar component the property may be used to
|
||||
// specify a positive duration of time that the to-do is expected
|
||||
// to take for its completion."
|
||||
|
||||
putAndGet := func(t *testing.T, uid, vtodoBody string) *ics.VTodo {
|
||||
t.Helper()
|
||||
e := setupTestEnv(t)
|
||||
path := "/dav/projects/36/" + uid + ".ics"
|
||||
rec := caldavPUT(t, e, path, vtodoBody)
|
||||
require.True(t, rec.Code >= 200 && rec.Code < 300)
|
||||
rec2 := caldavGET(t, e, path)
|
||||
require.Equal(t, 200, rec2.Code)
|
||||
cal := parseICalFromResponse(t, rec2)
|
||||
return getVTodo(t, cal)
|
||||
}
|
||||
|
||||
t.Run("DTSTART + DURATION computes end date", func(t *testing.T) {
|
||||
// When DTSTART and DURATION are specified, Vikunja should compute
|
||||
// EndDate = DTSTART + DURATION (pkg/caldav/parsing.go:412-414)
|
||||
vtodo := NewVTodo("rt-duration", "Duration Test").
|
||||
DtStart(time.Date(2024, 6, 1, 9, 0, 0, 0, time.UTC)).
|
||||
Duration("PT2H").
|
||||
Build()
|
||||
result := putAndGet(t, "rt-duration", vtodo)
|
||||
|
||||
// Vikunja stores DTSTART and EndDate (DTSTART+DURATION)
|
||||
// On export, it may output DTSTART and DTEND, or DTSTART and DURATION
|
||||
body := result.Serialize(&ics.SerializationConfiguration{MaxLength: 75, PropertyMaxLength: 75, NewLine: "\r\n"})
|
||||
hasEnd := strings.Contains(body, "DTEND") || strings.Contains(body, "DURATION")
|
||||
assert.True(t, hasEnd,
|
||||
"Should preserve end time information (via DTEND or DURATION). Got:\n%s", body)
|
||||
})
|
||||
|
||||
t.Run("DTSTART + DUE are both preserved", func(t *testing.T) {
|
||||
vtodo := NewVTodo("rt-start-due", "Start and Due").
|
||||
DtStart(time.Date(2024, 6, 1, 9, 0, 0, 0, time.UTC)).
|
||||
Due(time.Date(2024, 6, 15, 17, 0, 0, 0, time.UTC)).
|
||||
Build()
|
||||
result := putAndGet(t, "rt-start-due", vtodo)
|
||||
|
||||
startStr := getVTodoProperty(result, ics.ComponentPropertyDtStart)
|
||||
dueStr := getVTodoProperty(result, ics.ComponentPropertyDue)
|
||||
assert.Contains(t, startStr, "20240601", "DTSTART should be preserved")
|
||||
assert.Contains(t, dueStr, "20240615", "DUE should be preserved")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user