feat(projects): always store identifiers as uppercase (#2775)

This commit is contained in:
kolaente 2026-05-19 10:35:43 +02:00 committed by GitHub
parent c761ab9761
commit 21ce33f8fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 190 additions and 77 deletions

View File

@ -2,7 +2,7 @@
id: 1 id: 1
title: Test1 title: Test1
description: Lorem Ipsum description: Lorem Ipsum
identifier: test1 identifier: TEST1
owner_id: 1 owner_id: 1
position: 3 position: 3
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -11,7 +11,7 @@
id: 2 id: 2
title: Test2 title: Test2
description: Lorem Ipsum description: Lorem Ipsum
identifier: test2 identifier: TEST2
owner_id: 3 owner_id: 3
position: 2 position: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -20,7 +20,7 @@
id: 3 id: 3
title: Test3 title: Test3
description: Lorem Ipsum description: Lorem Ipsum
identifier: test3 identifier: TEST3
owner_id: 3 owner_id: 3
position: 1 position: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -29,7 +29,7 @@
id: 4 id: 4
title: Test4 title: Test4
description: Lorem Ipsum description: Lorem Ipsum
identifier: test4 identifier: TEST4
owner_id: 3 owner_id: 3
position: 4 position: 4
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -38,7 +38,7 @@
id: 5 id: 5
title: Test5 title: Test5
description: Lorem Ipsum description: Lorem Ipsum
identifier: test5 identifier: TEST5
owner_id: 5 owner_id: 5
position: 5 position: 5
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -47,7 +47,7 @@
id: 6 id: 6
title: Test6 title: Test6
description: Lorem Ipsum description: Lorem Ipsum
identifier: test6 identifier: TEST6
owner_id: 6 owner_id: 6
position: 6 position: 6
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -56,7 +56,7 @@
id: 7 id: 7
title: Test7 title: Test7
description: Lorem Ipsum description: Lorem Ipsum
identifier: test7 identifier: TEST7
owner_id: 6 owner_id: 6
position: 7 position: 7
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -65,7 +65,7 @@
id: 8 id: 8
title: Test8 title: Test8
description: Lorem Ipsum description: Lorem Ipsum
identifier: test8 identifier: TEST8
owner_id: 6 owner_id: 6
position: 8 position: 8
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -74,7 +74,7 @@
id: 9 id: 9
title: Test9 title: Test9
description: Lorem Ipsum description: Lorem Ipsum
identifier: test9 identifier: TEST9
owner_id: 6 owner_id: 6
position: 9 position: 9
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -83,7 +83,7 @@
id: 10 id: 10
title: Test10 title: Test10
description: Lorem Ipsum description: Lorem Ipsum
identifier: test10 identifier: TEST10
owner_id: 6 owner_id: 6
position: 10 position: 10
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -92,7 +92,7 @@
id: 11 id: 11
title: Test11 title: Test11
description: Lorem Ipsum description: Lorem Ipsum
identifier: test11 identifier: TEST11
owner_id: 6 owner_id: 6
position: 11 position: 11
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -101,7 +101,7 @@
id: 12 id: 12
title: Test12 title: Test12
description: Lorem Ipsum description: Lorem Ipsum
identifier: test12 identifier: TEST12
owner_id: 6 owner_id: 6
position: 12 position: 12
parent_project_id: 27 parent_project_id: 27
@ -111,7 +111,7 @@
id: 13 id: 13
title: Test13 title: Test13
description: Lorem Ipsum description: Lorem Ipsum
identifier: test13 identifier: TEST13
owner_id: 6 owner_id: 6
position: 13 position: 13
parent_project_id: 28 parent_project_id: 28
@ -121,7 +121,7 @@
id: 14 id: 14
title: Test14 title: Test14
description: Lorem Ipsum description: Lorem Ipsum
identifier: test14 identifier: TEST14
owner_id: 6 owner_id: 6
position: 14 position: 14
parent_project_id: 29 parent_project_id: 29
@ -131,7 +131,7 @@
id: 15 id: 15
title: Test15 title: Test15
description: Lorem Ipsum description: Lorem Ipsum
identifier: test15 identifier: TEST15
owner_id: 6 owner_id: 6
position: 15 position: 15
parent_project_id: 32 parent_project_id: 32
@ -141,7 +141,7 @@
id: 16 id: 16
title: Test16 title: Test16
description: Lorem Ipsum description: Lorem Ipsum
identifier: test16 identifier: TEST16
owner_id: 6 owner_id: 6
position: 16 position: 16
parent_project_id: 33 parent_project_id: 33
@ -151,7 +151,7 @@
id: 17 id: 17
title: Test17 title: Test17
description: Lorem Ipsum description: Lorem Ipsum
identifier: test17 identifier: TEST17
owner_id: 6 owner_id: 6
position: 17 position: 17
parent_project_id: 34 parent_project_id: 34
@ -163,7 +163,7 @@
id: 18 id: 18
title: Test18 title: Test18
description: Lorem Ipsum description: Lorem Ipsum
identifier: test18 identifier: TEST18
owner_id: 7 owner_id: 7
position: 18 position: 18
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -172,7 +172,7 @@
id: 19 id: 19
title: Test19 title: Test19
description: Lorem Ipsum description: Lorem Ipsum
identifier: test19 identifier: TEST19
owner_id: 7 owner_id: 7
position: 19 position: 19
parent_project_id: 29 parent_project_id: 29
@ -183,7 +183,7 @@
id: 20 id: 20
title: Test20 title: Test20
description: Lorem Ipsum description: Lorem Ipsum
identifier: test20 identifier: TEST20
owner_id: 13 owner_id: 13
position: 20 position: 20
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -192,7 +192,7 @@
id: 21 id: 21
title: Test21 archived through parent list title: Test21 archived through parent list
description: Lorem Ipsum description: Lorem Ipsum
identifier: test21 identifier: TEST21
owner_id: 1 owner_id: 1
position: 21 position: 21
parent_project_id: 22 parent_project_id: 22
@ -202,7 +202,7 @@
id: 22 id: 22
title: Test22 archived individually title: Test22 archived individually
description: Lorem Ipsum description: Lorem Ipsum
identifier: test22 identifier: TEST22
owner_id: 1 owner_id: 1
is_archived: 1 is_archived: 1
position: 22 position: 22
@ -212,7 +212,7 @@
id: 23 id: 23
title: Test23 title: Test23
description: Lorem Ipsum description: Lorem Ipsum
identifier: test23 identifier: TEST23
owner_id: 12 owner_id: 12
position: 23 position: 23
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -221,7 +221,7 @@
id: 24 id: 24
title: Test24 title: Test24
description: Lorem Ipsum description: Lorem Ipsum
identifier: test6 identifier: TEST6
owner_id: 6 owner_id: 6
position: 7 position: 7
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -302,7 +302,7 @@
id: 35 id: 35
title: Test35 with background title: Test35 with background
description: Lorem Ipsum description: Lorem Ipsum
identifier: test6 identifier: TEST6
owner_id: 6 owner_id: 6
background_file_id: 1 background_file_id: 1
position: 8 position: 8
@ -312,7 +312,7 @@
id: 36 id: 36
title: Project 36 for Caldav tests title: Project 36 for Caldav tests
description: Lorem Ipsum description: Lorem Ipsum
identifier: test36 identifier: TEST36
owner_id: 15 owner_id: 15
position: 1 position: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -321,7 +321,7 @@
id: 37 id: 37
title: Project 37 title: Project 37
description: Lorem Ipsum description: Lorem Ipsum
identifier: test37 identifier: TEST37
owner_id: 16 owner_id: 16
position: 1 position: 1
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -330,7 +330,7 @@
id: 38 id: 38
title: Project 38 for Caldav tests title: Project 38 for Caldav tests
description: Lorem Ipsum description: Lorem Ipsum
identifier: test38 identifier: TEST38
owner_id: 15 owner_id: 15
position: 2 position: 2
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -341,7 +341,7 @@
id: 39 id: 39
title: Orphaned project with deleted parent title: Orphaned project with deleted parent
description: This project has a parent_project_id pointing to a non-existent project description: This project has a parent_project_id pointing to a non-existent project
identifier: orph1 identifier: ORPH1
owner_id: 1 owner_id: 1
parent_project_id: 999999 parent_project_id: 999999
is_archived: 1 is_archived: 1
@ -354,7 +354,7 @@
id: 40 id: 40
title: Test40 child archived individually title: Test40 child archived individually
description: Lorem Ipsum description: Lorem Ipsum
identifier: test40 identifier: TEST40
owner_id: 1 owner_id: 1
parent_project_id: 3 parent_project_id: 3
is_archived: 1 is_archived: 1
@ -366,7 +366,7 @@
id: 41 id: 41
title: HierarchyParent title: HierarchyParent
description: Parent project for subtask permission hierarchy test description: Parent project for subtask permission hierarchy test
identifier: hier1 identifier: HIER1
owner_id: 6 owner_id: 6
position: 41 position: 41
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
@ -376,7 +376,7 @@
id: 42 id: 42
title: HierarchyChild title: HierarchyChild
description: Child project for subtask permission hierarchy test description: Child project for subtask permission hierarchy test
identifier: hier2 identifier: HIER2
owner_id: 6 owner_id: 6
parent_project_id: 41 parent_project_id: 41
position: 42 position: 42

View File

@ -0,0 +1,108 @@
// 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 migration
import (
"fmt"
"code.vikunja.io/api/pkg/log"
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20260519120000",
Description: "uppercase existing project identifiers",
Migrate: func(tx *xorm.Engine) error {
s := tx.NewSession()
defer s.Close()
if err := s.Begin(); err != nil {
return err
}
// Postgres/SQLite default to case-sensitive comparisons, so
// projects like "foo" and "FOO" may coexist today. Uppercasing
// them blindly would create duplicate identifiers and break the
// invariant that task identifiers built from them are unique.
// Detect each colliding group, keep the oldest project's
// identifier and clear the rest so the operator can re-assign
// them after the migration runs.
type collidingGroup struct {
UpperIdentifier string `xorm:"upper_identifier"`
}
var groups []collidingGroup
err := s.SQL(`
SELECT UPPER(identifier) AS upper_identifier FROM projects
WHERE identifier IS NOT NULL AND identifier <> ''
GROUP BY UPPER(identifier)
HAVING COUNT(*) > 1
`).Find(&groups)
if err != nil {
_ = s.Rollback()
return fmt.Errorf("failed to scan for colliding project identifiers: %w", err)
}
for _, g := range groups {
type projectRow struct {
ID int64
Identifier string
}
var rows []projectRow
err := s.SQL(
"SELECT id, identifier FROM projects WHERE UPPER(identifier) = ? ORDER BY id ASC",
g.UpperIdentifier,
).Find(&rows)
if err != nil {
_ = s.Rollback()
return err
}
if len(rows) < 2 {
continue
}
kept := rows[0]
for i := 1; i < len(rows); i++ {
log.Warningf(
"Project identifier collision during uppercase migration: clearing identifier %q on project %d (kept %q on project %d). Re-assign a unique identifier after the migration.",
rows[i].Identifier, rows[i].ID, kept.Identifier, kept.ID,
)
if _, err := s.Exec(
"UPDATE projects SET identifier = ? WHERE id = ?",
"", rows[i].ID,
); err != nil {
_ = s.Rollback()
return err
}
}
}
// UPPER() is supported by MySQL, PostgreSQL and SQLite.
if _, err := s.Exec("UPDATE projects SET identifier = UPPER(identifier) WHERE identifier IS NOT NULL AND identifier <> UPPER(identifier)"); err != nil {
_ = s.Rollback()
return err
}
return s.Commit()
},
Rollback: func(_ *xorm.Engine) error {
return nil
},
})
}

