Files
vikunja/pkg/models/task_delete_cron_test.go
kallegrens 82ee780161 feat(caldav): implement RFC 6578 sync-collection REPORT to sync task deletions
iOS Reminders (and other RFC 6578 clients) never learned about tasks
deleted on the Vikunja side: caldav-go answers unknown REPORT types
with 412, which makes iOS silently stop syncing, and deleted tasks left
no protocol-level trace a client could observe.

Intercept sync-collection REPORTs before caldav-go sees them and answer
per RFC 6578: tasks changed since the sync token as 200 entries and
deleted tasks as 404 entries. Deletion records come from task
soft-deletes, which are kept for 30 days before being purged.

Token edge cases: delta comparisons are inclusive because tokens have
second granularity (re-reported items keep their etag, so clients skip
the download), and tokens older than the soft-delete retention answer
403 + valid-sync-token to force a full resync instead of a delta that
would silently miss purged deletions.
2026-07-07 11:57:04 +00:00

52 lines
1.9 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package models
import (
"testing"
"time"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
)
func TestDeleteExpiredTasks(t *testing.T) {
// Task 51 was soft-deleted at this time in the fixtures
deletedAt := time.Date(2018, 12, 1, 1, 12, 4, 0, time.UTC)
t.Run("older than the retention period", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
files.InitTestFileFixtures(t)
deleteExpiredTasks(deletedAt.Add(TaskDeleteRetention + 24*time.Hour))
db.AssertMissing(t, "tasks", map[string]interface{}{"id": 51})
db.AssertMissing(t, "task_reminders", map[string]interface{}{"task_id": 51})
db.AssertMissing(t, "label_tasks", map[string]interface{}{"task_id": 51})
db.AssertMissing(t, "subscriptions", map[string]interface{}{"entity_id": 51, "entity_type": SubscriptionEntityTask})
})
t.Run("newer than the retention period", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
deleteExpiredTasks(deletedAt.Add(TaskDeleteRetention - 24*time.Hour))
db.AssertExists(t, "tasks", map[string]interface{}{"id": 51}, false)
db.AssertExists(t, "task_reminders", map[string]interface{}{"task_id": 51}, false)
})
}