feat(caldav): Add support for subtasks (i.e. RELATED-TO
property) in CalDAV (#1634)
As I mentioned [here](https://kolaente.dev/vikunja/api/pulls/1442#issuecomment-55215), this is mainly a cleanup of @zewaren 's original [PR](https://kolaente.dev/vikunja/api/pulls/1442). It adds support for the `RELATED-TO` property in CalDAV's `VTODO` and the `RELTYPE=PARENT` and `RELTYPE=CHILD` relationships. In other words, it allows for `ParentTask->SubTask` relations to be handled supported through CalDAV. In addition to the included tests, this has been tested by both @zewaren & myself with DAVx5 & Tasks (Android) and it's been working great. Resolves https://kolaente.dev/vikunja/api/issues/1345 Co-authored-by: Miguel A. Arroyo <miguel@codeheads.dev> Co-authored-by: Erwan Martin <public@fzwte.net> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1634 Reviewed-by: konrad <k@knt.li> Co-authored-by: Miguel Arroyo <mayanez@noreply.kolaente.de> Co-committed-by: Miguel Arroyo <mayanez@noreply.kolaente.de>
This commit is contained in:
@ -17,6 +17,7 @@
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -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,91 @@ func persistLabels(s *xorm.Session, a web.Auth, task *models.Task, labels []*mod
|
||||
return task.UpdateTaskLabels(s, a, labels)
|
||||
}
|
||||
|
||||
func removeStaleRelations(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}
|
||||
// FIXME: Optimize to get only required attributes (ie. RelatedTasks).
|
||||
err = existingTask.ReadOne(s, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for relationKind, relatedTasks := range existingTask.RelatedTasks {
|
||||
|
||||
for _, relatedTask := range relatedTasks {
|
||||
relationInNewList := slices.ContainsFunc(newRelations[relationKind], func(newRelation *models.Task) bool { return newRelation.UID == relatedTask.UID })
|
||||
|
||||
if !relationInNewList {
|
||||
rel := models.TaskRelation{
|
||||
TaskID: task.ID,
|
||||
OtherTaskID: relatedTask.ID,
|
||||
RelationKind: relationKind,
|
||||
}
|
||||
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) {
|
||||
|
||||
err = removeStaleRelations(s, a, task, newRelations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the current relations exist:
|
||||
for relationType, relatedTasksInVTODO := range newRelations {
|
||||
// Persist each relation independently:
|
||||
for _, relatedTaskInVTODO := range relatedTasksInVTODO {
|
||||
|
||||
var relatedTask *models.Task
|
||||
createDummy := false
|
||||
|
||||
// Get the task from the DB:
|
||||
relatedTaskInDB, err := models.GetTaskSimpleByUUID(s, relatedTaskInVTODO.UID)
|
||||
if err != nil {
|
||||
relatedTask = relatedTaskInVTODO
|
||||
createDummy = true
|
||||
} else {
|
||||
relatedTask = relatedTaskInDB
|
||||
}
|
||||
|
||||
// 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.
|
||||
// In the worst case, this was an error by the client and we are left with
|
||||
// this dummy task to clean up.
|
||||
if createDummy {
|
||||
relatedTask.ProjectID = task.ProjectID
|
||||
relatedTask.Title = "DUMMY-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
|
||||
|
484
pkg/routes/caldav/listStorageProvider_test.go
Normal file
484
pkg/routes/caldav/listStorageProvider_test.go
Normal file
@ -0,0 +1,484 @@
|
||||
// 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/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/samedi/caldav-go/data"
|
||||
"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",
|
||||
}
|
||||
|
||||
config.InitDefaultConfig()
|
||||
files.InitTests()
|
||||
user.InitTests()
|
||||
models.SetupTests()
|
||||
|
||||
//
|
||||
// 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 taskUIDChild = "uid_child1"
|
||||
const taskContentChild = `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: taskUIDChild},
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Create the subtask:
|
||||
_, err := storage.CreateResource(taskUIDChild, taskContentChild)
|
||||
assert.NoError(t, err)
|
||||
|
||||
const taskUID = "uid_grand_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_grand_child1
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav grand child task 1
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
RELATED-TO;RELTYPE=PARENT:uid_child1
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
storage = &VikunjaCaldavProjectStorage{
|
||||
project: &models.ProjectWithTasksAndBuckets{Project: models.Project{ID: 36}},
|
||||
task: &models.Task{UID: taskUID},
|
||||
user: u,
|
||||
}
|
||||
|
||||
// Create the task:
|
||||
var taskResource *data.Resource
|
||||
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_child1")
|
||||
|
||||
// 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_child1", parentTask.UID)
|
||||
|
||||
// Get the child task and check that it now has a parent and a child:
|
||||
tasks, err = models.GetTasksByUIDs(s, []string{"uid_child1"}, 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_Update(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
|
||||
RELATED-TO;RELTYPE=CHILD:uid-caldav-test-child-task
|
||||
RELATED-TO;RELTYPE=CHILD:uid-caldav-test-child-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:
|
||||
_, 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)
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user