View File

@ -989,6 +989,11 @@ func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) (err er
} }
} }
// Identifiers are stored uppercase so lookups and the uniqueness check
// below behave consistently across DBs (Postgres/SQLite are
// case-sensitive by default, MySQL is not).
project.Identifier = strings.ToUpper(project.Identifier)
// Check if the identifier is unique and not empty // Check if the identifier is unique and not empty
if project.Identifier != "" { if project.Identifier != "" {
exists, err := s. exists, err := s.

View File

@ -95,7 +95,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
ID: 1, ID: 1,
Title: "task #1", Title: "task #1",
Description: "Lorem Ipsum", Description: "Lorem Ipsum",
Identifier: "test1-1", Identifier: "TEST1-1",
Index: 1, Index: 1,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -168,7 +168,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task2 := &Task{ task2 := &Task{
ID: 2, ID: 2,
Title: "task #2 done", Title: "task #2 done",
Identifier: "test1-2", Identifier: "TEST1-2",
Index: 2, Index: 2,
Done: true, Done: true,
CreatedByID: 1, CreatedByID: 1,
@ -198,7 +198,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task3 := &Task{ task3 := &Task{
ID: 3, ID: 3,
Title: "task #3 high prio", Title: "task #3 high prio",
Identifier: "test1-3", Identifier: "TEST1-3",
Index: 3, Index: 3,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -211,7 +211,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task4 := &Task{ task4 := &Task{
ID: 4, ID: 4,
Title: "task #4 low prio", Title: "task #4 low prio",
Identifier: "test1-4", Identifier: "TEST1-4",
Index: 4, Index: 4,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -224,7 +224,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task5 := &Task{ task5 := &Task{
ID: 5, ID: 5,
Title: "task #5 higher due date", Title: "task #5 higher due date",
Identifier: "test1-5", Identifier: "TEST1-5",
Index: 5, Index: 5,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -238,7 +238,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
ID: 6, ID: 6,
Title: "task #6 lower due date", Title: "task #6 lower due date",
Description: "This has something unique", Description: "This has something unique",
Identifier: "test1-6", Identifier: "TEST1-6",
Index: 6, Index: 6,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -251,7 +251,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task7 := &Task{ task7 := &Task{
ID: 7, ID: 7,
Title: "task #7 with start date", Title: "task #7 with start date",
Identifier: "test1-7", Identifier: "TEST1-7",
Index: 7, Index: 7,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -264,7 +264,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task8 := &Task{ task8 := &Task{
ID: 8, ID: 8,
Title: "task #8 with end date", Title: "task #8 with end date",
Identifier: "test1-8", Identifier: "TEST1-8",
Index: 8, Index: 8,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -277,7 +277,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task9 := &Task{ task9 := &Task{
ID: 9, ID: 9,
Title: "task #9 with start and end date", Title: "task #9 with start and end date",
Identifier: "test1-9", Identifier: "TEST1-9",
Index: 9, Index: 9,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -291,7 +291,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task10 := &Task{ task10 := &Task{
ID: 10, ID: 10,
Title: "task #10 basic", Title: "task #10 basic",
Identifier: "test1-10", Identifier: "TEST1-10",
Index: 10, Index: 10,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -303,7 +303,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task11 := &Task{ task11 := &Task{
ID: 11, ID: 11,
Title: "task #11 basic", Title: "task #11 basic",
Identifier: "test1-11", Identifier: "TEST1-11",
Index: 11, Index: 11,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -315,7 +315,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task12 := &Task{ task12 := &Task{
ID: 12, ID: 12,
Title: "task #12 basic", Title: "task #12 basic",
Identifier: "test1-12", Identifier: "TEST1-12",
Index: 12, Index: 12,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -327,7 +327,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task15 := &Task{ task15 := &Task{
ID: 15, ID: 15,
Title: "task #15", Title: "task #15",
Identifier: "test6-1", Identifier: "TEST6-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -340,7 +340,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task16 := &Task{ task16 := &Task{
ID: 16, ID: 16,
Title: "task #16", Title: "task #16",
Identifier: "test7-1", Identifier: "TEST7-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -352,7 +352,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task17 := &Task{ task17 := &Task{
ID: 17, ID: 17,
Title: "task #17", Title: "task #17",
Identifier: "test8-1", Identifier: "TEST8-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -364,7 +364,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task18 := &Task{ task18 := &Task{
ID: 18, ID: 18,
Title: "task #18", Title: "task #18",
Identifier: "test9-1", Identifier: "TEST9-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -376,7 +376,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task19 := &Task{ task19 := &Task{
ID: 19, ID: 19,
Title: "task #19", Title: "task #19",
Identifier: "test10-1", Identifier: "TEST10-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -388,7 +388,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task20 := &Task{ task20 := &Task{
ID: 20, ID: 20,
Title: "task #20", Title: "task #20",
Identifier: "test11-1", Identifier: "TEST11-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -436,7 +436,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task24 := &Task{ task24 := &Task{
ID: 24, ID: 24,
Title: "task #24", Title: "task #24",
Identifier: "test15-1", Identifier: "TEST15-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -448,7 +448,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task25 := &Task{ task25 := &Task{
ID: 25, ID: 25,
Title: "task #25", Title: "task #25",
Identifier: "test16-1", Identifier: "TEST16-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -460,7 +460,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task26 := &Task{ task26 := &Task{
ID: 26, ID: 26,
Title: "task #26", Title: "task #26",
Identifier: "test17-1", Identifier: "TEST17-1",
Index: 1, Index: 1,
CreatedByID: 6, CreatedByID: 6,
CreatedBy: user6, CreatedBy: user6,
@ -472,7 +472,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task27 := &Task{ task27 := &Task{
ID: 27, ID: 27,
Title: "task #27 with reminders and start_date", Title: "task #27 with reminders and start_date",
Identifier: "test1-18", Identifier: "TEST1-18",
Index: 18, Index: 18,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -501,7 +501,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task28 := &Task{ task28 := &Task{
ID: 28, ID: 28,
Title: "task #28 with repeat after, start_date, end_date and due_date", Title: "task #28 with repeat after, start_date, end_date and due_date",
Identifier: "test1-13", Identifier: "TEST1-13",
Index: 13, Index: 13,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -517,7 +517,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task29 := &Task{ task29 := &Task{
ID: 29, ID: 29,
Title: "task #29 with parent task (1)", Title: "task #29 with parent task (1)",
Identifier: "test1-14", Identifier: "TEST1-14",
Index: 14, Index: 14,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -543,7 +543,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task30 := &Task{ task30 := &Task{
ID: 30, ID: 30,
Title: "task #30 with assignees", Title: "task #30 with assignees",
Identifier: "test1-15", Identifier: "TEST1-15",
Index: 15, Index: 15,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -559,7 +559,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task31 := &Task{ task31 := &Task{
ID: 31, ID: 31,
Title: "task #31 with color", Title: "task #31 with color",
Identifier: "test1-16", Identifier: "TEST1-16",
Index: 16, Index: 16,
HexColor: "f0f0f0", HexColor: "f0f0f0",
CreatedByID: 1, CreatedByID: 1,
@ -572,7 +572,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task32 := &Task{ task32 := &Task{
ID: 32, ID: 32,
Title: "task #32", Title: "task #32",
Identifier: "test3-1", Identifier: "TEST3-1",
Index: 1, Index: 1,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -584,7 +584,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task33 := &Task{ task33 := &Task{
ID: 33, ID: 33,
Title: "task #33 with percent done", Title: "task #33 with percent done",
Identifier: "test1-17", Identifier: "TEST1-17",
Index: 17, Index: 17,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -608,7 +608,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task47 := &Task{ task47 := &Task{
ID: 47, ID: 47,
Title: "task #47 with reminders outside window", Title: "task #47 with reminders outside window",
Identifier: "test1-32", Identifier: "TEST1-32",
Index: 32, Index: 32,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,
@ -635,7 +635,7 @@ func TestTaskCollection_ReadAll(t *testing.T) {
ID: 48, ID: 48,
Title: "Landingpages update", Title: "Landingpages update",
Description: "Update all landingpages with new branding", Description: "Update all landingpages with new branding",
Identifier: "test1-33", Identifier: "TEST1-33",
Index: 33, Index: 33,
CreatedByID: 1, CreatedByID: 1,
CreatedBy: user1, CreatedBy: user1,

View File

@ -132,49 +132,49 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) { t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"TEST1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
t.Run("by priority desc", func(t *testing.T) { t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
}) })
t.Run("by priority asc", func(t *testing.T) { t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"TEST1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
// should equal duedate asc // should equal duedate asc
t.Run("by due_date", func(t *testing.T) { t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by duedate desc", func(t *testing.T) { t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
}) })
// Due date without unix suffix // Due date without unix suffix
t.Run("by duedate asc without suffix", func(t *testing.T) { t.Run("by duedate asc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by due_date without suffix", func(t *testing.T) { t.Run("by due_date without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by duedate desc without suffix", func(t *testing.T) { t.Run("by duedate desc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
}) })
t.Run("by duedate asc", func(t *testing.T) { t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("invalid sort parameter", func(t *testing.T) { t.Run("invalid sort parameter", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
@ -380,33 +380,33 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) { t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"TEST1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
t.Run("by priority desc", func(t *testing.T) { t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`) assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
}) })
t.Run("by priority asc", func(t *testing.T) { t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"TEST1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":39,"title":"task #39","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":25,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"#0","index":0,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":47,"title":"task #47 with reminders outside window","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":[{"reminder":"2018-08-01T12:00:00Z","relative_period":0,"relative_to":""},{"reminder":"2019-03-01T12:00:00Z","relative_period":0,"relative_to":""}],"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-32","index":32,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":48,"title":"Landingpages update","description":"Update all landingpages with new branding","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-33","index":33,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
}) })
// should equal duedate asc // should equal duedate asc
t.Run("by due_date", func(t *testing.T) { t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("by duedate desc", func(t *testing.T) { t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`) assert.Contains(t, rec.Body.String(), `[{"id":28,"title":"task #28 with repeat after, start_date, end_date and due_date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-02T22:25:24Z","reminders":null,"project_id":1,"repeat_after":3600,"repeat_mode":0,"priority":0,"start_date":"2018-11-30T22:25:24Z","end_date":"2018-12-13T11:20:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-13","index":13,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
}) })
t.Run("by duedate asc", func(t *testing.T) { t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil) rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`) assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"This has something unique","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"TEST1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":0,"position":0,"reactions":null,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
}) })
t.Run("invalid parameter", func(t *testing.T) { t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all // Invalid parameter should not sort at all