WIP: feat(caldav): Add support for subtasks in CalDAV. #1442
@ -38,21 +38,21 @@ 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
|
||||
Alarms []Alarm
|
||||
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
|
||||
Alarms []Alarm
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
@ -147,9 +147,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 {
|
||||
|
@ -326,13 +326,52 @@ ACTION:DISPLAY
|
||||
DESCRIPTION:Todo #1
|
||||
END:VALARM
|
||||
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`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCaldavtasks := ParseTodos(tt.args.config, tt.args.todos)
|
||||
assert.Equal(t, tt.wantCaldavtasks, gotCaldavtasks)
|
||||
assert.Equal(t, gotCaldavtasks, tt.wantCaldavtasks)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
@ -51,6 +50,12 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
|
||||
})
|
||||
}
|
||||
|
||||
// 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,
|
||||
@ -58,17 +63,18 @@ 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,
|
||||
Alarms: alarms,
|
||||
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,
|
||||
Alarms: alarms,
|
||||
})
|
||||
}
|
||||
|
||||
@ -89,14 +95,11 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
if !ok {
|
||||
return nil, errors.New("VTODO element not found")
|
||||
}
|
||||
var parsedProperties = vTodo.UnknownPropertiesIANAProperties()
|
||||
// We put the vTodo details in a map to be able to handle them more easily
|
||||
task := make(map[string]ics.IANAProperty)
|
||||
var relation ics.IANAProperty
|
||||
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
|
||||
for _, c := range parsedProperties {
|
||||
task[c.IANAToken] = c
|
||||
if strings.HasPrefix(c.IANAToken, "RELATED-TO") {
|
||||
relation = c
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the priority
|
||||
@ -110,6 +113,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"].Value)
|
||||
|
||||
@ -139,23 +162,23 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
|
||||
}
|
||||
|
||||
if relation.Value != "" {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
subtask, err := models.GetTaskSimpleByUUID(s, relation.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vTask.RelatedTasks = make(map[models.RelationKind][]*models.Task)
|
||||
vTask.RelatedTasks[models.RelationKindSubtask] = []*models.Task{subtask}
|
||||
}
|
||||
|
||||
if task["STATUS"].Value == "COMPLETED" {
|
||||
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)
|
||||
}
|
||||
|
@ -289,6 +289,63 @@ END:VCALENDAR`,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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) {
|
||||
@ -306,8 +363,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
|
||||
@ -317,9 +374,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{
|
||||
@ -364,7 +421,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
|
||||
@ -392,12 +449,126 @@ ACTION:DISPLAY
|
||||
DESCRIPTION:Task 1
|
||||
END:VALARM
|
||||
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)
|
||||
}
|
||||
|
@ -236,3 +236,15 @@
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 39
|
||||
title: testbucket37
|
||||
project_id: 37
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 40
|
||||
title: testbucket38
|
||||
project_id: 38
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
|
@ -327,3 +327,12 @@
|
||||
position: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 38
|
||||
title: Project 38 for Caldav tests
|
||||
description: Lorem Ipsum
|
||||
identifier: test38
|
||||
owner_id: 15
|
||||
position: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
@ -34,3 +34,27 @@
|
||||
relation_kind: 'related'
|
||||
created_by_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
task_id: 41
|
||||
other_task_id: 43
|
||||
relation_kind: 'subtask'
|
||||
created_by_id: 15
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
task_id: 43
|
||||
other_task_id: 41
|
||||
relation_kind: 'parenttask'
|
||||
created_by_id: 15
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 9
|
||||
task_id: 41
|
||||
other_task_id: 44
|
||||
relation_kind: 'subtask'
|
||||
created_by_id: 15
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 10
|
||||
task_id: 44
|
||||
other_task_id: 41
|
||||
relation_kind: 'parenttask'
|
||||
created_by_id: 15
|
||||
created: 2018-12-01 15:13:12
|
||||
|
@ -374,5 +374,61 @@
|
||||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 1
|
||||
bucket_id: 38
|
||||
position: 39
|
||||
- id: 41
|
||||
uid: 'uid-caldav-test-parent-task'
|
||||
title: 'Parent task for Caldav Test'
|
||||
description: 'Description Caldav Test'
|
||||
priority: 3
|
||||
done: false
|
||||
created_by_id: 15
|
||||
project_id: 36
|
||||
index: 40
|
||||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 38
|
||||
position: 40
|
||||
- id: 42
|
||||
uid: 'uid-caldav-test-parent-task-2'
|
||||
title: 'Parent task for Caldav Test 2'
|
||||
description: 'Description Caldav Test 2'
|
||||
priority: 3
|
||||
done: false
|
||||
created_by_id: 15
|
||||
project_id: 36
|
||||
index: 41
|
||||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 38
|
||||
position: 41
|
||||
- id: 43
|
||||
uid: 'uid-caldav-test-child-task'
|
||||
title: 'Child task for Caldav Test'
|
||||
description: 'Description Caldav Test'
|
||||
priority: 3
|
||||
done: false
|
||||
created_by_id: 15
|
||||
project_id: 36
|
||||
index: 42
|
||||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 38
|
||||
position: 42
|
||||
- id: 44
|
||||
uid: 'uid-caldav-test-child-task-2'
|
||||
title: 'Child task for Caldav Test 2'
|
||||
description: 'Description Caldav Test'
|
||||
priority: 3
|
||||
done: false
|
||||
created_by_id: 15
|
||||
project_id: 36
|
||||
index: 43
|
||||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 38
|
||||
position: 43
|
||||
|
@ -100,3 +100,15 @@
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 18
|
||||
user_id: 15
|
||||
project_id: 36
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 19
|
||||
user_id: 15
|
||||
project_id: 38
|
||||
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())
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ import (
|
||||
|
||||
func TestAPIToken(t *testing.T) {
|
||||
t.Run("valid token", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
e, err := setupTestEnv(true)
|
||||
assert.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
|
||||
res := httptest.NewRecorder()
|
||||
@ -52,7 +52,7 @@ func TestAPIToken(t *testing.T) {
|
||||
assert.Contains(t, res.Body.String(), `"username":"user1"`)
|
||||
})
|
||||
t.Run("invalid token", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
e, err := setupTestEnv(true)
|
||||
assert.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
|
||||
res := httptest.NewRecorder()
|
||||
@ -65,7 +65,7 @@ func TestAPIToken(t *testing.T) {
|
||||
assert.Error(t, h(c))
|
||||
})
|
||||
t.Run("expired token", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
e, err := setupTestEnv(true)
|
||||
assert.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
|
||||
res := httptest.NewRecorder()
|
||||
@ -78,7 +78,7 @@ func TestAPIToken(t *testing.T) {
|
||||
assert.Error(t, h(c))
|
||||
})
|
||||
t.Run("valid token, invalid scope", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
e, err := setupTestEnv(true)
|
||||
assert.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects", nil)
|
||||
res := httptest.NewRecorder()
|
||||
@ -91,7 +91,7 @@ func TestAPIToken(t *testing.T) {
|
||||
assert.Error(t, h(c))
|
||||
})
|
||||
t.Run("jwt", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
e, err := setupTestEnv(true)
|
||||
assert.NoError(t, err)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
|
||||
res := httptest.NewRecorder()
|
||||
|
@ -28,7 +28,7 @@ const vtodo = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:List 36 for Caldav tests
|
||||
X-WR-CALNAME:Project 36 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid
|
||||
@ -46,7 +46,7 @@ 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": "36"})
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "36"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
|
||||
@ -56,12 +56,12 @@ func TestCaldav(t *testing.T) {
|
||||
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": "36", "task": "uid"})
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "36", "task": "uid"}, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 201, rec.Result().StatusCode)
|
||||
})
|
||||
t.Run("Export VTODO", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test"})
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "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")
|
||||
@ -75,3 +75,366 @@ func TestCaldav(t *testing.T) {
|
||||
assert.Contains(t, rec.Body.String(), "END:VALARM")
|
||||
})
|
||||
}
|
||||
|
||||
// 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 36 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 36 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 36 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 36 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": "36", "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": "36", "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": "36", "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": "36", "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": "36", "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": "36", "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": "36", "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": "36", "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 36 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": "36", "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": "36", "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 36 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": "36", "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": "36", "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 36 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": "36", "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": "36", "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 36 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 38 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": "36", "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": "38", "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": "36", "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": "38", "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 36 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 36 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 36 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": "36", "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": "36", "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": "36", "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": "36", "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": "36", "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": "36", "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 {
|
||||
konrad
commented
Same here, why wouldn't you want to load the fixtures? This creates a kind of unpredictable state for tests. Same here, why wouldn't you want to load the fixtures? This creates a kind of unpredictable state for tests.
|
||||
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)
|
||||
|
@ -147,7 +147,7 @@ func TestBucket_Delete(t *testing.T) {
|
||||
tasks := []*Task{}
|
||||
err = s.Where("bucket_id = ?", 1).Find(&tasks)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tasks, 16)
|
||||
assert.Len(t, tasks, 15)
|
||||
db.AssertMissing(t, "buckets", map[string]interface{}{
|
||||
"id": 2,
|
||||
"project_id": 1,
|
||||
|
@ -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()
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"code.vikunja.io/web"
|
||||
"github.com/samedi/caldav-go/data"
|
||||
"github.com/samedi/caldav-go/errs"
|
||||
"golang.org/x/exp/slices"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
@ -292,6 +293,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
|
||||
}
|
||||
@ -316,6 +324,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
|
||||
|
||||
// Explicitly 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()
|
||||
|
||||
@ -343,6 +355,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
|
||||
}
|
||||
@ -430,6 +448,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:
|
||||
konrad
commented
Why call this "legacy"? Why call this "legacy"?
zewaren
commented
Here "legacy" means that the tasks had a parent, but that parent is gone. This case is when a CALDAV client removes the parent relationship and pushes the task to us. We still have the relationship in our DB, and we need to remove it. I'm happy to use a better word if you have one in mind. Here "legacy" means that the tasks had a parent, but that parent is gone.
This case is when a CALDAV client removes the parent relationship and pushes the task to us. We still have the relationship in our DB, and we need to remove it.
I'm happy to use a better word if you have one in mind.
konrad
commented
Something like Something like `removeDeletedParentRelations`?
|
||||
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
|
||||
|
444
pkg/routes/caldav/listStorageProvider_test.go
Normal file
444
pkg/routes/caldav/listStorageProvider_test.go
Normal file
@ -0,0 +1,444 @@
|
||||
// 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 Licensee 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 Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
// This file tests logic related to handling tasks in CALDAV format
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Check logic related to creating sub-tasks
|
||||
func TestSubTask_Create(t *testing.T) {
|
||||
u := &user.User{
|
||||
ID: 15,
|
||||
Username: "user15",
|
||||
Email: "user15@example.com",
|
||||
}
|
||||
|
||||
//
|
||||
// Create a subtask
|
||||
//
|
||||
t.Run("create", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
const taskUID = "uid_child1"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 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-caldav-test-parent-task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: &models.Task{UID: taskUID},
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Create the subtask:
|
||||
taskResource, err := storage.CreateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the result CALDAV contains the relation:
|
||||
content, _ := taskResource.GetContentData()
|
||||
assert.Contains(t, content, "UID:"+taskUID)
|
||||
assert.Contains(t, content, "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task")
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
|
||||
// Check that the parent-child relationship is present:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 1)
|
||||
parentTask := task.RelatedTasks[models.RelationKindParenttask][0]
|
||||
assert.Equal(t, "uid-caldav-test-parent-task", parentTask.UID)
|
||||
})
|
||||
|
||||
//
|
||||
// Create a subtask on a subtask, i.e. create a grand-child
|
||||
//
|
||||
t.Run("create grandchild on child task", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
const taskUID = "uid_child1"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 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-caldav-test-child-task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: &models.Task{UID: taskUID},
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Create the task:
|
||||
taskResource, err := storage.CreateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the result CALDAV contains the relation:
|
||||
content, _ := taskResource.GetContentData()
|
||||
assert.Contains(t, content, "UID:"+taskUID)
|
||||
assert.Contains(t, content, "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-child-task")
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
|
||||
// Check that the parent-child relationship of the grandchildren is present:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 1)
|
||||
parentTask := task.RelatedTasks[models.RelationKindParenttask][0]
|
||||
assert.Equal(t, "uid-caldav-test-child-task", parentTask.UID)
|
||||
|
||||
// Get the child task and check that it now has a parent and a child:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{"uid-caldav-test-child-task"}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 1)
|
||||
parentTask = task.RelatedTasks[models.RelationKindParenttask][0]
|
||||
assert.Equal(t, "uid-caldav-test-parent-task", parentTask.UID)
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindSubtask], 1)
|
||||
childTask := task.RelatedTasks[models.RelationKindSubtask][0]
|
||||
assert.Equal(t, taskUID, childTask.UID)
|
||||
})
|
||||
|
||||
//
|
||||
// Create a subtask on a parent that we don't know anything about (yet)
|
||||
//
|
||||
t.Run("create subtask on unknown parent", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
// Create a subtask:
|
||||
const taskUID = "uid_child1"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 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-caldav-test-parent-doesnt-exist-yet
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: &models.Task{UID: taskUID},
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Create the task:
|
||||
taskResource, err := storage.CreateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the result CALDAV contains the relation:
|
||||
content, _ := taskResource.GetContentData()
|
||||
assert.Contains(t, content, "UID:"+taskUID)
|
||||
assert.Contains(t, content, "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-doesnt-exist-yet")
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
|
||||
// Check that the parent-child relationship is present:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 1)
|
||||
parentTask := task.RelatedTasks[models.RelationKindParenttask][0]
|
||||
assert.Equal(t, "uid-caldav-test-parent-doesnt-exist-yet", parentTask.UID)
|
||||
|
||||
// Check that the non-existent parent task was created in the process:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{"uid-caldav-test-parent-doesnt-exist-yet"}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
assert.Equal(t, "uid-caldav-test-parent-doesnt-exist-yet", task.UID)
|
||||
})
|
||||
}
|
||||
|
||||
// Logic related to editing tasks and subtasks
|
||||
func TestSubTask_Edit(t *testing.T) {
|
||||
u := &user.User{
|
||||
ID: 15,
|
||||
Username: "user15",
|
||||
Email: "user15@example.com",
|
||||
}
|
||||
|
||||
//
|
||||
// Edit a subtask and check that the relations are not gone
|
||||
//
|
||||
t.Run("edit subtask", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
// Edit the subtask:
|
||||
const taskUID = "uid-caldav-test-child-task"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid-caldav-test-child-task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Child task for Caldav Test (edited)
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: task,
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Edit the task:
|
||||
taskResource, err := storage.UpdateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the result CALDAV still contains the relation:
|
||||
content, _ := taskResource.GetContentData()
|
||||
assert.Contains(t, content, "UID:"+taskUID)
|
||||
assert.Contains(t, content, "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task")
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
|
||||
// Check that the parent-child relationship is still present:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 1)
|
||||
parentTask := task.RelatedTasks[models.RelationKindParenttask][0]
|
||||
assert.Equal(t, "uid-caldav-test-parent-task", parentTask.UID)
|
||||
})
|
||||
|
||||
//
|
||||
// Edit a parent task and check that the subtasks are still linked
|
||||
//
|
||||
t.Run("edit parent", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
// Edit the parent task:
|
||||
const taskUID = "uid-caldav-test-parent-task"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid-caldav-test-parent-task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Parent task for Caldav Test (edited)
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: task,
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Edit the task:
|
||||
_, err = storage.UpdateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
|
||||
// Check that the subtasks are still linked:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindSubtask], 2)
|
||||
existingSubTask := task.RelatedTasks[models.RelationKindSubtask][0]
|
||||
assert.Equal(t, "uid-caldav-test-child-task", existingSubTask.UID)
|
||||
existingSubTask = task.RelatedTasks[models.RelationKindSubtask][1]
|
||||
assert.Equal(t, "uid-caldav-test-child-task-2", existingSubTask.UID)
|
||||
})
|
||||
|
||||
//
|
||||
// Edit a subtask and change its parent
|
||||
//
|
||||
t.Run("edit subtask change parent", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
// Edit the subtask:
|
||||
const taskUID = "uid-caldav-test-child-task"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid-caldav-test-child-task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Child task for Caldav Test (edited)
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task-2
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: task,
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Edit the task:
|
||||
taskResource, err := storage.UpdateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the result CALDAV contains the new relation:
|
||||
content, _ := taskResource.GetContentData()
|
||||
assert.Contains(t, content, "UID:"+taskUID)
|
||||
assert.Contains(t, content, "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task-2")
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
|
||||
// Check that the parent-child relationship has changed to the new parent:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 1)
|
||||
parentTask := task.RelatedTasks[models.RelationKindParenttask][0]
|
||||
assert.Equal(t, "uid-caldav-test-parent-task-2", parentTask.UID)
|
||||
|
||||
// Get the previous parent from the DB and check that its previous child is gone:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{"uid-caldav-test-parent-task"}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindSubtask], 1)
|
||||
// We're gone, but our former sibling is still there:
|
||||
formerSiblingSubTask := task.RelatedTasks[models.RelationKindSubtask][0]
|
||||
assert.Equal(t, "uid-caldav-test-child-task-2", formerSiblingSubTask.UID)
|
||||
})
|
||||
|
||||
//
|
||||
// Edit a subtask and remove its parent
|
||||
//
|
||||
t.Run("edit subtask remove parent", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
// Edit the subtask:
|
||||
const taskUID = "uid-caldav-test-child-task"
|
||||
const taskContent = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:Project 36 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid-caldav-test-child-task
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Child task for Caldav Test (edited)
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
tasks, err := models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task := tasks[0]
|
||||
storage := &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: task,
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Edit the task:
|
||||
taskResource, err := storage.UpdateResource(taskUID, taskContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the result CALDAV contains the new relation:
|
||||
content, _ := taskResource.GetContentData()
|
||||
assert.Contains(t, content, "UID:"+taskUID)
|
||||
assert.NotContains(t, content, "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task")
|
||||
|
||||
// Get the task from the DB:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{taskUID}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
|
||||
// Check that the parent-child relationship is gone:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindParenttask], 0)
|
||||
|
||||
// Get the previous parent from the DB and check that its child is gone:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{"uid-caldav-test-parent-task"}, u)
|
||||
assert.NoError(t, err)
|
||||
task = tasks[0]
|
||||
// We're gone, but our former sibling is still there:
|
||||
assert.Len(t, task.RelatedTasks[models.RelationKindSubtask], 1)
|
||||
formerSiblingSubTask := task.RelatedTasks[models.RelationKindSubtask][0]
|
||||
assert.Equal(t, "uid-caldav-test-child-task-2", formerSiblingSubTask.UID)
|
||||
})
|
||||
}
|
36
pkg/routes/caldav/main_test.go
Normal file
36
pkg/routes/caldav/main_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
// 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 Licensee 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 Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
// TestMain is the main test function used to bootstrap the test env
|
||||
func TestMain(m *testing.M) {
|
||||
config.InitDefaultConfig()
|
||||
files.InitTests(true)
|
||||
user.InitTests()
|
||||
models.SetupTests()
|
||||
|
||||
m.Run()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user
Why would you not want to load this? The files fixtures are mostly only required for file fixtures, but I feel like this adds unnecessary overhead.
So, the way the test infrastructure is currently configured on the main branch is that every HTTP request resets the database by loading all the fixtures.
This prevents us from being able to run more complicated integration tests, that require multiple HTTP calls.
The best example is the test with the grand children.
That test creates a hierarchy of tasks with multiple parent/child levels. It checks that the tasks can be synchronized in any order (example: grand-children first), with the end result being always consistent.
How would you achieve such an integration test, without being able to keep some state between HTTP requests the same way an actual live client would expect?
Shouldn't the relation be present already when the test starts? (loaded from fixtures)
The test you described should probably be multiple tests, one for the creation of a hierarchy, one for the modification, one to check if it is populated properly, etc.