feat(caldav): Add support for subtasks in CalDAV.
Some checks failed
continuous-integration/drone/pr Build is failing
Some checks failed
continuous-integration/drone/pr Build is failing
When exporting tasks with CalDAV, export their relationship to their parent tasks. When creating/updating tasks from CalDAV, create/maintain their parent relationship using task relations.
This commit is contained in:
parent
6aadaaaffc
commit
0eda1d9785
1
go.mod
1
go.mod
@ -65,6 +65,7 @@ require (
|
||||
github.com/wneessen/go-mail v0.3.8
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
||||
golang.org/x/image v0.6.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/sync v0.1.0
|
||||
|
2
go.sum
2
go.sum
@ -782,6 +782,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
|
@ -51,20 +51,20 @@ type Todo struct {
|
||||
UID string
|
||||
|
||||
// Optional
|
||||
Summary string
|
||||
Description string
|
||||
Completed time.Time
|
||||
Organizer *user.User
|
||||
Priority int64 // 0-9, 1 is highest
|
||||
RelatedToUID string
|
||||
Color string
|
||||
Categories []string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
RepeatAfter int64
|
||||
RepeatMode models.TaskRepeatMode
|
||||
Summary string
|
||||
Description string
|
||||
Completed time.Time
|
||||
Organizer *user.User
|
||||
Priority int64 // 0-9, 1 is highest
|
||||
RelatedToParentUID string
|
||||
Color string
|
||||
Categories []string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
RepeatAfter int64
|
||||
RepeatMode models.TaskRepeatMode
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
@ -209,9 +209,9 @@ STATUS:COMPLETED`
|
||||
ORGANIZER;CN=:` + t.Organizer.Username
|
||||
}
|
||||
|
||||
if t.RelatedToUID != "" {
|
||||
if t.RelatedToParentUID != "" {
|
||||
caldavtodos += `
|
||||
RELATED-TO:` + t.RelatedToUID
|
||||
RELATED-TO;RELTYPE=PARENT:` + t.RelatedToParentUID
|
||||
}
|
||||
|
||||
if t.DueDate.Unix() > 0 {
|
||||
|
@ -520,6 +520,45 @@ X-FUNAMBOL-COLOR:#affffeFF
|
||||
CATEGORIES:label1,label2
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with parent task",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
Color: "ffffff",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Color: "affffe",
|
||||
RelatedToParentUID: "another_random_uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
X-APPLE-CALENDAR-COLOR:#ffffffFF
|
||||
X-OUTLOOK-COLOR:#ffffffFF
|
||||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
RELATED-TO;RELTYPE=PARENT:another_random_uid
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
|
@ -39,6 +39,12 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
|
||||
categories = append(categories, label.Title)
|
||||
}
|
||||
|
||||
// Find the UID of the parent task, if it exists:
|
||||
var parentTaskUID string
|
||||
if parentTasks, ok := t.RelatedTasks[models.RelationKindParenttask]; ok {
|
||||
parentTaskUID = parentTasks[0].UID
|
||||
}
|
||||
|
||||
caldavtodos = append(caldavtodos, &Todo{
|
||||
Timestamp: t.Updated,
|
||||
UID: t.UID,
|
||||
@ -46,16 +52,17 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
|
||||
Description: t.Description,
|
||||
Completed: t.DoneAt,
|
||||
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
|
||||
Categories: categories,
|
||||
Priority: t.Priority,
|
||||
Start: t.StartDate,
|
||||
End: t.EndDate,
|
||||
Created: t.Created,
|
||||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
RepeatAfter: t.RepeatAfter,
|
||||
RepeatMode: t.RepeatMode,
|
||||
Categories: categories,
|
||||
Priority: t.Priority,
|
||||
RelatedToParentUID: parentTaskUID,
|
||||
Start: t.StartDate,
|
||||
End: t.EndDate,
|
||||
Created: t.Created,
|
||||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
RepeatAfter: t.RepeatAfter,
|
||||
RepeatMode: t.RepeatMode,
|
||||
})
|
||||
}
|
||||
|
||||
@ -73,9 +80,11 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var parsedProperties = parsed.Components[0].UnknownPropertiesIANAProperties()
|
||||
|
||||
// We put the task details in a map to be able to handle them more easily
|
||||
task := make(map[string]string)
|
||||
for _, c := range parsed.Components[0].UnknownPropertiesIANAProperties() {
|
||||
for _, c := range parsedProperties {
|
||||
task[c.IANAToken] = c.Value
|
||||
}
|
||||
|
||||
@ -90,6 +99,26 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
priority = parseVTODOPriority(priorityParsed)
|
||||
}
|
||||
|
||||
// Check if the task has a parent:
|
||||
var parentTaskUID string
|
||||
for _, c := range parsedProperties {
|
||||
// Check if the entry is a relation:
|
||||
if c.IANAToken != "RELATED-TO" {
|
||||
continue
|
||||
}
|
||||
// Check if the relation has a type:
|
||||
if _, ok := c.ICalParameters["RELTYPE"]; !ok {
|
||||
continue
|
||||
}
|
||||
// Check that the type is "PARENT":
|
||||
if len(c.ICalParameters["RELTYPE"]) != 1 || c.ICalParameters["RELTYPE"][0] != "PARENT" {
|
||||
continue
|
||||
}
|
||||
|
||||
// We have the id of the parent task:
|
||||
parentTaskUID = c.Value
|
||||
}
|
||||
|
||||
// Parse the enddate
|
||||
duration, _ := time.ParseDuration(task["DURATION"])
|
||||
|
||||
@ -123,6 +152,19 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
vTask.Done = true
|
||||
}
|
||||
|
||||
// Check if the task has a parent and create a dummy relation if yes:
|
||||
if parentTaskUID != "" {
|
||||
var parentTaskUID = parentTaskUID
|
||||
|
||||
if vTask.RelatedTasks == nil {
|
||||
vTask.RelatedTasks = make(models.RelatedTaskMap)
|
||||
}
|
||||
|
||||
vTask.RelatedTasks[models.RelationKindParenttask] = append(vTask.RelatedTasks[models.RelationKindParenttask], &models.Task{
|
||||
UID: parentTaskUID,
|
||||
})
|
||||
}
|
||||
|
||||
if duration > 0 && !vTask.StartDate.IsZero() {
|
||||
vTask.EndDate = vTask.StartDate.Add(duration)
|
||||
}
|
||||
|
@ -118,6 +118,63 @@ END:VCALENDAR`,
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With parent",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:SubTask #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
RELATED-TO;RELTYPE=PARENT:randomuid_parent
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "SubTask #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindParenttask: {
|
||||
{
|
||||
UID: "randomuid_parent",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With non-parent relation we ignore",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Parent task
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
RELATED-TO;RELTYPE=CHILD:randomuid_child
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Parent task",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -135,8 +192,8 @@ END:VCALENDAR`,
|
||||
|
||||
func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||
type args struct {
|
||||
list *models.ProjectWithTasksAndBuckets
|
||||
tasks []*models.TaskWithComments
|
||||
project *models.ProjectWithTasksAndBuckets
|
||||
tasks []*models.TaskWithComments
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -146,9 +203,9 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||
{
|
||||
name: "Format single Task as Caldav",
|
||||
args: args{
|
||||
list: &models.ProjectWithTasksAndBuckets{
|
||||
project: &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: "List title",
|
||||
Title: "Project title",
|
||||
},
|
||||
},
|
||||
tasks: []*models.TaskWithComments{
|
||||
@ -183,7 +240,7 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:List title
|
||||
X-WR-CALNAME:Project title
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
@ -201,12 +258,126 @@ RRULE:FREQ=SECONDLY;INTERVAL=86400
|
||||
CATEGORIES:label1,label2
|
||||
LAST-MODIFIED:20181201T011205Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "Format tasks with relationship as Caldav",
|
||||
args: args{
|
||||
project: &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: "Project title",
|
||||
},
|
||||
},
|
||||
tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Parent task",
|
||||
UID: "randomuid_parent",
|
||||
Description: "This is a parent task",
|
||||
Priority: 3,
|
||||
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
{
|
||||
Title: "Subtask 1",
|
||||
UID: "randomuid_child_1",
|
||||
Description: "This is the first child task",
|
||||
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
{
|
||||
Title: "Subtask 2",
|
||||
UID: "randomuid_child_2",
|
||||
Description: "This is the second child task",
|
||||
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Subtask 1",
|
||||
UID: "randomuid_child_1",
|
||||
Description: "This is the first child task",
|
||||
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindParenttask: {
|
||||
{
|
||||
Title: "Parent task",
|
||||
UID: "randomuid_parent",
|
||||
Description: "This is a parent task",
|
||||
Priority: 3,
|
||||
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Subtask 2",
|
||||
UID: "randomuid_child_2",
|
||||
Description: "This is the second child task",
|
||||
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindParenttask: {
|
||||
{
|
||||
Title: "Parent task",
|
||||
UID: "randomuid_parent",
|
||||
Description: "This is a parent task",
|
||||
Priority: 3,
|
||||
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldav: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project title
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid_parent
|
||||
DTSTAMP:20181201T011205Z
|
||||
SUMMARY:Parent task
|
||||
DESCRIPTION:This is a parent task
|
||||
CREATED:20181201T011201Z
|
||||
PRIORITY:3
|
||||
LAST-MODIFIED:20181201T011205Z
|
||||
END:VTODO
|
||||
BEGIN:VTODO
|
||||
UID:randomuid_child_1
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Subtask 1
|
||||
DESCRIPTION:This is the first child task
|
||||
RELATED-TO;RELTYPE=PARENT:randomuid_parent
|
||||
CREATED:20181201T011204Z
|
||||
LAST-MODIFIED:20181201T011204Z
|
||||
END:VTODO
|
||||
BEGIN:VTODO
|
||||
UID:randomuid_child_2
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Subtask 2
|
||||
DESCRIPTION:This is the second child task
|
||||
RELATED-TO;RELTYPE=PARENT:randomuid_parent
|
||||
CREATED:20181201T011204Z
|
||||
LAST-MODIFIED:20181201T011204Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetCaldavTodosForTasks(tt.args.list, tt.args.tasks)
|
||||
got := GetCaldavTodosForTasks(tt.args.project, tt.args.tasks)
|
||||
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
|
||||
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
|
||||
}
|
||||
|
@ -224,3 +224,9 @@
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 37
|
||||
title: testbucket37
|
||||
project_id: 27
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
|
@ -255,7 +255,7 @@
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 26
|
||||
title: List 26 for Caldav tests
|
||||
title: Project 26 for Caldav tests
|
||||
description: Lorem Ipsum
|
||||
identifier: test26
|
||||
owner_id: 15
|
||||
@ -263,3 +263,14 @@
|
||||
position: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
-
|
||||
id: 27
|
||||
title: Project 27 for Caldav tests
|
||||
description: Lorem Ipsum
|
||||
identifier: test27
|
||||
owner_id: 15
|
||||
namespace_id: 18
|
||||
position: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
@ -52,3 +52,9 @@
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 10
|
||||
user_id: 15
|
||||
project_id: 27
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
@ -59,7 +59,7 @@ func InitTestFileFixtures(t *testing.T) {
|
||||
}
|
||||
|
||||
// InitTests handles the actual bootstrapping of the test env
|
||||
func InitTests() {
|
||||
func InitTests(loadFixtures bool) {
|
||||
var err error
|
||||
x, err = db.CreateTestEngine()
|
||||
if err != nil {
|
||||
@ -71,9 +71,11 @@ func InitTests() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.InitTestFixtures("files")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
if loadFixtures {
|
||||
err = db.InitTestFixtures("files")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
InitTestFileHandler()
|
||||
|
@ -23,6 +23,6 @@ import (
|
||||
|
||||
// TestMain is the main test function used to bootstrap the test env
|
||||
func TestMain(m *testing.M) {
|
||||
InitTests()
|
||||
InitTests(true)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ const vtodo = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:List 26 for Caldav tests
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid
|
||||
@ -42,22 +42,22 @@ END:VCALENDAR`
|
||||
|
||||
func TestCaldav(t *testing.T) {
|
||||
t.Run("Delivers VTODO for project", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"})
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
|
||||
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:List 26 for Caldav tests")
|
||||
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:Project 26 for Caldav tests")
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
|
||||
assert.Contains(t, rec.Body.String(), "END:VTODO")
|
||||
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
|
||||
})
|
||||
t.Run("Import VTODO", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"})
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
t.Run("Export VTODO", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"})
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")
|
||||
@ -67,3 +67,366 @@ func TestCaldav(t *testing.T) {
|
||||
assert.Contains(t, rec.Body.String(), "CATEGORIES:Label #4")
|
||||
})
|
||||
}
|
||||
|
||||
// Here we check that the CALDAV implementation correctly supports subtasks:
|
||||
func TestCaldavSubtasks(t *testing.T) {
|
||||
const vtodoParentTask = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_parent_task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav parent task
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
const vtodoChildTask1 = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_child1
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav child task 1
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
const vtodoChildTask2 = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_child2
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav child task 2
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
const vtodoGrandChildTask = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_grand_child
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav grand child task
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
t.Run("Import parent task", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Import children tasks", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask1, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask2, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Import grand child task", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoGrandChildTask, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Check the relationship between all the tasks by fetching them one by one", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_parent_task")
|
||||
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_child1")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_child2")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child1")
|
||||
})
|
||||
|
||||
const vtodoEditedGrandChildTask = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_grand_child
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav grand child task edited
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
t.Run("Update the grand child task again and check that the relation is still there", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoEditedGrandChildTask, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav grand child task edited")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child1")
|
||||
})
|
||||
|
||||
const vtodoChildTask2WithoutRelation = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_child2
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav child task 2
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
t.Run("Remove the relation from the second child", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask2WithoutRelation, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
|
||||
// Check that the relation was removed from the DB, and isn't returned anymore:
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child2"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_child2")
|
||||
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||
})
|
||||
|
||||
const vtodoGrandChildTaskNewParent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_grand_child
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav grand child task edited
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
t.Run("Update the grand child task again and change its parent", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoGrandChildTaskNewParent, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||
})
|
||||
}
|
||||
|
||||
// Here we check that the CALDAV implementation correctly supports task relations from different lists:
|
||||
func TestCaldavSubtasksDifferentLists(t *testing.T) {
|
||||
const vtodoParentTask = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_parent_task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav parent task
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
const vtodoChildTask1 = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 27 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_child1
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav child task 1
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
t.Run("Import parent task", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Import child tasks into a different list", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask1, nil, map[string]string{"project": "27", "task": "uid_child1"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Check the relationship between all the tasks by fetching them one by one", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_parent_task")
|
||||
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "27", "task": "uid_child1"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_child1")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||
})
|
||||
}
|
||||
|
||||
// Here we check that subtasks are handled properly even if the children tasks are created before the parent tasks
|
||||
func TestCaldavSubtasksInverseOrder(t *testing.T) {
|
||||
const vtodoGrandChildTask = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_grand_child
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav grand child task
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
const vtodoChildTask1 = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_child1
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav child task 1
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_parent_task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
const vtodoParentTask = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid_parent_task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav parent task
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
t.Run("Import grand child task", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoGrandChildTask, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Import children tasks", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask1, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Import parent task", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
|
||||
t.Run("Check the relationship between all the tasks by fetching them one by one", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_parent_task"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_parent_task")
|
||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav parent task")
|
||||
assert.NotContains(t, rec.Body.String(), "RELATED-TO")
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_child1"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_child1")
|
||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav child task 1")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_task")
|
||||
|
||||
rec, err = newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid_grand_child"}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 200)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child")
|
||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Caldav grand child task")
|
||||
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child1")
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -58,20 +58,22 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func setupTestEnv() (e *echo.Echo, err error) {
|
||||
func setupTestEnv(loadFixtures bool) (e *echo.Echo, err error) {
|
||||
config.InitDefaultConfig()
|
||||
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
// Some tests use the file engine, so we'll need to initialize that
|
||||
files.InitTests()
|
||||
files.InitTests(loadFixtures)
|
||||
user.InitTests()
|
||||
models.SetupTests()
|
||||
events.Fake()
|
||||
keyvalue.InitStorage()
|
||||
|
||||
err = db.LoadFixtures()
|
||||
if err != nil {
|
||||
return
|
||||
if loadFixtures {
|
||||
err = db.LoadFixtures()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
e = routes.NewEcho()
|
||||
@ -79,9 +81,9 @@ func setupTestEnv() (e *echo.Echo, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values) (c echo.Context, rec *httptest.ResponseRecorder) {
|
||||
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values, loadFixtures bool) (c echo.Context, rec *httptest.ResponseRecorder) {
|
||||
// Setup
|
||||
e, err := setupTestEnv()
|
||||
e, err := setupTestEnv(loadFixtures)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Do the actual request
|
||||
@ -95,7 +97,7 @@ func bootstrapTestRequest(t *testing.T, method string, payload string, queryPara
|
||||
}
|
||||
|
||||
func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) error, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, true)
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
@ -124,8 +126,8 @@ func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.
|
||||
c.Set("user", tken)
|
||||
}
|
||||
|
||||
func testRequestSetup(t *testing.T, method string, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, c echo.Context) {
|
||||
c, rec = bootstrapTestRequest(t, method, payload, queryParams)
|
||||
func testRequestSetup(t *testing.T, method string, payload string, queryParams url.Values, urlParams map[string]string, loadFixtures bool) (rec *httptest.ResponseRecorder, c echo.Context) {
|
||||
c, rec = bootstrapTestRequest(t, method, payload, queryParams, loadFixtures)
|
||||
|
||||
var paramNames []string
|
||||
var paramValues []string
|
||||
@ -139,21 +141,21 @@ func testRequestSetup(t *testing.T, method string, payload string, queryParams u
|
||||
}
|
||||
|
||||
func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, true)
|
||||
addUserTokenToContext(t, user, c)
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
|
||||
func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.HandlerFunc, share *models.LinkSharing, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, true)
|
||||
addLinkShareTokenToContext(t, share, c)
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
|
||||
func newCaldavTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
func newCaldavTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string, loadFixtures bool) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams, loadFixtures)
|
||||
c.Request().Header.Set(echo.HeaderContentType, echo.MIMETextPlain)
|
||||
|
||||
result, _ := caldav.BasicAuth(user.Username, "1234", c)
|
||||
|
@ -59,7 +59,7 @@ func TestMain(m *testing.M) {
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
|
||||
// Some tests use the file engine, so we'll need to initialize that
|
||||
files.InitTests()
|
||||
files.InitTests(true)
|
||||
|
||||
user.InitTests()
|
||||
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
// TestMain is the main test function used to bootstrap the test env
|
||||
func TestMain(m *testing.M) {
|
||||
user.InitTests()
|
||||
files.InitTests()
|
||||
files.InitTests(true)
|
||||
models.SetupTests()
|
||||
events.Fake()
|
||||
os.Exit(m.Run())
|
||||
|
@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
|
||||
// Some tests use the file engine, so we'll need to initialize that
|
||||
files.InitTests()
|
||||
files.InitTests(true)
|
||||
user.InitTests()
|
||||
models.SetupTests()
|
||||
events.Fake()
|
||||
|
@ -36,7 +36,7 @@ func TestMain(m *testing.M) {
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
|
||||
// Some tests use the file engine, so we'll need to initialize that
|
||||
files.InitTests()
|
||||
files.InitTests(true)
|
||||
user.InitTests()
|
||||
models.SetupTests()
|
||||
events.Fake()
|
||||
|
@ -17,6 +17,7 @@
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -294,6 +295,13 @@ func (vcls *VikunjaCaldavProjectStorage) CreateResource(rpath, content string) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vcls.task.ProjectID = vcls.project.ID
|
||||
err = persistRelations(s, vcls.user, vcls.task, vTask.RelatedTasks)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -318,6 +326,10 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||
// At this point, we already have the right task in vcls.task, so we can use that ID directly
|
||||
vTask.ID = vcls.task.ID
|
||||
|
||||
// Explicitely set the ProjectID in case the task now belongs to a different project:
|
||||
vTask.ProjectID = vcls.project.ID
|
||||
vcls.task.ProjectID = vcls.project.ID
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
@ -345,6 +357,12 @@ func (vcls *VikunjaCaldavProjectStorage) UpdateResource(rpath, content string) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = persistRelations(s, vcls.user, vcls.task, vTask.RelatedTasks)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -432,6 +450,88 @@ func persistLabels(s *xorm.Session, a web.Auth, task *models.Task, labels []*mod
|
||||
return task.UpdateTaskLabels(s, a, labels)
|
||||
}
|
||||
|
||||
// When a VTODO entry doesn't have a parent anymore, but we do, we need to remove it as well.
|
||||
func removeLegacyParentRelations(s *xorm.Session, a web.Auth, task *models.Task, newRelations map[models.RelationKind][]*models.Task) (err error) {
|
||||
|
||||
// Get the existing task with details:
|
||||
existingTask := &models.Task{ID: task.ID}
|
||||
err = existingTask.ReadOne(s, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Loop through all the existing task's parent relationships:
|
||||
if _, ok := existingTask.RelatedTasks[models.RelationKindParenttask]; ok {
|
||||
for _, parentTask := range existingTask.RelatedTasks[models.RelationKindParenttask] {
|
||||
|
||||
// Check if the existing parent relation is in the new list:
|
||||
parentRelationInNewList := slices.ContainsFunc(newRelations[models.RelationKindParenttask], func(newRelation *models.Task) bool { return newRelation.UID == parentTask.UID })
|
||||
|
||||
// Remove the relations if it's not there in the new list anymore:
|
||||
if !parentRelationInNewList {
|
||||
rel := models.TaskRelation{
|
||||
TaskID: task.ID,
|
||||
OtherTaskID: parentTask.ID,
|
||||
RelationKind: models.RelationKindParenttask,
|
||||
}
|
||||
err = rel.Delete(s, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Persist new relations provided by the VTODO entry:
|
||||
func persistRelations(s *xorm.Session, a web.Auth, task *models.Task, newRelations map[models.RelationKind][]*models.Task) (err error) {
|
||||
|
||||
// Remove existing "parent" relations that are not present in the new list:
|
||||
err = removeLegacyParentRelations(s, a, task, newRelations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the current relations exist:
|
||||
for relationType, relatedTasks := range newRelations {
|
||||
|
||||
// Persist each relation independently:
|
||||
for _, relatedTask := range relatedTasks {
|
||||
|
||||
// Get the task from the DB:
|
||||
has, err := s.Get(relatedTask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the related task doesn't exist, create a dummy one now in the same list.
|
||||
// It'll probably be populated right after in a following request:
|
||||
if !has {
|
||||
relatedTask.ProjectID = task.ProjectID
|
||||
relatedTask.Title = "UID-" + relatedTask.UID
|
||||
err = relatedTask.Create(s, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the relation:
|
||||
rel := models.TaskRelation{
|
||||
TaskID: task.ID,
|
||||
OtherTaskID: relatedTask.ID,
|
||||
RelationKind: relationType,
|
||||
}
|
||||
err = rel.Create(s, a)
|
||||
if err != nil && !models.IsErrRelationAlreadyExists(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// VikunjaProjectResourceAdapter holds the actual resource
|
||||
type VikunjaProjectResourceAdapter struct {
|
||||
project *models.ProjectWithTasksAndBuckets
|
||||
|
Loading…
x
Reference in New Issue
Block a user