From 725361741a7f0d98b1c77066a92905b277da5f31 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 21:11:15 +0100 Subject: [PATCH 01/60] Start refactoring task sorting --- pkg/models/error.go | 54 +++++++++++++++++++ pkg/models/label.go | 1 - pkg/models/task_collection.go | 98 +++++++++++++++++++++++++++-------- pkg/models/tasks.go | 90 ++++++++++++++++++++++---------- 4 files changed, 193 insertions(+), 50 deletions(-) diff --git a/pkg/models/error.go b/pkg/models/error.go index 5839062be..317ef6a31 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -788,6 +788,60 @@ func (err ErrTaskAttachmentIsTooLarge) HTTPError() web.HTTPError { } } +// ErrInvalidSortParam represents an error where the provided sort param is invalid +type ErrInvalidSortParam struct { + SortBy string +} + +// IsErrInvalidSortParam checks if an error is ErrInvalidSortParam. +func IsErrInvalidSortParam(err error) bool { + _, ok := err.(ErrInvalidSortParam) + return ok +} + +func (err ErrInvalidSortParam) Error() string { + return fmt.Sprintf("Sort param is invalid [SortBy: %s]", err.SortBy) +} + +// ErrCodeInvalidSortParam holds the unique world-error code of this error +const ErrCodeInvalidSortParam = 4013 + +// HTTPError holds the http error description +func (err ErrInvalidSortParam) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeInvalidSortParam, + Message: fmt.Sprintf("The task sort param '%s' is invalid.", err.SortBy), + } +} + +// ErrInvalidSortOrder represents an error where the provided sort order is invalid +type ErrInvalidSortOrder struct { + OrderBy string +} + +// IsErrInvalidSortOrder checks if an error is ErrInvalidSortOrder. +func IsErrInvalidSortOrder(err error) bool { + _, ok := err.(ErrInvalidSortOrder) + return ok +} + +func (err ErrInvalidSortOrder) Error() string { + return fmt.Sprintf("Sort order is invalid [OrderBy: %s]", err.OrderBy) +} + +// ErrCodeInvalidSortOrder holds the unique world-error code of this error +const ErrCodeInvalidSortOrder = 4014 + +// HTTPError holds the http error description +func (err ErrInvalidSortOrder) HTTPError() web.HTTPError { + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeInvalidSortOrder, + Message: fmt.Sprintf("The task sort order '%s' is invalid. Allowed is either asc or desc.", err.OrderBy), + } +} + // ================= // Namespace errors // ================= diff --git a/pkg/models/label.go b/pkg/models/label.go index 88b2f31f3..18d93a842 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -209,7 +209,6 @@ func getUserTaskIDs(u *User) (taskIDs []int64, err error) { tasks, _, _, err := getRawTasksForLists(lists, &taskOptions{ startDate: time.Unix(0, 0), endDate: time.Unix(0, 0), - sortby: SortTasksByUnsorted, page: -1, perPage: 0, }) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index db79f953a..fb1f8f150 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -24,16 +24,58 @@ import ( // TaskCollection is a struct used to hold filter details and not clutter the Task struct with information not related to actual tasks. type TaskCollection struct { - ListID int64 `param:"list"` - Sorting string `query:"sort"` // Parameter to sort by - StartDateSortUnix int64 `query:"startdate"` - EndDateSortUnix int64 `query:"enddate"` + ListID int64 `param:"list"` + StartDateSortUnix int64 `query:"startdate"` + EndDateSortUnix int64 `query:"enddate"` Lists []*List + // The query parameter to sort by. This is for ex. done, priority, etc. + SortBy []string `query:"sort_by"` + // The query parameter to order the items by. This can be either asc or desc, with asc being the default. + OrderBy []string `query:"order_by"` + web.CRUDable `xorm:"-" json:"-"` web.Rights `xorm:"-" json:"-"` } +type sortParam struct { + sortBy string + orderBy string // asc or desc +} + +func (sp *sortParam) validate() error { + if sp.orderBy != "asc" && sp.orderBy != "desc" { + return ErrInvalidSortOrder{OrderBy: sp.orderBy} + } + switch sp.sortBy { + case + "id", + "text", + "description", + "done", + "done_at_unix", + "due_date_unix", + "created_by_id", + "list_id", + "repeat_after", + "priority", + "start_date_unix", + "end_data_unix", + "hex_color", + "percent_done", + "uid", + "created", + "updated": + return nil + } + return ErrInvalidSortParam{SortBy: sp.sortBy} +} + +// TODO: Docs +// TODO: Swagger +// TODO: Tests +// TODO: Sorting + // ReadAll gets all tasks for a collection // @Summary Get tasks on a list // @Description Returns all tasks for the current list. @@ -52,31 +94,45 @@ type TaskCollection struct { // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{listID}/tasks [get] func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { - var sortby SortBy - switch tf.Sorting { - case "priority": - sortby = SortTasksByPriorityDesc - case "prioritydesc": - sortby = SortTasksByPriorityDesc - case "priorityasc": - sortby = SortTasksByPriorityAsc - case "duedate": - sortby = SortTasksByDueDateDesc - case "duedatedesc": - sortby = SortTasksByDueDateDesc - case "duedateasc": - sortby = SortTasksByDueDateAsc - default: - sortby = SortTasksByUnsorted + //var sortby SortBy + //switch tf.Sorting { + //case "priority": + // sortby = SortTasksByPriorityDesc + //case "prioritydesc": + // sortby = SortTasksByPriorityDesc + //case "priorityasc": + // sortby = SortTasksByPriorityAsc + //case "duedate": + // sortby = SortTasksByDueDateDesc + //case "duedatedesc": + // sortby = SortTasksByDueDateDesc + //case "duedateasc": + // sortby = SortTasksByDueDateAsc + //default: + // sortby = SortTasksByUnsorted + //} + + var sort = make([]*sortParam, 0, len(tf.SortBy)) + for i, s := range tf.SortBy { + sortParam := &sortParam{ + sortBy: s, + orderBy: "asc", + } + // This checks if tf.OrderBy has an entry with the same index as the current entry from tf.SortBy + // Taken from https://stackoverflow.com/a/27252199/10924593 + if len(tf.OrderBy) > i { + sortParam.orderBy = tf.OrderBy[i] + } + sort = append(sort, sortParam) } taskopts := &taskOptions{ search: search, - sortby: sortby, startDate: time.Unix(tf.StartDateSortUnix, 0), endDate: time.Unix(tf.EndDateSortUnix, 0), page: page, perPage: perPage, + sortby: sort, } shareAuth, is := a.(*LinkSharing) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index eea98e3da..cc8d1cd2c 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -119,11 +119,11 @@ const ( type taskOptions struct { search string - sortby SortBy startDate time.Time endDate time.Time page int perPage int + sortby []*sortParam } // ReadAll is a dummy function to still have that endpoint documented @@ -154,16 +154,31 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T listIDs = append(listIDs, l.ID) } + //var orderby string + //switch opts.sortby { + //case SortTasksByPriorityDesc: + // orderby = "priority desc" + //case SortTasksByPriorityAsc: + // orderby = "priority asc" + //case SortTasksByDueDateDesc: + // orderby = "due_date_unix desc" + //case SortTasksByDueDateAsc: + // orderby = "due_date_unix asc" + //} + + // Since xorm does not use placeholders for order by, it is possible to expose this with sql injection if we're directly + // passing user input to the db. + // As a workaround to prevent this, we check for valid column names here prior to passing it to the db. var orderby string - switch opts.sortby { - case SortTasksByPriorityDesc: - orderby = "priority desc" - case SortTasksByPriorityAsc: - orderby = "priority asc" - case SortTasksByDueDateDesc: - orderby = "due_date_unix desc" - case SortTasksByDueDateAsc: - orderby = "due_date_unix asc" + for i, param := range opts.sortby { + // Validate the params + if err := param.validate(); err != nil { + return nil, 0, 0, err + } + orderby += param.sortBy + " " + param.orderBy + if (i + 1) < len(opts.sortby) { + orderby += ", " + } } taskMap = make(map[int64]*Task) @@ -239,24 +254,43 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo return tasks, resultCount, totalItems, err } -func sortTasks(tasks []*Task, by SortBy) { - switch by { - case SortTasksByPriorityDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - case SortTasksByPriorityAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - case SortTasksByDueDateDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - case SortTasksByDueDateAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) +func sortTasks(tasks []*Task, by []*sortParam) { + for _, param := range by { + switch param.sortBy { + case "id", + "text", + "description", + "done", + "done_at_unix", + "due_date_unix", + "created_by_id", + "list_id", + "repeat_after", + "priority", + "start_date_unix", + "end_data_unix", + "hex_color", + "percent_done", + "uid", + "created", + "updated": + case "": + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority > tasks[j].Priority + }) + case SortTasksByPriorityAsc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority < tasks[j].Priority + }) + case SortTasksByDueDateDesc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix > tasks[j].DueDateUnix + }) + case SortTasksByDueDateAsc: + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix < tasks[j].DueDateUnix + }) + } } } -- 2.40.1 From 69e2358255ba2299e8b548137cbe815c1a3365c3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 21:22:35 +0100 Subject: [PATCH 02/60] Add sort tasks after getting them --- pkg/models/tasks.go | 224 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 190 insertions(+), 34 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index cc8d1cd2c..bbbe413fa 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -247,7 +247,7 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo if err != nil { return nil, 0, 0, err } - // Because the list is sorted by id which we don't want (since we're dealing with maps) + // Because the list is fully unsorted (since we're dealing with maps) // we have to manually sort the tasks again here. sortTasks(tasks, opts.sortby) @@ -255,41 +255,197 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo } func sortTasks(tasks []*Task, by []*sortParam) { + // FIXME: There is so much room for improvement here... for _, param := range by { switch param.sortBy { - case "id", - "text", - "description", - "done", - "done_at_unix", - "due_date_unix", - "created_by_id", - "list_id", - "repeat_after", - "priority", - "start_date_unix", - "end_data_unix", - "hex_color", - "percent_done", - "uid", - "created", - "updated": - case "": - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - case SortTasksByPriorityAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - case SortTasksByDueDateDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - case SortTasksByDueDateAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) + case "text": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Text < tasks[j].Text + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Text > tasks[j].Text + }) + } + case "description": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Description < tasks[j].Description + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Description > tasks[j].Description + }) + } + case "done": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Done == tasks[j].Done + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Done != tasks[j].Done + }) + } + case "done_at_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix + }) + } + case "due_date_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix < tasks[j].DueDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix > tasks[j].DueDateUnix + }) + } + case "created_by_id": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].CreatedByID < tasks[j].CreatedByID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].CreatedByID > tasks[j].CreatedByID + }) + } + case "list_id": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ListID < tasks[j].ListID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ListID > tasks[j].ListID + }) + } + case "repeat_after": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].RepeatAfter < tasks[j].RepeatAfter + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].RepeatAfter > tasks[j].RepeatAfter + }) + } + case "priority": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority < tasks[j].Priority + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority > tasks[j].Priority + }) + } + case "start_date_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].StartDateUnix < tasks[j].StartDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].StartDateUnix > tasks[j].StartDateUnix + }) + } + case "end_data_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].EndDateUnix < tasks[j].EndDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].EndDateUnix > tasks[j].EndDateUnix + }) + } + case "hex_color": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].HexColor < tasks[j].HexColor + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].HexColor > tasks[j].HexColor + }) + } + case "percent_done": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].PercentDone < tasks[j].PercentDone + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].PercentDone > tasks[j].PercentDone + }) + } + case "uid": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].UID < tasks[j].UID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].UID > tasks[j].UID + }) + } + case "created": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Created < tasks[j].Created + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Created > tasks[j].Created + }) + } + case "updated": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Updated < tasks[j].Updated + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Updated > tasks[j].Updated + }) + } + default: + // Sorting by ID is the default, so we don't need an extra case for it + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ID < tasks[j].ID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ID > tasks[j].ID + }) + } } } } -- 2.40.1 From 0e93ab8e89ac386740ef4ac173da3e8c35e0e6bc Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 21:39:48 +0100 Subject: [PATCH 03/60] Add test for validation of task sort order --- pkg/models/task_collection_test.go | 91 ++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 pkg/models/task_collection_test.go diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go new file mode 100644 index 000000000..10416874a --- /dev/null +++ b/pkg/models/task_collection_test.go @@ -0,0 +1,91 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSortParamValidation(t *testing.T) { + t.Run("Test valid order by", func(t *testing.T) { + t.Run("asc", func(t *testing.T) { + sort := &sortParam{ + orderBy: "asc", + sortBy: "id", + } + err := sort.validate() + assert.NoError(t, err) + }) + t.Run("desc", func(t *testing.T) { + sort := &sortParam{ + orderBy: "desc", + sortBy: "id", + } + err := sort.validate() + assert.NoError(t, err) + }) + }) + t.Run("Test valid sort by", func(t *testing.T) { + for _, test := range []string{ + "id", + "text", + "description", + "done", + "done_at_unix", + "due_date_unix", + "created_by_id", + "list_id", + "repeat_after", + "priority", + "start_date_unix", + "end_data_unix", + "hex_color", + "percent_done", + "uid", + "created", + "updated", + } { + t.Run(test, func(t *testing.T) { + sort := &sortParam{ + orderBy: "asc", + sortBy: test, + } + err := sort.validate() + assert.NoError(t, err) + }) + } + }) + t.Run("Test invalid order by", func(t *testing.T) { + sort := &sortParam{ + orderBy: "somethingInvalid", + sortBy: "id", + } + err := sort.validate() + assert.Error(t, err) + assert.True(t, IsErrInvalidSortOrder(err)) + }) + t.Run("Test invalid sort by", func(t *testing.T) { + sort := &sortParam{ + orderBy: "asc", + sortBy: "somethingInvalid", + } + err := sort.validate() + assert.Error(t, err) + assert.True(t, IsErrInvalidSortParam(err)) + }) +} -- 2.40.1 From c30f9e15d7463ff39d20bf9121cf3ed8ebb7ac87 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 22:01:16 +0100 Subject: [PATCH 04/60] Fix tests --- pkg/models/task_readall_test.go | 287 ++++++++++++++++++++++++++++---- 1 file changed, 252 insertions(+), 35 deletions(-) diff --git a/pkg/models/task_readall_test.go b/pkg/models/task_readall_test.go index 7b27fa13a..cb06fc78b 100644 --- a/pkg/models/task_readall_test.go +++ b/pkg/models/task_readall_test.go @@ -17,7 +17,7 @@ import ( "code.vikunja.io/web" ) -func sortTasksForTesting(by SortBy) (tasks []*Task) { +func sortTasksForTesting(by []*sortParam) (tasks []*Task) { user1 := &User{ ID: 1, Username: "user1", @@ -436,23 +436,199 @@ func sortTasksForTesting(by SortBy) (tasks []*Task) { }, } - switch by { - case SortTasksByPriorityDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - case SortTasksByPriorityAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - case SortTasksByDueDateDesc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - case SortTasksByDueDateAsc: - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) + // We copy and paste the whole method in here instead of calling it to be able to verify it is working correctly. + // Otherwise we would test the functionality of the function by comparing it to the functions output, which is always the same. + for _, param := range by { + switch param.sortBy { + case "text": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Text < tasks[j].Text + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Text > tasks[j].Text + }) + } + case "description": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Description < tasks[j].Description + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Description > tasks[j].Description + }) + } + case "done": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Done == tasks[j].Done + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Done != tasks[j].Done + }) + } + case "done_at_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix + }) + } + case "due_date_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix < tasks[j].DueDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix > tasks[j].DueDateUnix + }) + } + case "created_by_id": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].CreatedByID < tasks[j].CreatedByID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].CreatedByID > tasks[j].CreatedByID + }) + } + case "list_id": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ListID < tasks[j].ListID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ListID > tasks[j].ListID + }) + } + case "repeat_after": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].RepeatAfter < tasks[j].RepeatAfter + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].RepeatAfter > tasks[j].RepeatAfter + }) + } + case "priority": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority < tasks[j].Priority + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority > tasks[j].Priority + }) + } + case "start_date_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].StartDateUnix < tasks[j].StartDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].StartDateUnix > tasks[j].StartDateUnix + }) + } + case "end_data_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].EndDateUnix < tasks[j].EndDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].EndDateUnix > tasks[j].EndDateUnix + }) + } + case "hex_color": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].HexColor < tasks[j].HexColor + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].HexColor > tasks[j].HexColor + }) + } + case "percent_done": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].PercentDone < tasks[j].PercentDone + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].PercentDone > tasks[j].PercentDone + }) + } + case "uid": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].UID < tasks[j].UID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].UID > tasks[j].UID + }) + } + case "created": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Created < tasks[j].Created + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Created > tasks[j].Created + }) + } + case "updated": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Updated < tasks[j].Updated + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Updated > tasks[j].Updated + }) + } + default: + // Sorting by ID is the default, so we don't need an extra case for it + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ID < tasks[j].ID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ID > tasks[j].ID + }) + } + } } return @@ -472,10 +648,11 @@ func TestTask_ReadAll(t *testing.T) { type fields struct { ListID int64 - Sorting string StartDateSortUnix int64 EndDateSortUnix int64 Lists []*List + SortBy []string + OrderBy []string CRUDable web.CRUDable Rights web.Rights } @@ -499,78 +676,112 @@ func TestTask_ReadAll(t *testing.T) { a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByUnsorted), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "id", + orderBy: "asc", + }, + }), wantErr: false, }, { - name: "ReadAll Tasks sorted by priority (desc)", + name: "ReadAll Tasks sorted by priority default asc", fields: fields{ - Sorting: "priority", + SortBy: []string{"priority"}, }, args: args{ search: "", a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByPriorityDesc), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "priority", + orderBy: "asc", + }, + }), wantErr: false, }, { name: "ReadAll Tasks sorted by priority asc", fields: fields{ - Sorting: "priorityasc", + SortBy: []string{"priority"}, + OrderBy: []string{"asc"}, }, args: args{ search: "", a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByPriorityAsc), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "priority", + orderBy: "asc", + }, + }), wantErr: false, }, { name: "ReadAll Tasks sorted by priority desc", fields: fields{ - Sorting: "prioritydesc", + SortBy: []string{"priority"}, + OrderBy: []string{"desc"}, }, args: args{ search: "", a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByPriorityDesc), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "priority", + orderBy: "desc", + }, + }), wantErr: false, }, { - name: "ReadAll Tasks sorted by due date default desc", + name: "ReadAll Tasks sorted by due date default asc", fields: fields{ - Sorting: "duedate", + SortBy: []string{"due_date_unix"}, }, args: args{ search: "", a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByDueDateDesc), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "due_date_unix", + orderBy: "asc", + }, + }), wantErr: false, }, { name: "ReadAll Tasks sorted by due date asc", fields: fields{ - Sorting: "duedateasc", + SortBy: []string{"due_date_unix"}, + OrderBy: []string{"asc"}, }, args: args{ search: "", a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting(SortTasksByDueDateAsc), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "due_date_unix", + orderBy: "asc", + }, + }), wantErr: false, }, { name: "ReadAll Tasks sorted by due date desc", fields: fields{ - Sorting: "duedatedesc", + SortBy: []string{"due_date_unix"}, + OrderBy: []string{"desc"}, }, args: args{ search: "", @@ -578,7 +789,12 @@ func TestTask_ReadAll(t *testing.T) { page: 0, }, - want: sortTasksForTesting(SortTasksByDueDateDesc), + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "due_date_unix", + orderBy: "desc", + }, + }), wantErr: false, }, { @@ -699,9 +915,10 @@ func TestTask_ReadAll(t *testing.T) { t.Run(tt.name, func(t *testing.T) { lt := &TaskCollection{ ListID: tt.fields.ListID, - Sorting: tt.fields.Sorting, StartDateSortUnix: tt.fields.StartDateSortUnix, EndDateSortUnix: tt.fields.EndDateSortUnix, + SortBy: tt.fields.SortBy, + OrderBy: tt.fields.OrderBy, CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } -- 2.40.1 From 652b5da6f0019e33ee06dfaabbaa94e85b06a897 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 22:03:19 +0100 Subject: [PATCH 05/60] Move task collection tests to task collection test file --- pkg/models/task_collection_test.go | 942 ++++++++++++++++++++++++++++- pkg/models/task_readall_test.go | 935 ---------------------------- 2 files changed, 932 insertions(+), 945 deletions(-) delete mode 100644 pkg/models/task_readall_test.go diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 10416874a..3665421d5 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -17,26 +17,31 @@ package models import ( + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/files" + "code.vikunja.io/web" "github.com/stretchr/testify/assert" + "gopkg.in/d4l3k/messagediff.v1" + "sort" "testing" ) func TestSortParamValidation(t *testing.T) { t.Run("Test valid order by", func(t *testing.T) { t.Run("asc", func(t *testing.T) { - sort := &sortParam{ + s := &sortParam{ orderBy: "asc", sortBy: "id", } - err := sort.validate() + err := s.validate() assert.NoError(t, err) }) t.Run("desc", func(t *testing.T) { - sort := &sortParam{ + s := &sortParam{ orderBy: "desc", sortBy: "id", } - err := sort.validate() + err := s.validate() assert.NoError(t, err) }) }) @@ -61,31 +66,948 @@ func TestSortParamValidation(t *testing.T) { "updated", } { t.Run(test, func(t *testing.T) { - sort := &sortParam{ + s := &sortParam{ orderBy: "asc", sortBy: test, } - err := sort.validate() + err := s.validate() assert.NoError(t, err) }) } }) t.Run("Test invalid order by", func(t *testing.T) { - sort := &sortParam{ + s := &sortParam{ orderBy: "somethingInvalid", sortBy: "id", } - err := sort.validate() + err := s.validate() assert.Error(t, err) assert.True(t, IsErrInvalidSortOrder(err)) }) t.Run("Test invalid sort by", func(t *testing.T) { - sort := &sortParam{ + s := &sortParam{ orderBy: "asc", sortBy: "somethingInvalid", } - err := sort.validate() + err := s.validate() assert.Error(t, err) assert.True(t, IsErrInvalidSortParam(err)) }) } + +func sortTasksForTesting(by []*sortParam) (tasks []*Task) { + user1 := &User{ + ID: 1, + Username: "user1", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" + } + user2 := &User{ + ID: 2, + Username: "user2", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", // hash for "" + } + user6 := &User{ + ID: 6, + Username: "user6", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", // hash for "" + } + + tasks = []*Task{ + { + ID: 1, + Text: "task #1", + Description: "Lorem Ipsum", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Labels: []*Label{ + { + ID: 4, + Title: "Label #4 - visible via other task", + CreatedByID: 2, + CreatedBy: user2, + Updated: 0, + Created: 0, + }, + }, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindSubtask: { + { + ID: 29, + Text: "task #29 with parent task (1)", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + }, + }, + }, + Attachments: []*TaskAttachment{ + { + ID: 1, + TaskID: 1, + FileID: 1, + CreatedByID: 1, + CreatedBy: user1, + File: &files.File{ + ID: 1, + Name: "test", + Size: 100, + CreatedUnix: 1570998791, + CreatedByID: 1, + }, + }, + { + ID: 2, + TaskID: 1, + FileID: 9999, + CreatedByID: 1, + CreatedBy: user1, + }, + }, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 2, + Text: "task #2 done", + Done: true, + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Labels: []*Label{ + { + ID: 4, + Title: "Label #4 - visible via other task", + CreatedByID: 2, + CreatedBy: user2, + Updated: 0, + Created: 0, + }, + }, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 3, + Text: "task #3 high prio", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + Priority: 100, + }, + { + ID: 4, + Text: "task #4 low prio", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + Priority: 1, + }, + { + ID: 5, + Text: "task #5 higher due date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543636724, + }, + { + ID: 6, + Text: "task #6 lower due date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543616724, + }, + { + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + }, + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + { + ID: 10, + Text: "task #10 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 11, + Text: "task #11 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 12, + Text: "task #12 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 15, + Text: "task #15", + CreatedByID: 6, + CreatedBy: user6, + ListID: 6, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 16, + Text: "task #16", + CreatedByID: 6, + CreatedBy: user6, + ListID: 7, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 17, + Text: "task #17", + CreatedByID: 6, + CreatedBy: user6, + ListID: 8, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 18, + Text: "task #18", + CreatedByID: 6, + CreatedBy: user6, + ListID: 9, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 19, + Text: "task #19", + CreatedByID: 6, + CreatedBy: user6, + ListID: 10, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 20, + Text: "task #20", + CreatedByID: 6, + CreatedBy: user6, + ListID: 11, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 21, + Text: "task #21", + CreatedByID: 6, + CreatedBy: user6, + ListID: 12, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 22, + Text: "task #22", + CreatedByID: 6, + CreatedBy: user6, + ListID: 13, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 23, + Text: "task #23", + CreatedByID: 6, + CreatedBy: user6, + ListID: 14, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 24, + Text: "task #24", + CreatedByID: 6, + CreatedBy: user6, + ListID: 15, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 25, + Text: "task #25", + CreatedByID: 6, + CreatedBy: user6, + ListID: 16, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 26, + Text: "task #26", + CreatedByID: 6, + CreatedBy: user6, + ListID: 17, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 27, + Text: "task #27 with reminders", + CreatedByID: 1, + CreatedBy: user1, + RemindersUnix: []int64{1543626724, 1543626824}, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 28, + Text: "task #28 with repeat after", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + RepeatAfter: 3600, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 29, + Text: "task #29 with parent task (1)", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindParenttask: { + { + ID: 1, + Text: "task #1", + Description: "Lorem Ipsum", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, + }, + }, + }, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 30, + Text: "task #30 with assignees", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Assignees: []*User{ + user1, + user2, + }, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 31, + Text: "task #31 with color", + HexColor: "f0f0f0", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 32, + Text: "task #32", + CreatedByID: 1, + CreatedBy: user1, + ListID: 3, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + { + ID: 33, + Text: "task #33 with percent done", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + PercentDone: 0.5, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + }, + } + + // We copy and paste the whole method in here instead of calling it to be able to verify it is working correctly. + // Otherwise we would test the functionality of the function by comparing it to the functions output, which is always the same. + for _, param := range by { + switch param.sortBy { + case "text": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Text < tasks[j].Text + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Text > tasks[j].Text + }) + } + case "description": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Description < tasks[j].Description + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Description > tasks[j].Description + }) + } + case "done": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Done == tasks[j].Done + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Done != tasks[j].Done + }) + } + case "done_at_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix + }) + } + case "due_date_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix < tasks[j].DueDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].DueDateUnix > tasks[j].DueDateUnix + }) + } + case "created_by_id": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].CreatedByID < tasks[j].CreatedByID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].CreatedByID > tasks[j].CreatedByID + }) + } + case "list_id": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ListID < tasks[j].ListID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ListID > tasks[j].ListID + }) + } + case "repeat_after": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].RepeatAfter < tasks[j].RepeatAfter + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].RepeatAfter > tasks[j].RepeatAfter + }) + } + case "priority": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority < tasks[j].Priority + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Priority > tasks[j].Priority + }) + } + case "start_date_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].StartDateUnix < tasks[j].StartDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].StartDateUnix > tasks[j].StartDateUnix + }) + } + case "end_data_unix": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].EndDateUnix < tasks[j].EndDateUnix + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].EndDateUnix > tasks[j].EndDateUnix + }) + } + case "hex_color": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].HexColor < tasks[j].HexColor + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].HexColor > tasks[j].HexColor + }) + } + case "percent_done": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].PercentDone < tasks[j].PercentDone + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].PercentDone > tasks[j].PercentDone + }) + } + case "uid": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].UID < tasks[j].UID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].UID > tasks[j].UID + }) + } + case "created": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Created < tasks[j].Created + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Created > tasks[j].Created + }) + } + case "updated": + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Updated < tasks[j].Updated + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].Updated > tasks[j].Updated + }) + } + default: + // Sorting by ID is the default, so we don't need an extra case for it + if param.orderBy == "asc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ID < tasks[j].ID + }) + } + if param.orderBy == "desc" { + sort.Slice(tasks, func(i, j int) bool { + return tasks[i].ID > tasks[j].ID + }) + } + } + } + + return +} + +func TestTask_ReadAll(t *testing.T) { + assert.NoError(t, db.LoadFixtures()) + + // Dummy users + user1 := &User{ + ID: 1, + Username: "user1", + Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", + IsActive: true, + AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" + } + + type fields struct { + ListID int64 + StartDateSortUnix int64 + EndDateSortUnix int64 + Lists []*List + SortBy []string + OrderBy []string + CRUDable web.CRUDable + Rights web.Rights + } + type args struct { + search string + a web.Auth + page int + } + tests := []struct { + name string + fields fields + args args + want interface{} + wantErr bool + }{ + { + name: "ReadAll Tasks normally", + fields: fields{}, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "id", + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by priority default asc", + fields: fields{ + SortBy: []string{"priority"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "priority", + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by priority asc", + fields: fields{ + SortBy: []string{"priority"}, + OrderBy: []string{"asc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "priority", + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by priority desc", + fields: fields{ + SortBy: []string{"priority"}, + OrderBy: []string{"desc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "priority", + orderBy: "desc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by due date default asc", + fields: fields{ + SortBy: []string{"due_date_unix"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "due_date_unix", + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by due date asc", + fields: fields{ + SortBy: []string{"due_date_unix"}, + OrderBy: []string{"asc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "due_date_unix", + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by due date desc", + fields: fields{ + SortBy: []string{"due_date_unix"}, + OrderBy: []string{"desc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + + want: sortTasksForTesting([]*sortParam{ + { + sortBy: "due_date_unix", + orderBy: "desc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks with range", + fields: fields{ + StartDateSortUnix: 1544500000, + EndDateSortUnix: 1544600000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + { + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + }, + wantErr: false, + }, + { + name: "ReadAll Tasks with range", + fields: fields{ + StartDateSortUnix: 1544700000, + EndDateSortUnix: 1545000000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + }, + wantErr: false, + }, + { + name: "ReadAll Tasks with range without end date", + fields: fields{ + StartDateSortUnix: 1544700000, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + { + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + }, + { + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + lt := &TaskCollection{ + ListID: tt.fields.ListID, + StartDateSortUnix: tt.fields.StartDateSortUnix, + EndDateSortUnix: tt.fields.EndDateSortUnix, + SortBy: tt.fields.SortBy, + OrderBy: tt.fields.OrderBy, + CRUDable: tt.fields.CRUDable, + Rights: tt.fields.Rights, + } + got, _, _, err := lt.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) + if (err != nil) != tt.wantErr { + t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr) + return + } + if diff, equal := messagediff.PrettyDiff(got, tt.want); !equal { + t.Errorf("Test %s, LabelTask.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff) + } + }) + } +} diff --git a/pkg/models/task_readall_test.go b/pkg/models/task_readall_test.go deleted file mode 100644 index cb06fc78b..000000000 --- a/pkg/models/task_readall_test.go +++ /dev/null @@ -1,935 +0,0 @@ -/* - * Copyright (c) 2018 the Vikunja Authors. All rights reserved. - * Use of this source code is governed by a LPGLv3-style - * license that can be found in the LICENSE file. - */ - -package models - -import ( - "code.vikunja.io/api/pkg/db" - "code.vikunja.io/api/pkg/files" - "github.com/stretchr/testify/assert" - "gopkg.in/d4l3k/messagediff.v1" - "sort" - "testing" - - "code.vikunja.io/web" -) - -func sortTasksForTesting(by []*sortParam) (tasks []*Task) { - user1 := &User{ - ID: 1, - Username: "user1", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" - } - user2 := &User{ - ID: 2, - Username: "user2", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - AvatarURL: "ab53a2911ddf9b4817ac01ddcd3d975f", // hash for "" - } - user6 := &User{ - ID: 6, - Username: "user6", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", // hash for "" - } - - tasks = []*Task{ - { - ID: 1, - Text: "task #1", - Description: "Lorem Ipsum", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Labels: []*Label{ - { - ID: 4, - Title: "Label #4 - visible via other task", - CreatedByID: 2, - CreatedBy: user2, - Updated: 0, - Created: 0, - }, - }, - RelatedTasks: map[RelationKind][]*Task{ - RelationKindSubtask: { - { - ID: 29, - Text: "task #29 with parent task (1)", - CreatedByID: 1, - ListID: 1, - Created: 1543626724, - Updated: 1543626724, - }, - }, - }, - Attachments: []*TaskAttachment{ - { - ID: 1, - TaskID: 1, - FileID: 1, - CreatedByID: 1, - CreatedBy: user1, - File: &files.File{ - ID: 1, - Name: "test", - Size: 100, - CreatedUnix: 1570998791, - CreatedByID: 1, - }, - }, - { - ID: 2, - TaskID: 1, - FileID: 9999, - CreatedByID: 1, - CreatedBy: user1, - }, - }, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 2, - Text: "task #2 done", - Done: true, - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Labels: []*Label{ - { - ID: 4, - Title: "Label #4 - visible via other task", - CreatedByID: 2, - CreatedBy: user2, - Updated: 0, - Created: 0, - }, - }, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 3, - Text: "task #3 high prio", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - Priority: 100, - }, - { - ID: 4, - Text: "task #4 low prio", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - Priority: 1, - }, - { - ID: 5, - Text: "task #5 higher due date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - DueDateUnix: 1543636724, - }, - { - ID: 6, - Text: "task #6 lower due date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - DueDateUnix: 1543616724, - }, - { - ID: 7, - Text: "task #7 with start date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - }, - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - { - ID: 10, - Text: "task #10 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 11, - Text: "task #11 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 12, - Text: "task #12 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 15, - Text: "task #15", - CreatedByID: 6, - CreatedBy: user6, - ListID: 6, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 16, - Text: "task #16", - CreatedByID: 6, - CreatedBy: user6, - ListID: 7, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 17, - Text: "task #17", - CreatedByID: 6, - CreatedBy: user6, - ListID: 8, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 18, - Text: "task #18", - CreatedByID: 6, - CreatedBy: user6, - ListID: 9, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 19, - Text: "task #19", - CreatedByID: 6, - CreatedBy: user6, - ListID: 10, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 20, - Text: "task #20", - CreatedByID: 6, - CreatedBy: user6, - ListID: 11, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 21, - Text: "task #21", - CreatedByID: 6, - CreatedBy: user6, - ListID: 12, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 22, - Text: "task #22", - CreatedByID: 6, - CreatedBy: user6, - ListID: 13, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 23, - Text: "task #23", - CreatedByID: 6, - CreatedBy: user6, - ListID: 14, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 24, - Text: "task #24", - CreatedByID: 6, - CreatedBy: user6, - ListID: 15, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 25, - Text: "task #25", - CreatedByID: 6, - CreatedBy: user6, - ListID: 16, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 26, - Text: "task #26", - CreatedByID: 6, - CreatedBy: user6, - ListID: 17, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 27, - Text: "task #27 with reminders", - CreatedByID: 1, - CreatedBy: user1, - RemindersUnix: []int64{1543626724, 1543626824}, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 28, - Text: "task #28 with repeat after", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - RepeatAfter: 3600, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 29, - Text: "task #29 with parent task (1)", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{ - RelationKindParenttask: { - { - ID: 1, - Text: "task #1", - Description: "Lorem Ipsum", - CreatedByID: 1, - ListID: 1, - Created: 1543626724, - Updated: 1543626724, - }, - }, - }, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 30, - Text: "task #30 with assignees", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Assignees: []*User{ - user1, - user2, - }, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 31, - Text: "task #31 with color", - HexColor: "f0f0f0", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 32, - Text: "task #32", - CreatedByID: 1, - CreatedBy: user1, - ListID: 3, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 33, - Text: "task #33 with percent done", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - PercentDone: 0.5, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - } - - // We copy and paste the whole method in here instead of calling it to be able to verify it is working correctly. - // Otherwise we would test the functionality of the function by comparing it to the functions output, which is always the same. - for _, param := range by { - switch param.sortBy { - case "text": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Text < tasks[j].Text - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Text > tasks[j].Text - }) - } - case "description": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Description < tasks[j].Description - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Description > tasks[j].Description - }) - } - case "done": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Done == tasks[j].Done - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Done != tasks[j].Done - }) - } - case "done_at_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix - }) - } - case "due_date_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - } - case "created_by_id": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].CreatedByID < tasks[j].CreatedByID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].CreatedByID > tasks[j].CreatedByID - }) - } - case "list_id": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ListID < tasks[j].ListID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ListID > tasks[j].ListID - }) - } - case "repeat_after": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].RepeatAfter < tasks[j].RepeatAfter - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].RepeatAfter > tasks[j].RepeatAfter - }) - } - case "priority": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - } - case "start_date_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].StartDateUnix < tasks[j].StartDateUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].StartDateUnix > tasks[j].StartDateUnix - }) - } - case "end_data_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].EndDateUnix < tasks[j].EndDateUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].EndDateUnix > tasks[j].EndDateUnix - }) - } - case "hex_color": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].HexColor < tasks[j].HexColor - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].HexColor > tasks[j].HexColor - }) - } - case "percent_done": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].PercentDone < tasks[j].PercentDone - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].PercentDone > tasks[j].PercentDone - }) - } - case "uid": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].UID < tasks[j].UID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].UID > tasks[j].UID - }) - } - case "created": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Created < tasks[j].Created - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Created > tasks[j].Created - }) - } - case "updated": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Updated < tasks[j].Updated - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Updated > tasks[j].Updated - }) - } - default: - // Sorting by ID is the default, so we don't need an extra case for it - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ID < tasks[j].ID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ID > tasks[j].ID - }) - } - } - } - - return -} - -func TestTask_ReadAll(t *testing.T) { - assert.NoError(t, db.LoadFixtures()) - - // Dummy users - user1 := &User{ - ID: 1, - Username: "user1", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" - } - - type fields struct { - ListID int64 - StartDateSortUnix int64 - EndDateSortUnix int64 - Lists []*List - SortBy []string - OrderBy []string - CRUDable web.CRUDable - Rights web.Rights - } - type args struct { - search string - a web.Auth - page int - } - tests := []struct { - name string - fields fields - args args - want interface{} - wantErr bool - }{ - { - name: "ReadAll Tasks normally", - fields: fields{}, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "id", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority default asc", - fields: fields{ - SortBy: []string{"priority"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "priority", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority asc", - fields: fields{ - SortBy: []string{"priority"}, - OrderBy: []string{"asc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "priority", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority desc", - fields: fields{ - SortBy: []string{"priority"}, - OrderBy: []string{"desc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "priority", - orderBy: "desc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date default asc", - fields: fields{ - SortBy: []string{"due_date_unix"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "due_date_unix", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date asc", - fields: fields{ - SortBy: []string{"due_date_unix"}, - OrderBy: []string{"asc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "due_date_unix", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date desc", - fields: fields{ - SortBy: []string{"due_date_unix"}, - OrderBy: []string{"desc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "due_date_unix", - orderBy: "desc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks with range", - fields: fields{ - StartDateSortUnix: 1544500000, - EndDateSortUnix: 1544600000, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: []*Task{ - { - ID: 7, - Text: "task #7 with start date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - }, - wantErr: false, - }, - { - name: "ReadAll Tasks with range", - fields: fields{ - StartDateSortUnix: 1544700000, - EndDateSortUnix: 1545000000, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: []*Task{ - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - }, - wantErr: false, - }, - { - name: "ReadAll Tasks with range without end date", - fields: fields{ - StartDateSortUnix: 1544700000, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: []*Task{ - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - lt := &TaskCollection{ - ListID: tt.fields.ListID, - StartDateSortUnix: tt.fields.StartDateSortUnix, - EndDateSortUnix: tt.fields.EndDateSortUnix, - SortBy: tt.fields.SortBy, - OrderBy: tt.fields.OrderBy, - CRUDable: tt.fields.CRUDable, - Rights: tt.fields.Rights, - } - got, _, _, err := lt.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) - if (err != nil) != tt.wantErr { - t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr) - return - } - if diff, equal := messagediff.PrettyDiff(got, tt.want); !equal { - t.Errorf("Test %s, LabelTask.ReadAll() = %v, want %v, \ndiff: %v", tt.name, got, tt.want, diff) - } - }) - } -} -- 2.40.1 From 8e3f5ced80515f62708657098cac7ef8f54283c9 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 22:12:17 +0100 Subject: [PATCH 06/60] Add more tests for task sorting --- pkg/models/task_collection.go | 2 +- pkg/models/task_collection_test.go | 203 ++++++++++++----------------- pkg/models/tasks.go | 2 +- 3 files changed, 88 insertions(+), 119 deletions(-) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index fb1f8f150..fab1a5e72 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -60,7 +60,7 @@ func (sp *sortParam) validate() error { "repeat_after", "priority", "start_date_unix", - "end_data_unix", + "end_date_unix", "hex_color", "percent_done", "uid", diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 3665421d5..385894df0 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -58,7 +58,7 @@ func TestSortParamValidation(t *testing.T) { "repeat_after", "priority", "start_date_unix", - "end_data_unix", + "end_date_unix", "hex_color", "percent_done", "uid", @@ -628,7 +628,7 @@ func sortTasksForTesting(by []*sortParam) (tasks []*Task) { return tasks[i].StartDateUnix > tasks[j].StartDateUnix }) } - case "end_data_unix": + case "end_date_unix": if param.orderBy == "asc" { sort.Slice(tasks, func(i, j int) bool { return tasks[i].EndDateUnix < tasks[j].EndDateUnix @@ -739,13 +739,14 @@ func TestTask_ReadAll(t *testing.T) { a web.Auth page int } - tests := []struct { + type testcase struct { name string fields fields args args want interface{} wantErr bool - }{ + } + tests := []testcase{ { name: "ReadAll Tasks normally", fields: fields{}, @@ -762,119 +763,6 @@ func TestTask_ReadAll(t *testing.T) { }), wantErr: false, }, - { - name: "ReadAll Tasks sorted by priority default asc", - fields: fields{ - SortBy: []string{"priority"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "priority", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority asc", - fields: fields{ - SortBy: []string{"priority"}, - OrderBy: []string{"asc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "priority", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by priority desc", - fields: fields{ - SortBy: []string{"priority"}, - OrderBy: []string{"desc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "priority", - orderBy: "desc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date default asc", - fields: fields{ - SortBy: []string{"due_date_unix"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "due_date_unix", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date asc", - fields: fields{ - SortBy: []string{"due_date_unix"}, - OrderBy: []string{"asc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "due_date_unix", - orderBy: "asc", - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by due date desc", - fields: fields{ - SortBy: []string{"due_date_unix"}, - OrderBy: []string{"desc"}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "due_date_unix", - orderBy: "desc", - }, - }), - wantErr: false, - }, { name: "ReadAll Tasks with range", fields: fields{ @@ -989,6 +877,87 @@ func TestTask_ReadAll(t *testing.T) { wantErr: false, }, } + + // Add more cases programatically + for _, test := range []string{ + "id", + "text", + "description", + "done", + "done_at_unix", + "due_date_unix", + "created_by_id", + "list_id", + "repeat_after", + "priority", + "start_date_unix", + "end_date_unix", + "hex_color", + "percent_done", + "uid", + "created", + "updated", + } { + tests = append(tests, []testcase{ + { + name: "ReadAll Tasks sorted by " + test + " default asc", + fields: fields{ + SortBy: []string{test}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: test, + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by " + test + " asc", + fields: fields{ + SortBy: []string{test}, + OrderBy: []string{"asc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: test, + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by " + test + " desc", + fields: fields{ + SortBy: []string{test}, + OrderBy: []string{"desc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: test, + orderBy: "desc", + }, + }), + wantErr: false, + }, + }...) + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lt := &TaskCollection{ diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index bbbe413fa..9e5311dc1 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -368,7 +368,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { return tasks[i].StartDateUnix > tasks[j].StartDateUnix }) } - case "end_data_unix": + case "end_date_unix": if param.orderBy == "asc" { sort.Slice(tasks, func(i, j int) bool { return tasks[i].EndDateUnix < tasks[j].EndDateUnix -- 2.40.1 From eb9ecd51f25053a2230e5a70f9c9230781720657 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 22:36:41 +0100 Subject: [PATCH 07/60] Add more test cases --- pkg/models/task_collection_test.go | 85 ++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 385894df0..c355acbf0 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -878,8 +878,7 @@ func TestTask_ReadAll(t *testing.T) { }, } - // Add more cases programatically - for _, test := range []string{ + sortByFields := []string{ "id", "text", "description", @@ -897,7 +896,10 @@ func TestTask_ReadAll(t *testing.T) { "uid", "created", "updated", - } { + } + // Add more cases programatically + // Simple cases + for _, test := range sortByFields { tests = append(tests, []testcase{ { name: "ReadAll Tasks sorted by " + test + " default asc", @@ -958,6 +960,83 @@ func TestTask_ReadAll(t *testing.T) { }...) } + // Cases with two parameters + for _, outerTest := range sortByFields { + // We don't test all cases with everything here because this would mean an enormous amount of test cases + for _, test := range []string{"text", "done"} { + tests = append(tests, []testcase{ + { + name: "ReadAll Tasks sorted by " + outerTest + " and " + test + "default asc", + fields: fields{ + SortBy: []string{outerTest, test}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: outerTest, + orderBy: "asc", + }, + { + sortBy: test, + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by " + outerTest + " and " + test + " asc", + fields: fields{ + SortBy: []string{outerTest, test}, + OrderBy: []string{"asc", "asc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: outerTest, + orderBy: "asc", + }, + { + sortBy: test, + orderBy: "asc", + }, + }), + wantErr: false, + }, + { + name: "ReadAll Tasks sorted by " + outerTest + " and " + test + " desc", + fields: fields{ + SortBy: []string{outerTest, test}, + OrderBy: []string{"desc", "desc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: sortTasksForTesting([]*sortParam{ + { + sortBy: outerTest, + orderBy: "desc", + }, + { + sortBy: test, + orderBy: "desc", + }, + }), + wantErr: false, + }, + }...) + } + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lt := &TaskCollection{ -- 2.40.1 From a5690b05569c87cb709ac5349c8ec6b71fd16706 Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Tue, 3 Dec 2019 22:22:07 +0100 Subject: [PATCH 08/60] Replace big switch-case in sortTasks function with a map --- pkg/models/tasks.go | 264 +++++++++++++------------------------------- 1 file changed, 75 insertions(+), 189 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 9e5311dc1..01e35006e 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -255,198 +255,84 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo } func sortTasks(tasks []*Task, by []*sortParam) { - // FIXME: There is so much room for improvement here... + const defaultComparator = "default" + + type TaskComparator func(lhs, rhs *Task) bool + + // This is a map of properties that can be sorted by + // and their appropriate comparator function. + // The comparator function sort in ascending mode. + var comparators = map[string]TaskComparator{ + "text": func(lhs, rhs *Task) bool { + return lhs.Text < rhs.Text + }, + "description": func(lhs, rhs *Task) bool { + return lhs.Description < rhs.Description + }, + "done": func(lhs, rhs *Task) bool { + return lhs.Done == rhs.Done + }, + "done_at_unix": func(lhs, rhs *Task) bool { + return lhs.DoneAtUnix < rhs.DoneAtUnix + }, + "due_date_unix": func(lhs, rhs *Task) bool { + return lhs.DueDateUnix < rhs.DueDateUnix + }, + "created_by_id": func(lhs, rhs *Task) bool { + return lhs.CreatedByID < rhs.CreatedByID + }, + "list_id": func(lhs, rhs *Task) bool { + return lhs.ListID < rhs.ListID + }, + "repeat_after": func(lhs, rhs *Task) bool { + return lhs.RepeatAfter < rhs.RepeatAfter + }, + "priority": func(lhs, rhs *Task) bool { + return lhs.Priority < rhs.Priority + }, + "start_date_unix": func(lhs, rhs *Task) bool { + return lhs.Priority < rhs.Priority + }, + "end_date_unix": func(lhs, rhs *Task) bool { + return lhs.EndDateUnix < rhs.EndDateUnix + }, + "hex_color": func(lhs, rhs *Task) bool { + return lhs.EndDateUnix < rhs.EndDateUnix + }, + "percent_done": func(lhs, rhs *Task) bool { + return lhs.PercentDone < rhs.PercentDone + }, + "uid": func(lhs, rhs *Task) bool { + return lhs.UID < rhs.UID + }, + "created": func(lhs, rhs *Task) bool { + return lhs.Created < rhs.Created + }, + "updated": func(lhs, rhs *Task) bool { + return lhs.Updated < rhs.Updated + }, + defaultComparator: func(lhs, rhs *Task) bool { + return lhs.Updated < rhs.Updated + }, + } + for _, param := range by { - switch param.sortBy { - case "text": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Text < tasks[j].Text - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Text > tasks[j].Text - }) - } - case "description": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Description < tasks[j].Description - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Description > tasks[j].Description - }) - } - case "done": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Done == tasks[j].Done - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Done != tasks[j].Done - }) - } - case "done_at_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix - }) - } - case "due_date_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - } - case "created_by_id": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].CreatedByID < tasks[j].CreatedByID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].CreatedByID > tasks[j].CreatedByID - }) - } - case "list_id": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ListID < tasks[j].ListID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ListID > tasks[j].ListID - }) - } - case "repeat_after": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].RepeatAfter < tasks[j].RepeatAfter - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].RepeatAfter > tasks[j].RepeatAfter - }) - } - case "priority": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - } - case "start_date_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].StartDateUnix < tasks[j].StartDateUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].StartDateUnix > tasks[j].StartDateUnix - }) - } - case "end_date_unix": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].EndDateUnix < tasks[j].EndDateUnix - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].EndDateUnix > tasks[j].EndDateUnix - }) - } - case "hex_color": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].HexColor < tasks[j].HexColor - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].HexColor > tasks[j].HexColor - }) - } - case "percent_done": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].PercentDone < tasks[j].PercentDone - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].PercentDone > tasks[j].PercentDone - }) - } - case "uid": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].UID < tasks[j].UID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].UID > tasks[j].UID - }) - } - case "created": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Created < tasks[j].Created - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Created > tasks[j].Created - }) - } - case "updated": - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Updated < tasks[j].Updated - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Updated > tasks[j].Updated - }) - } - default: - // Sorting by ID is the default, so we don't need an extra case for it - if param.orderBy == "asc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ID < tasks[j].ID - }) - } - if param.orderBy == "desc" { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ID > tasks[j].ID - }) + comparator, ok := comparators[param.sortBy] + if !ok { + // TODO: Handle case that a suitable comparator has not been found + } + + // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). + if param.orderBy == "desc" { + oldComparator := comparator + comparator = func(lhs, rhs *Task) bool { + return !oldComparator(lhs, rhs) } } + + sort.Slice(tasks, func(i, j int) bool { + return comparator(tasks[i], tasks[j]) + }) } } -- 2.40.1 From deb526bb4053225a5c0ea6e3b654bca564230885 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 22:50:02 +0100 Subject: [PATCH 09/60] Fix integration tests --- pkg/integrations/task_collection_test.go | 41 ++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 586556617..46b5b6732 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -90,35 +90,36 @@ func TestTaskCollection(t *testing.T) { assert.NotContains(t, rec.Body.String(), `task #14`) }) t.Run("Sort Order", func(t *testing.T) { - // should equal priority desc + // TODO: Add more cases + // should equal priority asc t.Run("by priority", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priority"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by priority desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"prioritydesc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) }) t.Run("by priority asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priorityasc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) - // should equal duedate desc + // should equal duedate asc t.Run("by duedate", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedate"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, urlParams) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by duedate desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedatedesc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) }) t.Run("by duedate asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedateasc"}}, urlParams) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"asc"}}, urlParams) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) @@ -234,35 +235,35 @@ func TestTaskCollection(t *testing.T) { assert.NotContains(t, rec.Body.String(), `task #14`) }) t.Run("Sort Order", func(t *testing.T) { - // should equal priority desc + // should equal priority asc t.Run("by priority", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priority"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) + assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by priority desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"prioritydesc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`) }) t.Run("by priority asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priorityasc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) - // should equal duedate desc + // should equal duedate asc t.Run("by duedate", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedate"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) + assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("by duedate desc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedatedesc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`) }) t.Run("by duedate asc", func(t *testing.T) { - rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedateasc"}}, nil) + rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) -- 2.40.1 From bf37cae1bf1d45900a40bb9ef62826e5bd7ca83d Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 22:53:58 +0100 Subject: [PATCH 10/60] Fix wrong sort cases --- pkg/models/tasks.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 01e35006e..f0a25fe57 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -291,13 +291,13 @@ func sortTasks(tasks []*Task, by []*sortParam) { return lhs.Priority < rhs.Priority }, "start_date_unix": func(lhs, rhs *Task) bool { - return lhs.Priority < rhs.Priority + return lhs.StartDateUnix < rhs.StartDateUnix }, "end_date_unix": func(lhs, rhs *Task) bool { return lhs.EndDateUnix < rhs.EndDateUnix }, "hex_color": func(lhs, rhs *Task) bool { - return lhs.EndDateUnix < rhs.EndDateUnix + return lhs.HexColor < rhs.HexColor }, "percent_done": func(lhs, rhs *Task) bool { return lhs.PercentDone < rhs.PercentDone @@ -312,7 +312,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { return lhs.Updated < rhs.Updated }, defaultComparator: func(lhs, rhs *Task) bool { - return lhs.Updated < rhs.Updated + return lhs.ID < rhs.ID }, } -- 2.40.1 From 29b61d93fdd9e65c235bbb6cee611f4fc6d44994 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 23:11:37 +0100 Subject: [PATCH 11/60] Add ID as sorting paramter --- pkg/models/tasks.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index f0a25fe57..56338fff7 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -261,7 +261,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { // This is a map of properties that can be sorted by // and their appropriate comparator function. - // The comparator function sort in ascending mode. + // The comparator function sorts in ascending mode. var comparators = map[string]TaskComparator{ "text": func(lhs, rhs *Task) bool { return lhs.Text < rhs.Text @@ -311,6 +311,9 @@ func sortTasks(tasks []*Task, by []*sortParam) { "updated": func(lhs, rhs *Task) bool { return lhs.Updated < rhs.Updated }, + "id": func(lhs, rhs *Task) bool { + return lhs.ID < rhs.ID + }, defaultComparator: func(lhs, rhs *Task) bool { return lhs.ID < rhs.ID }, -- 2.40.1 From 7307eca0c3dc7fe124ddf5304452859519b984ec Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 23:11:58 +0100 Subject: [PATCH 12/60] Add panic for invalid comparator --- pkg/models/tasks.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 56338fff7..a1920641a 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -320,9 +320,11 @@ func sortTasks(tasks []*Task, by []*sortParam) { } for _, param := range by { - comparator, ok := comparators[param.sortBy] + var comparator TaskComparator + var ok bool + comparator, ok = comparators[param.sortBy] if !ok { - // TODO: Handle case that a suitable comparator has not been found + panic("No suitable comparator for sortBy found! Param was " + param.sortBy) } // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). -- 2.40.1 From a9121e1ce1a05dbc4c5f1be5d3f99d93670ed7da Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Tue, 3 Dec 2019 23:08:53 +0100 Subject: [PATCH 13/60] Refactor task sorting to use consts instead of hardcorded strings In order to avoid string copy-pasting and/or spelling/typing mistakes, use global constants for sortable task properties instead. Also, make order (ascending, descending) a typed bool instead of a string. --- pkg/models/task_collection.go | 88 ++++++++---- pkg/models/task_collection_test.go | 212 ++++++++++++++--------------- pkg/models/tasks.go | 43 +++--- 3 files changed, 190 insertions(+), 153 deletions(-) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index fab1a5e72..f46d36b5b 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -38,34 +38,74 @@ type TaskCollection struct { web.Rights `xorm:"-" json:"-"` } -type sortParam struct { - sortBy string - orderBy string // asc or desc +type ( + sortParam struct { + sortBy sortProperty + orderBy sortOrder // asc or desc + } + + sortProperty string + + sortOrder bool +) + +const ( + PropertyID sortProperty = "id" + PropertyText sortProperty = "text" + PropertyDescription sortProperty = "description" + PropertyDone sortProperty = "done" + PropertyDoneAtUnix sortProperty = "done_at_unix" + PropertyDueDateUnix sortProperty = "due_date_unix" + PropertyCreatedByID sortProperty = "created_by_id" + PropertyListID sortProperty = "list_id" + PropertyRepeatAfter sortProperty = "repeat_after" + PropertyPriority sortProperty = "priority" + PropertyStartDateUnix sortProperty = "start_date_unix" + PropertyEndDateUnix sortProperty = "end_date_unix" + PropertyHexColor sortProperty = "hex_color" + PropertyPercentDone sortProperty = "percent_done" + PropertyUID sortProperty = "uid" + PropertyCreated sortProperty = "created" + PropertyUpdated sortProperty = "updated" +) + +func (p sortProperty) String() string { + return string(p) +} + +const ( + OrderAscending sortOrder = false + OrderDescending sortOrder = true +) + +func (o sortOrder) String() string { + if o == OrderAscending { + return "asc" + } else { + return "desc" + } } func (sp *sortParam) validate() error { - if sp.orderBy != "asc" && sp.orderBy != "desc" { - return ErrInvalidSortOrder{OrderBy: sp.orderBy} - } switch sp.sortBy { case - "id", - "text", - "description", - "done", - "done_at_unix", - "due_date_unix", - "created_by_id", - "list_id", - "repeat_after", - "priority", - "start_date_unix", - "end_date_unix", - "hex_color", - "percent_done", - "uid", - "created", - "updated": + PropertyID, + PropertyText, + PropertyDescription, + PropertyDone, + PropertyDoneAtUnix, + PropertyDueDateUnix, + PropertyCreatedByID, + PropertyListID, + PropertyRepeatAfter, + PropertyPriority, + PropertyStartDateUnix, + PropertyEndDateUnix, + PropertyHexColor, + PropertyPercentDone, + PropertyUID, + PropertyCreated, + PropertyUpdated: return nil } return ErrInvalidSortParam{SortBy: sp.sortBy} @@ -116,7 +156,7 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i for i, s := range tf.SortBy { sortParam := &sortParam{ sortBy: s, - orderBy: "asc", + orderBy: OrderAscending, } // This checks if tf.OrderBy has an entry with the same index as the current entry from tf.SortBy // Taken from https://stackoverflow.com/a/27252199/10924593 diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index c355acbf0..a704098cd 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -28,17 +28,17 @@ import ( func TestSortParamValidation(t *testing.T) { t.Run("Test valid order by", func(t *testing.T) { - t.Run("asc", func(t *testing.T) { + t.Run(OrderAscending.String(), func(t *testing.T) { s := &sortParam{ - orderBy: "asc", + orderBy: OrderAscending, sortBy: "id", } err := s.validate() assert.NoError(t, err) }) - t.Run("desc", func(t *testing.T) { + t.Run(OrderDescending.String(), func(t *testing.T) { s := &sortParam{ - orderBy: "desc", + orderBy: OrderDescending, sortBy: "id", } err := s.validate() @@ -46,28 +46,28 @@ func TestSortParamValidation(t *testing.T) { }) }) t.Run("Test valid sort by", func(t *testing.T) { - for _, test := range []string{ - "id", - "text", - "description", - "done", - "done_at_unix", - "due_date_unix", - "created_by_id", - "list_id", - "repeat_after", - "priority", - "start_date_unix", - "end_date_unix", - "hex_color", - "percent_done", - "uid", - "created", - "updated", + for _, test := range []sortProperty{ + PropertyID, + PropertyText, + PropertyDescription, + PropertyDone, + PropertyDoneAtUnix, + PropertyDueDateUnix, + PropertyCreatedByID, + PropertyListID, + PropertyRepeatAfter, + PropertyPriority, + PropertyStartDateUnix, + PropertyEndDateUnix, + PropertyHexColor, + PropertyPercentDone, + PropertyUID, + PropertyCreated, + PropertyUpdated, } { - t.Run(test, func(t *testing.T) { + t.Run(test.String(), func(t *testing.T) { s := &sortParam{ - orderBy: "asc", + orderBy: OrderAscending, sortBy: test, } err := s.validate() @@ -86,7 +86,7 @@ func TestSortParamValidation(t *testing.T) { }) t.Run("Test invalid sort by", func(t *testing.T) { s := &sortParam{ - orderBy: "asc", + orderBy: OrderAscending, sortBy: "somethingInvalid", } err := s.validate() @@ -519,189 +519,189 @@ func sortTasksForTesting(by []*sortParam) (tasks []*Task) { for _, param := range by { switch param.sortBy { case "text": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Text < tasks[j].Text }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Text > tasks[j].Text }) } case "description": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Description < tasks[j].Description }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Description > tasks[j].Description }) } case "done": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Done == tasks[j].Done }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Done != tasks[j].Done }) } case "done_at_unix": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix }) } case "due_date_unix": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DueDateUnix < tasks[j].DueDateUnix }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DueDateUnix > tasks[j].DueDateUnix }) } case "created_by_id": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].CreatedByID < tasks[j].CreatedByID }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].CreatedByID > tasks[j].CreatedByID }) } case "list_id": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ListID < tasks[j].ListID }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ListID > tasks[j].ListID }) } case "repeat_after": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].RepeatAfter < tasks[j].RepeatAfter }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].RepeatAfter > tasks[j].RepeatAfter }) } case "priority": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Priority < tasks[j].Priority }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Priority > tasks[j].Priority }) } case "start_date_unix": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].StartDateUnix < tasks[j].StartDateUnix }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].StartDateUnix > tasks[j].StartDateUnix }) } case "end_date_unix": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].EndDateUnix < tasks[j].EndDateUnix }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].EndDateUnix > tasks[j].EndDateUnix }) } case "hex_color": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].HexColor < tasks[j].HexColor }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].HexColor > tasks[j].HexColor }) } case "percent_done": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].PercentDone < tasks[j].PercentDone }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].PercentDone > tasks[j].PercentDone }) } case "uid": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].UID < tasks[j].UID }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].UID > tasks[j].UID }) } case "created": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created < tasks[j].Created }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created > tasks[j].Created }) } case "updated": - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Updated < tasks[j].Updated }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Updated > tasks[j].Updated }) } default: // Sorting by ID is the default, so we don't need an extra case for it - if param.orderBy == "asc" { + if param.orderBy == OrderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ID < tasks[j].ID }) } - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ID > tasks[j].ID }) @@ -729,8 +729,8 @@ func TestTask_ReadAll(t *testing.T) { StartDateSortUnix int64 EndDateSortUnix int64 Lists []*List - SortBy []string - OrderBy []string + SortBy []sortProperty + OrderBy []sortOrder CRUDable web.CRUDable Rights web.Rights } @@ -758,7 +758,7 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: "id", - orderBy: "asc", + orderBy: OrderAscending, }, }), wantErr: false, @@ -878,33 +878,33 @@ func TestTask_ReadAll(t *testing.T) { }, } - sortByFields := []string{ - "id", - "text", - "description", - "done", - "done_at_unix", - "due_date_unix", - "created_by_id", - "list_id", - "repeat_after", - "priority", - "start_date_unix", - "end_date_unix", - "hex_color", - "percent_done", - "uid", - "created", - "updated", + sortByFields := []sortProperty{ + PropertyID, + PropertyText, + PropertyDescription, + PropertyDone, + PropertyDoneAtUnix, + PropertyDueDateUnix, + PropertyCreatedByID, + PropertyListID, + PropertyRepeatAfter, + PropertyPriority, + PropertyStartDateUnix, + PropertyEndDateUnix, + PropertyHexColor, + PropertyPercentDone, + PropertyUID, + PropertyCreated, + PropertyUpdated, } // Add more cases programatically // Simple cases for _, test := range sortByFields { tests = append(tests, []testcase{ { - name: "ReadAll Tasks sorted by " + test + " default asc", + name: "ReadAll Tasks sorted by " + test.String() + " default asc", fields: fields{ - SortBy: []string{test}, + SortBy: []sortProperty{test}, }, args: args{ search: "", @@ -914,16 +914,16 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: test, - orderBy: "asc", + orderBy: OrderAscending, }, }), wantErr: false, }, { - name: "ReadAll Tasks sorted by " + test + " asc", + name: "ReadAll Tasks sorted by " + test.String() + " asc", fields: fields{ - SortBy: []string{test}, - OrderBy: []string{"asc"}, + SortBy: []sortProperty{test}, + OrderBy: []sortOrder{OrderAscending}, }, args: args{ search: "", @@ -933,16 +933,16 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: test, - orderBy: "asc", + orderBy: OrderAscending, }, }), wantErr: false, }, { - name: "ReadAll Tasks sorted by " + test + " desc", + name: "ReadAll Tasks sorted by " + test.String() + " desc", fields: fields{ - SortBy: []string{test}, - OrderBy: []string{"desc"}, + SortBy: []sortProperty{test}, + OrderBy: []sortOrder{OrderDescending}, }, args: args{ search: "", @@ -952,7 +952,7 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: test, - orderBy: "desc", + orderBy: OrderDescending, }, }), wantErr: false, @@ -963,12 +963,12 @@ func TestTask_ReadAll(t *testing.T) { // Cases with two parameters for _, outerTest := range sortByFields { // We don't test all cases with everything here because this would mean an enormous amount of test cases - for _, test := range []string{"text", "done"} { + for _, test := range []sortProperty{"text", "done"} { tests = append(tests, []testcase{ { - name: "ReadAll Tasks sorted by " + outerTest + " and " + test + "default asc", + name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + "default asc", fields: fields{ - SortBy: []string{outerTest, test}, + SortBy: []sortProperty{outerTest, test}, }, args: args{ search: "", @@ -978,20 +978,20 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: outerTest, - orderBy: "asc", + orderBy: OrderAscending, }, { sortBy: test, - orderBy: "asc", + orderBy: OrderAscending, }, }), wantErr: false, }, { - name: "ReadAll Tasks sorted by " + outerTest + " and " + test + " asc", + name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " asc", fields: fields{ - SortBy: []string{outerTest, test}, - OrderBy: []string{"asc", "asc"}, + SortBy: []sortProperty{outerTest, test}, + OrderBy: []sortOrder{OrderAscending, OrderAscending}, }, args: args{ search: "", @@ -1001,20 +1001,20 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: outerTest, - orderBy: "asc", + orderBy: OrderAscending, }, { sortBy: test, - orderBy: "asc", + orderBy: OrderAscending, }, }), wantErr: false, }, { - name: "ReadAll Tasks sorted by " + outerTest + " and " + test + " desc", + name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " desc", fields: fields{ - SortBy: []string{outerTest, test}, - OrderBy: []string{"desc", "desc"}, + SortBy: []sortProperty{outerTest, test}, + OrderBy: []sortOrder{OrderDescending, OrderDescending}, }, args: args{ search: "", @@ -1024,11 +1024,11 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: outerTest, - orderBy: "desc", + orderBy: OrderDescending, }, { sortBy: test, - orderBy: "desc", + orderBy: OrderDescending, }, }), wantErr: false, diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index a1920641a..e3a9820e1 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -255,66 +255,63 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo } func sortTasks(tasks []*Task, by []*sortParam) { - const defaultComparator = "default" + const defaultComparator = PropertyID type TaskComparator func(lhs, rhs *Task) bool // This is a map of properties that can be sorted by // and their appropriate comparator function. // The comparator function sorts in ascending mode. - var comparators = map[string]TaskComparator{ - "text": func(lhs, rhs *Task) bool { + var comparators = map[sortProperty]TaskComparator{ + PropertyText: func(lhs, rhs *Task) bool { return lhs.Text < rhs.Text }, - "description": func(lhs, rhs *Task) bool { + PropertyDescription: func(lhs, rhs *Task) bool { return lhs.Description < rhs.Description }, - "done": func(lhs, rhs *Task) bool { + PropertyDone: func(lhs, rhs *Task) bool { return lhs.Done == rhs.Done }, - "done_at_unix": func(lhs, rhs *Task) bool { + PropertyDoneAtUnix: func(lhs, rhs *Task) bool { return lhs.DoneAtUnix < rhs.DoneAtUnix }, - "due_date_unix": func(lhs, rhs *Task) bool { + PropertyDueDateUnix: func(lhs, rhs *Task) bool { return lhs.DueDateUnix < rhs.DueDateUnix }, - "created_by_id": func(lhs, rhs *Task) bool { + PropertyCreatedByID: func(lhs, rhs *Task) bool { return lhs.CreatedByID < rhs.CreatedByID }, - "list_id": func(lhs, rhs *Task) bool { + PropertyListID: func(lhs, rhs *Task) bool { return lhs.ListID < rhs.ListID }, - "repeat_after": func(lhs, rhs *Task) bool { + PropertyRepeatAfter: func(lhs, rhs *Task) bool { return lhs.RepeatAfter < rhs.RepeatAfter }, - "priority": func(lhs, rhs *Task) bool { + PropertyPriority: func(lhs, rhs *Task) bool { return lhs.Priority < rhs.Priority }, - "start_date_unix": func(lhs, rhs *Task) bool { + PropertyStartDateUnix: func(lhs, rhs *Task) bool { return lhs.StartDateUnix < rhs.StartDateUnix }, - "end_date_unix": func(lhs, rhs *Task) bool { + PropertyEndDateUnix: func(lhs, rhs *Task) bool { return lhs.EndDateUnix < rhs.EndDateUnix }, - "hex_color": func(lhs, rhs *Task) bool { + PropertyHexColor: func(lhs, rhs *Task) bool { return lhs.HexColor < rhs.HexColor }, - "percent_done": func(lhs, rhs *Task) bool { + PropertyPercentDone: func(lhs, rhs *Task) bool { return lhs.PercentDone < rhs.PercentDone }, - "uid": func(lhs, rhs *Task) bool { + PropertyUID: func(lhs, rhs *Task) bool { return lhs.UID < rhs.UID }, - "created": func(lhs, rhs *Task) bool { + PropertyCreated: func(lhs, rhs *Task) bool { return lhs.Created < rhs.Created }, - "updated": func(lhs, rhs *Task) bool { + PropertyUpdated: func(lhs, rhs *Task) bool { return lhs.Updated < rhs.Updated }, - "id": func(lhs, rhs *Task) bool { - return lhs.ID < rhs.ID - }, - defaultComparator: func(lhs, rhs *Task) bool { + PropertyID: func(lhs, rhs *Task) bool { return lhs.ID < rhs.ID }, } @@ -328,7 +325,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { } // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). - if param.orderBy == "desc" { + if param.orderBy == OrderDescending { oldComparator := comparator comparator = func(lhs, rhs *Task) bool { return !oldComparator(lhs, rhs) -- 2.40.1 From 89cf1e5c46c5746f256b9dd086396a01608d7928 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 3 Dec 2019 23:19:30 +0100 Subject: [PATCH 14/60] Add todo --- pkg/integrations/task_collection_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 46b5b6732..92b513833 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -91,6 +91,7 @@ func TestTaskCollection(t *testing.T) { }) t.Run("Sort Order", func(t *testing.T) { // TODO: Add more cases + // TODO: Add invalid test cases (one should be enough, we have the unit test of the validation method for the rest) // should equal priority asc t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) -- 2.40.1 From 1115df5d510b4f1fed9d8bb00434731086d0c9e3 Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Tue, 3 Dec 2019 23:40:28 +0100 Subject: [PATCH 15/60] Fix task sorting when using multiple properties Let TaskComparator return an int instead of a bool for three-way comparison. Introduce a combined TaskComparator that steps through each property comparator until it finds a difference between two tasks. This fixes the previous methodology where the sorting for the last property in the list overwrote any other property-based sorting that happened before that. --- pkg/models/tasks.go | 204 +++++++++++++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 67 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index e3a9820e1..df248f7b6 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -254,72 +254,135 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo return tasks, resultCount, totalItems, err } +type TaskComparator func(lhs, rhs *Task) int + +// TODO: Move somewhere else +func stringCompare(lhs, rhs string) int { + if lhs > rhs { + return 1 + } + if lhs < rhs { + return -1 + } + return 0 +} + +func int64Compare(lhs, rhs int64) int { + if lhs > rhs { + return 1 + } + if lhs < rhs { + return -1 + } + return 0 +} + +func float64Compare(lhs, rhs float64) int { + if lhs > rhs { + return 1 + } + if lhs < rhs { + return -1 + } + return 0 +} + +func boolCompare(lhs, rhs, first bool) int { + // TODO: Can we simplify this? + if !lhs && rhs { + if first { + return 1 + } else { + return -1 + } + } + if lhs && !rhs { + if first { + return -1 + } else { + return 1 + } + } + return 0 +} + +// This is a map of properties that can be sorted by +// and their appropriate comparator function. +// The comparator function sorts in ascending mode. +var propertyComparators = map[sortProperty]TaskComparator{ + PropertyText: func(lhs, rhs *Task) int { + return stringCompare(lhs.Text, rhs.Text) + }, + PropertyDescription: func(lhs, rhs *Task) int { + return stringCompare(lhs.Description, rhs.Description) + }, + PropertyDone: func(lhs, rhs *Task) int { + return boolCompare(lhs.Done, rhs.Done, true) + }, + PropertyDoneAtUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.DoneAtUnix, rhs.DoneAtUnix) + }, + PropertyDueDateUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.DueDateUnix, rhs.DueDateUnix) + }, + PropertyCreatedByID: func(lhs, rhs *Task) int { + return int64Compare(lhs.CreatedByID, rhs.CreatedByID) + }, + PropertyListID: func(lhs, rhs *Task) int { + return int64Compare(lhs.ListID, rhs.ListID) + }, + PropertyRepeatAfter: func(lhs, rhs *Task) int { + return int64Compare(lhs.RepeatAfter, rhs.RepeatAfter) + }, + PropertyPriority: func(lhs, rhs *Task) int { + return int64Compare(lhs.Priority, rhs.Priority) + }, + PropertyStartDateUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.StartDateUnix, rhs.StartDateUnix) + }, + PropertyEndDateUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.EndDateUnix, rhs.EndDateUnix) + }, + PropertyHexColor: func(lhs, rhs *Task) int { + return stringCompare(lhs.HexColor, rhs.HexColor) + }, + PropertyPercentDone: func(lhs, rhs *Task) int { + return float64Compare(lhs.PercentDone, rhs.PercentDone) + }, + PropertyUID: func(lhs, rhs *Task) int { + return stringCompare(lhs.UID, rhs.UID) + }, + PropertyCreated: func(lhs, rhs *Task) int { + return int64Compare(lhs.Created, rhs.Created) + }, + PropertyUpdated: func(lhs, rhs *Task) int { + return int64Compare(lhs.Updated, rhs.Updated) + }, + PropertyID: func(lhs, rhs *Task) int { + return int64Compare(lhs.ID, rhs.ID) + }, +} + +// Creates a TaskComparator that sorts by the first comparator and falls back to +// the second one (and so on...) if the properties were equal. +func combineComparators(comparators ...TaskComparator) TaskComparator { + return func(lhs, rhs *Task) int { + for _, compare := range comparators { + res := compare(lhs, rhs) + if res != 0 { + return res + } + } + return 0 + } +} + func sortTasks(tasks []*Task, by []*sortParam) { const defaultComparator = PropertyID - type TaskComparator func(lhs, rhs *Task) bool - - // This is a map of properties that can be sorted by - // and their appropriate comparator function. - // The comparator function sorts in ascending mode. - var comparators = map[sortProperty]TaskComparator{ - PropertyText: func(lhs, rhs *Task) bool { - return lhs.Text < rhs.Text - }, - PropertyDescription: func(lhs, rhs *Task) bool { - return lhs.Description < rhs.Description - }, - PropertyDone: func(lhs, rhs *Task) bool { - return lhs.Done == rhs.Done - }, - PropertyDoneAtUnix: func(lhs, rhs *Task) bool { - return lhs.DoneAtUnix < rhs.DoneAtUnix - }, - PropertyDueDateUnix: func(lhs, rhs *Task) bool { - return lhs.DueDateUnix < rhs.DueDateUnix - }, - PropertyCreatedByID: func(lhs, rhs *Task) bool { - return lhs.CreatedByID < rhs.CreatedByID - }, - PropertyListID: func(lhs, rhs *Task) bool { - return lhs.ListID < rhs.ListID - }, - PropertyRepeatAfter: func(lhs, rhs *Task) bool { - return lhs.RepeatAfter < rhs.RepeatAfter - }, - PropertyPriority: func(lhs, rhs *Task) bool { - return lhs.Priority < rhs.Priority - }, - PropertyStartDateUnix: func(lhs, rhs *Task) bool { - return lhs.StartDateUnix < rhs.StartDateUnix - }, - PropertyEndDateUnix: func(lhs, rhs *Task) bool { - return lhs.EndDateUnix < rhs.EndDateUnix - }, - PropertyHexColor: func(lhs, rhs *Task) bool { - return lhs.HexColor < rhs.HexColor - }, - PropertyPercentDone: func(lhs, rhs *Task) bool { - return lhs.PercentDone < rhs.PercentDone - }, - PropertyUID: func(lhs, rhs *Task) bool { - return lhs.UID < rhs.UID - }, - PropertyCreated: func(lhs, rhs *Task) bool { - return lhs.Created < rhs.Created - }, - PropertyUpdated: func(lhs, rhs *Task) bool { - return lhs.Updated < rhs.Updated - }, - PropertyID: func(lhs, rhs *Task) bool { - return lhs.ID < rhs.ID - }, - } - + comparators := make([]TaskComparator, 0, len(by)) for _, param := range by { - var comparator TaskComparator - var ok bool - comparator, ok = comparators[param.sortBy] + comparator, ok := propertyComparators[param.sortBy] if !ok { panic("No suitable comparator for sortBy found! Param was " + param.sortBy) } @@ -327,15 +390,22 @@ func sortTasks(tasks []*Task, by []*sortParam) { // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). if param.orderBy == OrderDescending { oldComparator := comparator - comparator = func(lhs, rhs *Task) bool { - return !oldComparator(lhs, rhs) + comparator = func(lhs, rhs *Task) int { + return -oldComparator(lhs, rhs) } } - sort.Slice(tasks, func(i, j int) bool { - return comparator(tasks[i], tasks[j]) - }) + comparators = append(comparators, comparator) } + + combinedComparator := combineComparators(comparators...) + + sort.Slice(tasks, func(i, j int) bool { + lhs, rhs := tasks[i], tasks[j] + + res := combinedComparator(lhs, rhs) + return res <= 0 + }) } // GetTasksByListID gets all todotasks for a list -- 2.40.1 From 51a0539732441215f01429cd2c96632b45e662ad Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 08:43:58 +0100 Subject: [PATCH 16/60] Satisfy CI linter tool --- pkg/models/task_collection.go | 80 ++++++------- pkg/models/task_collection_test.go | 176 ++++++++++++++--------------- pkg/models/tasks.go | 54 +++++---- 3 files changed, 154 insertions(+), 156 deletions(-) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index f46d36b5b..9729ba485 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -49,24 +49,25 @@ type ( sortOrder bool ) +// TODO: Prefix them with "task" or so because they are available in the whole package. const ( - PropertyID sortProperty = "id" - PropertyText sortProperty = "text" - PropertyDescription sortProperty = "description" - PropertyDone sortProperty = "done" - PropertyDoneAtUnix sortProperty = "done_at_unix" - PropertyDueDateUnix sortProperty = "due_date_unix" - PropertyCreatedByID sortProperty = "created_by_id" - PropertyListID sortProperty = "list_id" - PropertyRepeatAfter sortProperty = "repeat_after" - PropertyPriority sortProperty = "priority" - PropertyStartDateUnix sortProperty = "start_date_unix" - PropertyEndDateUnix sortProperty = "end_date_unix" - PropertyHexColor sortProperty = "hex_color" - PropertyPercentDone sortProperty = "percent_done" - PropertyUID sortProperty = "uid" - PropertyCreated sortProperty = "created" - PropertyUpdated sortProperty = "updated" + propertyID sortProperty = "id" + propertyText sortProperty = "text" + propertyDescription sortProperty = "description" + propertyDone sortProperty = "done" + propertyDoneAtUnix sortProperty = "done_at_unix" + propertyDueDateUnix sortProperty = "due_date_unix" + propertyCreatedByID sortProperty = "created_by_id" + propertyListID sortProperty = "list_id" + propertyRepeatAfter sortProperty = "repeat_after" + propertyPriority sortProperty = "priority" + propertyStartDateUnix sortProperty = "start_date_unix" + propertyEndDateUnix sortProperty = "end_date_unix" + propertyHexColor sortProperty = "hex_color" + propertyPercentDone sortProperty = "percent_done" + propertyUID sortProperty = "uid" + propertyCreated sortProperty = "created" + propertyUpdated sortProperty = "updated" ) func (p sortProperty) String() string { @@ -74,38 +75,37 @@ func (p sortProperty) String() string { } const ( - OrderAscending sortOrder = false - OrderDescending sortOrder = true + orderAscending sortOrder = false + orderDescending sortOrder = true ) func (o sortOrder) String() string { - if o == OrderAscending { + if o == orderAscending { return "asc" - } else { - return "desc" } + return "desc" } func (sp *sortParam) validate() error { switch sp.sortBy { case - PropertyID, - PropertyText, - PropertyDescription, - PropertyDone, - PropertyDoneAtUnix, - PropertyDueDateUnix, - PropertyCreatedByID, - PropertyListID, - PropertyRepeatAfter, - PropertyPriority, - PropertyStartDateUnix, - PropertyEndDateUnix, - PropertyHexColor, - PropertyPercentDone, - PropertyUID, - PropertyCreated, - PropertyUpdated: + propertyID, + propertyText, + propertyDescription, + propertyDone, + propertyDoneAtUnix, + propertyDueDateUnix, + propertyCreatedByID, + propertyListID, + propertyRepeatAfter, + propertyPriority, + propertyStartDateUnix, + propertyEndDateUnix, + propertyHexColor, + propertyPercentDone, + propertyUID, + propertyCreated, + propertyUpdated: return nil } return ErrInvalidSortParam{SortBy: sp.sortBy} @@ -156,7 +156,7 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i for i, s := range tf.SortBy { sortParam := &sortParam{ sortBy: s, - orderBy: OrderAscending, + orderBy: orderAscending, } // This checks if tf.OrderBy has an entry with the same index as the current entry from tf.SortBy // Taken from https://stackoverflow.com/a/27252199/10924593 diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index a704098cd..3c19481a2 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -28,17 +28,17 @@ import ( func TestSortParamValidation(t *testing.T) { t.Run("Test valid order by", func(t *testing.T) { - t.Run(OrderAscending.String(), func(t *testing.T) { + t.Run(orderAscending.String(), func(t *testing.T) { s := &sortParam{ - orderBy: OrderAscending, + orderBy: orderAscending, sortBy: "id", } err := s.validate() assert.NoError(t, err) }) - t.Run(OrderDescending.String(), func(t *testing.T) { + t.Run(orderDescending.String(), func(t *testing.T) { s := &sortParam{ - orderBy: OrderDescending, + orderBy: orderDescending, sortBy: "id", } err := s.validate() @@ -47,27 +47,27 @@ func TestSortParamValidation(t *testing.T) { }) t.Run("Test valid sort by", func(t *testing.T) { for _, test := range []sortProperty{ - PropertyID, - PropertyText, - PropertyDescription, - PropertyDone, - PropertyDoneAtUnix, - PropertyDueDateUnix, - PropertyCreatedByID, - PropertyListID, - PropertyRepeatAfter, - PropertyPriority, - PropertyStartDateUnix, - PropertyEndDateUnix, - PropertyHexColor, - PropertyPercentDone, - PropertyUID, - PropertyCreated, - PropertyUpdated, + propertyID, + propertyText, + propertyDescription, + propertyDone, + propertyDoneAtUnix, + propertyDueDateUnix, + propertyCreatedByID, + propertyListID, + propertyRepeatAfter, + propertyPriority, + propertyStartDateUnix, + propertyEndDateUnix, + propertyHexColor, + propertyPercentDone, + propertyUID, + propertyCreated, + propertyUpdated, } { t.Run(test.String(), func(t *testing.T) { s := &sortParam{ - orderBy: OrderAscending, + orderBy: orderAscending, sortBy: test, } err := s.validate() @@ -86,7 +86,7 @@ func TestSortParamValidation(t *testing.T) { }) t.Run("Test invalid sort by", func(t *testing.T) { s := &sortParam{ - orderBy: OrderAscending, + orderBy: orderAscending, sortBy: "somethingInvalid", } err := s.validate() @@ -519,189 +519,189 @@ func sortTasksForTesting(by []*sortParam) (tasks []*Task) { for _, param := range by { switch param.sortBy { case "text": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Text < tasks[j].Text }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Text > tasks[j].Text }) } case "description": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Description < tasks[j].Description }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Description > tasks[j].Description }) } case "done": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Done == tasks[j].Done }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Done != tasks[j].Done }) } case "done_at_unix": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix }) } case "due_date_unix": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DueDateUnix < tasks[j].DueDateUnix }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].DueDateUnix > tasks[j].DueDateUnix }) } case "created_by_id": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].CreatedByID < tasks[j].CreatedByID }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].CreatedByID > tasks[j].CreatedByID }) } case "list_id": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ListID < tasks[j].ListID }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ListID > tasks[j].ListID }) } case "repeat_after": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].RepeatAfter < tasks[j].RepeatAfter }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].RepeatAfter > tasks[j].RepeatAfter }) } case "priority": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Priority < tasks[j].Priority }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Priority > tasks[j].Priority }) } case "start_date_unix": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].StartDateUnix < tasks[j].StartDateUnix }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].StartDateUnix > tasks[j].StartDateUnix }) } case "end_date_unix": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].EndDateUnix < tasks[j].EndDateUnix }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].EndDateUnix > tasks[j].EndDateUnix }) } case "hex_color": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].HexColor < tasks[j].HexColor }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].HexColor > tasks[j].HexColor }) } case "percent_done": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].PercentDone < tasks[j].PercentDone }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].PercentDone > tasks[j].PercentDone }) } case "uid": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].UID < tasks[j].UID }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].UID > tasks[j].UID }) } case "created": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created < tasks[j].Created }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Created > tasks[j].Created }) } case "updated": - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Updated < tasks[j].Updated }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].Updated > tasks[j].Updated }) } default: // Sorting by ID is the default, so we don't need an extra case for it - if param.orderBy == OrderAscending { + if param.orderBy == orderAscending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ID < tasks[j].ID }) } - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { sort.Slice(tasks, func(i, j int) bool { return tasks[i].ID > tasks[j].ID }) @@ -758,7 +758,7 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: "id", - orderBy: OrderAscending, + orderBy: orderAscending, }, }), wantErr: false, @@ -879,23 +879,23 @@ func TestTask_ReadAll(t *testing.T) { } sortByFields := []sortProperty{ - PropertyID, - PropertyText, - PropertyDescription, - PropertyDone, - PropertyDoneAtUnix, - PropertyDueDateUnix, - PropertyCreatedByID, - PropertyListID, - PropertyRepeatAfter, - PropertyPriority, - PropertyStartDateUnix, - PropertyEndDateUnix, - PropertyHexColor, - PropertyPercentDone, - PropertyUID, - PropertyCreated, - PropertyUpdated, + propertyID, + propertyText, + propertyDescription, + propertyDone, + propertyDoneAtUnix, + propertyDueDateUnix, + propertyCreatedByID, + propertyListID, + propertyRepeatAfter, + propertyPriority, + propertyStartDateUnix, + propertyEndDateUnix, + propertyHexColor, + propertyPercentDone, + propertyUID, + propertyCreated, + propertyUpdated, } // Add more cases programatically // Simple cases @@ -914,7 +914,7 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: test, - orderBy: OrderAscending, + orderBy: orderAscending, }, }), wantErr: false, @@ -923,7 +923,7 @@ func TestTask_ReadAll(t *testing.T) { name: "ReadAll Tasks sorted by " + test.String() + " asc", fields: fields{ SortBy: []sortProperty{test}, - OrderBy: []sortOrder{OrderAscending}, + OrderBy: []sortOrder{orderAscending}, }, args: args{ search: "", @@ -933,7 +933,7 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: test, - orderBy: OrderAscending, + orderBy: orderAscending, }, }), wantErr: false, @@ -942,7 +942,7 @@ func TestTask_ReadAll(t *testing.T) { name: "ReadAll Tasks sorted by " + test.String() + " desc", fields: fields{ SortBy: []sortProperty{test}, - OrderBy: []sortOrder{OrderDescending}, + OrderBy: []sortOrder{orderDescending}, }, args: args{ search: "", @@ -952,7 +952,7 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: test, - orderBy: OrderDescending, + orderBy: orderDescending, }, }), wantErr: false, @@ -978,11 +978,11 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: outerTest, - orderBy: OrderAscending, + orderBy: orderAscending, }, { sortBy: test, - orderBy: OrderAscending, + orderBy: orderAscending, }, }), wantErr: false, @@ -991,7 +991,7 @@ func TestTask_ReadAll(t *testing.T) { name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " asc", fields: fields{ SortBy: []sortProperty{outerTest, test}, - OrderBy: []sortOrder{OrderAscending, OrderAscending}, + OrderBy: []sortOrder{orderAscending, orderAscending}, }, args: args{ search: "", @@ -1001,11 +1001,11 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: outerTest, - orderBy: OrderAscending, + orderBy: orderAscending, }, { sortBy: test, - orderBy: OrderAscending, + orderBy: orderAscending, }, }), wantErr: false, @@ -1014,7 +1014,7 @@ func TestTask_ReadAll(t *testing.T) { name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " desc", fields: fields{ SortBy: []sortProperty{outerTest, test}, - OrderBy: []sortOrder{OrderDescending, OrderDescending}, + OrderBy: []sortOrder{orderDescending, orderDescending}, }, args: args{ search: "", @@ -1024,11 +1024,11 @@ func TestTask_ReadAll(t *testing.T) { want: sortTasksForTesting([]*sortParam{ { sortBy: outerTest, - orderBy: OrderDescending, + orderBy: orderDescending, }, { sortBy: test, - orderBy: OrderDescending, + orderBy: orderDescending, }, }), wantErr: false, diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index df248f7b6..dcb15ae47 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -254,7 +254,7 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo return tasks, resultCount, totalItems, err } -type TaskComparator func(lhs, rhs *Task) int +type taskComparator func(lhs, rhs *Task) int // TODO: Move somewhere else func stringCompare(lhs, rhs string) int { @@ -292,16 +292,14 @@ func boolCompare(lhs, rhs, first bool) int { if !lhs && rhs { if first { return 1 - } else { - return -1 } + return -1 } if lhs && !rhs { if first { return -1 - } else { - return 1 } + return 1 } return 0 } @@ -309,63 +307,63 @@ func boolCompare(lhs, rhs, first bool) int { // This is a map of properties that can be sorted by // and their appropriate comparator function. // The comparator function sorts in ascending mode. -var propertyComparators = map[sortProperty]TaskComparator{ - PropertyText: func(lhs, rhs *Task) int { +var propertyComparators = map[sortProperty]taskComparator{ + propertyText: func(lhs, rhs *Task) int { return stringCompare(lhs.Text, rhs.Text) }, - PropertyDescription: func(lhs, rhs *Task) int { + propertyDescription: func(lhs, rhs *Task) int { return stringCompare(lhs.Description, rhs.Description) }, - PropertyDone: func(lhs, rhs *Task) int { + propertyDone: func(lhs, rhs *Task) int { return boolCompare(lhs.Done, rhs.Done, true) }, - PropertyDoneAtUnix: func(lhs, rhs *Task) int { + propertyDoneAtUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.DoneAtUnix, rhs.DoneAtUnix) }, - PropertyDueDateUnix: func(lhs, rhs *Task) int { + propertyDueDateUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.DueDateUnix, rhs.DueDateUnix) }, - PropertyCreatedByID: func(lhs, rhs *Task) int { + propertyCreatedByID: func(lhs, rhs *Task) int { return int64Compare(lhs.CreatedByID, rhs.CreatedByID) }, - PropertyListID: func(lhs, rhs *Task) int { + propertyListID: func(lhs, rhs *Task) int { return int64Compare(lhs.ListID, rhs.ListID) }, - PropertyRepeatAfter: func(lhs, rhs *Task) int { + propertyRepeatAfter: func(lhs, rhs *Task) int { return int64Compare(lhs.RepeatAfter, rhs.RepeatAfter) }, - PropertyPriority: func(lhs, rhs *Task) int { + propertyPriority: func(lhs, rhs *Task) int { return int64Compare(lhs.Priority, rhs.Priority) }, - PropertyStartDateUnix: func(lhs, rhs *Task) int { + propertyStartDateUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.StartDateUnix, rhs.StartDateUnix) }, - PropertyEndDateUnix: func(lhs, rhs *Task) int { + propertyEndDateUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.EndDateUnix, rhs.EndDateUnix) }, - PropertyHexColor: func(lhs, rhs *Task) int { + propertyHexColor: func(lhs, rhs *Task) int { return stringCompare(lhs.HexColor, rhs.HexColor) }, - PropertyPercentDone: func(lhs, rhs *Task) int { + propertyPercentDone: func(lhs, rhs *Task) int { return float64Compare(lhs.PercentDone, rhs.PercentDone) }, - PropertyUID: func(lhs, rhs *Task) int { + propertyUID: func(lhs, rhs *Task) int { return stringCompare(lhs.UID, rhs.UID) }, - PropertyCreated: func(lhs, rhs *Task) int { + propertyCreated: func(lhs, rhs *Task) int { return int64Compare(lhs.Created, rhs.Created) }, - PropertyUpdated: func(lhs, rhs *Task) int { + propertyUpdated: func(lhs, rhs *Task) int { return int64Compare(lhs.Updated, rhs.Updated) }, - PropertyID: func(lhs, rhs *Task) int { + propertyID: func(lhs, rhs *Task) int { return int64Compare(lhs.ID, rhs.ID) }, } -// Creates a TaskComparator that sorts by the first comparator and falls back to +// Creates a taskComparator that sorts by the first comparator and falls back to // the second one (and so on...) if the properties were equal. -func combineComparators(comparators ...TaskComparator) TaskComparator { +func combineComparators(comparators ...taskComparator) taskComparator { return func(lhs, rhs *Task) int { for _, compare := range comparators { res := compare(lhs, rhs) @@ -378,9 +376,9 @@ func combineComparators(comparators ...TaskComparator) TaskComparator { } func sortTasks(tasks []*Task, by []*sortParam) { - const defaultComparator = PropertyID + const defaultComparator = propertyID - comparators := make([]TaskComparator, 0, len(by)) + comparators := make([]taskComparator, 0, len(by)) for _, param := range by { comparator, ok := propertyComparators[param.sortBy] if !ok { @@ -388,7 +386,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { } // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). - if param.orderBy == OrderDescending { + if param.orderBy == orderDescending { oldComparator := comparator comparator = func(lhs, rhs *Task) int { return -oldComparator(lhs, rhs) -- 2.40.1 From 0adf561945f960071c37df57a30fe60aae36c290 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 18:13:21 +0100 Subject: [PATCH 17/60] Make it build again --- pkg/models/error.go | 2 +- pkg/models/task_collection.go | 11 +++++++++-- pkg/models/tasks.go | 2 +- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pkg/models/error.go b/pkg/models/error.go index 317ef6a31..9cc456bee 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -790,7 +790,7 @@ func (err ErrTaskAttachmentIsTooLarge) HTTPError() web.HTTPError { // ErrInvalidSortParam represents an error where the provided sort param is invalid type ErrInvalidSortParam struct { - SortBy string + SortBy sortProperty } // IsErrInvalidSortParam checks if an error is ErrInvalidSortParam. diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 9729ba485..11f02f822 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -86,6 +86,13 @@ func (o sortOrder) String() string { return "desc" } +func getSortOrderFromString(s string) sortOrder { + if s == "asc" { + return orderAscending + } + return orderDescending +} + func (sp *sortParam) validate() error { switch sp.sortBy { case @@ -155,13 +162,13 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i var sort = make([]*sortParam, 0, len(tf.SortBy)) for i, s := range tf.SortBy { sortParam := &sortParam{ - sortBy: s, + sortBy: sortProperty(s), orderBy: orderAscending, } // This checks if tf.OrderBy has an entry with the same index as the current entry from tf.SortBy // Taken from https://stackoverflow.com/a/27252199/10924593 if len(tf.OrderBy) > i { - sortParam.orderBy = tf.OrderBy[i] + sortParam.orderBy = getSortOrderFromString(tf.OrderBy[i]) } sort = append(sort, sortParam) } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index dcb15ae47..7b71fc2cb 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -175,7 +175,7 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T if err := param.validate(); err != nil { return nil, 0, 0, err } - orderby += param.sortBy + " " + param.orderBy + orderby += param.sortBy.String() + " " + param.orderBy.String() if (i + 1) < len(opts.sortby) { orderby += ", " } -- 2.40.1 From d3d581a0a78459ca16e9ff9b186deea668fdf28c Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 18:17:05 +0100 Subject: [PATCH 18/60] Move task sorting to seperate file --- pkg/models/task_collection.go | 80 --------- pkg/models/task_collection_sort.go | 251 +++++++++++++++++++++++++++++ pkg/models/tasks.go | 152 ----------------- 3 files changed, 251 insertions(+), 232 deletions(-) create mode 100644 pkg/models/task_collection_sort.go diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 11f02f822..04fe75a27 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -38,86 +38,6 @@ type TaskCollection struct { web.Rights `xorm:"-" json:"-"` } -type ( - sortParam struct { - sortBy sortProperty - orderBy sortOrder // asc or desc - } - - sortProperty string - - sortOrder bool -) - -// TODO: Prefix them with "task" or so because they are available in the whole package. -const ( - propertyID sortProperty = "id" - propertyText sortProperty = "text" - propertyDescription sortProperty = "description" - propertyDone sortProperty = "done" - propertyDoneAtUnix sortProperty = "done_at_unix" - propertyDueDateUnix sortProperty = "due_date_unix" - propertyCreatedByID sortProperty = "created_by_id" - propertyListID sortProperty = "list_id" - propertyRepeatAfter sortProperty = "repeat_after" - propertyPriority sortProperty = "priority" - propertyStartDateUnix sortProperty = "start_date_unix" - propertyEndDateUnix sortProperty = "end_date_unix" - propertyHexColor sortProperty = "hex_color" - propertyPercentDone sortProperty = "percent_done" - propertyUID sortProperty = "uid" - propertyCreated sortProperty = "created" - propertyUpdated sortProperty = "updated" -) - -func (p sortProperty) String() string { - return string(p) -} - -const ( - orderAscending sortOrder = false - orderDescending sortOrder = true -) - -func (o sortOrder) String() string { - if o == orderAscending { - return "asc" - } - return "desc" -} - -func getSortOrderFromString(s string) sortOrder { - if s == "asc" { - return orderAscending - } - return orderDescending -} - -func (sp *sortParam) validate() error { - switch sp.sortBy { - case - propertyID, - propertyText, - propertyDescription, - propertyDone, - propertyDoneAtUnix, - propertyDueDateUnix, - propertyCreatedByID, - propertyListID, - propertyRepeatAfter, - propertyPriority, - propertyStartDateUnix, - propertyEndDateUnix, - propertyHexColor, - propertyPercentDone, - propertyUID, - propertyCreated, - propertyUpdated: - return nil - } - return ErrInvalidSortParam{SortBy: sp.sortBy} -} - // TODO: Docs // TODO: Swagger // TODO: Tests diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go new file mode 100644 index 000000000..06e7d4fbc --- /dev/null +++ b/pkg/models/task_collection_sort.go @@ -0,0 +1,251 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import "sort" + +type ( + sortParam struct { + sortBy sortProperty + orderBy sortOrder // asc or desc + } + + sortProperty string + + sortOrder bool +) + +// TODO: Prefix them with "task" or so because they are available in the whole package. +const ( + propertyID sortProperty = "id" + propertyText sortProperty = "text" + propertyDescription sortProperty = "description" + propertyDone sortProperty = "done" + propertyDoneAtUnix sortProperty = "done_at_unix" + propertyDueDateUnix sortProperty = "due_date_unix" + propertyCreatedByID sortProperty = "created_by_id" + propertyListID sortProperty = "list_id" + propertyRepeatAfter sortProperty = "repeat_after" + propertyPriority sortProperty = "priority" + propertyStartDateUnix sortProperty = "start_date_unix" + propertyEndDateUnix sortProperty = "end_date_unix" + propertyHexColor sortProperty = "hex_color" + propertyPercentDone sortProperty = "percent_done" + propertyUID sortProperty = "uid" + propertyCreated sortProperty = "created" + propertyUpdated sortProperty = "updated" +) + +func (p sortProperty) String() string { + return string(p) +} + +const ( + orderAscending sortOrder = false + orderDescending sortOrder = true +) + +func (o sortOrder) String() string { + if o == orderAscending { + return "asc" + } + return "desc" +} + +func getSortOrderFromString(s string) sortOrder { + if s == "asc" { + return orderAscending + } + return orderDescending +} + +func (sp *sortParam) validate() error { + switch sp.sortBy { + case + propertyID, + propertyText, + propertyDescription, + propertyDone, + propertyDoneAtUnix, + propertyDueDateUnix, + propertyCreatedByID, + propertyListID, + propertyRepeatAfter, + propertyPriority, + propertyStartDateUnix, + propertyEndDateUnix, + propertyHexColor, + propertyPercentDone, + propertyUID, + propertyCreated, + propertyUpdated: + return nil + } + return ErrInvalidSortParam{SortBy: sp.sortBy} +} + +type taskComparator func(lhs, rhs *Task) int + +// TODO: Move somewhere else +func stringCompare(lhs, rhs string) int { + if lhs > rhs { + return 1 + } + if lhs < rhs { + return -1 + } + return 0 +} + +func int64Compare(lhs, rhs int64) int { + if lhs > rhs { + return 1 + } + if lhs < rhs { + return -1 + } + return 0 +} + +func float64Compare(lhs, rhs float64) int { + if lhs > rhs { + return 1 + } + if lhs < rhs { + return -1 + } + return 0 +} + +func boolCompare(lhs, rhs, first bool) int { + // TODO: Can we simplify this? + if !lhs && rhs { + if first { + return 1 + } + return -1 + } + if lhs && !rhs { + if first { + return -1 + } + return 1 + } + return 0 +} + +// This is a map of properties that can be sorted by +// and their appropriate comparator function. +// The comparator function sorts in ascending mode. +var propertyComparators = map[sortProperty]taskComparator{ + propertyText: func(lhs, rhs *Task) int { + return stringCompare(lhs.Text, rhs.Text) + }, + propertyDescription: func(lhs, rhs *Task) int { + return stringCompare(lhs.Description, rhs.Description) + }, + propertyDone: func(lhs, rhs *Task) int { + return boolCompare(lhs.Done, rhs.Done, true) + }, + propertyDoneAtUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.DoneAtUnix, rhs.DoneAtUnix) + }, + propertyDueDateUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.DueDateUnix, rhs.DueDateUnix) + }, + propertyCreatedByID: func(lhs, rhs *Task) int { + return int64Compare(lhs.CreatedByID, rhs.CreatedByID) + }, + propertyListID: func(lhs, rhs *Task) int { + return int64Compare(lhs.ListID, rhs.ListID) + }, + propertyRepeatAfter: func(lhs, rhs *Task) int { + return int64Compare(lhs.RepeatAfter, rhs.RepeatAfter) + }, + propertyPriority: func(lhs, rhs *Task) int { + return int64Compare(lhs.Priority, rhs.Priority) + }, + propertyStartDateUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.StartDateUnix, rhs.StartDateUnix) + }, + propertyEndDateUnix: func(lhs, rhs *Task) int { + return int64Compare(lhs.EndDateUnix, rhs.EndDateUnix) + }, + propertyHexColor: func(lhs, rhs *Task) int { + return stringCompare(lhs.HexColor, rhs.HexColor) + }, + propertyPercentDone: func(lhs, rhs *Task) int { + return float64Compare(lhs.PercentDone, rhs.PercentDone) + }, + propertyUID: func(lhs, rhs *Task) int { + return stringCompare(lhs.UID, rhs.UID) + }, + propertyCreated: func(lhs, rhs *Task) int { + return int64Compare(lhs.Created, rhs.Created) + }, + propertyUpdated: func(lhs, rhs *Task) int { + return int64Compare(lhs.Updated, rhs.Updated) + }, + propertyID: func(lhs, rhs *Task) int { + return int64Compare(lhs.ID, rhs.ID) + }, +} + +// Creates a taskComparator that sorts by the first comparator and falls back to +// the second one (and so on...) if the properties were equal. +func combineComparators(comparators ...taskComparator) taskComparator { + return func(lhs, rhs *Task) int { + for _, compare := range comparators { + res := compare(lhs, rhs) + if res != 0 { + return res + } + } + return 0 + } +} + +func sortTasks(tasks []*Task, by []*sortParam) { + const defaultComparator = propertyID + + comparators := make([]taskComparator, 0, len(by)) + for _, param := range by { + comparator, ok := propertyComparators[param.sortBy] + if !ok { + panic("No suitable comparator for sortBy found! Param was " + param.sortBy) + } + + // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). + if param.orderBy == orderDescending { + oldComparator := comparator + comparator = func(lhs, rhs *Task) int { + return -oldComparator(lhs, rhs) + } + } + + comparators = append(comparators, comparator) + } + + combinedComparator := combineComparators(comparators...) + + sort.Slice(tasks, func(i, j int) bool { + lhs, rhs := tasks[i], tasks[j] + + res := combinedComparator(lhs, rhs) + return res <= 0 + }) +} diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 7b71fc2cb..db4f09293 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -254,158 +254,6 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo return tasks, resultCount, totalItems, err } -type taskComparator func(lhs, rhs *Task) int - -// TODO: Move somewhere else -func stringCompare(lhs, rhs string) int { - if lhs > rhs { - return 1 - } - if lhs < rhs { - return -1 - } - return 0 -} - -func int64Compare(lhs, rhs int64) int { - if lhs > rhs { - return 1 - } - if lhs < rhs { - return -1 - } - return 0 -} - -func float64Compare(lhs, rhs float64) int { - if lhs > rhs { - return 1 - } - if lhs < rhs { - return -1 - } - return 0 -} - -func boolCompare(lhs, rhs, first bool) int { - // TODO: Can we simplify this? - if !lhs && rhs { - if first { - return 1 - } - return -1 - } - if lhs && !rhs { - if first { - return -1 - } - return 1 - } - return 0 -} - -// This is a map of properties that can be sorted by -// and their appropriate comparator function. -// The comparator function sorts in ascending mode. -var propertyComparators = map[sortProperty]taskComparator{ - propertyText: func(lhs, rhs *Task) int { - return stringCompare(lhs.Text, rhs.Text) - }, - propertyDescription: func(lhs, rhs *Task) int { - return stringCompare(lhs.Description, rhs.Description) - }, - propertyDone: func(lhs, rhs *Task) int { - return boolCompare(lhs.Done, rhs.Done, true) - }, - propertyDoneAtUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.DoneAtUnix, rhs.DoneAtUnix) - }, - propertyDueDateUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.DueDateUnix, rhs.DueDateUnix) - }, - propertyCreatedByID: func(lhs, rhs *Task) int { - return int64Compare(lhs.CreatedByID, rhs.CreatedByID) - }, - propertyListID: func(lhs, rhs *Task) int { - return int64Compare(lhs.ListID, rhs.ListID) - }, - propertyRepeatAfter: func(lhs, rhs *Task) int { - return int64Compare(lhs.RepeatAfter, rhs.RepeatAfter) - }, - propertyPriority: func(lhs, rhs *Task) int { - return int64Compare(lhs.Priority, rhs.Priority) - }, - propertyStartDateUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.StartDateUnix, rhs.StartDateUnix) - }, - propertyEndDateUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.EndDateUnix, rhs.EndDateUnix) - }, - propertyHexColor: func(lhs, rhs *Task) int { - return stringCompare(lhs.HexColor, rhs.HexColor) - }, - propertyPercentDone: func(lhs, rhs *Task) int { - return float64Compare(lhs.PercentDone, rhs.PercentDone) - }, - propertyUID: func(lhs, rhs *Task) int { - return stringCompare(lhs.UID, rhs.UID) - }, - propertyCreated: func(lhs, rhs *Task) int { - return int64Compare(lhs.Created, rhs.Created) - }, - propertyUpdated: func(lhs, rhs *Task) int { - return int64Compare(lhs.Updated, rhs.Updated) - }, - propertyID: func(lhs, rhs *Task) int { - return int64Compare(lhs.ID, rhs.ID) - }, -} - -// Creates a taskComparator that sorts by the first comparator and falls back to -// the second one (and so on...) if the properties were equal. -func combineComparators(comparators ...taskComparator) taskComparator { - return func(lhs, rhs *Task) int { - for _, compare := range comparators { - res := compare(lhs, rhs) - if res != 0 { - return res - } - } - return 0 - } -} - -func sortTasks(tasks []*Task, by []*sortParam) { - const defaultComparator = propertyID - - comparators := make([]taskComparator, 0, len(by)) - for _, param := range by { - comparator, ok := propertyComparators[param.sortBy] - if !ok { - panic("No suitable comparator for sortBy found! Param was " + param.sortBy) - } - - // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). - if param.orderBy == orderDescending { - oldComparator := comparator - comparator = func(lhs, rhs *Task) int { - return -oldComparator(lhs, rhs) - } - } - - comparators = append(comparators, comparator) - } - - combinedComparator := combineComparators(comparators...) - - sort.Slice(tasks, func(i, j int) bool { - lhs, rhs := tasks[i], tasks[j] - - res := combinedComparator(lhs, rhs) - return res <= 0 - }) -} - // GetTasksByListID gets all todotasks for a list func GetTasksByListID(listID int64) (tasks []*Task, err error) { // make a map so we can put in a lot of other stuff more easily -- 2.40.1 From f7fb6edcd19e24d5ce38255ed552512571c67cf3 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 18:41:09 +0100 Subject: [PATCH 19/60] Use string instead of bool for order const --- pkg/models/task_collection_sort.go | 17 +++--- pkg/models/task_collection_test.go | 92 ++++-------------------------- 2 files changed, 20 insertions(+), 89 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 06e7d4fbc..9f60c9f50 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -26,7 +26,7 @@ type ( sortProperty string - sortOrder bool + sortOrder string ) // TODO: Prefix them with "task" or so because they are available in the whole package. @@ -55,22 +55,23 @@ func (p sortProperty) String() string { } const ( - orderAscending sortOrder = false - orderDescending sortOrder = true + orderInvalid sortOrder = "invalid" + orderAscending sortOrder = "asc" + orderDescending sortOrder = "desc" ) func (o sortOrder) String() string { - if o == orderAscending { - return "asc" - } - return "desc" + return string(o) } func getSortOrderFromString(s string) sortOrder { if s == "asc" { return orderAscending } - return orderDescending + if s == "desc" { + return orderDescending + } + return orderInvalid } func (sp *sortParam) validate() error { diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 3c19481a2..a38a07186 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -26,75 +26,6 @@ import ( "testing" ) -func TestSortParamValidation(t *testing.T) { - t.Run("Test valid order by", func(t *testing.T) { - t.Run(orderAscending.String(), func(t *testing.T) { - s := &sortParam{ - orderBy: orderAscending, - sortBy: "id", - } - err := s.validate() - assert.NoError(t, err) - }) - t.Run(orderDescending.String(), func(t *testing.T) { - s := &sortParam{ - orderBy: orderDescending, - sortBy: "id", - } - err := s.validate() - assert.NoError(t, err) - }) - }) - t.Run("Test valid sort by", func(t *testing.T) { - for _, test := range []sortProperty{ - propertyID, - propertyText, - propertyDescription, - propertyDone, - propertyDoneAtUnix, - propertyDueDateUnix, - propertyCreatedByID, - propertyListID, - propertyRepeatAfter, - propertyPriority, - propertyStartDateUnix, - propertyEndDateUnix, - propertyHexColor, - propertyPercentDone, - propertyUID, - propertyCreated, - propertyUpdated, - } { - t.Run(test.String(), func(t *testing.T) { - s := &sortParam{ - orderBy: orderAscending, - sortBy: test, - } - err := s.validate() - assert.NoError(t, err) - }) - } - }) - t.Run("Test invalid order by", func(t *testing.T) { - s := &sortParam{ - orderBy: "somethingInvalid", - sortBy: "id", - } - err := s.validate() - assert.Error(t, err) - assert.True(t, IsErrInvalidSortOrder(err)) - }) - t.Run("Test invalid sort by", func(t *testing.T) { - s := &sortParam{ - orderBy: orderAscending, - sortBy: "somethingInvalid", - } - err := s.validate() - assert.Error(t, err) - assert.True(t, IsErrInvalidSortParam(err)) - }) -} - func sortTasksForTesting(by []*sortParam) (tasks []*Task) { user1 := &User{ ID: 1, @@ -729,8 +660,8 @@ func TestTask_ReadAll(t *testing.T) { StartDateSortUnix int64 EndDateSortUnix int64 Lists []*List - SortBy []sortProperty - OrderBy []sortOrder + SortBy []string // Is a string, since this is the place where a query string comes from the user + OrderBy []string CRUDable web.CRUDable Rights web.Rights } @@ -904,7 +835,7 @@ func TestTask_ReadAll(t *testing.T) { { name: "ReadAll Tasks sorted by " + test.String() + " default asc", fields: fields{ - SortBy: []sortProperty{test}, + SortBy: []string{test.String()}, }, args: args{ search: "", @@ -922,8 +853,7 @@ func TestTask_ReadAll(t *testing.T) { { name: "ReadAll Tasks sorted by " + test.String() + " asc", fields: fields{ - SortBy: []sortProperty{test}, - OrderBy: []sortOrder{orderAscending}, + SortBy: []string{test.String()}, }, args: args{ search: "", @@ -941,8 +871,8 @@ func TestTask_ReadAll(t *testing.T) { { name: "ReadAll Tasks sorted by " + test.String() + " desc", fields: fields{ - SortBy: []sortProperty{test}, - OrderBy: []sortOrder{orderDescending}, + SortBy: []string{test.String()}, + OrderBy: []string{orderDescending.String()}, }, args: args{ search: "", @@ -968,7 +898,7 @@ func TestTask_ReadAll(t *testing.T) { { name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + "default asc", fields: fields{ - SortBy: []sortProperty{outerTest, test}, + SortBy: []string{outerTest.String(), test.String()}, }, args: args{ search: "", @@ -990,8 +920,8 @@ func TestTask_ReadAll(t *testing.T) { { name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " asc", fields: fields{ - SortBy: []sortProperty{outerTest, test}, - OrderBy: []sortOrder{orderAscending, orderAscending}, + SortBy: []string{outerTest.String(), test.String()}, + OrderBy: []string{orderAscending.String(), orderAscending.String()}, }, args: args{ search: "", @@ -1013,8 +943,8 @@ func TestTask_ReadAll(t *testing.T) { { name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " desc", fields: fields{ - SortBy: []sortProperty{outerTest, test}, - OrderBy: []sortOrder{orderDescending, orderDescending}, + SortBy: []string{outerTest.String(), test.String()}, + OrderBy: []string{orderDescending.String(), orderDescending.String()}, }, args: args{ search: "", -- 2.40.1 From 415ad6bd77f7f5015715e9da460ab54cec5cd56f Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 18:45:53 +0100 Subject: [PATCH 20/60] Add unit tests for task collection sort --- pkg/models/task_collection_sort_test.go | 252 ++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 pkg/models/task_collection_sort_test.go diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go new file mode 100644 index 000000000..a5e29e0c2 --- /dev/null +++ b/pkg/models/task_collection_sort_test.go @@ -0,0 +1,252 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2019 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSortParamValidation(t *testing.T) { + t.Run("Test valid order by", func(t *testing.T) { + t.Run(orderAscending.String(), func(t *testing.T) { + s := &sortParam{ + orderBy: orderAscending, + sortBy: "id", + } + err := s.validate() + assert.NoError(t, err) + }) + t.Run(orderDescending.String(), func(t *testing.T) { + s := &sortParam{ + orderBy: orderDescending, + sortBy: "id", + } + err := s.validate() + assert.NoError(t, err) + }) + }) + t.Run("Test valid sort by", func(t *testing.T) { + for _, test := range []sortProperty{ + propertyID, + propertyText, + propertyDescription, + propertyDone, + propertyDoneAtUnix, + propertyDueDateUnix, + propertyCreatedByID, + propertyListID, + propertyRepeatAfter, + propertyPriority, + propertyStartDateUnix, + propertyEndDateUnix, + propertyHexColor, + propertyPercentDone, + propertyUID, + propertyCreated, + propertyUpdated, + } { + t.Run(test.String(), func(t *testing.T) { + s := &sortParam{ + orderBy: orderAscending, + sortBy: test, + } + err := s.validate() + assert.NoError(t, err) + }) + } + }) + t.Run("Test invalid order by", func(t *testing.T) { + s := &sortParam{ + orderBy: "somethingInvalid", + sortBy: "id", + } + err := s.validate() + assert.Error(t, err) + assert.True(t, IsErrInvalidSortOrder(err)) + }) + t.Run("Test invalid sort by", func(t *testing.T) { + s := &sortParam{ + orderBy: orderAscending, + sortBy: "somethingInvalid", + } + err := s.validate() + assert.Error(t, err) + assert.True(t, IsErrInvalidSortParam(err)) + }) +} + +func TestTaskSort(t *testing.T) { + task1 := &Task{ + ID: 1, + Text: "aaa", + Description: "Lorem Ipsum", + Done: true, + DoneAtUnix: 0, + DueDateUnix: 0, + CreatedByID: 1, + ListID: 1, + RepeatAfter: 0, + Priority: 0, + StartDateUnix: 0, + EndDateUnix: 0, + HexColor: "", + PercentDone: 0, + UID: "", + Created: 1543626724, + Updated: 1543626724, + } + task2 := &Task{ + ID: 2, + Text: "bbb", + Description: "Lorem Ipsum", + Done: true, + DoneAtUnix: 0, + DueDateUnix: 0, + CreatedByID: 1, + ListID: 1, + RepeatAfter: 0, + Priority: 0, + StartDateUnix: 0, + EndDateUnix: 0, + HexColor: "", + PercentDone: 0, + UID: "", + Created: 1543626724, + Updated: 1543626724, + } + task3 := &Task{ + ID: 3, + Text: "ccc", + Priority: 100, + } + task4 := &Task{ + ID: 4, + Text: "ddd", + Priority: 1, + } + task5 := &Task{ + ID: 5, + Text: "efg", + DueDateUnix: 1543636724, + } + task6 := &Task{ + ID: 6, + Text: "eef", + DueDateUnix: 1543616724, + } + task7 := &Task{ + ID: 7, + Text: "mmmn", + StartDateUnix: 1544600000, + } + task8 := &Task{ + ID: 8, + Text: "b123", + EndDateUnix: 1544700000, + } + task9 := &Task{ + ID: 9, + Text: "a123", + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + } + task10 := &Task{ + ID: 10, + Text: "zzz", + } + + tasks := []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + } + + t.Run("sort by id asc default", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: propertyID, + }, + } + sorted := tasks + sortTasks(sorted, by) + assert.Equal(t, []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }, sorted) + }) + t.Run("sort by id asc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: propertyID, + orderBy: orderAscending, + }, + } + sorted := tasks + sortTasks(sorted, by) + assert.Equal(t, []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }, sorted) + }) + t.Run("sort by id desc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: propertyID, + orderBy: orderDescending, + }, + } + sorted := tasks + sortTasks(sorted, by) + assert.Equal(t, []*Task{ + task10, + task9, + task8, + task7, + task6, + task5, + task4, + task3, + task2, + task1, + }, sorted) + }) + +} -- 2.40.1 From d80fcf2a69f61c6ccefe3fc532e19550738b4860 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 19:03:31 +0100 Subject: [PATCH 21/60] Move to individual task variables for task sorting --- pkg/models/task_collection_test.go | 1407 ++++++++++++---------------- 1 file changed, 581 insertions(+), 826 deletions(-) diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index a38a07186..62f236cf7 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -22,11 +22,13 @@ import ( "code.vikunja.io/web" "github.com/stretchr/testify/assert" "gopkg.in/d4l3k/messagediff.v1" - "sort" "testing" ) -func sortTasksForTesting(by []*sortParam) (tasks []*Task) { +func TestTask_ReadAll(t *testing.T) { + assert.NoError(t, db.LoadFixtures()) + + // Dummy users user1 := &User{ ID: 1, Username: "user1", @@ -48,611 +50,400 @@ func sortTasksForTesting(by []*sortParam) (tasks []*Task) { AvatarURL: "3efbe51f864c6666bc27caf4c6ff90ed", // hash for "" } - tasks = []*Task{ - { - ID: 1, - Text: "task #1", - Description: "Lorem Ipsum", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Labels: []*Label{ + // We use individual variables for the tasks here to be able to rearrange or remove ones more easily + task1 := &Task{ + ID: 1, + Text: "task #1", + Description: "Lorem Ipsum", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Labels: []*Label{ + { + ID: 4, + Title: "Label #4 - visible via other task", + CreatedByID: 2, + CreatedBy: user2, + Updated: 0, + Created: 0, + }, + }, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindSubtask: { { - ID: 4, - Title: "Label #4 - visible via other task", - CreatedByID: 2, - CreatedBy: user2, - Updated: 0, - Created: 0, + ID: 29, + Text: "task #29 with parent task (1)", + CreatedByID: 1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, }, }, - RelatedTasks: map[RelationKind][]*Task{ - RelationKindSubtask: { - { - ID: 29, - Text: "task #29 with parent task (1)", - CreatedByID: 1, - ListID: 1, - Created: 1543626724, - Updated: 1543626724, - }, + }, + Attachments: []*TaskAttachment{ + { + ID: 1, + TaskID: 1, + FileID: 1, + CreatedByID: 1, + CreatedBy: user1, + File: &files.File{ + ID: 1, + Name: "test", + Size: 100, + CreatedUnix: 1570998791, + CreatedByID: 1, }, }, - Attachments: []*TaskAttachment{ + { + ID: 2, + TaskID: 1, + FileID: 9999, + CreatedByID: 1, + CreatedBy: user1, + }, + }, + Created: 1543626724, + Updated: 1543626724, + } + task2 := &Task{ + ID: 2, + Text: "task #2 done", + Done: true, + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Labels: []*Label{ + { + ID: 4, + Title: "Label #4 - visible via other task", + CreatedByID: 2, + CreatedBy: user2, + Updated: 0, + Created: 0, + }, + }, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task3 := &Task{ + ID: 3, + Text: "task #3 high prio", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + Priority: 100, + } + task4 := &Task{ + ID: 4, + Text: "task #4 low prio", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + Priority: 1, + } + task5 := &Task{ + ID: 5, + Text: "task #5 higher due date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543636724, + } + task6 := &Task{ + ID: 6, + Text: "task #6 lower due date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + DueDateUnix: 1543616724, + } + task7 := &Task{ + ID: 7, + Text: "task #7 with start date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + } + task8 := &Task{ + ID: 8, + Text: "task #8 with end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + EndDateUnix: 1544700000, + } + task9 := &Task{ + ID: 9, + Text: "task #9 with start and end date", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + StartDateUnix: 1544600000, + EndDateUnix: 1544700000, + } + task10 := &Task{ + ID: 10, + Text: "task #10 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task11 := &Task{ + ID: 11, + Text: "task #11 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task12 := &Task{ + ID: 12, + Text: "task #12 basic", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task15 := &Task{ + ID: 15, + Text: "task #15", + CreatedByID: 6, + CreatedBy: user6, + ListID: 6, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task16 := &Task{ + ID: 16, + Text: "task #16", + CreatedByID: 6, + CreatedBy: user6, + ListID: 7, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task17 := &Task{ + ID: 17, + Text: "task #17", + CreatedByID: 6, + CreatedBy: user6, + ListID: 8, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task18 := &Task{ + ID: 18, + Text: "task #18", + CreatedByID: 6, + CreatedBy: user6, + ListID: 9, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task19 := &Task{ + ID: 19, + Text: "task #19", + CreatedByID: 6, + CreatedBy: user6, + ListID: 10, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task20 := &Task{ + ID: 20, + Text: "task #20", + CreatedByID: 6, + CreatedBy: user6, + ListID: 11, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task21 := &Task{ + ID: 21, + Text: "task #21", + CreatedByID: 6, + CreatedBy: user6, + ListID: 12, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task22 := &Task{ + ID: 22, + Text: "task #22", + CreatedByID: 6, + CreatedBy: user6, + ListID: 13, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task23 := &Task{ + ID: 23, + Text: "task #23", + CreatedByID: 6, + CreatedBy: user6, + ListID: 14, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task24 := &Task{ + ID: 24, + Text: "task #24", + CreatedByID: 6, + CreatedBy: user6, + ListID: 15, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task25 := &Task{ + ID: 25, + Text: "task #25", + CreatedByID: 6, + CreatedBy: user6, + ListID: 16, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task26 := &Task{ + ID: 26, + Text: "task #26", + CreatedByID: 6, + CreatedBy: user6, + ListID: 17, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task27 := &Task{ + ID: 27, + Text: "task #27 with reminders", + CreatedByID: 1, + CreatedBy: user1, + RemindersUnix: []int64{1543626724, 1543626824}, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task28 := &Task{ + ID: 28, + Text: "task #28 with repeat after", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + RepeatAfter: 3600, + Created: 1543626724, + Updated: 1543626724, + } + task29 := &Task{ + ID: 29, + Text: "task #29 with parent task (1)", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindParenttask: { { ID: 1, - TaskID: 1, - FileID: 1, + Text: "task #1", + Description: "Lorem Ipsum", CreatedByID: 1, - CreatedBy: user1, - File: &files.File{ - ID: 1, - Name: "test", - Size: 100, - CreatedUnix: 1570998791, - CreatedByID: 1, - }, - }, - { - ID: 2, - TaskID: 1, - FileID: 9999, - CreatedByID: 1, - CreatedBy: user1, + ListID: 1, + Created: 1543626724, + Updated: 1543626724, }, }, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 2, - Text: "task #2 done", - Done: true, - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Labels: []*Label{ - { - ID: 4, - Title: "Label #4 - visible via other task", - CreatedByID: 2, - CreatedBy: user2, - Updated: 0, - Created: 0, - }, - }, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 3, - Text: "task #3 high prio", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - Priority: 100, - }, - { - ID: 4, - Text: "task #4 low prio", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - Priority: 1, - }, - { - ID: 5, - Text: "task #5 higher due date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - DueDateUnix: 1543636724, - }, - { - ID: 6, - Text: "task #6 lower due date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - DueDateUnix: 1543616724, - }, - { - ID: 7, - Text: "task #7 with start date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - }, - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, - { - ID: 10, - Text: "task #10 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 11, - Text: "task #11 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 12, - Text: "task #12 basic", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 15, - Text: "task #15", - CreatedByID: 6, - CreatedBy: user6, - ListID: 6, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 16, - Text: "task #16", - CreatedByID: 6, - CreatedBy: user6, - ListID: 7, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 17, - Text: "task #17", - CreatedByID: 6, - CreatedBy: user6, - ListID: 8, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 18, - Text: "task #18", - CreatedByID: 6, - CreatedBy: user6, - ListID: 9, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 19, - Text: "task #19", - CreatedByID: 6, - CreatedBy: user6, - ListID: 10, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 20, - Text: "task #20", - CreatedByID: 6, - CreatedBy: user6, - ListID: 11, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 21, - Text: "task #21", - CreatedByID: 6, - CreatedBy: user6, - ListID: 12, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 22, - Text: "task #22", - CreatedByID: 6, - CreatedBy: user6, - ListID: 13, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 23, - Text: "task #23", - CreatedByID: 6, - CreatedBy: user6, - ListID: 14, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 24, - Text: "task #24", - CreatedByID: 6, - CreatedBy: user6, - ListID: 15, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 25, - Text: "task #25", - CreatedByID: 6, - CreatedBy: user6, - ListID: 16, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 26, - Text: "task #26", - CreatedByID: 6, - CreatedBy: user6, - ListID: 17, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 27, - Text: "task #27 with reminders", - CreatedByID: 1, - CreatedBy: user1, - RemindersUnix: []int64{1543626724, 1543626824}, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 28, - Text: "task #28 with repeat after", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - RepeatAfter: 3600, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 29, - Text: "task #29 with parent task (1)", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{ - RelationKindParenttask: { - { - ID: 1, - Text: "task #1", - Description: "Lorem Ipsum", - CreatedByID: 1, - ListID: 1, - Created: 1543626724, - Updated: 1543626724, - }, - }, - }, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 30, - Text: "task #30 with assignees", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - Assignees: []*User{ - user1, - user2, - }, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 31, - Text: "task #31 with color", - HexColor: "f0f0f0", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 32, - Text: "task #32", - CreatedByID: 1, - CreatedBy: user1, - ListID: 3, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - }, - { - ID: 33, - Text: "task #33 with percent done", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - PercentDone: 0.5, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, }, + Created: 1543626724, + Updated: 1543626724, } - - // We copy and paste the whole method in here instead of calling it to be able to verify it is working correctly. - // Otherwise we would test the functionality of the function by comparing it to the functions output, which is always the same. - for _, param := range by { - switch param.sortBy { - case "text": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Text < tasks[j].Text - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Text > tasks[j].Text - }) - } - case "description": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Description < tasks[j].Description - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Description > tasks[j].Description - }) - } - case "done": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Done == tasks[j].Done - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Done != tasks[j].Done - }) - } - case "done_at_unix": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DoneAtUnix < tasks[j].DoneAtUnix - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DoneAtUnix > tasks[j].DoneAtUnix - }) - } - case "due_date_unix": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix < tasks[j].DueDateUnix - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].DueDateUnix > tasks[j].DueDateUnix - }) - } - case "created_by_id": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].CreatedByID < tasks[j].CreatedByID - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].CreatedByID > tasks[j].CreatedByID - }) - } - case "list_id": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ListID < tasks[j].ListID - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ListID > tasks[j].ListID - }) - } - case "repeat_after": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].RepeatAfter < tasks[j].RepeatAfter - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].RepeatAfter > tasks[j].RepeatAfter - }) - } - case "priority": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority < tasks[j].Priority - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Priority > tasks[j].Priority - }) - } - case "start_date_unix": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].StartDateUnix < tasks[j].StartDateUnix - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].StartDateUnix > tasks[j].StartDateUnix - }) - } - case "end_date_unix": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].EndDateUnix < tasks[j].EndDateUnix - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].EndDateUnix > tasks[j].EndDateUnix - }) - } - case "hex_color": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].HexColor < tasks[j].HexColor - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].HexColor > tasks[j].HexColor - }) - } - case "percent_done": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].PercentDone < tasks[j].PercentDone - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].PercentDone > tasks[j].PercentDone - }) - } - case "uid": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].UID < tasks[j].UID - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].UID > tasks[j].UID - }) - } - case "created": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Created < tasks[j].Created - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Created > tasks[j].Created - }) - } - case "updated": - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Updated < tasks[j].Updated - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].Updated > tasks[j].Updated - }) - } - default: - // Sorting by ID is the default, so we don't need an extra case for it - if param.orderBy == orderAscending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ID < tasks[j].ID - }) - } - if param.orderBy == orderDescending { - sort.Slice(tasks, func(i, j int) bool { - return tasks[i].ID > tasks[j].ID - }) - } - } + task30 := &Task{ + ID: 30, + Text: "task #30 with assignees", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + Assignees: []*User{ + user1, + user2, + }, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, } - - return -} - -func TestTask_ReadAll(t *testing.T) { - assert.NoError(t, db.LoadFixtures()) - - // Dummy users - user1 := &User{ - ID: 1, - Username: "user1", - Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", - IsActive: true, - AvatarURL: "111d68d06e2d317b5a59c2c6c5bad808", // hash for "" + task31 := &Task{ + ID: 31, + Text: "task #31 with color", + HexColor: "f0f0f0", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task32 := &Task{ + ID: 32, + Text: "task #32", + CreatedByID: 1, + CreatedBy: user1, + ListID: 3, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, + } + task33 := &Task{ + ID: 33, + Text: "task #33 with percent done", + CreatedByID: 1, + CreatedBy: user1, + ListID: 1, + PercentDone: 0.5, + RelatedTasks: map[RelationKind][]*Task{}, + Created: 1543626724, + Updated: 1543626724, } type fields struct { @@ -686,12 +477,39 @@ func TestTask_ReadAll(t *testing.T) { a: &User{ID: 1}, page: 0, }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: "id", - orderBy: orderAscending, - }, - }), + want: []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + task11, + task12, + task15, + task16, + task17, + task18, + task19, + task20, + task21, + task22, + task23, + task24, + task25, + task26, + task27, + task28, + task29, + task30, + task31, + task32, + task33, + }, wantErr: false, }, { @@ -706,29 +524,8 @@ func TestTask_ReadAll(t *testing.T) { page: 0, }, want: []*Task{ - { - ID: 7, - Text: "task #7 with start date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, + task7, + task9, }, wantErr: false, }, @@ -744,29 +541,8 @@ func TestTask_ReadAll(t *testing.T) { page: 0, }, want: []*Task{ - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, + task8, + task9, }, wantErr: false, }, @@ -781,191 +557,170 @@ func TestTask_ReadAll(t *testing.T) { page: 0, }, want: []*Task{ - { - ID: 8, - Text: "task #8 with end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - EndDateUnix: 1544700000, - }, - { - ID: 9, - Text: "task #9 with start and end date", - CreatedByID: 1, - CreatedBy: user1, - ListID: 1, - RelatedTasks: map[RelationKind][]*Task{}, - Created: 1543626724, - Updated: 1543626724, - StartDateUnix: 1544600000, - EndDateUnix: 1544700000, - }, + task8, + task9, }, wantErr: false, }, } - sortByFields := []sortProperty{ - propertyID, - propertyText, - propertyDescription, - propertyDone, - propertyDoneAtUnix, - propertyDueDateUnix, - propertyCreatedByID, - propertyListID, - propertyRepeatAfter, - propertyPriority, - propertyStartDateUnix, - propertyEndDateUnix, - propertyHexColor, - propertyPercentDone, - propertyUID, - propertyCreated, - propertyUpdated, - } - // Add more cases programatically - // Simple cases - for _, test := range sortByFields { - tests = append(tests, []testcase{ - { - name: "ReadAll Tasks sorted by " + test.String() + " default asc", - fields: fields{ - SortBy: []string{test.String()}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: test, - orderBy: orderAscending, - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by " + test.String() + " asc", - fields: fields{ - SortBy: []string{test.String()}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: test, - orderBy: orderAscending, - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by " + test.String() + " desc", - fields: fields{ - SortBy: []string{test.String()}, - OrderBy: []string{orderDescending.String()}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: test, - orderBy: orderDescending, - }, - }), - wantErr: false, - }, - }...) - } - - // Cases with two parameters - for _, outerTest := range sortByFields { - // We don't test all cases with everything here because this would mean an enormous amount of test cases - for _, test := range []sortProperty{"text", "done"} { - tests = append(tests, []testcase{ - { - name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + "default asc", - fields: fields{ - SortBy: []string{outerTest.String(), test.String()}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: outerTest, - orderBy: orderAscending, - }, - { - sortBy: test, - orderBy: orderAscending, - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " asc", - fields: fields{ - SortBy: []string{outerTest.String(), test.String()}, - OrderBy: []string{orderAscending.String(), orderAscending.String()}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: outerTest, - orderBy: orderAscending, - }, - { - sortBy: test, - orderBy: orderAscending, - }, - }), - wantErr: false, - }, - { - name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " desc", - fields: fields{ - SortBy: []string{outerTest.String(), test.String()}, - OrderBy: []string{orderDescending.String(), orderDescending.String()}, - }, - args: args{ - search: "", - a: &User{ID: 1}, - page: 0, - }, - want: sortTasksForTesting([]*sortParam{ - { - sortBy: outerTest, - orderBy: orderDescending, - }, - { - sortBy: test, - orderBy: orderDescending, - }, - }), - wantErr: false, - }, - }...) - } - } + //sortByFields := []sortProperty{ + // propertyID, + // propertyText, + // propertyDescription, + // propertyDone, + // propertyDoneAtUnix, + // propertyDueDateUnix, + // propertyCreatedByID, + // propertyListID, + // propertyRepeatAfter, + // propertyPriority, + // propertyStartDateUnix, + // propertyEndDateUnix, + // propertyHexColor, + // propertyPercentDone, + // propertyUID, + // propertyCreated, + // propertyUpdated, + //} + //// Add more cases programatically + //// Simple cases + //for _, test := range sortByFields { + // tests = append(tests, []testcase{ + // { + // name: "ReadAll Tasks sorted by " + test.String() + " default asc", + // fields: fields{ + // SortBy: []string{test.String()}, + // }, + // args: args{ + // search: "", + // a: &User{ID: 1}, + // page: 0, + // }, + // want: sortTasksForTesting([]*sortParam{ + // { + // sortBy: test, + // orderBy: orderAscending, + // }, + // }), + // wantErr: false, + // }, + // { + // name: "ReadAll Tasks sorted by " + test.String() + " asc", + // fields: fields{ + // SortBy: []string{test.String()}, + // }, + // args: args{ + // search: "", + // a: &User{ID: 1}, + // page: 0, + // }, + // want: sortTasksForTesting([]*sortParam{ + // { + // sortBy: test, + // orderBy: orderAscending, + // }, + // }), + // wantErr: false, + // }, + // { + // name: "ReadAll Tasks sorted by " + test.String() + " desc", + // fields: fields{ + // SortBy: []string{test.String()}, + // OrderBy: []string{orderDescending.String()}, + // }, + // args: args{ + // search: "", + // a: &User{ID: 1}, + // page: 0, + // }, + // want: sortTasksForTesting([]*sortParam{ + // { + // sortBy: test, + // orderBy: orderDescending, + // }, + // }), + // wantErr: false, + // }, + // }...) + //} + // + //// Cases with two parameters + //for _, outerTest := range sortByFields { + // // We don't test all cases with everything here because this would mean an enormous amount of test cases + // for _, test := range []sortProperty{"text", "done"} { + // tests = append(tests, []testcase{ + // { + // name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + "default asc", + // fields: fields{ + // SortBy: []string{outerTest.String(), test.String()}, + // }, + // args: args{ + // search: "", + // a: &User{ID: 1}, + // page: 0, + // }, + // want: sortTasksForTesting([]*sortParam{ + // { + // sortBy: outerTest, + // orderBy: orderAscending, + // }, + // { + // sortBy: test, + // orderBy: orderAscending, + // }, + // }), + // wantErr: false, + // }, + // { + // name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " asc", + // fields: fields{ + // SortBy: []string{outerTest.String(), test.String()}, + // OrderBy: []string{orderAscending.String(), orderAscending.String()}, + // }, + // args: args{ + // search: "", + // a: &User{ID: 1}, + // page: 0, + // }, + // want: sortTasksForTesting([]*sortParam{ + // { + // sortBy: outerTest, + // orderBy: orderAscending, + // }, + // { + // sortBy: test, + // orderBy: orderAscending, + // }, + // }), + // wantErr: false, + // }, + // { + // name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " desc", + // fields: fields{ + // SortBy: []string{outerTest.String(), test.String()}, + // OrderBy: []string{orderDescending.String(), orderDescending.String()}, + // }, + // args: args{ + // search: "", + // a: &User{ID: 1}, + // page: 0, + // }, + // want: sortTasksForTesting([]*sortParam{ + // { + // sortBy: outerTest, + // orderBy: orderDescending, + // }, + // { + // sortBy: test, + // orderBy: orderDescending, + // }, + // }), + // wantErr: false, + // }, + // }...) + // } + //} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { -- 2.40.1 From 6f6ca71414929b17063693fb0144e42639a2eecc Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 19:06:59 +0100 Subject: [PATCH 22/60] Fix validation test --- pkg/models/error.go | 2 +- pkg/models/task_collection_sort.go | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/models/error.go b/pkg/models/error.go index 9cc456bee..ffbff4ad0 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -817,7 +817,7 @@ func (err ErrInvalidSortParam) HTTPError() web.HTTPError { // ErrInvalidSortOrder represents an error where the provided sort order is invalid type ErrInvalidSortOrder struct { - OrderBy string + OrderBy sortOrder } // IsErrInvalidSortOrder checks if an error is ErrInvalidSortOrder. diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 9f60c9f50..2139cd221 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -75,6 +75,9 @@ func getSortOrderFromString(s string) sortOrder { } func (sp *sortParam) validate() error { + if sp.orderBy != orderDescending && sp.orderBy != orderAscending { + return ErrInvalidSortOrder{OrderBy: sp.orderBy} + } switch sp.sortBy { case propertyID, -- 2.40.1 From 9faa9244064f218bfe4694e21ed83f87f183dae4 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 19:07:30 +0100 Subject: [PATCH 23/60] Fix static check --- pkg/models/task_collection_sort.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 2139cd221..6bc090a7e 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -224,7 +224,6 @@ func combineComparators(comparators ...taskComparator) taskComparator { } func sortTasks(tasks []*Task, by []*sortParam) { - const defaultComparator = propertyID comparators := make([]taskComparator, 0, len(by)) for _, param := range by { -- 2.40.1 From 387a85efb5d43525028e282307025f4342de8166 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 19:22:39 +0100 Subject: [PATCH 24/60] Fix sorting by ID asc by default --- pkg/models/task_collection_sort.go | 5 +++++ pkg/models/task_collection_test.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 6bc090a7e..c285f1466 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -225,6 +225,11 @@ func combineComparators(comparators ...taskComparator) taskComparator { func sortTasks(tasks []*Task, by []*sortParam) { + // Sort by id asc by default if no param was provided + if len(by) == 0 { + by = []*sortParam{{sortBy: propertyID, orderBy: orderAscending}} + } + comparators := make([]taskComparator, 0, len(by)) for _, param := range by { comparator, ok := propertyComparators[param.sortBy] diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 62f236cf7..4db7038fd 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -25,7 +25,7 @@ import ( "testing" ) -func TestTask_ReadAll(t *testing.T) { +func TestTaskCollection_ReadAll(t *testing.T) { assert.NoError(t, db.LoadFixtures()) // Dummy users -- 2.40.1 From e9f385f09abf986c90702c7e17b0da7438a30f8d Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 19:22:12 +0100 Subject: [PATCH 25/60] Prefix task property name constants with "task" The properties are available in the whole models package. Prefixing them with "task" makes it clear that they are meant to be used on tasks only. --- pkg/models/task_collection_sort.go | 103 ++++++++++++------------ pkg/models/task_collection_sort_test.go | 40 ++++----- pkg/models/task_collection_test.go | 34 ++++---- 3 files changed, 88 insertions(+), 89 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index c285f1466..355b40271 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -29,25 +29,24 @@ type ( sortOrder string ) -// TODO: Prefix them with "task" or so because they are available in the whole package. const ( - propertyID sortProperty = "id" - propertyText sortProperty = "text" - propertyDescription sortProperty = "description" - propertyDone sortProperty = "done" - propertyDoneAtUnix sortProperty = "done_at_unix" - propertyDueDateUnix sortProperty = "due_date_unix" - propertyCreatedByID sortProperty = "created_by_id" - propertyListID sortProperty = "list_id" - propertyRepeatAfter sortProperty = "repeat_after" - propertyPriority sortProperty = "priority" - propertyStartDateUnix sortProperty = "start_date_unix" - propertyEndDateUnix sortProperty = "end_date_unix" - propertyHexColor sortProperty = "hex_color" - propertyPercentDone sortProperty = "percent_done" - propertyUID sortProperty = "uid" - propertyCreated sortProperty = "created" - propertyUpdated sortProperty = "updated" + taskPropertyID sortProperty = "id" + taskPropertyText sortProperty = "text" + taskPropertyDescription sortProperty = "description" + taskPropertyDone sortProperty = "done" + taskPropertyDoneAtUnix sortProperty = "done_at_unix" + taskPropertyDueDateUnix sortProperty = "due_date_unix" + taskPropertyCreatedByID sortProperty = "created_by_id" + taskPropertyListID sortProperty = "list_id" + taskPropertyRepeatAfter sortProperty = "repeat_after" + taskPropertyPriority sortProperty = "priority" + taskPropertyStartDateUnix sortProperty = "start_date_unix" + taskPropertyEndDateUnix sortProperty = "end_date_unix" + taskPropertyHexColor sortProperty = "hex_color" + taskPropertyPercentDone sortProperty = "percent_done" + taskPropertyUID sortProperty = "uid" + taskPropertyCreated sortProperty = "created" + taskPropertyUpdated sortProperty = "updated" ) func (p sortProperty) String() string { @@ -80,23 +79,23 @@ func (sp *sortParam) validate() error { } switch sp.sortBy { case - propertyID, - propertyText, - propertyDescription, - propertyDone, - propertyDoneAtUnix, - propertyDueDateUnix, - propertyCreatedByID, - propertyListID, - propertyRepeatAfter, - propertyPriority, - propertyStartDateUnix, - propertyEndDateUnix, - propertyHexColor, - propertyPercentDone, - propertyUID, - propertyCreated, - propertyUpdated: + taskPropertyID, + taskPropertyText, + taskPropertyDescription, + taskPropertyDone, + taskPropertyDoneAtUnix, + taskPropertyDueDateUnix, + taskPropertyCreatedByID, + taskPropertyListID, + taskPropertyRepeatAfter, + taskPropertyPriority, + taskPropertyStartDateUnix, + taskPropertyEndDateUnix, + taskPropertyHexColor, + taskPropertyPercentDone, + taskPropertyUID, + taskPropertyCreated, + taskPropertyUpdated: return nil } return ErrInvalidSortParam{SortBy: sp.sortBy} @@ -156,55 +155,55 @@ func boolCompare(lhs, rhs, first bool) int { // and their appropriate comparator function. // The comparator function sorts in ascending mode. var propertyComparators = map[sortProperty]taskComparator{ - propertyText: func(lhs, rhs *Task) int { + taskPropertyText: func(lhs, rhs *Task) int { return stringCompare(lhs.Text, rhs.Text) }, - propertyDescription: func(lhs, rhs *Task) int { + taskPropertyDescription: func(lhs, rhs *Task) int { return stringCompare(lhs.Description, rhs.Description) }, - propertyDone: func(lhs, rhs *Task) int { + taskPropertyDone: func(lhs, rhs *Task) int { return boolCompare(lhs.Done, rhs.Done, true) }, - propertyDoneAtUnix: func(lhs, rhs *Task) int { + taskPropertyDoneAtUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.DoneAtUnix, rhs.DoneAtUnix) }, - propertyDueDateUnix: func(lhs, rhs *Task) int { + taskPropertyDueDateUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.DueDateUnix, rhs.DueDateUnix) }, - propertyCreatedByID: func(lhs, rhs *Task) int { + taskPropertyCreatedByID: func(lhs, rhs *Task) int { return int64Compare(lhs.CreatedByID, rhs.CreatedByID) }, - propertyListID: func(lhs, rhs *Task) int { + taskPropertyListID: func(lhs, rhs *Task) int { return int64Compare(lhs.ListID, rhs.ListID) }, - propertyRepeatAfter: func(lhs, rhs *Task) int { + taskPropertyRepeatAfter: func(lhs, rhs *Task) int { return int64Compare(lhs.RepeatAfter, rhs.RepeatAfter) }, - propertyPriority: func(lhs, rhs *Task) int { + taskPropertyPriority: func(lhs, rhs *Task) int { return int64Compare(lhs.Priority, rhs.Priority) }, - propertyStartDateUnix: func(lhs, rhs *Task) int { + taskPropertyStartDateUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.StartDateUnix, rhs.StartDateUnix) }, - propertyEndDateUnix: func(lhs, rhs *Task) int { + taskPropertyEndDateUnix: func(lhs, rhs *Task) int { return int64Compare(lhs.EndDateUnix, rhs.EndDateUnix) }, - propertyHexColor: func(lhs, rhs *Task) int { + taskPropertyHexColor: func(lhs, rhs *Task) int { return stringCompare(lhs.HexColor, rhs.HexColor) }, - propertyPercentDone: func(lhs, rhs *Task) int { + taskPropertyPercentDone: func(lhs, rhs *Task) int { return float64Compare(lhs.PercentDone, rhs.PercentDone) }, - propertyUID: func(lhs, rhs *Task) int { + taskPropertyUID: func(lhs, rhs *Task) int { return stringCompare(lhs.UID, rhs.UID) }, - propertyCreated: func(lhs, rhs *Task) int { + taskPropertyCreated: func(lhs, rhs *Task) int { return int64Compare(lhs.Created, rhs.Created) }, - propertyUpdated: func(lhs, rhs *Task) int { + taskPropertyUpdated: func(lhs, rhs *Task) int { return int64Compare(lhs.Updated, rhs.Updated) }, - propertyID: func(lhs, rhs *Task) int { + taskPropertyID: func(lhs, rhs *Task) int { return int64Compare(lhs.ID, rhs.ID) }, } diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index a5e29e0c2..0d34309ac 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -42,23 +42,23 @@ func TestSortParamValidation(t *testing.T) { }) t.Run("Test valid sort by", func(t *testing.T) { for _, test := range []sortProperty{ - propertyID, - propertyText, - propertyDescription, - propertyDone, - propertyDoneAtUnix, - propertyDueDateUnix, - propertyCreatedByID, - propertyListID, - propertyRepeatAfter, - propertyPriority, - propertyStartDateUnix, - propertyEndDateUnix, - propertyHexColor, - propertyPercentDone, - propertyUID, - propertyCreated, - propertyUpdated, + taskPropertyID, + taskPropertyText, + taskPropertyDescription, + taskPropertyDone, + taskPropertyDoneAtUnix, + taskPropertyDueDateUnix, + taskPropertyCreatedByID, + taskPropertyListID, + taskPropertyRepeatAfter, + taskPropertyPriority, + taskPropertyStartDateUnix, + taskPropertyEndDateUnix, + taskPropertyHexColor, + taskPropertyPercentDone, + taskPropertyUID, + taskPropertyCreated, + taskPropertyUpdated, } { t.Run(test.String(), func(t *testing.T) { s := &sortParam{ @@ -186,7 +186,7 @@ func TestTaskSort(t *testing.T) { t.Run("sort by id asc default", func(t *testing.T) { by := []*sortParam{ { - sortBy: propertyID, + sortBy: taskPropertyID, }, } sorted := tasks @@ -207,7 +207,7 @@ func TestTaskSort(t *testing.T) { t.Run("sort by id asc", func(t *testing.T) { by := []*sortParam{ { - sortBy: propertyID, + sortBy: taskPropertyID, orderBy: orderAscending, }, } @@ -229,7 +229,7 @@ func TestTaskSort(t *testing.T) { t.Run("sort by id desc", func(t *testing.T) { by := []*sortParam{ { - sortBy: propertyID, + sortBy: taskPropertyID, orderBy: orderDescending, }, } diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index 4db7038fd..b3e66f113 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -565,23 +565,23 @@ func TestTaskCollection_ReadAll(t *testing.T) { } //sortByFields := []sortProperty{ - // propertyID, - // propertyText, - // propertyDescription, - // propertyDone, - // propertyDoneAtUnix, - // propertyDueDateUnix, - // propertyCreatedByID, - // propertyListID, - // propertyRepeatAfter, - // propertyPriority, - // propertyStartDateUnix, - // propertyEndDateUnix, - // propertyHexColor, - // propertyPercentDone, - // propertyUID, - // propertyCreated, - // propertyUpdated, + // taskPropertyID, + // taskPropertyText, + // taskPropertyDescription, + // taskPropertyDone, + // taskPropertyDoneAtUnix, + // taskPropertyDueDateUnix, + // taskPropertyCreatedByID, + // taskPropertyListID, + // taskPropertyRepeatAfter, + // taskPropertyPriority, + // taskPropertyStartDateUnix, + // taskPropertyEndDateUnix, + // taskPropertyHexColor, + // taskPropertyPercentDone, + // taskPropertyUID, + // taskPropertyCreated, + // taskPropertyUpdated, //} //// Add more cases programatically //// Simple cases -- 2.40.1 From c3bd9f43b303b819c186702ee7f0314dd790469f Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 19:32:05 +0100 Subject: [PATCH 26/60] Add function for easier sorting testing --- pkg/models/task_collection_sort.go | 2 +- pkg/models/task_collection_sort_test.go | 125 ++++++++++++------------ 2 files changed, 62 insertions(+), 65 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 355b40271..88eaa4a8b 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -226,7 +226,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { // Sort by id asc by default if no param was provided if len(by) == 0 { - by = []*sortParam{{sortBy: propertyID, orderBy: orderAscending}} + by = []*sortParam{{sortBy: taskPropertyID, orderBy: orderAscending}} } comparators := make([]taskComparator, 0, len(by)) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 0d34309ac..ab333aef7 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -90,6 +90,50 @@ func TestSortParamValidation(t *testing.T) { }) } +func buildSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSortedTasks []*Task) { + t.Run("asc default", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: sortProperty, + }, + } + sorted := referenceTasks + sortTasks(sorted, by) + assert.Equal(t, ascSortedTasks, sorted) + }) + t.Run("asc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: sortProperty, + orderBy: orderAscending, + }, + } + sorted := referenceTasks + sortTasks(sorted, by) + assert.Equal(t, ascSortedTasks, sorted) + }) + t.Run("desc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: sortProperty, + orderBy: orderDescending, + }, + } + + // Reversing the asc sorted control group + // Algorithm taken from https://github.com/golang/go/wiki/SliceTricks#reversing + descReferenceTasks := referenceTasks + for i := len(descReferenceTasks)/2 - 1; i >= 0; i-- { + opp := len(descReferenceTasks) - 1 - i + descReferenceTasks[i], descReferenceTasks[opp] = descReferenceTasks[opp], descReferenceTasks[i] + } + + sorted := referenceTasks + sortTasks(sorted, by) + assert.Equal(t, descReferenceTasks, sorted) + }) +} + func TestTaskSort(t *testing.T) { task1 := &Task{ ID: 1, @@ -183,70 +227,23 @@ func TestTaskSort(t *testing.T) { task10, } - t.Run("sort by id asc default", func(t *testing.T) { - by := []*sortParam{ - { - sortBy: taskPropertyID, - }, - } - sorted := tasks - sortTasks(sorted, by) - assert.Equal(t, []*Task{ - task1, - task2, - task3, - task4, - task5, - task6, - task7, - task8, - task9, - task10, - }, sorted) - }) - t.Run("sort by id asc", func(t *testing.T) { - by := []*sortParam{ - { - sortBy: taskPropertyID, - orderBy: orderAscending, - }, - } - sorted := tasks - sortTasks(sorted, by) - assert.Equal(t, []*Task{ - task1, - task2, - task3, - task4, - task5, - task6, - task7, - task8, - task9, - task10, - }, sorted) - }) - t.Run("sort by id desc", func(t *testing.T) { - by := []*sortParam{ - { - sortBy: taskPropertyID, - orderBy: orderDescending, - }, - } - sorted := tasks - sortTasks(sorted, by) - assert.Equal(t, []*Task{ - task10, - task9, - task8, - task7, - task6, - task5, - task4, - task3, - task2, - task1, - }, sorted) + t.Run("id", func(t *testing.T) { + buildSortTest( + t, + taskPropertyID, + tasks, + []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }) }) } -- 2.40.1 From ea9f9463f1c5dd210b55e665b9632244bef571a6 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 20:39:17 +0100 Subject: [PATCH 27/60] Add more test cases for sorting --- pkg/models/task_collection_sort_test.go | 397 +++++++++++++++++++++--- 1 file changed, 350 insertions(+), 47 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index ab333aef7..4ee330be5 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -18,6 +18,7 @@ package models import ( "github.com/stretchr/testify/assert" + "gopkg.in/d4l3k/messagediff.v1" "testing" ) @@ -90,7 +91,7 @@ func TestSortParamValidation(t *testing.T) { }) } -func buildSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSortedTasks []*Task) { +func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSortedTasks []*Task) { t.Run("asc default", func(t *testing.T) { by := []*sortParam{ { @@ -99,7 +100,9 @@ func buildSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascS } sorted := referenceTasks sortTasks(sorted, by) - assert.Equal(t, ascSortedTasks, sorted) + if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { + t.Errorf("Not Equal, got = %v, want %v, \ndiff: %v", sorted, ascSortedTasks, diff) + } }) t.Run("asc", func(t *testing.T) { by := []*sortParam{ @@ -110,7 +113,9 @@ func buildSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascS } sorted := referenceTasks sortTasks(sorted, by) - assert.Equal(t, ascSortedTasks, sorted) + if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { + t.Errorf("Not Equal, got = %v, want %v, \ndiff: %v", sorted, ascSortedTasks, diff) + } }) t.Run("desc", func(t *testing.T) { by := []*sortParam{ @@ -122,81 +127,86 @@ func buildSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascS // Reversing the asc sorted control group // Algorithm taken from https://github.com/golang/go/wiki/SliceTricks#reversing - descReferenceTasks := referenceTasks - for i := len(descReferenceTasks)/2 - 1; i >= 0; i-- { - opp := len(descReferenceTasks) - 1 - i - descReferenceTasks[i], descReferenceTasks[opp] = descReferenceTasks[opp], descReferenceTasks[i] + descSortedTasks := ascSortedTasks + for i := len(descSortedTasks)/2 - 1; i >= 0; i-- { + opp := len(descSortedTasks) - 1 - i + descSortedTasks[i], descSortedTasks[opp] = descSortedTasks[opp], descSortedTasks[i] } sorted := referenceTasks sortTasks(sorted, by) - assert.Equal(t, descReferenceTasks, sorted) + assert.Equal(t, descSortedTasks, sorted) + if diff, equal := messagediff.PrettyDiff(sorted, descSortedTasks); !equal { + t.Errorf("Not Equal, got = %v, want %v, \ndiff: %v", sorted, descSortedTasks, diff) + } }) } func TestTaskSort(t *testing.T) { task1 := &Task{ - ID: 1, - Text: "aaa", - Description: "Lorem Ipsum", - Done: true, - DoneAtUnix: 0, - DueDateUnix: 0, - CreatedByID: 1, - ListID: 1, - RepeatAfter: 0, - Priority: 0, - StartDateUnix: 0, - EndDateUnix: 0, - HexColor: "", - PercentDone: 0, - UID: "", - Created: 1543626724, - Updated: 1543626724, + ID: 1, + Text: "aaa", + Description: "Lorem Ipsum", + Done: true, + DoneAtUnix: 1543626000, + ListID: 1, + UID: "JywtBPCESImlyKugvaZWrxmXAFAWXFISMeXYImEh", + Created: 1543626724, + Updated: 1543626724, } task2 := &Task{ ID: 2, Text: "bbb", - Description: "Lorem Ipsum", + Description: "Arem Ipsum", Done: true, - DoneAtUnix: 0, - DueDateUnix: 0, + DoneAtUnix: 1543626724, CreatedByID: 1, - ListID: 1, - RepeatAfter: 0, - Priority: 0, - StartDateUnix: 0, - EndDateUnix: 0, - HexColor: "", - PercentDone: 0, - UID: "", - Created: 1543626724, - Updated: 1543626724, + ListID: 2, + PercentDone: 0.3, + StartDateUnix: 1543626724, + Created: 1553626724, + Updated: 1553626724, } task3 := &Task{ - ID: 3, - Text: "ccc", - Priority: 100, + ID: 3, + Text: "ccc", + DueDateUnix: 1583626724, + Priority: 100, + ListID: 3, + HexColor: "000000", + PercentDone: 0.1, + Updated: 1555555555, } task4 := &Task{ - ID: 4, - Text: "ddd", - Priority: 1, + ID: 4, + Text: "ddd", + Priority: 1, + StartDateUnix: 1643626724, + ListID: 1, } task5 := &Task{ ID: 5, Text: "efg", + Priority: 50, + UID: "shggzCHQWLhGNMNsOGOCOjcVkInOYjTAnORqTkdL", DueDateUnix: 1543636724, + Updated: 1565555555, } task6 := &Task{ ID: 6, Text: "eef", DueDateUnix: 1543616724, + RepeatAfter: 6400, + CreatedByID: 2, + HexColor: "ffffff", } task7 := &Task{ ID: 7, Text: "mmmn", + Description: "Zoremis", StartDateUnix: 1544600000, + EndDateUnix: 1584600000, + UID: "tyzCZuLMSKhwclJOsDyDcUdyVAPBDOPHNTBOLTcW", } task8 := &Task{ ID: 8, @@ -205,13 +215,18 @@ func TestTaskSort(t *testing.T) { } task9 := &Task{ ID: 9, + Done: true, + DoneAtUnix: 1573626724, Text: "a123", + RepeatAfter: 86000, StartDateUnix: 1544600000, EndDateUnix: 1544700000, } task10 := &Task{ - ID: 10, - Text: "zzz", + ID: 10, + Text: "zzz", + Priority: 10, + PercentDone: 1, } tasks := []*Task{ @@ -228,7 +243,7 @@ func TestTaskSort(t *testing.T) { } t.Run("id", func(t *testing.T) { - buildSortTest( + runSortTest( t, taskPropertyID, tasks, @@ -245,5 +260,293 @@ func TestTaskSort(t *testing.T) { task10, }) }) + t.Run("text", func(t *testing.T) { + runSortTest( + t, + taskPropertyText, + tasks, + []*Task{ + task9, + task1, + task8, + task2, + task3, + task4, + task6, + task5, + task7, + task10, + }) + }) + t.Run("description", func(t *testing.T) { + runSortTest( + t, + taskPropertyDescription, + tasks, + []*Task{ + task6, + task8, + task5, + task9, + task4, + task3, + task10, + task2, + task1, + task7, + }) + }) + t.Run("done", func(t *testing.T) { + runSortTest( + t, + taskPropertyDone, + tasks, + []*Task{ + task1, + task2, + task9, + task10, + task5, + task8, + task7, + task3, + task4, + task6, + }) + }) + t.Run("done at", func(t *testing.T) { + runSortTest( + t, + taskPropertyDoneAtUnix, + tasks, + []*Task{ + task10, + task8, + task5, + task6, + task4, + task3, + task7, + task1, + task2, + task9, + }) + }) + t.Run("due date", func(t *testing.T) { + runSortTest( + t, + taskPropertyDueDateUnix, + tasks, + []*Task{ + task1, + task2, + task4, + task7, + task8, + task9, + task10, + task6, + task5, + task3, + }) + }) + t.Run("created by id", func(t *testing.T) { + runSortTest( + t, + taskPropertyCreatedByID, + tasks, + []*Task{ + task5, + task7, + task8, + task10, + task4, + task3, + task1, + task9, + task2, + task6, + }) + }) + t.Run("list id", func(t *testing.T) { + runSortTest( + t, + taskPropertyListID, + tasks, + []*Task{ + task6, + task9, + task8, + task7, + task5, + task10, + task4, + task1, + task2, + task3, + }) + }) + t.Run("repeat after", func(t *testing.T) { + runSortTest( + t, + taskPropertyRepeatAfter, + tasks, + []*Task{ + task1, + task4, + task10, + task5, + task7, + task8, + task3, + task2, + task6, + task9, + }) + }) + t.Run("priority", func(t *testing.T) { + runSortTest( + t, + taskPropertyPriority, + tasks, + []*Task{ + task8, + task7, + task2, + task1, + task9, + task6, + task4, + task10, + task5, + task3, + }) + }) + t.Run("start date", func(t *testing.T) { + runSortTest( + t, + taskPropertyStartDateUnix, + tasks, + []*Task{ + task3, + task8, + task6, + task1, + task5, + task10, + task2, + task9, + task7, + task4, + }) + }) + t.Run("end date", func(t *testing.T) { + runSortTest( + t, + taskPropertyEndDateUnix, + tasks, + []*Task{ + task2, + task3, + task10, + task5, + task1, + task6, + task4, + task8, + task9, + task7, + }) + }) + t.Run("hex color", func(t *testing.T) { + runSortTest( + t, + taskPropertyHexColor, + tasks, + []*Task{ + task8, + task5, + task1, + task4, + task10, + task7, + task2, + task9, + task3, + task6, + }) + }) + t.Run("percent done", func(t *testing.T) { + runSortTest( + t, + taskPropertyPercentDone, + tasks, + []*Task{ + task4, + task9, + task1, + task5, + task8, + task6, + task7, + task3, + task2, + task10, + }) + }) + t.Run("uid", func(t *testing.T) { + runSortTest( + t, + taskPropertyUID, + tasks, + []*Task{ + task3, + task6, + task8, + task9, + task10, + task2, + task4, + task1, + task5, + task7, + }) + }) + t.Run("created", func(t *testing.T) { + runSortTest( + t, + taskPropertyCreated, + tasks, + []*Task{ + task7, + task9, + task10, + task4, + task8, + task6, + task3, + task5, + task1, + task2, + }) + }) + t.Run("updated", func(t *testing.T) { + runSortTest( + t, + taskPropertyUpdated, + tasks, + []*Task{ + task6, + task8, + task4, + task10, + task9, + task7, + task1, + task2, + task3, + task5, + }) + }) } -- 2.40.1 From b075dafe84ef97b9500b6f9b41b07cca9984ecb9 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 20:51:04 +0100 Subject: [PATCH 28/60] Sort test optimizations --- pkg/models/task_collection_sort_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 4ee330be5..9c1b93b83 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -101,7 +101,7 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor sorted := referenceTasks sortTasks(sorted, by) if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { - t.Errorf("Not Equal, got = %v, want %v, \ndiff: %v", sorted, ascSortedTasks, diff) + t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, ascSortedTasks, diff) } }) t.Run("asc", func(t *testing.T) { @@ -114,7 +114,7 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor sorted := referenceTasks sortTasks(sorted, by) if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { - t.Errorf("Not Equal, got = %v, want %v, \ndiff: %v", sorted, ascSortedTasks, diff) + t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, ascSortedTasks, diff) } }) t.Run("desc", func(t *testing.T) { @@ -135,9 +135,8 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor sorted := referenceTasks sortTasks(sorted, by) - assert.Equal(t, descSortedTasks, sorted) if diff, equal := messagediff.PrettyDiff(sorted, descSortedTasks); !equal { - t.Errorf("Not Equal, got = %v, want %v, \ndiff: %v", sorted, descSortedTasks, diff) + t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, descSortedTasks, diff) } }) } -- 2.40.1 From c274e98e516b65f02f4785124ab04ed85f6c5975 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 20:51:31 +0100 Subject: [PATCH 29/60] Properly negate oldcomparator --- pkg/models/task_collection_sort.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 88eaa4a8b..54897c335 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -240,7 +240,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { if param.orderBy == orderDescending { oldComparator := comparator comparator = func(lhs, rhs *Task) int { - return -oldComparator(lhs, rhs) + return oldComparator(lhs, rhs) * -1 } } -- 2.40.1 From ddd739f3939113e4bc6c6c62de2483e73668673f Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 21:01:04 +0100 Subject: [PATCH 30/60] Replace handwritten taskComparators with automatically generated ones Use reflection to automatically generate different taskComparators depending on the field type. --- pkg/models/task_collection_sort.go | 160 ++++++++++++----------------- 1 file changed, 66 insertions(+), 94 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 54897c335..793325cfc 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -16,7 +16,11 @@ package models -import "sort" +import ( + "fmt" + "reflect" + "sort" +) type ( sortParam struct { @@ -101,117 +105,85 @@ func (sp *sortParam) validate() error { return ErrInvalidSortParam{SortBy: sp.sortBy} } -type taskComparator func(lhs, rhs *Task) int +type taskComparator func(lhs, rhs *Task) int64 -// TODO: Move somewhere else -func stringCompare(lhs, rhs string) int { - if lhs > rhs { - return 1 +func mustMakeComparator(fieldName string) taskComparator { + field, ok := reflect.TypeOf(&Task{}).Elem().FieldByName(fieldName) + if !ok { + panic(fmt.Sprintf("Field '%s' has not been found on Task", fieldName)) } - if lhs < rhs { - return -1 - } - return 0 -} -func int64Compare(lhs, rhs int64) int { - if lhs > rhs { - return 1 + extractProp := func(task *Task) interface{} { + return reflect.ValueOf(task).Elem().FieldByIndex(field.Index).Interface() } - if lhs < rhs { - return -1 - } - return 0 -} -func float64Compare(lhs, rhs float64) int { - if lhs > rhs { - return 1 - } - if lhs < rhs { - return -1 - } - return 0 -} - -func boolCompare(lhs, rhs, first bool) int { - // TODO: Can we simplify this? - if !lhs && rhs { - if first { - return 1 + switch field.Type.Kind() { + case reflect.Int64: + return func(lhs, rhs *Task) int64 { + return extractProp(lhs).(int64) - extractProp(rhs).(int64) } - return -1 - } - if lhs && !rhs { - if first { - return -1 + case reflect.Float64: + return func(lhs, rhs *Task) int64 { + floatLhs, floatRhs := extractProp(lhs).(float64), extractProp(rhs).(float64) + if floatLhs > floatRhs { + return 1 + } else if floatLhs < floatRhs { + return -1 + } + return 0 } - return 1 + case reflect.String: + return func(lhs, rhs *Task) int64 { + strLhs, strRhs := extractProp(lhs).(string), extractProp(rhs).(string) + if strLhs > strRhs { + return 1 + } else if strLhs < strRhs { + return -1 + } + return 0 + } + case reflect.Bool: + return func(lhs, rhs *Task) int64 { + boolLhs, boolRhs := extractProp(lhs).(bool), extractProp(rhs).(bool) + if boolLhs && !boolRhs { + return 1 + } else if !boolLhs && boolRhs { + return -1 + } + return 0 + } + default: + panic(fmt.Sprintf("Unsupported type for sorting: %s", field.Type.Name())) } - return 0 } // This is a map of properties that can be sorted by // and their appropriate comparator function. // The comparator function sorts in ascending mode. var propertyComparators = map[sortProperty]taskComparator{ - taskPropertyText: func(lhs, rhs *Task) int { - return stringCompare(lhs.Text, rhs.Text) - }, - taskPropertyDescription: func(lhs, rhs *Task) int { - return stringCompare(lhs.Description, rhs.Description) - }, - taskPropertyDone: func(lhs, rhs *Task) int { - return boolCompare(lhs.Done, rhs.Done, true) - }, - taskPropertyDoneAtUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.DoneAtUnix, rhs.DoneAtUnix) - }, - taskPropertyDueDateUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.DueDateUnix, rhs.DueDateUnix) - }, - taskPropertyCreatedByID: func(lhs, rhs *Task) int { - return int64Compare(lhs.CreatedByID, rhs.CreatedByID) - }, - taskPropertyListID: func(lhs, rhs *Task) int { - return int64Compare(lhs.ListID, rhs.ListID) - }, - taskPropertyRepeatAfter: func(lhs, rhs *Task) int { - return int64Compare(lhs.RepeatAfter, rhs.RepeatAfter) - }, - taskPropertyPriority: func(lhs, rhs *Task) int { - return int64Compare(lhs.Priority, rhs.Priority) - }, - taskPropertyStartDateUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.StartDateUnix, rhs.StartDateUnix) - }, - taskPropertyEndDateUnix: func(lhs, rhs *Task) int { - return int64Compare(lhs.EndDateUnix, rhs.EndDateUnix) - }, - taskPropertyHexColor: func(lhs, rhs *Task) int { - return stringCompare(lhs.HexColor, rhs.HexColor) - }, - taskPropertyPercentDone: func(lhs, rhs *Task) int { - return float64Compare(lhs.PercentDone, rhs.PercentDone) - }, - taskPropertyUID: func(lhs, rhs *Task) int { - return stringCompare(lhs.UID, rhs.UID) - }, - taskPropertyCreated: func(lhs, rhs *Task) int { - return int64Compare(lhs.Created, rhs.Created) - }, - taskPropertyUpdated: func(lhs, rhs *Task) int { - return int64Compare(lhs.Updated, rhs.Updated) - }, - taskPropertyID: func(lhs, rhs *Task) int { - return int64Compare(lhs.ID, rhs.ID) - }, + taskPropertyText: mustMakeComparator("Text"), + taskPropertyDescription: mustMakeComparator("Description"), + taskPropertyDone: mustMakeComparator("Done"), + taskPropertyDoneAtUnix: mustMakeComparator("DoneAtUnix"), + taskPropertyDueDateUnix: mustMakeComparator("DueDateUnix"), + taskPropertyCreatedByID: mustMakeComparator("CreatedByID"), + taskPropertyListID: mustMakeComparator("ListID"), + taskPropertyRepeatAfter: mustMakeComparator("RepeatAfter"), + taskPropertyPriority: mustMakeComparator("Priority"), + taskPropertyStartDateUnix: mustMakeComparator("StartDateUnix"), + taskPropertyEndDateUnix: mustMakeComparator("EndDateUnix"), + taskPropertyHexColor: mustMakeComparator("HexColor"), + taskPropertyPercentDone: mustMakeComparator("PercentDone"), + taskPropertyUID: mustMakeComparator("UID"), + taskPropertyCreated: mustMakeComparator("Created"), + taskPropertyUpdated: mustMakeComparator("Updated"), + taskPropertyID: mustMakeComparator("ID"), } // Creates a taskComparator that sorts by the first comparator and falls back to // the second one (and so on...) if the properties were equal. func combineComparators(comparators ...taskComparator) taskComparator { - return func(lhs, rhs *Task) int { + return func(lhs, rhs *Task) int64 { for _, compare := range comparators { res := compare(lhs, rhs) if res != 0 { @@ -239,7 +211,7 @@ func sortTasks(tasks []*Task, by []*sortParam) { // This is a descending sort, so we need to negate the comparator (i.e. switch the inputs). if param.orderBy == orderDescending { oldComparator := comparator - comparator = func(lhs, rhs *Task) int { + comparator = func(lhs, rhs *Task) int64 { return oldComparator(lhs, rhs) * -1 } } -- 2.40.1 From 5240262b6bd02474e440c91e2256ff158f8c2e93 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 21:18:04 +0100 Subject: [PATCH 31/60] Fix lint --- pkg/models/task_collection_sort.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 793325cfc..d5b2df01a 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -124,30 +124,30 @@ func mustMakeComparator(fieldName string) taskComparator { } case reflect.Float64: return func(lhs, rhs *Task) int64 { - floatLhs, floatRhs := extractProp(lhs).(float64), extractProp(rhs).(float64) - if floatLhs > floatRhs { + floatLHS, floatRHS := extractProp(lhs).(float64), extractProp(rhs).(float64) + if floatLHS > floatRHS { return 1 - } else if floatLhs < floatRhs { + } else if floatLHS < floatRHS { return -1 } return 0 } case reflect.String: return func(lhs, rhs *Task) int64 { - strLhs, strRhs := extractProp(lhs).(string), extractProp(rhs).(string) - if strLhs > strRhs { + strLHS, strRHS := extractProp(lhs).(string), extractProp(rhs).(string) + if strLHS > strRHS { return 1 - } else if strLhs < strRhs { + } else if strLHS < strRHS { return -1 } return 0 } case reflect.Bool: return func(lhs, rhs *Task) int64 { - boolLhs, boolRhs := extractProp(lhs).(bool), extractProp(rhs).(bool) - if boolLhs && !boolRhs { + boolLHS, boolRHS := extractProp(lhs).(bool), extractProp(rhs).(bool) + if boolLHS && !boolRHS { return 1 - } else if !boolLhs && boolRhs { + } else if !boolLHS && boolRHS { return -1 } return 0 -- 2.40.1 From c704d3e1629da2b4f5c736d8e09719cfc151eb3c Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 21:35:52 +0100 Subject: [PATCH 32/60] Deepcopy reference slice in task sorting tests --- go.mod | 4 +++- go.sum | 2 ++ pkg/models/task_collection_sort_test.go | 7 ++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 539a2bf1c..c05ac43a3 100644 --- a/go.mod +++ b/go.mod @@ -57,12 +57,12 @@ require ( github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-sqlite3 v1.10.0 + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/olekukonko/tablewriter v0.0.1 github.com/onsi/ginkgo v1.7.0 // indirect github.com/onsi/gomega v1.4.3 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml v1.4.0 // indirect - github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v0.9.2 github.com/samedi/caldav-go v3.0.0+incompatible github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b @@ -97,3 +97,5 @@ require ( ) replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7 + +go 1.13 diff --git a/go.sum b/go.sum index 37ba03ddc..b555cb5ac 100644 --- a/go.sum +++ b/go.sum @@ -249,6 +249,8 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 9c1b93b83..9ba3d2b49 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -17,6 +17,7 @@ package models import ( + "github.com/mohae/deepcopy" "github.com/stretchr/testify/assert" "gopkg.in/d4l3k/messagediff.v1" "testing" @@ -98,7 +99,7 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor sortBy: sortProperty, }, } - sorted := referenceTasks + sorted := deepcopy.Copy(referenceTasks).([]*Task) sortTasks(sorted, by) if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, ascSortedTasks, diff) @@ -111,7 +112,7 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor orderBy: orderAscending, }, } - sorted := referenceTasks + sorted := deepcopy.Copy(referenceTasks).([]*Task) sortTasks(sorted, by) if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, ascSortedTasks, diff) @@ -133,7 +134,7 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor descSortedTasks[i], descSortedTasks[opp] = descSortedTasks[opp], descSortedTasks[i] } - sorted := referenceTasks + sorted := deepcopy.Copy(referenceTasks).([]*Task) sortTasks(sorted, by) if diff, equal := messagediff.PrettyDiff(sorted, descSortedTasks); !equal { t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, descSortedTasks, diff) -- 2.40.1 From 5ec9297628e70a70a40e20655f2c9bbe651f39a2 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 21:41:11 +0100 Subject: [PATCH 33/60] Add deepcopy to vendor --- go.mod | 12 +- go.sum | 117 +---------------- vendor/github.com/mohae/deepcopy/.gitignore | 26 ++++ vendor/github.com/mohae/deepcopy/.travis.yml | 11 ++ vendor/github.com/mohae/deepcopy/LICENSE | 21 ++++ vendor/github.com/mohae/deepcopy/README.md | 8 ++ vendor/github.com/mohae/deepcopy/deepcopy.go | 125 +++++++++++++++++++ vendor/github.com/prometheus/procfs/go.mod | 2 - vendor/modules.txt | 2 + 9 files changed, 196 insertions(+), 128 deletions(-) create mode 100644 vendor/github.com/mohae/deepcopy/.gitignore create mode 100644 vendor/github.com/mohae/deepcopy/.travis.yml create mode 100644 vendor/github.com/mohae/deepcopy/LICENSE create mode 100644 vendor/github.com/mohae/deepcopy/README.md create mode 100644 vendor/github.com/mohae/deepcopy/deepcopy.go diff --git a/go.mod b/go.mod index c05ac43a3..f22c264bd 100644 --- a/go.mod +++ b/go.mod @@ -25,15 +25,12 @@ require ( github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae github.com/client9/misspell v0.3.4 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/creack/pty v1.1.9 // indirect github.com/d4l3k/messagediff v1.2.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 github.com/garyburd/redigo v1.6.0 // indirect - github.com/gin-gonic/gin v1.5.0 // indirect github.com/go-openapi/jsonreference v0.19.3 // indirect github.com/go-openapi/spec v0.19.4 // indirect - github.com/go-playground/universal-translator v0.17.0 // indirect github.com/go-redis/redis v6.15.2+incompatible github.com/go-sql-driver/mysql v1.4.1 github.com/go-xorm/builder v0.3.4 @@ -41,23 +38,21 @@ require ( github.com/go-xorm/tests v0.5.6 // indirect github.com/go-xorm/xorm v0.7.1 github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 + github.com/golang/protobuf v1.3.2 // indirect github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc github.com/imdario/mergo v0.3.7 github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb - github.com/json-iterator/go v1.1.8 // indirect - github.com/kr/pty v1.1.8 // indirect github.com/labstack/echo/v4 v4.1.11 github.com/labstack/gommon v0.3.0 github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef - github.com/leodido/go-urn v1.2.0 // indirect github.com/mailru/easyjson v0.7.0 // indirect github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-sqlite3 v1.10.0 - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/olekukonko/tablewriter v0.0.1 github.com/onsi/ginkgo v1.7.0 // indirect github.com/onsi/gomega v1.4.3 // indirect @@ -79,15 +74,12 @@ require ( golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 golang.org/x/lint v0.0.0-20190409202823-959b441ac422 golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d // indirect - golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect google.golang.org/appengine v1.5.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/d4l3k/messagediff.v1 v1.2.1 - gopkg.in/go-playground/validator.v9 v9.30.2 // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/testfixtures.v2 v2.5.3 gopkg.in/yaml.v2 v2.2.7 // indirect diff --git a/go.sum b/go.sum index b555cb5ac..f591dffc1 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,6 @@ cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c h1:L8aPCsaLQe9qytRavkRqipse64EbDK8mFijm+9SKf7I= -code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c/go.mod h1:9dOotUqYZJhDhimNh4Xo4e2i+8cR+qPFEQNCUzaplsI= -code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7 h1:P9ncMaJE7RbYqBXF9lwT0hab7EPwuHOPslz3k1VxFs8= -code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332 h1:gXxyLkjhgN+vqrLvPyqyScyG5fbu44FJp61TvntWM24= -code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174 h1:hBY+r6bzGEfHxolaXbiVoz2LBNNnyHZK7d7Ga4Jowu8= -code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a h1:exDC9eZ+SK0GT3zB/5f3OBahWzbTZlvX9OfZWgqlbeI= -code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79 h1:U2px27G/b082nUu8vO21wFNKF9BM+5YQJj4XRZiyn2I= -code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d h1:zhNidbAwqJSnkql03i7aHDUMyQo1vM8yR1Ks495FKvc= -code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d h1:Fw5eiTr4p82l4PLaML1ARgx3fjyebxVNvPsCz727brk= -code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= -code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa h1:rtYKpdT/6wGgxGNFUzl9Q/AHgS778+rSC20AcBPNu/I= -code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= code.vikunja.io/web v0.0.0-20191023202526-f337750c3573 h1:q+nf3ao4vLpoAaksuk6lkRAMAcD2grOPNj/HwjejLl4= code.vikunja.io/web v0.0.0-20191023202526-f337750c3573/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -31,8 +13,6 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -55,9 +35,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cweill/gotests v1.5.3 h1:k3t4wW/x/YNixWZJhUIn+mivmK5iV1tJVOwVYkx0UcU= github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,42 +58,27 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= -github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8= -github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8= -github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= @@ -147,7 +109,6 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -169,63 +130,42 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA= github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw= -github.com/kolaente/echo/v4 v4.0.0-20190507190305-3725a216d803/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4= -github.com/kolaente/echo/v4 v4.0.0-20190621113036-3b0700f6d073 h1:vOueKBVhcaAusqPDK/g7lRYOwqfQ0YI3oLrBV+iGZV8= -github.com/kolaente/echo/v4 v4.0.0-20190621113036-3b0700f6d073/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= -github.com/kolaente/echo/v4 v4.0.0-20190622213746-34000ea0f7d3 h1:5RyXbSrJtkFl1EGFgyDYwiBQMbj8sMtP33aFHnT+5YE= -github.com/kolaente/echo/v4 v4.0.0-20190622213746-34000ea0f7d3/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= -github.com/labstack/echo/v4 v4.1.5/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4= -github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv4= -github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f h1:fNJtR+TNyxTdYCZU40fc8Or8RyBqMOKYNv+Zay5gjvk= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= -github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU= github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw= -github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 h1:wL11wNW7dhKIcRCHSm4sHKPWz0tt4mwBsVodG7+Xyqg= -github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= @@ -245,7 +185,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -316,31 +255,20 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= -github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= -github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM= -github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U= -github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0= github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y= github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI= github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA= github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ulule/limiter/v3 v3.3.0 h1:DuMRthpkl1wW9Em6xOVw5HMHnbDumSIDydiMqP0PTXs= github.com/ulule/limiter/v3 v3.3.0/go.mod h1:E6sfg3hfRgW+yFvkE/rZf6YLqXYFMWTmZaZKvdEiQsA= github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -356,16 +284,11 @@ golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo= -golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1 h1:anGSYQpPhQwXlwsu5wmfq0nWkCNaMEMUwAv13Y92hd8= golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= @@ -378,9 +301,7 @@ golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -390,10 +311,6 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg= -golang.org/x/net v0.0.0-20191011234655-491137f69257/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8= -golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk= golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -401,34 +318,20 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTm golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg= -golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191023145028-b69606af412f h1:HNixo/W24k2W4EliZfUFl5ApIz/dMDShw52wmWfJ8/s= -golang.org/x/sys v0.0.0-20191023145028-b69606af412f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0= -golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -436,31 +339,16 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k= golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a h1:TwMENskLwU2NnWBzrJGEWHqSiGUkO/B4rfyhwqDxDYQ= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100 h1:OT2Y8iVtXGHPODZd6iwpndJmAYRiZc75IYxlufvlkLg= -golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12 h1:s9/f9YHBWfC3jIKMbJElk5+EwgC58Khn6t1EdLnQ9+k= -golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3 h1:4haCIJia9wHJUU7z9f7PTC8Nf599Ok93njSCHb5gJas= -golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 h1:0vQisIa3mUFShxg7Xyq8WFt/ArQ1soDk5A5uF62IJCc= -golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d h1:/iIZNFGxc/a7C3yWjGcnboV+Tkc7mxr+p6fDztwoxuM= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -472,6 +360,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0= gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44= @@ -479,8 +368,6 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= -gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/go-playground/validator.v9 v9.30.2/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M= @@ -493,8 +380,6 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA= diff --git a/vendor/github.com/mohae/deepcopy/.gitignore b/vendor/github.com/mohae/deepcopy/.gitignore new file mode 100644 index 000000000..5846dd153 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.gitignore @@ -0,0 +1,26 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*~ +*.out +*.log diff --git a/vendor/github.com/mohae/deepcopy/.travis.yml b/vendor/github.com/mohae/deepcopy/.travis.yml new file mode 100644 index 000000000..fd47a8cf7 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - tip + +matrix: + allow_failures: + - go: tip + +script: + - go test ./... diff --git a/vendor/github.com/mohae/deepcopy/LICENSE b/vendor/github.com/mohae/deepcopy/LICENSE new file mode 100644 index 000000000..419673f00 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Joel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mohae/deepcopy/README.md b/vendor/github.com/mohae/deepcopy/README.md new file mode 100644 index 000000000..f81841885 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/README.md @@ -0,0 +1,8 @@ +deepCopy +======== +[![GoDoc](https://godoc.org/github.com/mohae/deepcopy?status.svg)](https://godoc.org/github.com/mohae/deepcopy)[![Build Status](https://travis-ci.org/mohae/deepcopy.png)](https://travis-ci.org/mohae/deepcopy) + +DeepCopy makes deep copies of things: unexported field values are not copied. + +## Usage + cpy := deepcopy.Copy(orig) diff --git a/vendor/github.com/mohae/deepcopy/deepcopy.go b/vendor/github.com/mohae/deepcopy/deepcopy.go new file mode 100644 index 000000000..ba763ad09 --- /dev/null +++ b/vendor/github.com/mohae/deepcopy/deepcopy.go @@ -0,0 +1,125 @@ +// deepcopy makes deep copies of things. A standard copy will copy the +// pointers: deep copy copies the values pointed to. Unexported field +// values are not copied. +// +// Copyright (c)2014-2016, Joel Scoble (github.com/mohae), all rights reserved. +// License: MIT, for more details check the included LICENSE file. +package deepcopy + +import ( + "reflect" + "time" +) + +// Interface for delegating copy process to type +type Interface interface { + DeepCopy() interface{} +} + +// Iface is an alias to Copy; this exists for backwards compatibility reasons. +func Iface(iface interface{}) interface{} { + return Copy(iface) +} + +// Copy creates a deep copy of whatever is passed to it and returns the copy +// in an interface{}. The returned value will need to be asserted to the +// correct type. +func Copy(src interface{}) interface{} { + if src == nil { + return nil + } + + // Make the interface a reflect.Value + original := reflect.ValueOf(src) + + // Make a copy of the same type as the original. + cpy := reflect.New(original.Type()).Elem() + + // Recursively copy the original. + copyRecursive(original, cpy) + + // Return the copy as an interface. + return cpy.Interface() +} + +// copyRecursive does the actual copying of the interface. It currently has +// limited support for what it can handle. Add as needed. +func copyRecursive(original, cpy reflect.Value) { + // check for implement deepcopy.Interface + if original.CanInterface() { + if copier, ok := original.Interface().(Interface); ok { + cpy.Set(reflect.ValueOf(copier.DeepCopy())) + return + } + } + + // handle according to original's Kind + switch original.Kind() { + case reflect.Ptr: + // Get the actual value being pointed to. + originalValue := original.Elem() + + // if it isn't valid, return. + if !originalValue.IsValid() { + return + } + cpy.Set(reflect.New(originalValue.Type())) + copyRecursive(originalValue, cpy.Elem()) + + case reflect.Interface: + // If this is a nil, don't do anything + if original.IsNil() { + return + } + // Get the value for the interface, not the pointer. + originalValue := original.Elem() + + // Get the value by calling Elem(). + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + cpy.Set(copyValue) + + case reflect.Struct: + t, ok := original.Interface().(time.Time) + if ok { + cpy.Set(reflect.ValueOf(t)) + return + } + // Go through each field of the struct and copy it. + for i := 0; i < original.NumField(); i++ { + // The Type's StructField for a given field is checked to see if StructField.PkgPath + // is set to determine if the field is exported or not because CanSet() returns false + // for settable fields. I'm not sure why. -mohae + if original.Type().Field(i).PkgPath != "" { + continue + } + copyRecursive(original.Field(i), cpy.Field(i)) + } + + case reflect.Slice: + if original.IsNil() { + return + } + // Make a new slice and copy each element. + cpy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap())) + for i := 0; i < original.Len(); i++ { + copyRecursive(original.Index(i), cpy.Index(i)) + } + + case reflect.Map: + if original.IsNil() { + return + } + cpy.Set(reflect.MakeMap(original.Type())) + for _, key := range original.MapKeys() { + originalValue := original.MapIndex(key) + copyValue := reflect.New(originalValue.Type()).Elem() + copyRecursive(originalValue, copyValue) + copyKey := Copy(key.Interface()) + cpy.SetMapIndex(reflect.ValueOf(copyKey), copyValue) + } + + default: + cpy.Set(original) + } +} diff --git a/vendor/github.com/prometheus/procfs/go.mod b/vendor/github.com/prometheus/procfs/go.mod index 14631543a..e89ee6c90 100644 --- a/vendor/github.com/prometheus/procfs/go.mod +++ b/vendor/github.com/prometheus/procfs/go.mod @@ -1,3 +1 @@ module github.com/prometheus/procfs - -go 1.12 diff --git a/vendor/modules.txt b/vendor/modules.txt index 078cb3015..57b04dae6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -114,6 +114,8 @@ github.com/mattn/go-sqlite3 github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure +# github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 +github.com/mohae/deepcopy # github.com/olekukonko/tablewriter v0.0.1 github.com/olekukonko/tablewriter # github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 -- 2.40.1 From 91349b45e23ac8bbf3637e40d416932a1921e183 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 22:25:43 +0100 Subject: [PATCH 34/60] Add list of desc sorted tasks --- pkg/models/task_collection_sort_test.go | 222 ++++++++++++++++++++++-- 1 file changed, 209 insertions(+), 13 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 9ba3d2b49..4ad458aa6 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -92,7 +92,7 @@ func TestSortParamValidation(t *testing.T) { }) } -func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSortedTasks []*Task) { +func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSortedTasks, descSortedTasks []*Task) { t.Run("asc default", func(t *testing.T) { by := []*sortParam{ { @@ -126,14 +126,6 @@ func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSor }, } - // Reversing the asc sorted control group - // Algorithm taken from https://github.com/golang/go/wiki/SliceTricks#reversing - descSortedTasks := ascSortedTasks - for i := len(descSortedTasks)/2 - 1; i >= 0; i-- { - opp := len(descSortedTasks) - 1 - i - descSortedTasks[i], descSortedTasks[opp] = descSortedTasks[opp], descSortedTasks[i] - } - sorted := deepcopy.Copy(referenceTasks).([]*Task) sortTasks(sorted, by) if diff, equal := messagediff.PrettyDiff(sorted, descSortedTasks); !equal { @@ -258,6 +250,18 @@ func TestTaskSort(t *testing.T) { task8, task9, task10, + }, + []*Task{ + task10, + task9, + task8, + task7, + task6, + task5, + task4, + task3, + task2, + task1, }) }) t.Run("text", func(t *testing.T) { @@ -265,6 +269,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyText, tasks, + []*Task{ + task9, + task1, + task8, + task2, + task3, + task4, + task6, + task5, + task7, + task10, + }, []*Task{ task9, task1, @@ -284,13 +300,25 @@ func TestTaskSort(t *testing.T) { taskPropertyDescription, tasks, []*Task{ - task6, - task8, - task5, - task9, task4, task3, + task6, + task5, task10, + task9, + task8, + task2, + task1, + task7, + }, + []*Task{ + task4, + task3, + task6, + task5, + task10, + task9, + task8, task2, task1, task7, @@ -301,6 +329,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyDone, tasks, + []*Task{ + task1, + task2, + task9, + task10, + task5, + task8, + task7, + task3, + task4, + task6, + }, []*Task{ task1, task2, @@ -319,6 +359,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyDoneAtUnix, tasks, + []*Task{ + task10, + task8, + task5, + task6, + task4, + task3, + task7, + task1, + task2, + task9, + }, []*Task{ task10, task8, @@ -337,6 +389,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyDueDateUnix, tasks, + []*Task{ + task1, + task2, + task4, + task7, + task8, + task9, + task10, + task6, + task5, + task3, + }, []*Task{ task1, task2, @@ -355,6 +419,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyCreatedByID, tasks, + []*Task{ + task5, + task7, + task8, + task10, + task4, + task3, + task1, + task9, + task2, + task6, + }, []*Task{ task5, task7, @@ -373,6 +449,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyListID, tasks, + []*Task{ + task6, + task9, + task8, + task7, + task5, + task10, + task4, + task1, + task2, + task3, + }, []*Task{ task6, task9, @@ -391,6 +479,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyRepeatAfter, tasks, + []*Task{ + task1, + task4, + task10, + task5, + task7, + task8, + task3, + task2, + task6, + task9, + }, []*Task{ task1, task4, @@ -409,6 +509,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyPriority, tasks, + []*Task{ + task8, + task7, + task2, + task1, + task9, + task6, + task4, + task10, + task5, + task3, + }, []*Task{ task8, task7, @@ -427,6 +539,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyStartDateUnix, tasks, + []*Task{ + task3, + task8, + task6, + task1, + task5, + task10, + task2, + task9, + task7, + task4, + }, []*Task{ task3, task8, @@ -445,6 +569,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyEndDateUnix, tasks, + []*Task{ + task2, + task3, + task10, + task5, + task1, + task6, + task4, + task8, + task9, + task7, + }, []*Task{ task2, task3, @@ -463,6 +599,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyHexColor, tasks, + []*Task{ + task8, + task5, + task1, + task4, + task10, + task7, + task2, + task9, + task3, + task6, + }, []*Task{ task8, task5, @@ -481,6 +629,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyPercentDone, tasks, + []*Task{ + task4, + task9, + task1, + task5, + task8, + task6, + task7, + task3, + task2, + task10, + }, []*Task{ task4, task9, @@ -499,6 +659,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyUID, tasks, + []*Task{ + task3, + task6, + task8, + task9, + task10, + task2, + task4, + task1, + task5, + task7, + }, []*Task{ task3, task6, @@ -517,6 +689,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyCreated, tasks, + []*Task{ + task7, + task9, + task10, + task4, + task8, + task6, + task3, + task5, + task1, + task2, + }, []*Task{ task7, task9, @@ -535,6 +719,18 @@ func TestTaskSort(t *testing.T) { t, taskPropertyUpdated, tasks, + []*Task{ + task6, + task8, + task4, + task10, + task9, + task7, + task1, + task2, + task3, + task5, + }, []*Task{ task6, task8, -- 2.40.1 From f85598efc32ee7f0744b21aa98d9ab84473b1b30 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 22:27:36 +0100 Subject: [PATCH 35/60] Fix text task --- go.mod | 1 + pkg/models/task_collection_sort_test.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f22c264bd..84f5fcbda 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/onsi/gomega v1.4.3 // indirect github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/pelletier/go-toml v1.4.0 // indirect + github.com/pkg/errors v0.8.1 // indirect github.com/prometheus/client_golang v0.9.2 github.com/samedi/caldav-go v3.0.0+incompatible github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 4ad458aa6..037e1ff43 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -282,16 +282,16 @@ func TestTaskSort(t *testing.T) { task10, }, []*Task{ - task9, - task1, - task8, - task2, - task3, - task4, - task6, - task5, - task7, task10, + task7, + task5, + task6, + task4, + task3, + task2, + task8, + task1, + task9, }) }) t.Run("description", func(t *testing.T) { -- 2.40.1 From 9b4d481b0285eb802815650982a94587207a7d3e Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 22:32:10 +0100 Subject: [PATCH 36/60] Fix description task --- pkg/models/task_collection_sort_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 037e1ff43..4b789dab8 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -312,16 +312,16 @@ func TestTaskSort(t *testing.T) { task7, }, []*Task{ + task7, + task1, + task2, task4, task3, + task8, task6, task5, task10, task9, - task8, - task2, - task1, - task7, }) }) t.Run("done", func(t *testing.T) { -- 2.40.1 From 9865c75527077582bb1bfc7359bbb0344a64d86c Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 22:32:19 +0100 Subject: [PATCH 37/60] Fix done test --- pkg/models/task_collection_sort_test.go | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 4b789dab8..ce1128da6 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -330,28 +330,28 @@ func TestTaskSort(t *testing.T) { taskPropertyDone, tasks, []*Task{ - task1, - task2, - task9, - task10, - task5, - task8, - task7, - task3, task4, task6, + task5, + task10, + task3, + task8, + task7, + task9, + task2, + task1, }, []*Task{ - task1, - task2, task9, - task10, - task5, + task2, + task1, + task4, + task3, task8, task7, - task3, - task4, task6, + task5, + task10, }) }) t.Run("done at", func(t *testing.T) { -- 2.40.1 From 09b277b0218fc12dff3781c3ceedcb4832cc2da8 Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 22:34:16 +0100 Subject: [PATCH 38/60] Rewrite testing for task sorting --- pkg/models/task_collection_sort_test.go | 767 ++++++------------------ 1 file changed, 189 insertions(+), 578 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index ce1128da6..aa2b527d2 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -19,7 +19,8 @@ package models import ( "github.com/mohae/deepcopy" "github.com/stretchr/testify/assert" - "gopkg.in/d4l3k/messagediff.v1" + "math/rand" + "reflect" "testing" ) @@ -92,50 +93,8 @@ func TestSortParamValidation(t *testing.T) { }) } -func runSortTest(t *testing.T, sortProperty sortProperty, referenceTasks, ascSortedTasks, descSortedTasks []*Task) { - t.Run("asc default", func(t *testing.T) { - by := []*sortParam{ - { - sortBy: sortProperty, - }, - } - sorted := deepcopy.Copy(referenceTasks).([]*Task) - sortTasks(sorted, by) - if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { - t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, ascSortedTasks, diff) - } - }) - t.Run("asc", func(t *testing.T) { - by := []*sortParam{ - { - sortBy: sortProperty, - orderBy: orderAscending, - }, - } - sorted := deepcopy.Copy(referenceTasks).([]*Task) - sortTasks(sorted, by) - if diff, equal := messagediff.PrettyDiff(sorted, ascSortedTasks); !equal { - t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, ascSortedTasks, diff) - } - }) - t.Run("desc", func(t *testing.T) { - by := []*sortParam{ - { - sortBy: sortProperty, - orderBy: orderDescending, - }, - } - - sorted := deepcopy.Copy(referenceTasks).([]*Task) - sortTasks(sorted, by) - if diff, equal := messagediff.PrettyDiff(sorted, descSortedTasks); !equal { - t.Errorf("Not Equal,\ngot = %v,\nwant = %v,\ndiff: %v", sorted, descSortedTasks, diff) - } - }) -} - -func TestTaskSort(t *testing.T) { - task1 := &Task{ +var ( + task1 = &Task{ ID: 1, Text: "aaa", Description: "Lorem Ipsum", @@ -146,7 +105,7 @@ func TestTaskSort(t *testing.T) { Created: 1543626724, Updated: 1543626724, } - task2 := &Task{ + task2 = &Task{ ID: 2, Text: "bbb", Description: "Arem Ipsum", @@ -159,7 +118,7 @@ func TestTaskSort(t *testing.T) { Created: 1553626724, Updated: 1553626724, } - task3 := &Task{ + task3 = &Task{ ID: 3, Text: "ccc", DueDateUnix: 1583626724, @@ -169,14 +128,14 @@ func TestTaskSort(t *testing.T) { PercentDone: 0.1, Updated: 1555555555, } - task4 := &Task{ + task4 = &Task{ ID: 4, Text: "ddd", Priority: 1, StartDateUnix: 1643626724, ListID: 1, } - task5 := &Task{ + task5 = &Task{ ID: 5, Text: "efg", Priority: 50, @@ -184,7 +143,7 @@ func TestTaskSort(t *testing.T) { DueDateUnix: 1543636724, Updated: 1565555555, } - task6 := &Task{ + task6 = &Task{ ID: 6, Text: "eef", DueDateUnix: 1543616724, @@ -192,7 +151,7 @@ func TestTaskSort(t *testing.T) { CreatedByID: 2, HexColor: "ffffff", } - task7 := &Task{ + task7 = &Task{ ID: 7, Text: "mmmn", Description: "Zoremis", @@ -200,12 +159,12 @@ func TestTaskSort(t *testing.T) { EndDateUnix: 1584600000, UID: "tyzCZuLMSKhwclJOsDyDcUdyVAPBDOPHNTBOLTcW", } - task8 := &Task{ + task8 = &Task{ ID: 8, Text: "b123", EndDateUnix: 1544700000, } - task9 := &Task{ + task9 = &Task{ ID: 9, Done: true, DoneAtUnix: 1573626724, @@ -214,535 +173,187 @@ func TestTaskSort(t *testing.T) { StartDateUnix: 1544600000, EndDateUnix: 1544700000, } - task10 := &Task{ + task10 = &Task{ ID: 10, Text: "zzz", Priority: 10, PercentDone: 1, } +) - tasks := []*Task{ - task1, - task2, - task3, - task4, - task5, - task6, - task7, - task8, - task9, - task10, - } - - t.Run("id", func(t *testing.T) { - runSortTest( - t, - taskPropertyID, - tasks, - []*Task{ - task1, - task2, - task3, - task4, - task5, - task6, - task7, - task8, - task9, - task10, - }, - []*Task{ - task10, - task9, - task8, - task7, - task6, - task5, - task4, - task3, - task2, - task1, - }) - }) - t.Run("text", func(t *testing.T) { - runSortTest( - t, - taskPropertyText, - tasks, - []*Task{ - task9, - task1, - task8, - task2, - task3, - task4, - task6, - task5, - task7, - task10, - }, - []*Task{ - task10, - task7, - task5, - task6, - task4, - task3, - task2, - task8, - task1, - task9, - }) - }) - t.Run("description", func(t *testing.T) { - runSortTest( - t, - taskPropertyDescription, - tasks, - []*Task{ - task4, - task3, - task6, - task5, - task10, - task9, - task8, - task2, - task1, - task7, - }, - []*Task{ - task7, - task1, - task2, - task4, - task3, - task8, - task6, - task5, - task10, - task9, - }) - }) - t.Run("done", func(t *testing.T) { - runSortTest( - t, - taskPropertyDone, - tasks, - []*Task{ - task4, - task6, - task5, - task10, - task3, - task8, - task7, - task9, - task2, - task1, - }, - []*Task{ - task9, - task2, - task1, - task4, - task3, - task8, - task7, - task6, - task5, - task10, - }) - }) - t.Run("done at", func(t *testing.T) { - runSortTest( - t, - taskPropertyDoneAtUnix, - tasks, - []*Task{ - task10, - task8, - task5, - task6, - task4, - task3, - task7, - task1, - task2, - task9, - }, - []*Task{ - task10, - task8, - task5, - task6, - task4, - task3, - task7, - task1, - task2, - task9, - }) - }) - t.Run("due date", func(t *testing.T) { - runSortTest( - t, - taskPropertyDueDateUnix, - tasks, - []*Task{ - task1, - task2, - task4, - task7, - task8, - task9, - task10, - task6, - task5, - task3, - }, - []*Task{ - task1, - task2, - task4, - task7, - task8, - task9, - task10, - task6, - task5, - task3, - }) - }) - t.Run("created by id", func(t *testing.T) { - runSortTest( - t, - taskPropertyCreatedByID, - tasks, - []*Task{ - task5, - task7, - task8, - task10, - task4, - task3, - task1, - task9, - task2, - task6, - }, - []*Task{ - task5, - task7, - task8, - task10, - task4, - task3, - task1, - task9, - task2, - task6, - }) - }) - t.Run("list id", func(t *testing.T) { - runSortTest( - t, - taskPropertyListID, - tasks, - []*Task{ - task6, - task9, - task8, - task7, - task5, - task10, - task4, - task1, - task2, - task3, - }, - []*Task{ - task6, - task9, - task8, - task7, - task5, - task10, - task4, - task1, - task2, - task3, - }) - }) - t.Run("repeat after", func(t *testing.T) { - runSortTest( - t, - taskPropertyRepeatAfter, - tasks, - []*Task{ - task1, - task4, - task10, - task5, - task7, - task8, - task3, - task2, - task6, - task9, - }, - []*Task{ - task1, - task4, - task10, - task5, - task7, - task8, - task3, - task2, - task6, - task9, - }) - }) - t.Run("priority", func(t *testing.T) { - runSortTest( - t, - taskPropertyPriority, - tasks, - []*Task{ - task8, - task7, - task2, - task1, - task9, - task6, - task4, - task10, - task5, - task3, - }, - []*Task{ - task8, - task7, - task2, - task1, - task9, - task6, - task4, - task10, - task5, - task3, - }) - }) - t.Run("start date", func(t *testing.T) { - runSortTest( - t, - taskPropertyStartDateUnix, - tasks, - []*Task{ - task3, - task8, - task6, - task1, - task5, - task10, - task2, - task9, - task7, - task4, - }, - []*Task{ - task3, - task8, - task6, - task1, - task5, - task10, - task2, - task9, - task7, - task4, - }) - }) - t.Run("end date", func(t *testing.T) { - runSortTest( - t, - taskPropertyEndDateUnix, - tasks, - []*Task{ - task2, - task3, - task10, - task5, - task1, - task6, - task4, - task8, - task9, - task7, - }, - []*Task{ - task2, - task3, - task10, - task5, - task1, - task6, - task4, - task8, - task9, - task7, - }) - }) - t.Run("hex color", func(t *testing.T) { - runSortTest( - t, - taskPropertyHexColor, - tasks, - []*Task{ - task8, - task5, - task1, - task4, - task10, - task7, - task2, - task9, - task3, - task6, - }, - []*Task{ - task8, - task5, - task1, - task4, - task10, - task7, - task2, - task9, - task3, - task6, - }) - }) - t.Run("percent done", func(t *testing.T) { - runSortTest( - t, - taskPropertyPercentDone, - tasks, - []*Task{ - task4, - task9, - task1, - task5, - task8, - task6, - task7, - task3, - task2, - task10, - }, - []*Task{ - task4, - task9, - task1, - task5, - task8, - task6, - task7, - task3, - task2, - task10, - }) - }) - t.Run("uid", func(t *testing.T) { - runSortTest( - t, - taskPropertyUID, - tasks, - []*Task{ - task3, - task6, - task8, - task9, - task10, - task2, - task4, - task1, - task5, - task7, - }, - []*Task{ - task3, - task6, - task8, - task9, - task10, - task2, - task4, - task1, - task5, - task7, - }) - }) - t.Run("created", func(t *testing.T) { - runSortTest( - t, - taskPropertyCreated, - tasks, - []*Task{ - task7, - task9, - task10, - task4, - task8, - task6, - task3, - task5, - task1, - task2, - }, - []*Task{ - task7, - task9, - task10, - task4, - task8, - task6, - task3, - task5, - task1, - task2, - }) - }) - t.Run("updated", func(t *testing.T) { - runSortTest( - t, - taskPropertyUpdated, - tasks, - []*Task{ - task6, - task8, - task4, - task10, - task9, - task7, - task1, - task2, - task3, - task5, - }, - []*Task{ - task6, - task8, - task4, - task10, - task9, - task7, - task1, - task2, - task3, - task5, - }) - }) - +type taskSortTestCase struct { + name string + want []*Task + sortParams []*sortParam +} + +var taskSortTestCases = []taskSortTestCase{ + { + name: "Order by ID Ascending", + want: []*Task{ + task1, + task2, + task3, + task4, + task5, + task6, + task7, + task8, + task9, + task10, + }, + sortParams: []*sortParam{ + { + sortBy: taskPropertyID, + orderBy: orderAscending, + }, + }, + }, + { + name: "Order by ID Descending", + want: []*Task{ + task10, + task9, + task8, + task7, + task6, + task5, + task4, + task3, + task2, + task1, + }, + sortParams: []*sortParam{ + { + sortBy: taskPropertyID, + orderBy: orderDescending, + }, + }, + }, + { + name: "Order by Text Ascending", + want: []*Task{ + task9, + task1, + task8, + task2, + task3, + task4, + task6, + task5, + task7, + task10, + }, + sortParams: []*sortParam{ + { + sortBy: taskPropertyText, + orderBy: orderAscending, + }, + }, + }, + { + name: "Order by Text Descending", + want: []*Task{ + task10, + task7, + task5, + task6, + task4, + task3, + task2, + task8, + task1, + task9, + }, + sortParams: []*sortParam{ + { + sortBy: taskPropertyText, + orderBy: orderDescending, + }, + }, + }, + { + name: "Order by Done Ascending and Text Descending", + want: []*Task{ + // Done + task2, + task1, + task9, + + // Not done + task10, + task7, + task5, + task6, + task4, + task3, + task8, + }, + sortParams: []*sortParam{ + { + sortBy: taskPropertyDone, + orderBy: orderAscending, + }, + { + sortBy: taskPropertyText, + orderBy: orderDescending, + }, + }, + }, + { + name: "Order by Done Descending and Text Ascending", + want: []*Task{ + // Not done + task8, + task3, + task4, + task6, + task5, + task7, + task10, + + // Done + task9, + task1, + task2, + }, + sortParams: []*sortParam{ + { + sortBy: taskPropertyDone, + orderBy: orderDescending, + }, + { + sortBy: taskPropertyText, + orderBy: orderAscending, + }, + }, + }, +} + +func TestTaskSort(t *testing.T) { + for _, testCase := range taskSortTestCases { + t.Run(testCase.name, func(t *testing.T) { + got := deepcopy.Copy(testCase.want).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, testCase.sortParams) + + if !reflect.DeepEqual(got, testCase.want) { + t.Error("Slices do not match in order") + t.Error("Got:\n") + for _, task := range got { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + + t.Error("Want: \n") + for _, task := range testCase.want { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + } + }) + } } -- 2.40.1 From 2fca034aadbd10a897815e69df3a93d4b5e875a4 Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 22:45:29 +0100 Subject: [PATCH 39/60] Remove superfluous newline in test output --- pkg/models/task_collection_sort_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index aa2b527d2..de6c66803 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -344,12 +344,12 @@ func TestTaskSort(t *testing.T) { if !reflect.DeepEqual(got, testCase.want) { t.Error("Slices do not match in order") - t.Error("Got:\n") + t.Error("Got:") for _, task := range got { t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) } - t.Error("Want: \n") + t.Error("Want:") for _, task := range testCase.want { t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) } -- 2.40.1 From d0ff0ab43b4a6b1c285dfefd8bca5ec02a4e6e30 Mon Sep 17 00:00:00 2001 From: Simon Hilchenbach Date: Wed, 4 Dec 2019 22:49:53 +0100 Subject: [PATCH 40/60] Fix taskComparator for bool values It sorts the wrong way around. Switch the direction such that in ascending mode the true values come before the false values (and in descending mode vice-versa). --- pkg/models/task_collection_sort.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index d5b2df01a..cf20ea61f 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -145,9 +145,9 @@ func mustMakeComparator(fieldName string) taskComparator { case reflect.Bool: return func(lhs, rhs *Task) int64 { boolLHS, boolRHS := extractProp(lhs).(bool), extractProp(rhs).(bool) - if boolLHS && !boolRHS { + if !boolLHS && boolRHS { return 1 - } else if !boolLHS && boolRHS { + } else if boolLHS && !boolRHS { return -1 } return 0 -- 2.40.1 From 6dc275fa002b1a5e6f9b0f2f44951a6f95e490fa Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 22:51:37 +0100 Subject: [PATCH 41/60] Change tests to write less --- pkg/models/task_collection_sort_test.go | 129 +++++++++++++++++------- 1 file changed, 92 insertions(+), 37 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index de6c66803..bcf3bd8d4 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -182,15 +182,16 @@ var ( ) type taskSortTestCase struct { - name string - want []*Task - sortParams []*sortParam + name string + wantAsc []*Task + wantDesc []*Task + sortProperty sortProperty } var taskSortTestCases = []taskSortTestCase{ { - name: "Order by ID Ascending", - want: []*Task{ + name: "Order by ID", + wantAsc: []*Task{ task1, task2, task3, @@ -202,16 +203,7 @@ var taskSortTestCases = []taskSortTestCase{ task9, task10, }, - sortParams: []*sortParam{ - { - sortBy: taskPropertyID, - orderBy: orderAscending, - }, - }, - }, - { - name: "Order by ID Descending", - want: []*Task{ + wantDesc: []*Task{ task10, task9, task8, @@ -223,12 +215,7 @@ var taskSortTestCases = []taskSortTestCase{ task2, task1, }, - sortParams: []*sortParam{ - { - sortBy: taskPropertyID, - orderBy: orderDescending, - }, - }, + sortProperty: taskPropertyID, }, { name: "Order by Text Ascending", @@ -333,27 +320,95 @@ var taskSortTestCases = []taskSortTestCase{ func TestTaskSort(t *testing.T) { for _, testCase := range taskSortTestCases { t.Run(testCase.name, func(t *testing.T) { - got := deepcopy.Copy(testCase.want).([]*Task) + t.Run("asc default", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: testCase.sortProperty, + }, + } - // Destroy wanted order to obtain some slice we can sort - rand.Shuffle(len(got), func(i, j int) { - got[i], got[j] = got[j], got[i] + got := deepcopy.Copy(testCase.wantAsc).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, by) + + if !reflect.DeepEqual(got, testCase.wantAsc) { + t.Error("Slices do not match in order") + t.Error("Got:") + for _, task := range got { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + + t.Error("Want:") + for _, task := range testCase.wantAsc { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + } }) - - sortTasks(got, testCase.sortParams) - - if !reflect.DeepEqual(got, testCase.want) { - t.Error("Slices do not match in order") - t.Error("Got:") - for _, task := range got { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + t.Run("asc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: testCase.sortProperty, + orderBy: orderAscending, + }, } - t.Error("Want:") - for _, task := range testCase.want { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + got := deepcopy.Copy(testCase.wantAsc).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, by) + + if !reflect.DeepEqual(got, testCase.wantAsc) { + t.Error("Slices do not match in order") + t.Error("Got:") + for _, task := range got { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + + t.Error("Want:") + for _, task := range testCase.wantAsc { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } } - } + }) + t.Run("desc", func(t *testing.T) { + by := []*sortParam{ + { + sortBy: testCase.sortProperty, + orderBy: orderDescending, + }, + } + + got := deepcopy.Copy(testCase.wantAsc).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, by) + + if !reflect.DeepEqual(got, testCase.wantDesc) { + t.Error("Slices do not match in order") + t.Error("Got:") + for _, task := range got { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + + t.Error("Want:") + for _, task := range testCase.wantDesc { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + } + }) }) } } -- 2.40.1 From 3da710637d4daafea583ade49818fa8b051a31cd Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 22:54:34 +0100 Subject: [PATCH 42/60] Move assertions to seperate function --- pkg/models/task_collection_sort_test.go | 55 +++++++++---------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index bcf3bd8d4..b92c5e6b6 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -318,6 +318,22 @@ var taskSortTestCases = []taskSortTestCase{ } func TestTaskSort(t *testing.T) { + + assertTestSliceMatch := func(t *testing.T, got, want []*Task) { + if !reflect.DeepEqual(got, want) { + t.Error("Slices do not match in order") + t.Error("Got:") + for _, task := range got { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + + t.Error("Want:") + for _, task := range want { + t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + } + } + } + for _, testCase := range taskSortTestCases { t.Run(testCase.name, func(t *testing.T) { t.Run("asc default", func(t *testing.T) { @@ -336,18 +352,7 @@ func TestTaskSort(t *testing.T) { sortTasks(got, by) - if !reflect.DeepEqual(got, testCase.wantAsc) { - t.Error("Slices do not match in order") - t.Error("Got:") - for _, task := range got { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - - t.Error("Want:") - for _, task := range testCase.wantAsc { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - } + assertTestSliceMatch(t, got, testCase.wantAsc) }) t.Run("asc", func(t *testing.T) { by := []*sortParam{ @@ -366,18 +371,7 @@ func TestTaskSort(t *testing.T) { sortTasks(got, by) - if !reflect.DeepEqual(got, testCase.wantAsc) { - t.Error("Slices do not match in order") - t.Error("Got:") - for _, task := range got { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - - t.Error("Want:") - for _, task := range testCase.wantAsc { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - } + assertTestSliceMatch(t, got, testCase.wantAsc) }) t.Run("desc", func(t *testing.T) { by := []*sortParam{ @@ -396,18 +390,7 @@ func TestTaskSort(t *testing.T) { sortTasks(got, by) - if !reflect.DeepEqual(got, testCase.wantDesc) { - t.Error("Slices do not match in order") - t.Error("Got:") - for _, task := range got { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - - t.Error("Want:") - for _, task := range testCase.wantDesc { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - } + assertTestSliceMatch(t, got, testCase.wantAsc) }) }) } -- 2.40.1 From b1c88a33a2066a09069310fc1fe616b214bea3dd Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 23:00:24 +0100 Subject: [PATCH 43/60] Fix order by text test --- pkg/models/task_collection_sort_test.go | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index b92c5e6b6..074b5bbcb 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -218,8 +218,8 @@ var taskSortTestCases = []taskSortTestCase{ sortProperty: taskPropertyID, }, { - name: "Order by Text Ascending", - want: []*Task{ + name: "Order by Text", + wantAsc: []*Task{ task9, task1, task8, @@ -231,16 +231,7 @@ var taskSortTestCases = []taskSortTestCase{ task7, task10, }, - sortParams: []*sortParam{ - { - sortBy: taskPropertyText, - orderBy: orderAscending, - }, - }, - }, - { - name: "Order by Text Descending", - want: []*Task{ + wantDesc: []*Task{ task10, task7, task5, @@ -252,12 +243,7 @@ var taskSortTestCases = []taskSortTestCase{ task1, task9, }, - sortParams: []*sortParam{ - { - sortBy: taskPropertyText, - orderBy: orderDescending, - }, - }, + sortProperty: taskPropertyText, }, { name: "Order by Done Ascending and Text Descending", -- 2.40.1 From e9687c921f7693492ed1177b1c6ccb5b5cb5642b Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 23:15:18 +0100 Subject: [PATCH 44/60] Add more test cases --- pkg/models/task_collection_sort_test.go | 135 ++++++++++++++---------- 1 file changed, 79 insertions(+), 56 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 074b5bbcb..1f071d6dd 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -245,62 +245,6 @@ var taskSortTestCases = []taskSortTestCase{ }, sortProperty: taskPropertyText, }, - { - name: "Order by Done Ascending and Text Descending", - want: []*Task{ - // Done - task2, - task1, - task9, - - // Not done - task10, - task7, - task5, - task6, - task4, - task3, - task8, - }, - sortParams: []*sortParam{ - { - sortBy: taskPropertyDone, - orderBy: orderAscending, - }, - { - sortBy: taskPropertyText, - orderBy: orderDescending, - }, - }, - }, - { - name: "Order by Done Descending and Text Ascending", - want: []*Task{ - // Not done - task8, - task3, - task4, - task6, - task5, - task7, - task10, - - // Done - task9, - task1, - task2, - }, - sortParams: []*sortParam{ - { - sortBy: taskPropertyDone, - orderBy: orderDescending, - }, - { - sortBy: taskPropertyText, - orderBy: orderAscending, - }, - }, - }, } func TestTaskSort(t *testing.T) { @@ -380,4 +324,83 @@ func TestTaskSort(t *testing.T) { }) }) } + + // Other cases + t.Run("Order by Done Ascending and Text Descending", func(t *testing.T) { + want := []*Task{ + // Done + task2, + task1, + task9, + + // Not done + task10, + task7, + task5, + task6, + task4, + task3, + task8, + } + sortParams := []*sortParam{ + { + sortBy: taskPropertyDone, + orderBy: orderAscending, + }, + { + sortBy: taskPropertyText, + orderBy: orderDescending, + }, + } + + got := deepcopy.Copy(want).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, sortParams) + + assertTestSliceMatch(t, got, want) + }) + t.Run("Order by Done Descending and Text Ascending", func(t *testing.T) { + want := []*Task{ + // Not done + task8, + task3, + task4, + task6, + task5, + task7, + task10, + + // Done + task9, + task1, + task2, + } + sortParams := []*sortParam{ + { + sortBy: taskPropertyDone, + orderBy: orderDescending, + }, + { + sortBy: taskPropertyText, + orderBy: orderAscending, + }, + } + + got := deepcopy.Copy(want).([]*Task) + + // Destroy wanted order to obtain some slice we can sort + rand.Shuffle(len(got), func(i, j int) { + got[i], got[j] = got[j], got[i] + }) + + sortTasks(got, sortParams) + + assertTestSliceMatch(t, got, want) + + }) } -- 2.40.1 From 51b9d56ac5e533a2262a5c520c94b1fe590cc4af Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 23:16:19 +0100 Subject: [PATCH 45/60] Fix tests --- pkg/models/task_collection_sort_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 1f071d6dd..9619b36bc 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -311,7 +311,7 @@ func TestTaskSort(t *testing.T) { }, } - got := deepcopy.Copy(testCase.wantAsc).([]*Task) + got := deepcopy.Copy(testCase.wantDesc).([]*Task) // Destroy wanted order to obtain some slice we can sort rand.Shuffle(len(got), func(i, j int) { @@ -320,7 +320,7 @@ func TestTaskSort(t *testing.T) { sortTasks(got, by) - assertTestSliceMatch(t, got, testCase.wantAsc) + assertTestSliceMatch(t, got, testCase.wantDesc) }) }) } -- 2.40.1 From 03252b1af2ed81c5b620dad86249ab8243b49c45 Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 4 Dec 2019 23:17:58 +0100 Subject: [PATCH 46/60] property order --- pkg/models/task_collection_sort_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 9619b36bc..57c187ff0 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -190,7 +190,8 @@ type taskSortTestCase struct { var taskSortTestCases = []taskSortTestCase{ { - name: "Order by ID", + name: "ID", + sortProperty: taskPropertyID, wantAsc: []*Task{ task1, task2, @@ -215,10 +216,10 @@ var taskSortTestCases = []taskSortTestCase{ task2, task1, }, - sortProperty: taskPropertyID, }, { - name: "Order by Text", + name: "Text", + sortProperty: taskPropertyText, wantAsc: []*Task{ task9, task1, @@ -243,7 +244,6 @@ var taskSortTestCases = []taskSortTestCase{ task1, task9, }, - sortProperty: taskPropertyText, }, } -- 2.40.1 From de17fbeeaa20055352292909de18dcd96ca879df Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 21:32:14 +0100 Subject: [PATCH 47/60] Add more test cases --- pkg/models/task_collection_sort_test.go | 420 ++++++++++++++++++++++++ 1 file changed, 420 insertions(+) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 57c187ff0..6b0d5d82b 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -245,6 +245,426 @@ var taskSortTestCases = []taskSortTestCase{ task9, }, }, + { + name: "description", + sortProperty: taskPropertyDescription, + wantAsc: []*Task{ + task4, + task3, + task6, + task5, + task10, + task9, + task8, + task2, + task1, + task7, + }, + wantDesc: []*Task{ + task7, + task1, + task2, + task4, + task3, + task8, + task6, + task5, + task10, + task9, + }, + }, + { + name: "done", + sortProperty: taskPropertyDone, + wantAsc: []*Task{ + task4, + task6, + task5, + task10, + task3, + task8, + task7, + task9, + task2, + task1, + }, + wantDesc: []*Task{ + task9, + task2, + task1, + task4, + task3, + task8, + task7, + task6, + task5, + task10, + }, + }, + { + name: "done at", + sortProperty: taskPropertyDoneAtUnix, + wantAsc: []*Task{ + task10, + task8, + task5, + task6, + task4, + task3, + task7, + task1, + task2, + task9, + }, + wantDesc: []*Task{ + task10, + task8, + task5, + task6, + task4, + task3, + task7, + task1, + task2, + task9, + }, + }, + { + name: "due date", + sortProperty: taskPropertyDueDateUnix, + wantAsc: []*Task{ + task1, + task2, + task4, + task7, + task8, + task9, + task10, + task6, + task5, + task3, + }, + wantDesc: []*Task{ + task1, + task2, + task4, + task7, + task8, + task9, + task10, + task6, + task5, + task3, + }, + }, + { + name: "created by id", + sortProperty: taskPropertyCreatedByID, + wantAsc: []*Task{ + task5, + task7, + task8, + task10, + task4, + task3, + task1, + task9, + task2, + task6, + }, + wantDesc: []*Task{ + task5, + task7, + task8, + task10, + task4, + task3, + task1, + task9, + task2, + task6, + }, + }, + { + name: "list id", + sortProperty: taskPropertyListID, + wantAsc: []*Task{ + task6, + task9, + task8, + task7, + task5, + task10, + task4, + task1, + task2, + task3, + }, + wantDesc: []*Task{ + task6, + task9, + task8, + task7, + task5, + task10, + task4, + task1, + task2, + task3, + }, + }, + { + name: "repeat after", + sortProperty: taskPropertyRepeatAfter, + wantAsc: []*Task{ + task1, + task4, + task10, + task5, + task7, + task8, + task3, + task2, + task6, + task9, + }, + wantDesc: []*Task{ + task1, + task4, + task10, + task5, + task7, + task8, + task3, + task2, + task6, + task9, + }, + }, + { + name: "priority", + sortProperty: taskPropertyPriority, + wantAsc: []*Task{ + task8, + task7, + task2, + task1, + task9, + task6, + task4, + task10, + task5, + task3, + }, + wantDesc: []*Task{ + task8, + task7, + task2, + task1, + task9, + task6, + task4, + task10, + task5, + task3, + }, + }, + { + name: "start date", + sortProperty: taskPropertyStartDateUnix, + wantAsc: []*Task{ + task3, + task8, + task6, + task1, + task5, + task10, + task2, + task9, + task7, + task4, + }, + wantDesc: []*Task{ + task3, + task8, + task6, + task1, + task5, + task10, + task2, + task9, + task7, + task4, + }, + }, + { + name: "end date", + sortProperty: taskPropertyEndDateUnix, + wantAsc: []*Task{ + task2, + task3, + task10, + task5, + task1, + task6, + task4, + task8, + task9, + task7, + }, + wantDesc: []*Task{ + task2, + task3, + task10, + task5, + task1, + task6, + task4, + task8, + task9, + task7, + }, + }, + { + name: "hex color", + sortProperty: taskPropertyHexColor, + wantAsc: []*Task{ + task8, + task5, + task1, + task4, + task10, + task7, + task2, + task9, + task3, + task6, + }, + wantDesc: []*Task{ + task8, + task5, + task1, + task4, + task10, + task7, + task2, + task9, + task3, + task6, + }, + }, + { + name: "percent done", + sortProperty: taskPropertyPercentDone, + wantAsc: []*Task{ + task4, + task9, + task1, + task5, + task8, + task6, + task7, + task3, + task2, + task10, + }, + wantDesc: []*Task{ + task4, + task9, + task1, + task5, + task8, + task6, + task7, + task3, + task2, + task10, + }, + }, + { + name: "uid", + sortProperty: taskPropertyUID, + wantAsc: []*Task{ + task3, + task6, + task8, + task9, + task10, + task2, + task4, + task1, + task5, + task7, + }, + wantDesc: []*Task{ + task3, + task6, + task8, + task9, + task10, + task2, + task4, + task1, + task5, + task7, + }, + }, + { + name: "created", + sortProperty: taskPropertyCreated, + wantAsc: []*Task{ + task7, + task9, + task10, + task4, + task8, + task6, + task3, + task5, + task1, + task2, + }, + wantDesc: []*Task{ + task7, + task9, + task10, + task4, + task8, + task6, + task3, + task5, + task1, + task2, + }, + }, + { + name: "updated", + sortProperty: taskPropertyUpdated, + wantAsc: []*Task{ + task6, + task8, + task4, + task10, + task9, + task7, + task1, + task2, + task3, + task5, + }, + wantDesc: []*Task{ + task6, + task8, + task4, + task10, + task9, + task7, + task1, + task2, + task3, + task5, + }, + }, } func TestTaskSort(t *testing.T) { -- 2.40.1 From 702d06547926e617fbece821e065ea5c62f46928 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:02:29 +0100 Subject: [PATCH 48/60] let two tasks have the same text --- pkg/models/task_collection_sort_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 6b0d5d82b..9f1c68547 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -137,7 +137,7 @@ var ( } task5 = &Task{ ID: 5, - Text: "efg", + Text: "eef", Priority: 50, UID: "shggzCHQWLhGNMNsOGOCOjcVkInOYjTAnORqTkdL", DueDateUnix: 1543636724, -- 2.40.1 From b5c63f2efd2cd29be7c81fe0c862ae0042e896cc Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:06:49 +0100 Subject: [PATCH 49/60] Improve failing tests output --- pkg/models/task_collection_sort_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 9f1c68547..a82916f31 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -672,14 +672,13 @@ func TestTaskSort(t *testing.T) { assertTestSliceMatch := func(t *testing.T, got, want []*Task) { if !reflect.DeepEqual(got, want) { t.Error("Slices do not match in order") - t.Error("Got:") - for _, task := range got { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) - } - - t.Error("Want:") - for _, task := range want { - t.Errorf(" - Task ID %d (%s)", task.ID, task.Text) + t.Error("Got\t| Want") + for in, task := range got { + fail := "" + if task.ID != want[in].ID { + fail = "wrong" + } + t.Errorf("\t%d\t| %d \t%s", task.ID, want[in].ID, fail) } } } -- 2.40.1 From bf69ab664de693c8e2b6b10dd8bfe289829fad94 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:07:52 +0100 Subject: [PATCH 50/60] Make all test names lowercase --- pkg/models/task_collection_sort_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index a82916f31..7bc58b942 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -190,7 +190,7 @@ type taskSortTestCase struct { var taskSortTestCases = []taskSortTestCase{ { - name: "ID", + name: "id", sortProperty: taskPropertyID, wantAsc: []*Task{ task1, @@ -218,7 +218,7 @@ var taskSortTestCases = []taskSortTestCase{ }, }, { - name: "Text", + name: "text", sortProperty: taskPropertyText, wantAsc: []*Task{ task9, -- 2.40.1 From 1806a7235108e5cfe9790133ac49892e575600fa Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:16:45 +0100 Subject: [PATCH 51/60] Swagger docs for sorting --- pkg/models/task_collection.go | 27 +++----------------------- pkg/swagger/docs.go | 35 +++++++++++++++++----------------- pkg/swagger/swagger.json | 33 ++++++++++++++++---------------- pkg/swagger/swagger.yaml | 36 +++++++++++++++++++---------------- 4 files changed, 56 insertions(+), 75 deletions(-) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 04fe75a27..0c5cdad19 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -38,13 +38,8 @@ type TaskCollection struct { web.Rights `xorm:"-" json:"-"` } -// TODO: Docs -// TODO: Swagger -// TODO: Tests -// TODO: Sorting - // ReadAll gets all tasks for a collection -// @Summary Get tasks on a list +// @Summary Get tasks in a list // @Description Returns all tasks for the current list. // @tags task // @Accept json @@ -53,7 +48,8 @@ type TaskCollection struct { // @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." // @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search tasks by task text." -// @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc." +// @Param sort_by query string false "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `text`, `description`, `done`, `done_at_unix`, `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`." +// @Param order_by query string false "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`." // @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time." // @Param enddate query int false "The end date parameter to filter by. Expects a unix timestamp. If no start date, but an end date is specified, the start date is set to the current time." // @Security JWTKeyAuth @@ -61,23 +57,6 @@ type TaskCollection struct { // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{listID}/tasks [get] func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { - //var sortby SortBy - //switch tf.Sorting { - //case "priority": - // sortby = SortTasksByPriorityDesc - //case "prioritydesc": - // sortby = SortTasksByPriorityDesc - //case "priorityasc": - // sortby = SortTasksByPriorityAsc - //case "duedate": - // sortby = SortTasksByDueDateDesc - //case "duedatedesc": - // sortby = SortTasksByDueDateDesc - //case "duedateasc": - // sortby = SortTasksByDueDateAsc - //default: - // sortby = SortTasksByUnsorted - //} var sort = make([]*sortParam, 0, len(tf.SortBy)) for i, s := range tf.SortBy { diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 5d7f0b662..76873197e 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2019-12-01 13:20:36.822585642 +0100 CET m=+0.129079038 +// 2019-12-05 22:15:49.761451764 +0100 CET m=+0.171539379 package swagger @@ -407,7 +407,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -415,13 +415,13 @@ var doc = `{ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -429,13 +429,13 @@ var doc = `{ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "$ref": "#/definitions/code.vikunja.io.web.HTTPError" } @@ -984,7 +984,7 @@ var doc = `{ "tags": [ "task" ], - "summary": "Get tasks on a list", + "summary": "Get tasks in a list", "parameters": [ { "type": "integer", @@ -1013,8 +1013,14 @@ var doc = `{ }, { "type": "string", - "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", - "name": "sort", + "description": "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with ` + "`" + `order_by` + "`" + `. Possible values to sort by are ` + "`" + `id` + "`" + `, ` + "`" + `text` + "`" + `, ` + "`" + `description` + "`" + `, ` + "`" + `done` + "`" + `, ` + "`" + `done_at_unix` + "`" + `, ` + "`" + `due_date_unix` + "`" + `, ` + "`" + `created_by_id` + "`" + `, ` + "`" + `list_id` + "`" + `, ` + "`" + `repeat_after` + "`" + `, ` + "`" + `priority` + "`" + `, ` + "`" + `start_date_unix` + "`" + `, ` + "`" + `end_date_unix` + "`" + `, ` + "`" + `hex_color` + "`" + `, ` + "`" + `percent_done` + "`" + `, ` + "`" + `uid` + "`" + `, ` + "`" + `created` + "`" + `, ` + "`" + `updated` + "`" + `. Default is ` + "`" + `id` + "`" + `.", + "name": "sort_by", + "in": "query" + }, + { + "type": "string", + "description": "The ordering parameter. Possible values to order by are ` + "`" + `asc` + "`" + ` or ` + "`" + `desc` + "`" + `. Default is ` + "`" + `asc` + "`" + `.", + "name": "order_by", "in": "query" }, { @@ -4633,13 +4639,6 @@ var doc = `{ "type": "object", "$ref": "#/definitions/models.User" }, - "tasks": { - "description": "An array of tasks which belong to the list.", - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - }, "title": { "description": "The title of the list. You'll see this in the namespace overview.", "type": "string", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index af8c048ee..a3c54a4d2 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -389,7 +389,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns a list by its ID.", + "description": "Returns a team by its ID.", "consumes": [ "application/json" ], @@ -397,13 +397,13 @@ "application/json" ], "tags": [ - "list" + "team" ], - "summary": "Gets one list", + "summary": "Gets one team", "parameters": [ { "type": "integer", - "description": "List ID", + "description": "Team ID", "name": "id", "in": "path", "required": true @@ -411,13 +411,13 @@ ], "responses": { "200": { - "description": "The list", + "description": "The team", "schema": { - "$ref": "#/definitions/models.List" + "$ref": "#/definitions/models.Team" } }, "403": { - "description": "The user does not have access to the list", + "description": "The user does not have access to the team", "schema": { "$ref": "#/definitions/code.vikunja.io/web.HTTPError" } @@ -966,7 +966,7 @@ "tags": [ "task" ], - "summary": "Get tasks on a list", + "summary": "Get tasks in a list", "parameters": [ { "type": "integer", @@ -995,8 +995,14 @@ }, { "type": "string", - "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", - "name": "sort", + "description": "The sorting parameter. You can pass this multiple times to get the tasks ordered by multiple different parametes, along with `order_by`. Possible values to sort by are `id`, `text`, `description`, `done`, `done_at_unix`, `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, `created`, `updated`. Default is `id`.", + "name": "sort_by", + "in": "query" + }, + { + "type": "string", + "description": "The ordering parameter. Possible values to order by are `asc` or `desc`. Default is `asc`.", + "name": "order_by", "in": "query" }, { @@ -4614,13 +4620,6 @@ "type": "object", "$ref": "#/definitions/models.User" }, - "tasks": { - "description": "An array of tasks which belong to the list.", - "type": "array", - "items": { - "$ref": "#/definitions/models.Task" - } - }, "title": { "description": "The title of the list. You'll see this in the namespace overview.", "type": "string", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 5c7ea2c8d..b4c9d3146 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -236,11 +236,6 @@ definitions: $ref: '#/definitions/models.User' description: The user who created this list. type: object - tasks: - description: An array of tasks which belong to the list. - items: - $ref: '#/definitions/models.Task' - type: array title: description: The title of the list. You'll see this in the namespace overview. maxLength: 250 @@ -1138,9 +1133,9 @@ paths: get: consumes: - application/json - description: Returns a list by its ID. + description: Returns a team by its ID. parameters: - - description: List ID + - description: Team ID in: path name: id required: true @@ -1149,11 +1144,11 @@ paths: - application/json responses: "200": - description: The list + description: The team schema: - $ref: '#/definitions/models.List' + $ref: '#/definitions/models.Team' "403": - description: The user does not have access to the list + description: The user does not have access to the team schema: $ref: '#/definitions/code.vikunja.io/web.HTTPError' "500": @@ -1162,9 +1157,9 @@ paths: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Gets one list + summary: Gets one team tags: - - list + - team post: consumes: - application/json @@ -1667,10 +1662,19 @@ paths: in: query name: s type: string - - description: The sorting parameter. Possible values to sort by are priority, - prioritydesc, priorityasc, duedate, duedatedesc, duedateasc. + - description: The sorting parameter. You can pass this multiple times to get + the tasks ordered by multiple different parametes, along with `order_by`. + Possible values to sort by are `id`, `text`, `description`, `done`, `done_at_unix`, + `due_date_unix`, `created_by_id`, `list_id`, `repeat_after`, `priority`, + `start_date_unix`, `end_date_unix`, `hex_color`, `percent_done`, `uid`, + `created`, `updated`. Default is `id`. in: query - name: sort + name: sort_by + type: string + - description: The ordering parameter. Possible values to order by are `asc` + or `desc`. Default is `asc`. + in: query + name: order_by type: string - description: The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the @@ -1699,7 +1703,7 @@ paths: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Get tasks on a list + summary: Get tasks in a list tags: - task /lists/{listID}/teams/{teamID}: -- 2.40.1 From 60195a16a53037102b0fdda05bfcc47a274481a0 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:19:08 +0100 Subject: [PATCH 52/60] Add error code docs --- docs/content/doc/usage/errors.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/doc/usage/errors.md b/docs/content/doc/usage/errors.md index 6d9267845..cc89e1b4a 100644 --- a/docs/content/doc/usage/errors.md +++ b/docs/content/doc/usage/errors.md @@ -45,6 +45,8 @@ This document describes the different errors Vikunja can return. | 4010 | 400 | Cannot relate a task with itself. | | 4011 | 404 | The task attachment does not exist. | | 4012 | 400 | The task attachment is too large. | +| 4013 | 400 | The task sort param is invalid. | +| 4014 | 400 | The task sort order is invalid. | | 5001 | 404 | The namspace does not exist. | | 5003 | 403 | The user does not have access to the specified namespace. | | 5006 | 400 | The namespace name cannot be empty. | -- 2.40.1 From 938856dcb101c4145a1970dcfd409c5dc8091393 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:24:41 +0100 Subject: [PATCH 53/60] Move ID property to the top --- pkg/models/task_collection_sort.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index cf20ea61f..0f786e090 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -161,6 +161,7 @@ func mustMakeComparator(fieldName string) taskComparator { // and their appropriate comparator function. // The comparator function sorts in ascending mode. var propertyComparators = map[sortProperty]taskComparator{ + taskPropertyID: mustMakeComparator("ID"), taskPropertyText: mustMakeComparator("Text"), taskPropertyDescription: mustMakeComparator("Description"), taskPropertyDone: mustMakeComparator("Done"), @@ -177,7 +178,6 @@ var propertyComparators = map[sortProperty]taskComparator{ taskPropertyUID: mustMakeComparator("UID"), taskPropertyCreated: mustMakeComparator("Created"), taskPropertyUpdated: mustMakeComparator("Updated"), - taskPropertyID: mustMakeComparator("ID"), } // Creates a taskComparator that sorts by the first comparator and falls back to -- 2.40.1 From aed248c63f232c6ef50a06961f8890deb57600a7 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:25:05 +0100 Subject: [PATCH 54/60] Validate the sort params right before doing anything else with them --- pkg/models/task_collection.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/models/task_collection.go b/pkg/models/task_collection.go index 0c5cdad19..16cc1062e 100644 --- a/pkg/models/task_collection.go +++ b/pkg/models/task_collection.go @@ -60,16 +60,20 @@ func (tf *TaskCollection) ReadAll(a web.Auth, search string, page int, perPage i var sort = make([]*sortParam, 0, len(tf.SortBy)) for i, s := range tf.SortBy { - sortParam := &sortParam{ + param := &sortParam{ sortBy: sortProperty(s), orderBy: orderAscending, } // This checks if tf.OrderBy has an entry with the same index as the current entry from tf.SortBy // Taken from https://stackoverflow.com/a/27252199/10924593 if len(tf.OrderBy) > i { - sortParam.orderBy = getSortOrderFromString(tf.OrderBy[i]) + param.orderBy = getSortOrderFromString(tf.OrderBy[i]) } - sort = append(sort, sortParam) + // Param validation + if err := param.validate(); err != nil { + return nil, 0, 0, err + } + sort = append(sort, param) } taskopts := &taskOptions{ -- 2.40.1 From b8c2f0af2dfd731569e536a4be7796ddd6cc5df0 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 5 Dec 2019 22:25:14 +0100 Subject: [PATCH 55/60] Cleanup --- pkg/models/tasks.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 90a35693b..a4c9d4287 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -105,18 +105,6 @@ func (TaskReminder) TableName() string { return "task_reminders" } -// SortBy declares constants to sort -type SortBy int - -// These are possible sort options -const ( - SortTasksByUnsorted SortBy = -1 - SortTasksByDueDateAsc = iota - SortTasksByDueDateDesc - SortTasksByPriorityAsc - SortTasksByPriorityDesc -) - type taskOptions struct { search string startDate time.Time @@ -154,18 +142,6 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T listIDs = append(listIDs, l.ID) } - //var orderby string - //switch opts.sortby { - //case SortTasksByPriorityDesc: - // orderby = "priority desc" - //case SortTasksByPriorityAsc: - // orderby = "priority asc" - //case SortTasksByDueDateDesc: - // orderby = "due_date_unix desc" - //case SortTasksByDueDateAsc: - // orderby = "due_date_unix asc" - //} - // Since xorm does not use placeholders for order by, it is possible to expose this with sql injection if we're directly // passing user input to the db. // As a workaround to prevent this, we check for valid column names here prior to passing it to the db. -- 2.40.1 From fd83f1a888628699d01df76633ec915c7df60c76 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 14:24:29 +0100 Subject: [PATCH 56/60] Always sort at least by ID to have results when items are the same --- pkg/models/task_collection_sort.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 0f786e090..5fc7ba53e 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -196,10 +196,10 @@ func combineComparators(comparators ...taskComparator) taskComparator { func sortTasks(tasks []*Task, by []*sortParam) { - // Sort by id asc by default if no param was provided - if len(by) == 0 { - by = []*sortParam{{sortBy: taskPropertyID, orderBy: orderAscending}} - } + // Always sort at least by id asc so we have a consistent order of items every time + // If we would not do this, we would get a different order for items with the same content every time + // the slice is sorted. To circumvent this, we always order at least by ID. + by = append(by, &sortParam{sortBy: taskPropertyID, orderBy: orderAscending}) comparators := make([]taskComparator, 0, len(by)) for _, param := range by { -- 2.40.1 From 23808d6fad08a6803567e064d908f955f4f7ac57 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 14:49:29 +0100 Subject: [PATCH 57/60] Fix tests --- pkg/models/task_collection_sort_test.go | 334 ++++++++++++------------ 1 file changed, 169 insertions(+), 165 deletions(-) diff --git a/pkg/models/task_collection_sort_test.go b/pkg/models/task_collection_sort_test.go index 7bc58b942..d009e99c5 100644 --- a/pkg/models/task_collection_sort_test.go +++ b/pkg/models/task_collection_sort_test.go @@ -227,8 +227,8 @@ var taskSortTestCases = []taskSortTestCase{ task2, task3, task4, - task6, task5, + task6, task7, task10, }, @@ -249,13 +249,13 @@ var taskSortTestCases = []taskSortTestCase{ name: "description", sortProperty: taskPropertyDescription, wantAsc: []*Task{ - task4, task3, - task6, + task4, task5, - task10, - task9, + task6, task8, + task9, + task10, task2, task1, task7, @@ -264,69 +264,73 @@ var taskSortTestCases = []taskSortTestCase{ task7, task1, task2, - task4, task3, - task8, - task6, + task4, task5, - task10, + task6, + task8, task9, + task10, }, }, { name: "done", sortProperty: taskPropertyDone, wantAsc: []*Task{ - task4, - task6, - task5, - task10, - task3, - task8, - task7, - task9, - task2, + // These are done task1, + task2, + task9, + // These are not + task3, + task4, + task5, + task6, + task7, + task8, + task10, }, wantDesc: []*Task{ - task9, - task2, - task1, - task4, + // These are not task3, - task8, - task7, - task6, + task4, task5, + task6, + task7, + task8, task10, + // These are done + task1, + task2, + task9, }, }, { name: "done at", sortProperty: taskPropertyDoneAtUnix, wantAsc: []*Task{ - task10, - task8, + task3, + task4, task5, task6, - task4, - task3, task7, + task8, + task10, task1, task2, task9, }, wantDesc: []*Task{ - task10, - task8, + task9, + task2, + task1, + task3, + task4, task5, task6, - task4, - task3, task7, - task1, - task2, - task9, + task8, + task10, }, }, { @@ -345,6 +349,9 @@ var taskSortTestCases = []taskSortTestCase{ task3, }, wantDesc: []*Task{ + task3, + task5, + task6, task1, task2, task4, @@ -352,65 +359,62 @@ var taskSortTestCases = []taskSortTestCase{ task8, task9, task10, - task6, - task5, - task3, }, }, { name: "created by id", sortProperty: taskPropertyCreatedByID, wantAsc: []*Task{ + task1, + task3, + task4, task5, task7, task8, - task10, - task4, - task3, - task1, task9, + task10, task2, task6, }, wantDesc: []*Task{ + task6, + task2, + task1, + task3, + task4, task5, task7, task8, - task10, - task4, - task3, - task1, task9, - task2, - task6, + task10, }, }, { name: "list id", sortProperty: taskPropertyListID, wantAsc: []*Task{ - task6, - task9, - task8, - task7, task5, + task6, + task7, + task8, + task9, task10, - task4, task1, + task4, task2, task3, }, wantDesc: []*Task{ - task6, - task9, - task8, - task7, - task5, - task10, - task4, - task1, - task2, task3, + task2, + task1, + task4, + task5, + task6, + task7, + task8, + task9, + task10, }, }, { @@ -418,251 +422,251 @@ var taskSortTestCases = []taskSortTestCase{ sortProperty: taskPropertyRepeatAfter, wantAsc: []*Task{ task1, + task2, + task3, task4, - task10, task5, task7, task8, - task3, - task2, + task10, task6, task9, }, wantDesc: []*Task{ + task9, + task6, task1, + task2, + task3, task4, - task10, task5, task7, task8, - task3, - task2, - task6, - task9, + task10, }, }, { name: "priority", sortProperty: taskPropertyPriority, wantAsc: []*Task{ - task8, - task7, - task2, task1, - task9, + task2, task6, + task7, + task8, + task9, task4, task10, task5, task3, }, wantDesc: []*Task{ - task8, - task7, - task2, - task1, - task9, - task6, - task4, - task10, - task5, task3, + task5, + task10, + task4, + task1, + task2, + task6, + task7, + task8, + task9, }, }, { name: "start date", sortProperty: taskPropertyStartDateUnix, wantAsc: []*Task{ - task3, - task8, - task6, task1, + task3, task5, + task6, + task8, task10, task2, - task9, task7, + task9, task4, }, wantDesc: []*Task{ - task3, - task8, - task6, - task1, - task5, - task10, - task2, - task9, - task7, task4, + task7, + task9, + task2, + task1, + task3, + task5, + task6, + task8, + task10, }, }, { name: "end date", sortProperty: taskPropertyEndDateUnix, wantAsc: []*Task{ + task1, task2, task3, - task10, - task5, - task1, - task6, task4, + task5, + task6, + task10, task8, task9, task7, }, wantDesc: []*Task{ - task2, - task3, - task10, - task5, - task1, - task6, - task4, + task7, task8, task9, - task7, + task1, + task2, + task3, + task4, + task5, + task6, + task10, }, }, { name: "hex color", sortProperty: taskPropertyHexColor, wantAsc: []*Task{ - task8, - task5, task1, - task4, - task10, - task7, task2, + task4, + task5, + task7, + task8, task9, + task10, task3, task6, }, wantDesc: []*Task{ - task8, - task5, - task1, - task4, - task10, - task7, - task2, - task9, - task3, task6, + task3, + task1, + task2, + task4, + task5, + task7, + task8, + task9, + task10, }, }, { name: "percent done", sortProperty: taskPropertyPercentDone, wantAsc: []*Task{ - task4, - task9, task1, + task4, task5, - task8, task6, task7, + task8, + task9, task3, task2, task10, }, wantDesc: []*Task{ - task4, - task9, + task10, + task2, + task3, task1, + task4, task5, - task8, task6, task7, - task3, - task2, - task10, + task8, + task9, }, }, { name: "uid", sortProperty: taskPropertyUID, wantAsc: []*Task{ + task2, task3, + task4, task6, task8, task9, task10, - task2, - task4, task1, task5, task7, }, wantDesc: []*Task{ + task7, + task5, + task1, + task2, task3, + task4, task6, task8, task9, task10, - task2, - task4, - task1, - task5, - task7, }, }, { name: "created", sortProperty: taskPropertyCreated, wantAsc: []*Task{ + task3, + task4, + task5, + task6, task7, + task8, task9, task10, - task4, - task8, - task6, - task3, - task5, task1, task2, }, wantDesc: []*Task{ + task2, + task1, + task3, + task4, + task5, + task6, task7, + task8, task9, task10, - task4, - task8, - task6, - task3, - task5, - task1, - task2, }, }, { name: "updated", sortProperty: taskPropertyUpdated, wantAsc: []*Task{ - task6, - task8, task4, - task10, - task9, + task6, task7, + task8, + task9, + task10, task1, task2, task3, task5, }, wantDesc: []*Task{ - task6, - task8, - task4, - task10, - task9, - task7, - task1, - task2, - task3, task5, + task3, + task2, + task1, + task4, + task6, + task7, + task8, + task9, + task10, }, }, } @@ -789,8 +793,8 @@ func TestTaskSort(t *testing.T) { task8, task3, task4, - task6, task5, + task6, task7, task10, -- 2.40.1 From 143e7f7f968e8988151f7183c1054e1be9db97ed Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 15:00:38 +0100 Subject: [PATCH 58/60] Add check to not add the id sort param last if it was already added --- pkg/models/task_collection_sort.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/models/task_collection_sort.go b/pkg/models/task_collection_sort.go index 5fc7ba53e..f2cae7b7c 100644 --- a/pkg/models/task_collection_sort.go +++ b/pkg/models/task_collection_sort.go @@ -199,7 +199,10 @@ func sortTasks(tasks []*Task, by []*sortParam) { // Always sort at least by id asc so we have a consistent order of items every time // If we would not do this, we would get a different order for items with the same content every time // the slice is sorted. To circumvent this, we always order at least by ID. - by = append(by, &sortParam{sortBy: taskPropertyID, orderBy: orderAscending}) + if len(by) == 0 || + (len(by) > 0 && by[len(by)-1].sortBy != taskPropertyID) { // Don't sort by ID last if the id parameter is already passed as the last parameter. + by = append(by, &sortParam{sortBy: taskPropertyID, orderBy: orderAscending}) + } comparators := make([]taskComparator, 0, len(by)) for _, param := range by { -- 2.40.1 From c35eaf695080e3f263e4f2017ac8fc299e499004 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 15:00:52 +0100 Subject: [PATCH 59/60] Add test for sorting with fixtures --- pkg/models/task_collection_test.go | 205 +++++++---------------------- 1 file changed, 47 insertions(+), 158 deletions(-) diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index b3e66f113..effe5e5ee 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -512,6 +512,53 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, wantErr: false, }, + { + // For more sorting tests see task_collection_sort_test.go + name: "ReadAll Tasks sorted by done asc and id desc", + fields: fields{ + SortBy: []string{"done", "id"}, + OrderBy: []string{"asc", "desc"}, + }, + args: args{ + search: "", + a: &User{ID: 1}, + page: 0, + }, + want: []*Task{ + task2, + task33, + task32, + task31, + task30, + task29, + task28, + task27, + task26, + task25, + task24, + task23, + task22, + task21, + task20, + task19, + task18, + task17, + task16, + task15, + task12, + task11, + task10, + task9, + task8, + task7, + task6, + task5, + task4, + task3, + task1, + }, + wantErr: false, + }, { name: "ReadAll Tasks with range", fields: fields{ @@ -564,164 +611,6 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, } - //sortByFields := []sortProperty{ - // taskPropertyID, - // taskPropertyText, - // taskPropertyDescription, - // taskPropertyDone, - // taskPropertyDoneAtUnix, - // taskPropertyDueDateUnix, - // taskPropertyCreatedByID, - // taskPropertyListID, - // taskPropertyRepeatAfter, - // taskPropertyPriority, - // taskPropertyStartDateUnix, - // taskPropertyEndDateUnix, - // taskPropertyHexColor, - // taskPropertyPercentDone, - // taskPropertyUID, - // taskPropertyCreated, - // taskPropertyUpdated, - //} - //// Add more cases programatically - //// Simple cases - //for _, test := range sortByFields { - // tests = append(tests, []testcase{ - // { - // name: "ReadAll Tasks sorted by " + test.String() + " default asc", - // fields: fields{ - // SortBy: []string{test.String()}, - // }, - // args: args{ - // search: "", - // a: &User{ID: 1}, - // page: 0, - // }, - // want: sortTasksForTesting([]*sortParam{ - // { - // sortBy: test, - // orderBy: orderAscending, - // }, - // }), - // wantErr: false, - // }, - // { - // name: "ReadAll Tasks sorted by " + test.String() + " asc", - // fields: fields{ - // SortBy: []string{test.String()}, - // }, - // args: args{ - // search: "", - // a: &User{ID: 1}, - // page: 0, - // }, - // want: sortTasksForTesting([]*sortParam{ - // { - // sortBy: test, - // orderBy: orderAscending, - // }, - // }), - // wantErr: false, - // }, - // { - // name: "ReadAll Tasks sorted by " + test.String() + " desc", - // fields: fields{ - // SortBy: []string{test.String()}, - // OrderBy: []string{orderDescending.String()}, - // }, - // args: args{ - // search: "", - // a: &User{ID: 1}, - // page: 0, - // }, - // want: sortTasksForTesting([]*sortParam{ - // { - // sortBy: test, - // orderBy: orderDescending, - // }, - // }), - // wantErr: false, - // }, - // }...) - //} - // - //// Cases with two parameters - //for _, outerTest := range sortByFields { - // // We don't test all cases with everything here because this would mean an enormous amount of test cases - // for _, test := range []sortProperty{"text", "done"} { - // tests = append(tests, []testcase{ - // { - // name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + "default asc", - // fields: fields{ - // SortBy: []string{outerTest.String(), test.String()}, - // }, - // args: args{ - // search: "", - // a: &User{ID: 1}, - // page: 0, - // }, - // want: sortTasksForTesting([]*sortParam{ - // { - // sortBy: outerTest, - // orderBy: orderAscending, - // }, - // { - // sortBy: test, - // orderBy: orderAscending, - // }, - // }), - // wantErr: false, - // }, - // { - // name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " asc", - // fields: fields{ - // SortBy: []string{outerTest.String(), test.String()}, - // OrderBy: []string{orderAscending.String(), orderAscending.String()}, - // }, - // args: args{ - // search: "", - // a: &User{ID: 1}, - // page: 0, - // }, - // want: sortTasksForTesting([]*sortParam{ - // { - // sortBy: outerTest, - // orderBy: orderAscending, - // }, - // { - // sortBy: test, - // orderBy: orderAscending, - // }, - // }), - // wantErr: false, - // }, - // { - // name: "ReadAll Tasks sorted by " + outerTest.String() + " and " + test.String() + " desc", - // fields: fields{ - // SortBy: []string{outerTest.String(), test.String()}, - // OrderBy: []string{orderDescending.String(), orderDescending.String()}, - // }, - // args: args{ - // search: "", - // a: &User{ID: 1}, - // page: 0, - // }, - // want: sortTasksForTesting([]*sortParam{ - // { - // sortBy: outerTest, - // orderBy: orderDescending, - // }, - // { - // sortBy: test, - // orderBy: orderDescending, - // }, - // }), - // wantErr: false, - // }, - // }...) - // } - //} - for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lt := &TaskCollection{ -- 2.40.1 From 61fddb0af190b0c1d7862380e7e1267b8e71463d Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 15:11:38 +0100 Subject: [PATCH 60/60] Add integration test cases for invalid sort parameters --- pkg/integrations/task_collection_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 92b513833..e9f3e497d 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -91,7 +91,6 @@ func TestTaskCollection(t *testing.T) { }) t.Run("Sort Order", func(t *testing.T) { // TODO: Add more cases - // TODO: Add invalid test cases (one should be enough, we have the unit test of the validation method for the rest) // should equal priority asc t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams) @@ -124,6 +123,16 @@ func TestTaskCollection(t *testing.T) { assert.NoError(t, err) assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) + t.Run("invalid sort parameter", func(t *testing.T) { + _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams) + assert.Error(t, err) + assertHandlerErrorCode(t, err, models.ErrCodeInvalidSortParam) + }) + t.Run("invalid sort order", func(t *testing.T) { + _, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"id"}, "order_by": []string{"loremipsum"}}, urlParams) + assert.Error(t, err) + assertHandlerErrorCode(t, err, models.ErrCodeInvalidSortOrder) + }) t.Run("invalid parameter", func(t *testing.T) { // Invalid parameter should not sort at all rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, urlParams) -- 2.40.1