mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-08-03 09:06:10 -05:00
Two categories of fixes: 1. Use defer s.Close() instead of explicit s.Close() to prevent session leaks when require.FailNow() triggers runtime.Goexit(), which skips explicit close calls but runs deferred functions. Leaked sessions hold SQLite write locks that block all subsequent fixture loading. 2. Add s.Commit() before db.AssertExists/db.AssertMissing calls. These assertion helpers query via the global engine (not the test session), so they cannot see uncommitted data from the session's transaction. For block-scoped sessions (kanban_task_bucket_test.go), wrap each block in an anonymous function so defer runs at block boundary rather than deferring to the enclosing test function.
1044 lines
28 KiB
Go
1044 lines
28 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/events"
|
|
"code.vikunja.io/api/pkg/user"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
func TestTask_Create(t *testing.T) {
|
|
usr := &user.User{
|
|
ID: 1,
|
|
Username: "user1",
|
|
Email: "user1@example.com",
|
|
}
|
|
|
|
// We only test creating a task here, the permissions are all well tested in the web tests.
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
Title: "Lorem",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 1,
|
|
}
|
|
err := task.Create(s, usr)
|
|
require.NoError(t, err)
|
|
// Assert getting a uid
|
|
assert.NotEmpty(t, task.UID)
|
|
// Assert getting a new index
|
|
assert.NotEmpty(t, task.Index)
|
|
assert.Equal(t, int64(33), task.Index)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
|
"id": task.ID,
|
|
"title": "Lorem",
|
|
"description": "Lorem Ipsum Dolor",
|
|
"project_id": 1,
|
|
"created_by_id": 1,
|
|
}, false)
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": task.ID,
|
|
"bucket_id": 1,
|
|
}, false)
|
|
|
|
events.AssertDispatched(t, &TaskCreatedEvent{})
|
|
})
|
|
t.Run("with reminders", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
Title: "Lorem",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 1,
|
|
DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.UTC),
|
|
StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.UTC),
|
|
EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.UTC),
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
RelativeTo: "due_date",
|
|
RelativePeriod: 1,
|
|
},
|
|
{
|
|
RelativeTo: "start_date",
|
|
RelativePeriod: -2,
|
|
},
|
|
{
|
|
RelativeTo: "end_date",
|
|
RelativePeriod: -1,
|
|
},
|
|
{
|
|
Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.UTC),
|
|
},
|
|
}}
|
|
err := task.Create(s, usr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 1, 0, time.UTC), task.Reminders[0].Reminder)
|
|
assert.Equal(t, int64(1), task.Reminders[0].RelativePeriod)
|
|
assert.Equal(t, ReminderRelationDueDate, task.Reminders[0].RelativeTo)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 8, 0, time.UTC), task.Reminders[1].Reminder)
|
|
assert.Equal(t, ReminderRelationStartDate, task.Reminders[1].RelativeTo)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 19, 0, time.UTC), task.Reminders[2].Reminder)
|
|
assert.Equal(t, ReminderRelationEndDate, task.Reminders[2].RelativeTo)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 23, 0, 0, 0, time.UTC), task.Reminders[3].Reminder)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("empty title", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
Title: "",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 1,
|
|
}
|
|
err := task.Create(s, usr)
|
|
require.Error(t, err)
|
|
assert.True(t, IsErrTaskCannotBeEmpty(err))
|
|
})
|
|
t.Run("nonexistant project", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
Title: "Test",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 9999999,
|
|
}
|
|
err := task.Create(s, usr)
|
|
require.Error(t, err)
|
|
assert.True(t, IsErrProjectDoesNotExist(err))
|
|
})
|
|
t.Run("nonexistant user", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
nUser := &user.User{ID: 99999999}
|
|
task := &Task{
|
|
Title: "Test",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 1,
|
|
}
|
|
err := task.Create(s, nUser)
|
|
require.Error(t, err)
|
|
assert.True(t, user.IsErrUserDoesNotExist(err))
|
|
})
|
|
t.Run("default bucket different", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
Title: "Lorem",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 6,
|
|
}
|
|
err := task.Create(s, usr)
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.Commit())
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": task.ID,
|
|
"bucket_id": 22, // default bucket of project 6 but with a position of 2
|
|
}, false)
|
|
})
|
|
}
|
|
|
|
func TestTask_Update(t *testing.T) {
|
|
u := &user.User{ID: 1}
|
|
|
|
t.Run("normal", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
Title: "test10000",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 1,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
|
"id": 1,
|
|
"title": "test10000",
|
|
"description": "Lorem Ipsum Dolor",
|
|
"project_id": 1,
|
|
}, false)
|
|
})
|
|
t.Run("nonexistant task", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 9999999,
|
|
Title: "test10000",
|
|
Description: "Lorem Ipsum Dolor",
|
|
ProjectID: 1,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.Error(t, err)
|
|
assert.True(t, IsErrTaskDoesNotExist(err))
|
|
})
|
|
t.Run("default bucket when moving a task between projects", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
ProjectID: 2,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": task.ID,
|
|
// bucket 40 is the default bucket on project 2
|
|
"bucket_id": 40,
|
|
}, false)
|
|
})
|
|
t.Run("marking a task as done should move it to the done bucket", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
Done: true,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
assert.True(t, task.Done)
|
|
|
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
|
"id": 1,
|
|
"done": true,
|
|
}, false)
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": 1,
|
|
"bucket_id": 3,
|
|
}, false)
|
|
})
|
|
t.Run("marking a task as done should fire exactly ONE task.updated event", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
// Clear any events from previous operations
|
|
events.ClearDispatchedEvents()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
Done: true,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
// Verify exactly ONE task.updated event was dispatched
|
|
count := events.CountDispatchedEvents("task.updated")
|
|
assert.Equal(t, 1, count, "Expected exactly 1 task.updated event, got %d", count)
|
|
})
|
|
t.Run("move task to another project should use the default bucket", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
ProjectID: 2,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
|
"id": 1,
|
|
"project_id": 2,
|
|
}, false)
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": 1,
|
|
"bucket_id": 40,
|
|
}, false)
|
|
})
|
|
t.Run("move done task to another project with a done bucket", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 2,
|
|
Done: true,
|
|
ProjectID: 2,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
|
"id": task.ID,
|
|
"project_id": 2,
|
|
"done": true,
|
|
}, false)
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": task.ID,
|
|
"bucket_id": 4, // 4 is the done bucket
|
|
}, false)
|
|
})
|
|
t.Run("repeating tasks should not be moved to the done bucket", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 28,
|
|
Done: true,
|
|
RepeatAfter: 3600,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
assert.False(t, task.Done)
|
|
|
|
db.AssertExists(t, "tasks", map[string]interface{}{
|
|
"id": 28,
|
|
"done": false,
|
|
}, false)
|
|
db.AssertExists(t, "task_buckets", map[string]interface{}{
|
|
"task_id": 28,
|
|
"bucket_id": 1,
|
|
}, false)
|
|
})
|
|
t.Run("repeating tasks should set done_at when marked done", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
// Get the task before updating to check done_at was empty
|
|
taskBefore := &Task{ID: 28}
|
|
err := taskBefore.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.True(t, taskBefore.DoneAt.IsZero())
|
|
assert.False(t, taskBefore.Done)
|
|
|
|
// Mark the repeating task as done
|
|
task := &Task{
|
|
ID: 28,
|
|
Done: true,
|
|
RepeatAfter: 3600,
|
|
}
|
|
err = task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
// Task should be reset to not done (because it repeats) but done_at should be set
|
|
assert.False(t, task.Done)
|
|
assert.False(t, task.DoneAt.IsZero(), "done_at should be set for repeating tasks when marked as done")
|
|
|
|
// Verify in database
|
|
updatedTask := &Task{ID: 28}
|
|
err = updatedTask.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.False(t, updatedTask.Done)
|
|
assert.False(t, updatedTask.DoneAt.IsZero(), "done_at should be persisted in database for repeating tasks")
|
|
})
|
|
t.Run("moving a task between projects should give it a correct index", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 12,
|
|
ProjectID: 2, // From project 1
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), task.Index)
|
|
})
|
|
|
|
t.Run("reminders will be updated", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
ProjectID: 1,
|
|
Title: "test",
|
|
DueDate: time.Date(2023, time.March, 7, 22, 5, 0, 0, time.UTC),
|
|
StartDate: time.Date(2023, time.March, 7, 22, 5, 10, 0, time.UTC),
|
|
EndDate: time.Date(2023, time.March, 7, 22, 5, 20, 0, time.UTC),
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
RelativeTo: "due_date",
|
|
RelativePeriod: 1,
|
|
},
|
|
{
|
|
RelativeTo: "start_date",
|
|
RelativePeriod: -2,
|
|
},
|
|
{
|
|
RelativeTo: "end_date",
|
|
RelativePeriod: -1,
|
|
},
|
|
{
|
|
Reminder: time.Date(2023, time.March, 7, 23, 0, 0, 0, time.UTC),
|
|
},
|
|
}}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 1, 0, time.UTC), task.Reminders[0].Reminder)
|
|
assert.Equal(t, int64(1), task.Reminders[0].RelativePeriod)
|
|
assert.Equal(t, ReminderRelationDueDate, task.Reminders[0].RelativeTo)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 8, 0, time.UTC), task.Reminders[1].Reminder)
|
|
assert.Equal(t, ReminderRelationStartDate, task.Reminders[1].RelativeTo)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 22, 5, 19, 0, time.UTC), task.Reminders[2].Reminder)
|
|
assert.Equal(t, ReminderRelationEndDate, task.Reminders[2].RelativeTo)
|
|
assert.Equal(t, time.Date(2023, time.March, 7, 23, 0, 0, 0, time.UTC), task.Reminders[3].Reminder)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 4)
|
|
})
|
|
t.Run("the same reminder multiple times should be saved once", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
Title: "test",
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
Reminder: time.Unix(1674745156, 0),
|
|
},
|
|
{
|
|
Reminder: time.Unix(1674745156, 223),
|
|
},
|
|
},
|
|
ProjectID: 1,
|
|
}
|
|
err := task.Update(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 1)
|
|
})
|
|
t.Run("update relative reminder when start_date changes", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
// given task with start_date and relative reminder for start_date
|
|
taskBefore := &Task{
|
|
Title: "test",
|
|
ProjectID: 1,
|
|
StartDate: time.Date(2022, time.March, 8, 8, 5, 20, 0, time.UTC),
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
RelativeTo: "start_date",
|
|
RelativePeriod: -60,
|
|
},
|
|
}}
|
|
err := taskBefore.Create(s, u)
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Date(2022, time.March, 8, 8, 4, 20, 0, time.UTC), taskBefore.Reminders[0].Reminder)
|
|
|
|
// when start_date is modified
|
|
task := taskBefore
|
|
task.StartDate = time.Date(2023, time.March, 8, 8, 5, 0, 0, time.UTC)
|
|
err = task.Update(s, u)
|
|
require.NoError(t, err)
|
|
|
|
// then reminder time is updated
|
|
assert.Equal(t, time.Date(2023, time.March, 8, 8, 4, 0, 0, time.UTC), task.Reminders[0].Reminder)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("don't allow done_at change when passing fields", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
DoneAt: time.Now(),
|
|
}
|
|
|
|
err := task.updateSingleTask(s, u, []string{"done_at"})
|
|
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), `Task column done_at is invalid`)
|
|
require.NoError(t, s.Commit())
|
|
})
|
|
t.Run("ignore done_at when updating unrelated values", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
Title: "updated",
|
|
DoneAt: time.Now(),
|
|
}
|
|
|
|
err := task.Update(s, u)
|
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, s.Commit())
|
|
|
|
updatedTask := &Task{ID: 1}
|
|
err = updatedTask.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "updated", updatedTask.Title)
|
|
assert.True(t, updatedTask.DoneAt.IsZero())
|
|
})
|
|
}
|
|
|
|
func TestTask_Delete(t *testing.T) {
|
|
t.Run("normal", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{
|
|
ID: 1,
|
|
}
|
|
err := task.Delete(s, &user.User{ID: 1})
|
|
require.NoError(t, err)
|
|
err = s.Commit()
|
|
require.NoError(t, err)
|
|
|
|
db.AssertMissing(t, "tasks", map[string]interface{}{
|
|
"id": 1,
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestUpdateTasksHelper(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
u := &user.User{ID: 1}
|
|
updates := &Task{Title: "helper"}
|
|
updated, err := updateTasks(s, u, updates, []int64{10}, []string{"title"})
|
|
require.NoError(t, err)
|
|
require.Len(t, updated, 1)
|
|
assert.Equal(t, "helper", updated[0].Title)
|
|
assert.False(t, updated[0].Done)
|
|
}
|
|
|
|
func TestUpdateDone(t *testing.T) {
|
|
t.Run("marking a task as done", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
oldTask := &Task{Done: false}
|
|
newTask := &Task{Done: true}
|
|
updateDone(oldTask, newTask)
|
|
assert.NotEqual(t, time.Time{}, newTask.DoneAt)
|
|
})
|
|
t.Run("unmarking a task as done", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
oldTask := &Task{Done: true}
|
|
newTask := &Task{Done: false}
|
|
updateDone(oldTask, newTask)
|
|
assert.Equal(t, time.Time{}, newTask.DoneAt)
|
|
})
|
|
t.Run("no interval set, default repeat mode", func(t *testing.T) {
|
|
dueDate := time.Unix(1550000000, 0)
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 0,
|
|
RepeatMode: TaskRepeatModeDefault,
|
|
DueDate: dueDate,
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
DueDate: dueDate,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
assert.Equal(t, dueDate.Unix(), newTask.DueDate.Unix())
|
|
assert.True(t, newTask.Done)
|
|
})
|
|
t.Run("repeating interval", func(t *testing.T) {
|
|
t.Run("normal", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
DueDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
var expected = time.Unix(1550008600, 0)
|
|
for time.Since(expected) > 0 {
|
|
expected = expected.Add(time.Second * time.Duration(oldTask.RepeatAfter))
|
|
}
|
|
|
|
assert.Equal(t, expected, newTask.DueDate)
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("don't update if due date is zero", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
DueDate: time.Time{},
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
DueDate: time.Unix(1543626724, 0),
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
assert.Equal(t, time.Unix(1543626724, 0), newTask.DueDate)
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("update reminders", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
Reminder: time.Unix(1550000000, 0),
|
|
},
|
|
{
|
|
Reminder: time.Unix(1555000000, 0),
|
|
},
|
|
},
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
var expected1 = time.Unix(1550008600, 0)
|
|
var expected2 = time.Unix(1555008600, 0)
|
|
for time.Since(expected1) > 0 {
|
|
expected1 = expected1.Add(time.Duration(oldTask.RepeatAfter) * time.Second)
|
|
}
|
|
for time.Since(expected2) > 0 {
|
|
expected2 = expected2.Add(time.Duration(oldTask.RepeatAfter) * time.Second)
|
|
}
|
|
|
|
assert.Len(t, newTask.Reminders, 2)
|
|
assert.Equal(t, expected1, newTask.Reminders[0].Reminder)
|
|
assert.Equal(t, expected2, newTask.Reminders[1].Reminder)
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("update start date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
StartDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
var expected = time.Unix(1550008600, 0)
|
|
for time.Since(expected) > 0 {
|
|
expected = expected.Add(time.Second * time.Duration(oldTask.RepeatAfter))
|
|
}
|
|
|
|
assert.Equal(t, expected, newTask.StartDate)
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("update end date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
EndDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
var expected = time.Unix(1550008600, 0)
|
|
for time.Since(expected) > 0 {
|
|
expected = expected.Add(time.Second * time.Duration(oldTask.RepeatAfter))
|
|
}
|
|
|
|
assert.Equal(t, expected, newTask.EndDate)
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("ensure due date is repeated even if the original one is in the future", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
DueDate: time.Now().Add(time.Hour),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
expected := oldTask.DueDate.Add(time.Duration(oldTask.RepeatAfter) * time.Second)
|
|
assert.Equal(t, expected, newTask.DueDate)
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("repeat from current date", func(t *testing.T) {
|
|
t.Run("due date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
RepeatMode: TaskRepeatModeFromCurrentDate,
|
|
DueDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
|
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.DueDate.Unix())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("reminders", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
RepeatMode: TaskRepeatModeFromCurrentDate,
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
Reminder: time.Unix(1550000000, 0),
|
|
},
|
|
{
|
|
Reminder: time.Unix(1555000000, 0),
|
|
},
|
|
}}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
diff := oldTask.Reminders[1].Reminder.Sub(oldTask.Reminders[0].Reminder)
|
|
|
|
assert.Len(t, newTask.Reminders, 2)
|
|
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
|
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[0].Reminder.Unix())
|
|
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.Reminders[1].Reminder.Unix())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("start date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
RepeatMode: TaskRepeatModeFromCurrentDate,
|
|
StartDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
|
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.StartDate.Unix())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("end date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
RepeatMode: TaskRepeatModeFromCurrentDate,
|
|
EndDate: time.Unix(1560000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
|
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.EndDate.Unix())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("start and end date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatAfter: 8600,
|
|
RepeatMode: TaskRepeatModeFromCurrentDate,
|
|
StartDate: time.Unix(1550000000, 0),
|
|
EndDate: time.Unix(1560000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
updateDone(oldTask, newTask)
|
|
|
|
diff := oldTask.EndDate.Sub(oldTask.StartDate)
|
|
|
|
// Only comparing unix timestamps because time.Time use nanoseconds which can't ever possibly have the same value
|
|
assert.Equal(t, time.Now().Add(time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.StartDate.Unix())
|
|
assert.Equal(t, time.Now().Add(diff+time.Duration(oldTask.RepeatAfter)*time.Second).Unix(), newTask.EndDate.Unix())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
})
|
|
t.Run("repeat each month", func(t *testing.T) {
|
|
t.Run("due date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatMode: TaskRepeatModeMonth,
|
|
DueDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
oldDueDate := oldTask.DueDate
|
|
|
|
updateDone(oldTask, newTask)
|
|
|
|
assert.True(t, newTask.DueDate.After(oldDueDate))
|
|
assert.NotEqual(t, oldDueDate.Month(), newTask.DueDate.Month())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("reminders", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatMode: TaskRepeatModeMonth,
|
|
Reminders: []*TaskReminder{
|
|
{
|
|
Reminder: time.Unix(1550000000, 0),
|
|
},
|
|
{
|
|
Reminder: time.Unix(1555000000, 0),
|
|
},
|
|
}}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
oldReminders := make([]time.Time, len(oldTask.Reminders))
|
|
for i, r := range newTask.Reminders {
|
|
oldReminders[i] = r.Reminder
|
|
}
|
|
|
|
updateDone(oldTask, newTask)
|
|
|
|
assert.Len(t, newTask.Reminders, len(oldReminders))
|
|
for i, r := range newTask.Reminders {
|
|
assert.True(t, r.Reminder.After(oldReminders[i]))
|
|
assert.NotEqual(t, oldReminders[i].Month(), r.Reminder.Month())
|
|
}
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("start date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatMode: TaskRepeatModeMonth,
|
|
StartDate: time.Unix(1550000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
oldStartDate := oldTask.StartDate
|
|
|
|
updateDone(oldTask, newTask)
|
|
|
|
assert.True(t, newTask.StartDate.After(oldStartDate))
|
|
assert.NotEqual(t, oldStartDate.Month(), newTask.StartDate.Month())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("end date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatMode: TaskRepeatModeMonth,
|
|
EndDate: time.Unix(1560000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
oldEndDate := oldTask.EndDate
|
|
|
|
updateDone(oldTask, newTask)
|
|
|
|
assert.True(t, newTask.EndDate.After(oldEndDate))
|
|
assert.NotEqual(t, oldEndDate.Month(), newTask.EndDate.Month())
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
t.Run("start and end date", func(t *testing.T) {
|
|
oldTask := &Task{
|
|
Done: false,
|
|
RepeatMode: TaskRepeatModeMonth,
|
|
StartDate: time.Unix(1550000000, 0),
|
|
EndDate: time.Unix(1560000000, 0),
|
|
}
|
|
newTask := &Task{
|
|
Done: true,
|
|
}
|
|
oldStartDate := oldTask.StartDate
|
|
oldEndDate := oldTask.EndDate
|
|
oldDiff := oldTask.EndDate.Sub(oldTask.StartDate)
|
|
|
|
updateDone(oldTask, newTask)
|
|
|
|
assert.True(t, newTask.StartDate.After(oldStartDate))
|
|
assert.NotEqual(t, oldStartDate.Month(), newTask.StartDate.Month())
|
|
assert.True(t, newTask.EndDate.After(oldEndDate))
|
|
assert.NotEqual(t, oldEndDate.Month(), newTask.EndDate.Month())
|
|
assert.Equal(t, oldDiff, newTask.EndDate.Sub(newTask.StartDate))
|
|
assert.False(t, newTask.Done)
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestTask_ReadOne(t *testing.T) {
|
|
u := &user.User{ID: 1}
|
|
|
|
t.Run("default", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{ID: 1}
|
|
err := task.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "task #1", task.Title)
|
|
})
|
|
t.Run("nonexisting", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{ID: 99999}
|
|
err := task.ReadOne(s, u)
|
|
require.Error(t, err)
|
|
assert.True(t, IsErrTaskDoesNotExist(err))
|
|
})
|
|
t.Run("with subscription", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{ID: 22}
|
|
err := task.ReadOne(s, &user.User{ID: 6})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, task.Subscription)
|
|
})
|
|
t.Run("created by link share", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{ID: 37}
|
|
err := task.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "task #37", task.Title)
|
|
assert.Equal(t, int64(-2), task.CreatedByID)
|
|
assert.NotNil(t, task.CreatedBy)
|
|
assert.Equal(t, int64(-2), task.CreatedBy.ID)
|
|
})
|
|
t.Run("favorite", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{ID: 1}
|
|
err := task.ReadOne(s, u)
|
|
require.NoError(t, err)
|
|
assert.True(t, task.IsFavorite)
|
|
})
|
|
t.Run("favorite for a different user", func(t *testing.T) {
|
|
db.LoadAndAssertFixtures(t)
|
|
s := db.NewSession()
|
|
defer s.Close()
|
|
|
|
task := &Task{ID: 1}
|
|
err := task.ReadOne(s, &user.User{ID: 2})
|
|
require.NoError(t, err)
|
|
assert.False(t, task.IsFavorite)
|
|
})
|
|
}
|
|
|
|
func Test_getTaskIndexFromSearchString(t *testing.T) {
|
|
type args struct {
|
|
s string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantIndex int64
|
|
}{
|
|
{
|
|
name: "task index in text",
|
|
args: args{s: "Task #12"},
|
|
wantIndex: 12,
|
|
},
|
|
{
|
|
name: "no task index",
|
|
args: args{s: "Task"},
|
|
wantIndex: 0,
|
|
},
|
|
{
|
|
name: "not numeric but with prefix",
|
|
args: args{s: "Task #aaaaa"},
|
|
wantIndex: 0,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if gotIndex := getTaskIndexFromSearchString(tt.args.s); gotIndex != tt.wantIndex {
|
|
t.Errorf("getTaskIndexFromSearchString() = %v, want %v", gotIndex, tt.wantIndex)
|
|
}
|
|
})
|
|
}
|
|
}
|