diff --git a/pkg/db/fixtures/projects.yml b/pkg/db/fixtures/projects.yml index 86d2cba24..5ab8d6536 100644 --- a/pkg/db/fixtures/projects.yml +++ b/pkg/db/fixtures/projects.yml @@ -2,7 +2,7 @@ id: 1 title: Test1 description: Lorem Ipsum - identifier: test1 + identifier: TEST1 owner_id: 1 position: 3 updated: 2018-12-02 15:13:12 @@ -11,7 +11,7 @@ id: 2 title: Test2 description: Lorem Ipsum - identifier: test2 + identifier: TEST2 owner_id: 3 position: 2 updated: 2018-12-02 15:13:12 @@ -20,7 +20,7 @@ id: 3 title: Test3 description: Lorem Ipsum - identifier: test3 + identifier: TEST3 owner_id: 3 position: 1 updated: 2018-12-02 15:13:12 @@ -29,7 +29,7 @@ id: 4 title: Test4 description: Lorem Ipsum - identifier: test4 + identifier: TEST4 owner_id: 3 position: 4 updated: 2018-12-02 15:13:12 @@ -38,7 +38,7 @@ id: 5 title: Test5 description: Lorem Ipsum - identifier: test5 + identifier: TEST5 owner_id: 5 position: 5 updated: 2018-12-02 15:13:12 @@ -47,7 +47,7 @@ id: 6 title: Test6 description: Lorem Ipsum - identifier: test6 + identifier: TEST6 owner_id: 6 position: 6 updated: 2018-12-02 15:13:12 @@ -56,7 +56,7 @@ id: 7 title: Test7 description: Lorem Ipsum - identifier: test7 + identifier: TEST7 owner_id: 6 position: 7 updated: 2018-12-02 15:13:12 @@ -65,7 +65,7 @@ id: 8 title: Test8 description: Lorem Ipsum - identifier: test8 + identifier: TEST8 owner_id: 6 position: 8 updated: 2018-12-02 15:13:12 @@ -74,7 +74,7 @@ id: 9 title: Test9 description: Lorem Ipsum - identifier: test9 + identifier: TEST9 owner_id: 6 position: 9 updated: 2018-12-02 15:13:12 @@ -83,7 +83,7 @@ id: 10 title: Test10 description: Lorem Ipsum - identifier: test10 + identifier: TEST10 owner_id: 6 position: 10 updated: 2018-12-02 15:13:12 @@ -92,7 +92,7 @@ id: 11 title: Test11 description: Lorem Ipsum - identifier: test11 + identifier: TEST11 owner_id: 6 position: 11 updated: 2018-12-02 15:13:12 @@ -101,7 +101,7 @@ id: 12 title: Test12 description: Lorem Ipsum - identifier: test12 + identifier: TEST12 owner_id: 6 position: 12 parent_project_id: 27 @@ -111,7 +111,7 @@ id: 13 title: Test13 description: Lorem Ipsum - identifier: test13 + identifier: TEST13 owner_id: 6 position: 13 parent_project_id: 28 @@ -121,7 +121,7 @@ id: 14 title: Test14 description: Lorem Ipsum - identifier: test14 + identifier: TEST14 owner_id: 6 position: 14 parent_project_id: 29 @@ -131,7 +131,7 @@ id: 15 title: Test15 description: Lorem Ipsum - identifier: test15 + identifier: TEST15 owner_id: 6 position: 15 parent_project_id: 32 @@ -141,7 +141,7 @@ id: 16 title: Test16 description: Lorem Ipsum - identifier: test16 + identifier: TEST16 owner_id: 6 position: 16 parent_project_id: 33 @@ -151,7 +151,7 @@ id: 17 title: Test17 description: Lorem Ipsum - identifier: test17 + identifier: TEST17 owner_id: 6 position: 17 parent_project_id: 34 @@ -163,7 +163,7 @@ id: 18 title: Test18 description: Lorem Ipsum - identifier: test18 + identifier: TEST18 owner_id: 7 position: 18 updated: 2018-12-02 15:13:12 @@ -172,7 +172,7 @@ id: 19 title: Test19 description: Lorem Ipsum - identifier: test19 + identifier: TEST19 owner_id: 7 position: 19 parent_project_id: 29 @@ -183,7 +183,7 @@ id: 20 title: Test20 description: Lorem Ipsum - identifier: test20 + identifier: TEST20 owner_id: 13 position: 20 updated: 2018-12-02 15:13:12 @@ -192,7 +192,7 @@ id: 21 title: Test21 archived through parent list description: Lorem Ipsum - identifier: test21 + identifier: TEST21 owner_id: 1 position: 21 parent_project_id: 22 @@ -202,7 +202,7 @@ id: 22 title: Test22 archived individually description: Lorem Ipsum - identifier: test22 + identifier: TEST22 owner_id: 1 is_archived: 1 position: 22 @@ -212,7 +212,7 @@ id: 23 title: Test23 description: Lorem Ipsum - identifier: test23 + identifier: TEST23 owner_id: 12 position: 23 updated: 2018-12-02 15:13:12 @@ -221,7 +221,7 @@ id: 24 title: Test24 description: Lorem Ipsum - identifier: test6 + identifier: TEST6 owner_id: 6 position: 7 updated: 2018-12-02 15:13:12 @@ -302,7 +302,7 @@ id: 35 title: Test35 with background description: Lorem Ipsum - identifier: test6 + identifier: TEST6 owner_id: 6 background_file_id: 1 position: 8 @@ -312,7 +312,7 @@ id: 36 title: Project 36 for Caldav tests description: Lorem Ipsum - identifier: test36 + identifier: TEST36 owner_id: 15 position: 1 updated: 2018-12-02 15:13:12 @@ -321,7 +321,7 @@ id: 37 title: Project 37 description: Lorem Ipsum - identifier: test37 + identifier: TEST37 owner_id: 16 position: 1 updated: 2018-12-02 15:13:12 @@ -330,7 +330,7 @@ id: 38 title: Project 38 for Caldav tests description: Lorem Ipsum - identifier: test38 + identifier: TEST38 owner_id: 15 position: 2 updated: 2018-12-02 15:13:12 @@ -341,7 +341,7 @@ id: 39 title: Orphaned project with deleted parent description: This project has a parent_project_id pointing to a non-existent project - identifier: orph1 + identifier: ORPH1 owner_id: 1 parent_project_id: 999999 is_archived: 1 @@ -354,7 +354,7 @@ id: 40 title: Test40 child archived individually description: Lorem Ipsum - identifier: test40 + identifier: TEST40 owner_id: 1 parent_project_id: 3 is_archived: 1 @@ -366,7 +366,7 @@ id: 41 title: HierarchyParent description: Parent project for subtask permission hierarchy test - identifier: hier1 + identifier: HIER1 owner_id: 6 position: 41 updated: 2018-12-02 15:13:12 @@ -376,7 +376,7 @@ id: 42 title: HierarchyChild description: Child project for subtask permission hierarchy test - identifier: hier2 + identifier: HIER2 owner_id: 6 parent_project_id: 41 position: 42 diff --git a/pkg/migration/20260519120000.go b/pkg/migration/20260519120000.go new file mode 100644 index 000000000..cafb50a40 --- /dev/null +++ b/pkg/migration/20260519120000.go @@ -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 . + +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 + }, + }) +} diff --git a/pkg/models/project.go b/pkg/models/project.go index 55a297b26..c860ed062 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -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 if project.Identifier != "" { exists, err := s. diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 943181f17..e66d945b2 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -95,7 +95,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 1, Title: "task #1", Description: "Lorem Ipsum", - Identifier: "test1-1", + Identifier: "TEST1-1", Index: 1, CreatedByID: 1, CreatedBy: user1, @@ -168,7 +168,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task2 := &Task{ ID: 2, Title: "task #2 done", - Identifier: "test1-2", + Identifier: "TEST1-2", Index: 2, Done: true, CreatedByID: 1, @@ -198,7 +198,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task3 := &Task{ ID: 3, Title: "task #3 high prio", - Identifier: "test1-3", + Identifier: "TEST1-3", Index: 3, CreatedByID: 1, CreatedBy: user1, @@ -211,7 +211,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task4 := &Task{ ID: 4, Title: "task #4 low prio", - Identifier: "test1-4", + Identifier: "TEST1-4", Index: 4, CreatedByID: 1, CreatedBy: user1, @@ -224,7 +224,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task5 := &Task{ ID: 5, Title: "task #5 higher due date", - Identifier: "test1-5", + Identifier: "TEST1-5", Index: 5, CreatedByID: 1, CreatedBy: user1, @@ -238,7 +238,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 6, Title: "task #6 lower due date", Description: "This has something unique", - Identifier: "test1-6", + Identifier: "TEST1-6", Index: 6, CreatedByID: 1, CreatedBy: user1, @@ -251,7 +251,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task7 := &Task{ ID: 7, Title: "task #7 with start date", - Identifier: "test1-7", + Identifier: "TEST1-7", Index: 7, CreatedByID: 1, CreatedBy: user1, @@ -264,7 +264,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task8 := &Task{ ID: 8, Title: "task #8 with end date", - Identifier: "test1-8", + Identifier: "TEST1-8", Index: 8, CreatedByID: 1, CreatedBy: user1, @@ -277,7 +277,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task9 := &Task{ ID: 9, Title: "task #9 with start and end date", - Identifier: "test1-9", + Identifier: "TEST1-9", Index: 9, CreatedByID: 1, CreatedBy: user1, @@ -291,7 +291,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task10 := &Task{ ID: 10, Title: "task #10 basic", - Identifier: "test1-10", + Identifier: "TEST1-10", Index: 10, CreatedByID: 1, CreatedBy: user1, @@ -303,7 +303,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task11 := &Task{ ID: 11, Title: "task #11 basic", - Identifier: "test1-11", + Identifier: "TEST1-11", Index: 11, CreatedByID: 1, CreatedBy: user1, @@ -315,7 +315,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task12 := &Task{ ID: 12, Title: "task #12 basic", - Identifier: "test1-12", + Identifier: "TEST1-12", Index: 12, CreatedByID: 1, CreatedBy: user1, @@ -327,7 +327,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task15 := &Task{ ID: 15, Title: "task #15", - Identifier: "test6-1", + Identifier: "TEST6-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -340,7 +340,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task16 := &Task{ ID: 16, Title: "task #16", - Identifier: "test7-1", + Identifier: "TEST7-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -352,7 +352,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task17 := &Task{ ID: 17, Title: "task #17", - Identifier: "test8-1", + Identifier: "TEST8-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -364,7 +364,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task18 := &Task{ ID: 18, Title: "task #18", - Identifier: "test9-1", + Identifier: "TEST9-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -376,7 +376,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task19 := &Task{ ID: 19, Title: "task #19", - Identifier: "test10-1", + Identifier: "TEST10-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -388,7 +388,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task20 := &Task{ ID: 20, Title: "task #20", - Identifier: "test11-1", + Identifier: "TEST11-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -436,7 +436,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task24 := &Task{ ID: 24, Title: "task #24", - Identifier: "test15-1", + Identifier: "TEST15-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -448,7 +448,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task25 := &Task{ ID: 25, Title: "task #25", - Identifier: "test16-1", + Identifier: "TEST16-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -460,7 +460,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task26 := &Task{ ID: 26, Title: "task #26", - Identifier: "test17-1", + Identifier: "TEST17-1", Index: 1, CreatedByID: 6, CreatedBy: user6, @@ -472,7 +472,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task27 := &Task{ ID: 27, Title: "task #27 with reminders and start_date", - Identifier: "test1-18", + Identifier: "TEST1-18", Index: 18, CreatedByID: 1, CreatedBy: user1, @@ -501,7 +501,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task28 := &Task{ ID: 28, Title: "task #28 with repeat after, start_date, end_date and due_date", - Identifier: "test1-13", + Identifier: "TEST1-13", Index: 13, CreatedByID: 1, CreatedBy: user1, @@ -517,7 +517,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task29 := &Task{ ID: 29, Title: "task #29 with parent task (1)", - Identifier: "test1-14", + Identifier: "TEST1-14", Index: 14, CreatedByID: 1, CreatedBy: user1, @@ -543,7 +543,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task30 := &Task{ ID: 30, Title: "task #30 with assignees", - Identifier: "test1-15", + Identifier: "TEST1-15", Index: 15, CreatedByID: 1, CreatedBy: user1, @@ -559,7 +559,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31 := &Task{ ID: 31, Title: "task #31 with color", - Identifier: "test1-16", + Identifier: "TEST1-16", Index: 16, HexColor: "f0f0f0", CreatedByID: 1, @@ -572,7 +572,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task32 := &Task{ ID: 32, Title: "task #32", - Identifier: "test3-1", + Identifier: "TEST3-1", Index: 1, CreatedByID: 1, CreatedBy: user1, @@ -584,7 +584,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task33 := &Task{ ID: 33, Title: "task #33 with percent done", - Identifier: "test1-17", + Identifier: "TEST1-17", Index: 17, CreatedByID: 1, CreatedBy: user1, @@ -608,7 +608,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task47 := &Task{ ID: 47, Title: "task #47 with reminders outside window", - Identifier: "test1-32", + Identifier: "TEST1-32", Index: 32, CreatedByID: 1, CreatedBy: user1, @@ -635,7 +635,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 48, Title: "Landingpages update", Description: "Update all landingpages with new branding", - Identifier: "test1-33", + Identifier: "TEST1-33", Index: 33, CreatedByID: 1, CreatedBy: user1, diff --git a/pkg/webtests/task_collection_test.go b/pkg/webtests/task_collection_test.go index c8c295599..b87489d9d 100644 --- a/pkg/webtests/task_collection_test.go +++ b/pkg/webtests/task_collection_test.go @@ -132,49 +132,49 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) 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 t.Run("by due_date", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) 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 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) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams) 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) { _, 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) 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 t.Run("by due_date", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil) 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) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil) 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) { // Invalid parameter should not sort at all