From 618ca775b9e427c7d5f53902d8ad7d082928cecc Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 20:42:54 +0100 Subject: [PATCH 01/14] Add list identifiers --- pkg/models/list.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/models/list.go b/pkg/models/list.go index 7f52a6dcf..8ffd1a07c 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -29,8 +29,11 @@ type List struct { Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(3|250)" minLength:"3" maxLength:"250"` // The description of the list. Description string `xorm:"longtext null" json:"description"` - OwnerID int64 `xorm:"int(11) INDEX not null" json:"-"` - NamespaceID int64 `xorm:"int(11) INDEX not null" json:"-" param:"namespace"` + // The unique list short identifier. Used to build task identifiers. + Identifier string `xorm:"varchar(10) null" json:"identifier"` + + OwnerID int64 `xorm:"int(11) INDEX not null" json:"-"` + NamespaceID int64 `xorm:"int(11) INDEX not null" json:"-" param:"namespace"` // The user who created this list. Owner *User `xorm:"-" json:"owner" valid:"-"` -- 2.40.1 From 840dd567eb268dc924e3ec03c3189bfbe43d5573 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 21:13:22 +0100 Subject: [PATCH 02/14] Add list identifiers --- pkg/migration/20191207204427.go | 44 +++++++++++++++++++++++++++++++++ pkg/models/error.go | 23 +++++++++++++++++ pkg/models/fixtures/list.yml | 1 + pkg/models/list.go | 11 +++++++++ 4 files changed, 79 insertions(+) create mode 100644 pkg/migration/20191207204427.go diff --git a/pkg/migration/20191207204427.go b/pkg/migration/20191207204427.go new file mode 100644 index 000000000..e19187b96 --- /dev/null +++ b/pkg/migration/20191207204427.go @@ -0,0 +1,44 @@ +// Copyright 2019 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja 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. +// +// Vikunja 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 Vikunja. If not, see . + +package migration + +import ( + "github.com/go-xorm/xorm" + "src.techknowlogick.com/xormigrate" +) + +type list20191207204427 struct { + Identifier string `xorm:"varchar(10) null" json:"identifier"` +} + +func (list20191207204427) TableName() string { + return "list" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20191207204427", + Description: "Add task identifier to list", + Migrate: func(tx *xorm.Engine) error { + return tx.Sync2(list20191207204427{}) + }, + Rollback: func(tx *xorm.Engine) error { + return tx.DropTables(list20191207204427{}) + }, + }) +} diff --git a/pkg/models/error.go b/pkg/models/error.go index 0af2b978b..0acb6cbb0 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -471,6 +471,29 @@ func (err ErrListShareDoesNotExist) HTTPError() web.HTTPError { return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListShareDoesNotExist, Message: "The list share does not exist."} } +// ErrListIdentifierIsNotUnique represents a "ErrListIdentifierIsNotUnique" kind of error. Used if the provided list identifier is not unique. +type ErrListIdentifierIsNotUnique struct { + Identifier string +} + +// IsErrListIdentifierIsNotUnique checks if an error is a ErrListIdentifierIsNotUnique. +func IsErrListIdentifierIsNotUnique(err error) bool { + _, ok := err.(ErrListIdentifierIsNotUnique) + return ok +} + +func (err ErrListIdentifierIsNotUnique) Error() string { + return fmt.Sprintf("List identifier is not unique.") +} + +// ErrCodeListIdentifierIsNotUnique holds the unique world-error code of this error +const ErrCodeListIdentifierIsNotUnique = 3007 + +// HTTPError holds the http error description +func (err ErrListIdentifierIsNotUnique) HTTPError() web.HTTPError { + return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListIdentifierIsNotUnique, Message: "There already exists a list with this identifier."} +} + // ================ // List task errors // ================ diff --git a/pkg/models/fixtures/list.yml b/pkg/models/fixtures/list.yml index a86aa6084..209fe515d 100644 --- a/pkg/models/fixtures/list.yml +++ b/pkg/models/fixtures/list.yml @@ -2,6 +2,7 @@ id: 1 title: Test1 description: Lorem Ipsum + identifier: test owner_id: 1 namespace_id: 1 updated: 0 diff --git a/pkg/models/list.go b/pkg/models/list.go index 8ffd1a07c..901865f2e 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -268,6 +268,17 @@ func CreateOrUpdateList(list *List) (err error) { } } + // Check if the identifier is unique and not empty + if list.Identifier != "" { + exists, err := x.Where("identifier = ?", list.Identifier).Exist(&List{}) + if err != nil { + return err + } + if exists { + return ErrListIdentifierIsNotUnique{Identifier: list.Identifier} + } + } + if list.ID == 0 { _, err = x.Insert(list) metrics.UpdateCount(1, metrics.ListCountKey) -- 2.40.1 From 4aea6ce720a84f463aaa87c32af53b22f36df7d6 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 21:13:57 +0100 Subject: [PATCH 03/14] Refactor tests for lists Add test for list identifier --- pkg/models/list_create_test.go | 97 -------------------- pkg/models/list_read_test.go | 50 ----------- pkg/models/list_test.go | 158 +++++++++++++++++++++++++++++++++ pkg/models/unit_tests.go | 8 ++ 4 files changed, 166 insertions(+), 147 deletions(-) delete mode 100644 pkg/models/list_create_test.go delete mode 100644 pkg/models/list_read_test.go create mode 100644 pkg/models/list_test.go diff --git a/pkg/models/list_create_test.go b/pkg/models/list_create_test.go deleted file mode 100644 index 230265911..000000000 --- a/pkg/models/list_create_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-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 TestList_Create(t *testing.T) { - // Create test database - //assert.NoError(t, LoadFixtures()) - - // Get our doer - doer, err := GetUserByID(1) - assert.NoError(t, err) - - // Dummy list for testing - dummylist := List{ - Title: "test", - Description: "Lorem Ipsum", - NamespaceID: 1, - } - - // Check if the user can create - allowed, _ := dummylist.CanCreate(doer) - assert.True(t, allowed) - - // Create it - err = dummylist.Create(doer) - assert.NoError(t, err) - - // Get the list - newdummy := List{ID: dummylist.ID} - canRead, err := newdummy.CanRead(doer) - assert.NoError(t, err) - assert.True(t, canRead) - err = newdummy.ReadOne() - assert.NoError(t, err) - assert.Equal(t, dummylist.Title, newdummy.Title) - assert.Equal(t, dummylist.Description, newdummy.Description) - assert.Equal(t, dummylist.OwnerID, doer.ID) - - // Check if the user can see it - allowed, _ = dummylist.CanRead(doer) - assert.True(t, allowed) - - // Try updating a list - allowed, _ = dummylist.CanUpdate(doer) - assert.True(t, allowed) - dummylist.Description = "Lorem Ipsum dolor sit amet." - err = dummylist.Update() - assert.NoError(t, err) - - // Delete it - allowed, _ = dummylist.CanDelete(doer) - assert.True(t, allowed) - - err = dummylist.Delete() - assert.NoError(t, err) - - // Try updating a nonexistant list - err = dummylist.Update() - assert.Error(t, err) - assert.True(t, IsErrListDoesNotExist(err)) - - // Check creation with a nonexistant namespace - list3 := List{ - Title: "test", - Description: "Lorem Ipsum", - NamespaceID: 876694, - } - - err = list3.Create(doer) - assert.Error(t, err) - assert.True(t, IsErrNamespaceDoesNotExist(err)) - - // Try creating with a nonexistant owner - nUser := &User{ID: 9482385} - err = dummylist.Create(nUser) - assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) -} diff --git a/pkg/models/list_read_test.go b/pkg/models/list_read_test.go deleted file mode 100644 index 359a52684..000000000 --- a/pkg/models/list_read_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Vikunja is a todo-list application to facilitate your life. -// Copyright 2018-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" - "reflect" - "testing" -) - -func TestList_ReadAll(t *testing.T) { - // Create test database - //assert.NoError(t, LoadFixtures()) - - // Get all lists for our namespace - lists, err := GetListsByNamespaceID(1, &User{}) - assert.NoError(t, err) - assert.Equal(t, len(lists), 2) - - // Get all lists our user has access to - u, err := GetUserByID(1) - assert.NoError(t, err) - - lists2 := List{} - lists3, _, _, err := lists2.ReadAll(u, "", 1, 50) - - assert.NoError(t, err) - assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) - s := reflect.ValueOf(lists3) - assert.Equal(t, 16, s.Len()) - - // Try getting lists for a nonexistant user - _, _, _, err = lists2.ReadAll(&User{ID: 984234}, "", 1, 50) - assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) -} diff --git a/pkg/models/list_test.go b/pkg/models/list_test.go new file mode 100644 index 000000000..dd6cdef32 --- /dev/null +++ b/pkg/models/list_test.go @@ -0,0 +1,158 @@ +// Vikunja is a todo-list application to facilitate your life. +// Copyright 2018-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" + "reflect" + "testing" +) + +func TestList_CreateOrUpdate(t *testing.T) { + user := &User{ + ID: 1, + Username: "user1", + Email: "user1@example.com", + } + + t.Run("create", func(t *testing.T) { + t.Run("normal", func(t *testing.T) { + initFixtures(t) + list := List{ + Title: "test", + Description: "Lorem Ipsum", + NamespaceID: 1, + } + err := list.Create(user) + assert.NoError(t, err) + }) + t.Run("nonexistant namespace", func(t *testing.T) { + initFixtures(t) + list := List{ + Title: "test", + Description: "Lorem Ipsum", + NamespaceID: 999999, + } + + err := list.Create(user) + assert.Error(t, err) + assert.True(t, IsErrNamespaceDoesNotExist(err)) + }) + t.Run("nonexistant owner", func(t *testing.T) { + initFixtures(t) + user := &User{ID: 9482385} + list := List{ + Title: "test", + Description: "Lorem Ipsum", + NamespaceID: 1, + } + err := list.Create(user) + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) + t.Run("existing identifier", func(t *testing.T) { + initFixtures(t) + list := List{ + Title: "test", + Description: "Lorem Ipsum", + Identifier: "test", + NamespaceID: 1, + } + + err := list.Create(user) + assert.Error(t, err) + assert.True(t, IsErrListIdentifierIsNotUnique(err)) + }) + }) + + t.Run("update", func(t *testing.T) { + t.Run("normal", func(t *testing.T) { + initFixtures(t) + list := List{ + ID: 1, + Title: "test", + Description: "Lorem Ipsum", + NamespaceID: 1, + } + list.Description = "Lorem Ipsum dolor sit amet." + err := list.Update() + assert.NoError(t, err) + + }) + t.Run("nonexistant", func(t *testing.T) { + initFixtures(t) + list := List{ + ID: 99999999, + Title: "test", + } + err := list.Update() + assert.Error(t, err) + assert.True(t, IsErrListDoesNotExist(err)) + + }) + t.Run("existing identifier", func(t *testing.T) { + initFixtures(t) + list := List{ + Title: "test", + Description: "Lorem Ipsum", + Identifier: "test", + NamespaceID: 1, + } + + err := list.Create(user) + assert.Error(t, err) + assert.True(t, IsErrListIdentifierIsNotUnique(err)) + }) + }) +} + +func TestList_Delete(t *testing.T) { + initFixtures(t) + list := List{ + ID: 1, + } + err := list.Delete() + assert.NoError(t, err) +} + +func TestList_ReadAll(t *testing.T) { + initFixtures(t) + // Create test database + //assert.NoError(t, LoadFixtures()) + + // Get all lists for our namespace + lists, err := GetListsByNamespaceID(1, &User{}) + assert.NoError(t, err) + assert.Equal(t, len(lists), 2) + + // Get all lists our user has access to + u, err := GetUserByID(1) + assert.NoError(t, err) + + lists2 := List{} + lists3, _, _, err := lists2.ReadAll(u, "", 1, 50) + + assert.NoError(t, err) + assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) + s := reflect.ValueOf(lists3) + assert.Equal(t, 16, s.Len()) + + // Try getting lists for a nonexistant user + _, _, _, err = lists2.ReadAll(&User{ID: 984234}, "", 1, 50) + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) +} diff --git a/pkg/models/unit_tests.go b/pkg/models/unit_tests.go index 2fcb5345a..72c96f494 100644 --- a/pkg/models/unit_tests.go +++ b/pkg/models/unit_tests.go @@ -24,9 +24,11 @@ import ( "code.vikunja.io/api/pkg/mail" "fmt" "github.com/go-xorm/xorm" + "github.com/stretchr/testify/assert" "gopkg.in/testfixtures.v2" "os" "path/filepath" + "testing" ) // SetupTests takes care of seting up the db, fixtures etc. @@ -84,3 +86,9 @@ func createTestEngine(fixturesDir string) error { func initSchema(tx *xorm.Engine) error { return tx.Sync2(GetTables()...) } + +func initFixtures(t *testing.T) { + // Init db fixtures + err := db.LoadFixtures() + assert.NoError(t, err) +} -- 2.40.1 From 28c4c696148275836f1d6ad0efc8543636b7a783 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 21:19:04 +0100 Subject: [PATCH 04/14] More refactoring of old tests --- pkg/models/list_test.go | 47 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/pkg/models/list_test.go b/pkg/models/list_test.go index dd6cdef32..468038604 100644 --- a/pkg/models/list_test.go +++ b/pkg/models/list_test.go @@ -130,29 +130,28 @@ func TestList_Delete(t *testing.T) { } func TestList_ReadAll(t *testing.T) { - initFixtures(t) - // Create test database - //assert.NoError(t, LoadFixtures()) + t.Run("all in namespace", func(t *testing.T) { + initFixtures(t) + // Get all lists for our namespace + lists, err := GetListsByNamespaceID(1, &User{}) + assert.NoError(t, err) + assert.Equal(t, len(lists), 2) + }) + t.Run("all lists for user", func(t *testing.T) { + u := &User{ID: 1} + list := List{} + lists3, _, _, err := list.ReadAll(u, "", 1, 50) - // Get all lists for our namespace - lists, err := GetListsByNamespaceID(1, &User{}) - assert.NoError(t, err) - assert.Equal(t, len(lists), 2) - - // Get all lists our user has access to - u, err := GetUserByID(1) - assert.NoError(t, err) - - lists2 := List{} - lists3, _, _, err := lists2.ReadAll(u, "", 1, 50) - - assert.NoError(t, err) - assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) - s := reflect.ValueOf(lists3) - assert.Equal(t, 16, s.Len()) - - // Try getting lists for a nonexistant user - _, _, _, err = lists2.ReadAll(&User{ID: 984234}, "", 1, 50) - assert.Error(t, err) - assert.True(t, IsErrUserDoesNotExist(err)) + assert.NoError(t, err) + assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) + s := reflect.ValueOf(lists3) + assert.Equal(t, 16, s.Len()) + }) + t.Run("lists for nonexistant user", func(t *testing.T) { + user := &User{ID: 999999} + list := List{} + _, _, _, err := list.ReadAll(user, "", 1, 50) + assert.Error(t, err) + assert.True(t, IsErrUserDoesNotExist(err)) + }) } -- 2.40.1 From 0da6d4938249b36113e5258c136885ca6d38b548 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 21:21:03 +0100 Subject: [PATCH 05/14] Length validation of task identifiers --- pkg/models/list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/list.go b/pkg/models/list.go index 901865f2e..53bde652a 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -30,7 +30,7 @@ type List struct { // The description of the list. Description string `xorm:"longtext null" json:"description"` // The unique list short identifier. Used to build task identifiers. - Identifier string `xorm:"varchar(10) null" json:"identifier"` + Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"` OwnerID int64 `xorm:"int(11) INDEX not null" json:"-"` NamespaceID int64 `xorm:"int(11) INDEX not null" json:"-" param:"namespace"` -- 2.40.1 From 5777756f0b6e039fa2660e4145d261e84ddc5198 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 21:21:08 +0100 Subject: [PATCH 06/14] Swagger docs --- pkg/swagger/docs.go | 8 +++++++- pkg/swagger/swagger.json | 6 ++++++ pkg/swagger/swagger.yaml | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 76873197e..2b852088e 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-05 22:15:49.761451764 +0100 CET m=+0.171539379 +// 2019-12-07 21:20:14.32998445 +0100 CET m=+0.187124480 package swagger @@ -4634,6 +4634,12 @@ var doc = `{ "description": "The unique, numeric id of this list.", "type": "integer" }, + "identifier": { + "description": "The unique list short identifier. Used to build task identifiers.", + "type": "string", + "maxLength": 10, + "minLength": 0 + }, "owner": { "description": "The user who created this list.", "type": "object", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index a3c54a4d2..48f9cbe98 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -4615,6 +4615,12 @@ "description": "The unique, numeric id of this list.", "type": "integer" }, + "identifier": { + "description": "The unique list short identifier. Used to build task identifiers.", + "type": "string", + "maxLength": 10, + "minLength": 0 + }, "owner": { "description": "The user who created this list.", "type": "object", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index b4c9d3146..a0c92ccce 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -232,6 +232,11 @@ definitions: id: description: The unique, numeric id of this list. type: integer + identifier: + description: The unique list short identifier. Used to build task identifiers. + maxLength: 10 + minLength: 0 + type: string owner: $ref: '#/definitions/models.User' description: The user who created this list. -- 2.40.1 From 2485a65aadaca27d4afa12dd0861212728baaa11 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 21:23:00 +0100 Subject: [PATCH 07/14] Error code docs --- docs/content/doc/usage/errors.md | 1 + pkg/models/error.go | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/content/doc/usage/errors.md b/docs/content/doc/usage/errors.md index cc89e1b4a..a312284f9 100644 --- a/docs/content/doc/usage/errors.md +++ b/docs/content/doc/usage/errors.md @@ -33,6 +33,7 @@ This document describes the different errors Vikunja can return. | 3004 | 403 | The user needs to have read permissions on that list to perform that action. | | 3005 | 400 | The list title cannot be empty. | | 3006 | 404 | The list share does not exist. | +| 3007 | 400 | A list with this identifier already exists. | | 4001 | 400 | The list task text cannot be empty. | | 4002 | 404 | The list task does not exist. | | 4003 | 403 | All bulk editing tasks must belong to the same list. | diff --git a/pkg/models/error.go b/pkg/models/error.go index 0acb6cbb0..9824fe2b8 100644 --- a/pkg/models/error.go +++ b/pkg/models/error.go @@ -491,7 +491,11 @@ const ErrCodeListIdentifierIsNotUnique = 3007 // HTTPError holds the http error description func (err ErrListIdentifierIsNotUnique) HTTPError() web.HTTPError { - return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListIdentifierIsNotUnique, Message: "There already exists a list with this identifier."} + return web.HTTPError{ + HTTPCode: http.StatusBadRequest, + Code: ErrCodeListIdentifierIsNotUnique, + Message: "A list with this identifier already exists.", + } } // ================ -- 2.40.1 From 828a166c99ed6cb2716a99ab3f6bd60a8c5c363e Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 22:20:53 +0100 Subject: [PATCH 08/14] Add task index --- pkg/migration/20191207204427.go | 2 +- pkg/migration/20191207220736.go | 77 +++++++++++++++++++++++++++++++++ pkg/models/tasks.go | 31 +++++++------ 3 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 pkg/migration/20191207220736.go diff --git a/pkg/migration/20191207204427.go b/pkg/migration/20191207204427.go index e19187b96..7fbad8316 100644 --- a/pkg/migration/20191207204427.go +++ b/pkg/migration/20191207204427.go @@ -38,7 +38,7 @@ func init() { return tx.Sync2(list20191207204427{}) }, Rollback: func(tx *xorm.Engine) error { - return tx.DropTables(list20191207204427{}) + return dropTableColum(tx, "list", "indentifier") }, }) } diff --git a/pkg/migration/20191207220736.go b/pkg/migration/20191207220736.go new file mode 100644 index 000000000..88e7be455 --- /dev/null +++ b/pkg/migration/20191207220736.go @@ -0,0 +1,77 @@ +// Copyright 2019 Vikunja and contriubtors. All rights reserved. +// +// This file is part of Vikunja. +// +// Vikunja 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. +// +// Vikunja 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 Vikunja. If not, see . + +package migration + +import ( + "github.com/go-xorm/xorm" + "src.techknowlogick.com/xormigrate" +) + +type task20191207220736 struct { + ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"listtask"` + Index int64 `xorm:"int(11) not null default 0" json:"index"` + ListID int64 `xorm:"int(11) INDEX not null" json:"listID" param:"list"` +} + +func (task20191207220736) TableName() string { + return "tasks" +} + +func init() { + migrations = append(migrations, &xormigrate.Migration{ + ID: "20191207220736", + Description: "Add task index to tasks", + Migrate: func(tx *xorm.Engine) error { + err := tx.Sync2(task20191207220736{}) + if err != nil { + return err + } + + // Get all tasks, ordered by list and id + tasks := []*task20191207220736{} + err = tx. + OrderBy("list_id asc, id asc"). + Find(&tasks) + if err != nil { + return err + } + + var currentIndex int64 = 1 + for i, task := range tasks { + // Reset the current counter if we're encountering a new list + // We can do this because the list is sorted by list id + if i > 0 && tasks[i-1].ListID != task.ListID { + currentIndex = 1 + } + + task.Index = currentIndex + _, err = tx.Where("id = ?", task.ID).Update(task) + if err != nil { + return err + } + + currentIndex++ + } + + return nil + }, + Rollback: func(tx *xorm.Engine) error { + return dropTableColum(tx, "tasks", "index") + }, + }) +} diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index a4c9d4287..370dbf68a 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/web" "github.com/imdario/mergo" "sort" + "strconv" "time" ) @@ -62,6 +63,11 @@ type Task struct { // Determines how far a task is left from being done PercentDone float64 `xorm:"DOUBLE null" json:"percentDone"` + // The task identifier, based on the list identifier and the task's index + Identifier string `xorm:"-" json:"identifier"` + // The task index, calculated per list + Index int64 `xorm:"int(11) not null 0" json:"index"` + // The UID is currently not used for anything other than caldav, which is why we don't expose it over json UID string `xorm:"varchar(250) null" json:"-"` @@ -230,19 +236,6 @@ func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCo return tasks, resultCount, totalItems, err } -// 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 - taskMap := make(map[int64]*Task, len(tasks)) - err = x.Where("list_id = ?", listID).Find(&taskMap) - if err != nil { - return - } - - tasks, err = addMoreInfoToTasks(taskMap) - return -} - // GetTaskByIDSimple returns a raw task without extra data by the task ID func GetTaskByIDSimple(taskID int64) (task Task, err error) { if taskID < 1 { @@ -314,9 +307,11 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { // Get all users & task ids and put them into the array var userIDs []int64 var taskIDs []int64 + var listIDs []int64 for _, i := range taskMap { taskIDs = append(taskIDs, i.ID) userIDs = append(userIDs, i.CreatedByID) + listIDs = append(listIDs, i.ListID) } // Get all assignees @@ -399,6 +394,13 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { taskRemindersUnix[r.TaskID] = append(taskRemindersUnix[r.TaskID], r.ReminderUnix) } + // Get all identifiers + lists := make(map[int64]*List, len(listIDs)) + err = x.In("id", listIDs).Find(&lists) + if err != nil { + return + } + // Add all user objects to the appropriate tasks for _, task := range taskMap { @@ -410,6 +412,9 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { // Prepare the subtasks task.RelatedTasks = make(RelatedTaskMap) + + // Build the task identifier from the list identifier and task index + task.Identifier = lists[task.ListID].Identifier + strconv.FormatInt(task.Index, 10) } // Get all related tasks -- 2.40.1 From ea372628ad2c49d02ee7f2b11e72d24747879f30 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 22:28:47 +0100 Subject: [PATCH 09/14] Fix task index db --- pkg/models/tasks.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 370dbf68a..d4f47d02c 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -66,7 +66,7 @@ type Task struct { // The task identifier, based on the list identifier and the task's index Identifier string `xorm:"-" json:"identifier"` // The task index, calculated per list - Index int64 `xorm:"int(11) not null 0" json:"index"` + Index int64 `xorm:"int(11) not null default 0" json:"index"` // The UID is currently not used for anything other than caldav, which is why we don't expose it over json UID string `xorm:"varchar(250) null" json:"-"` @@ -790,7 +790,7 @@ func (t *Task) Delete() (err error) { // @Success 200 {object} models.Task "The task" // @Failure 404 {object} models.Message "Task not found" // @Failure 500 {object} models.Message "Internal error" -// @Router /tasks/all [get] +// @Router /tasks/{ID} [get] func (t *Task) ReadOne() (err error) { taskMap := make(map[int64]*Task, 1) -- 2.40.1 From e1f85d83eafffd87dac38398b378c5ee6698954f Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 22:51:12 +0100 Subject: [PATCH 10/14] Fix task for task identifier --- pkg/models/fixtures/list.yml | 21 +++++++++- pkg/models/fixtures/tasks.yml | 33 +++++++++++++++ pkg/models/list_test.go | 4 +- pkg/models/task_collection_test.go | 64 ++++++++++++++++++++++++++++++ pkg/models/tasks.go | 2 +- 5 files changed, 120 insertions(+), 4 deletions(-) diff --git a/pkg/models/fixtures/list.yml b/pkg/models/fixtures/list.yml index 209fe515d..fa8edcb9a 100644 --- a/pkg/models/fixtures/list.yml +++ b/pkg/models/fixtures/list.yml @@ -2,7 +2,7 @@ id: 1 title: Test1 description: Lorem Ipsum - identifier: test + identifier: test1 owner_id: 1 namespace_id: 1 updated: 0 @@ -11,6 +11,7 @@ id: 2 title: Test2 description: Lorem Ipsum + identifier: test2 owner_id: 3 namespace_id: 1 updated: 0 @@ -19,6 +20,7 @@ id: 3 title: Test3 description: Lorem Ipsum + identifier: test3 owner_id: 3 namespace_id: 2 updated: 0 @@ -27,6 +29,7 @@ id: 4 title: Test4 description: Lorem Ipsum + identifier: test4 owner_id: 3 namespace_id: 3 updated: 0 @@ -35,6 +38,7 @@ id: 5 title: Test5 description: Lorem Ipsum + identifier: test5 owner_id: 5 namespace_id: 5 updated: 0 @@ -43,6 +47,7 @@ id: 6 title: Test6 description: Lorem Ipsum + identifier: test6 owner_id: 6 namespace_id: 6 updated: 0 @@ -51,6 +56,7 @@ id: 7 title: Test7 description: Lorem Ipsum + identifier: test7 owner_id: 6 namespace_id: 6 updated: 0 @@ -59,6 +65,7 @@ id: 8 title: Test8 description: Lorem Ipsum + identifier: test8 owner_id: 6 namespace_id: 6 updated: 0 @@ -67,6 +74,7 @@ id: 9 title: Test9 description: Lorem Ipsum + identifier: test9 owner_id: 6 namespace_id: 6 updated: 0 @@ -75,6 +83,7 @@ id: 10 title: Test10 description: Lorem Ipsum + identifier: test10 owner_id: 6 namespace_id: 6 updated: 0 @@ -83,6 +92,7 @@ id: 11 title: Test11 description: Lorem Ipsum + identifier: test11 owner_id: 6 namespace_id: 6 updated: 0 @@ -91,6 +101,7 @@ id: 12 title: Test12 description: Lorem Ipsum + identifier: test12 owner_id: 6 namespace_id: 7 updated: 0 @@ -99,6 +110,7 @@ id: 13 title: Test13 description: Lorem Ipsum + identifier: test13 owner_id: 6 namespace_id: 8 updated: 0 @@ -107,6 +119,7 @@ id: 14 title: Test14 description: Lorem Ipsum + identifier: test14 owner_id: 6 namespace_id: 9 updated: 0 @@ -115,6 +128,7 @@ id: 15 title: Test15 description: Lorem Ipsum + identifier: test15 owner_id: 6 namespace_id: 10 updated: 0 @@ -123,6 +137,7 @@ id: 16 title: Test16 description: Lorem Ipsum + identifier: test16 owner_id: 6 namespace_id: 11 updated: 0 @@ -131,6 +146,7 @@ id: 17 title: Test17 description: Lorem Ipsum + identifier: test17 owner_id: 6 namespace_id: 12 updated: 0 @@ -141,6 +157,7 @@ id: 18 title: Test18 description: Lorem Ipsum + identifier: test18 owner_id: 7 namespace_id: 13 updated: 0 @@ -149,6 +166,7 @@ id: 19 title: Test19 description: Lorem Ipsum + identifier: test19 owner_id: 7 namespace_id: 14 updated: 0 @@ -157,6 +175,7 @@ id: 20 title: Test20 description: Lorem Ipsum + identifier: test20 owner_id: 13 namespace_id: 15 updated: 0 diff --git a/pkg/models/fixtures/tasks.yml b/pkg/models/fixtures/tasks.yml index 246ba4781..388181198 100644 --- a/pkg/models/fixtures/tasks.yml +++ b/pkg/models/fixtures/tasks.yml @@ -3,6 +3,7 @@ description: 'Lorem Ipsum' created_by_id: 1 list_id: 1 + index: 1 created: 1543626724 updated: 1543626724 - id: 2 @@ -10,12 +11,14 @@ done: true created_by_id: 1 list_id: 1 + index: 2 created: 1543626724 updated: 1543626724 - id: 3 text: 'task #3 high prio' created_by_id: 1 list_id: 1 + index: 3 created: 1543626724 updated: 1543626724 priority: 100 @@ -23,6 +26,7 @@ text: 'task #4 low prio' created_by_id: 1 list_id: 1 + index: 4 created: 1543626724 updated: 1543626724 priority: 1 @@ -30,6 +34,7 @@ text: 'task #5 higher due date' created_by_id: 1 list_id: 1 + index: 5 created: 1543626724 updated: 1543626724 due_date_unix: 1543636724 @@ -37,6 +42,7 @@ text: 'task #6 lower due date' created_by_id: 1 list_id: 1 + index: 6 created: 1543626724 updated: 1543626724 due_date_unix: 1543616724 @@ -44,6 +50,7 @@ text: 'task #7 with start date' created_by_id: 1 list_id: 1 + index: 7 created: 1543626724 updated: 1543626724 start_date_unix: 1544600000 @@ -51,6 +58,7 @@ text: 'task #8 with end date' created_by_id: 1 list_id: 1 + index: 8 created: 1543626724 updated: 1543626724 end_date_unix: 1544700000 @@ -58,6 +66,7 @@ text: 'task #9 with start and end date' created_by_id: 1 list_id: 1 + index: 9 created: 1543626724 updated: 1543626724 start_date_unix: 1544600000 @@ -66,108 +75,126 @@ text: 'task #10 basic' created_by_id: 1 list_id: 1 + index: 10 created: 1543626724 updated: 1543626724 - id: 11 text: 'task #11 basic' created_by_id: 1 list_id: 1 + index: 11 created: 1543626724 updated: 1543626724 - id: 12 text: 'task #12 basic' created_by_id: 1 list_id: 1 + index: 12 created: 1543626724 updated: 1543626724 - id: 13 text: 'task #13 basic other list' created_by_id: 1 list_id: 2 + index: 1 created: 1543626724 updated: 1543626724 - id: 14 text: 'task #14 basic other list' created_by_id: 5 list_id: 5 + index: 1 created: 1543626724 updated: 1543626724 - id: 15 text: 'task #15' created_by_id: 6 list_id: 6 + index: 1 created: 1543626724 updated: 1543626724 - id: 16 text: 'task #16' created_by_id: 6 list_id: 7 + index: 1 created: 1543626724 updated: 1543626724 - id: 17 text: 'task #17' created_by_id: 6 list_id: 8 + index: 1 created: 1543626724 updated: 1543626724 - id: 18 text: 'task #18' created_by_id: 6 list_id: 9 + index: 1 created: 1543626724 updated: 1543626724 - id: 19 text: 'task #19' created_by_id: 6 list_id: 10 + index: 1 created: 1543626724 updated: 1543626724 - id: 20 text: 'task #20' created_by_id: 6 list_id: 11 + index: 1 created: 1543626724 updated: 1543626724 - id: 21 text: 'task #21' created_by_id: 6 list_id: 12 + index: 1 created: 1543626724 updated: 1543626724 - id: 22 text: 'task #22' created_by_id: 6 list_id: 13 + index: 1 created: 1543626724 updated: 1543626724 - id: 23 text: 'task #23' created_by_id: 6 list_id: 14 + index: 1 created: 1543626724 updated: 1543626724 - id: 24 text: 'task #24' created_by_id: 6 list_id: 15 + index: 1 created: 1543626724 updated: 1543626724 - id: 25 text: 'task #25' created_by_id: 6 list_id: 16 + index: 1 created: 1543626724 updated: 1543626724 - id: 26 text: 'task #26' created_by_id: 6 list_id: 17 + index: 1 created: 1543626724 updated: 1543626724 - id: 27 text: 'task #27 with reminders' created_by_id: 1 list_id: 1 + index: 12 created: 1543626724 updated: 1543626724 - id: 28 @@ -176,24 +203,28 @@ created_by_id: 1 repeat_after: 3600 list_id: 1 + index: 13 created: 1543626724 updated: 1543626724 - id: 29 text: 'task #29 with parent task (1)' created_by_id: 1 list_id: 1 + index: 14 created: 1543626724 updated: 1543626724 - id: 30 text: 'task #30 with assignees' created_by_id: 1 list_id: 1 + index: 15 created: 1543626724 updated: 1543626724 - id: 31 text: 'task #31 with color' created_by_id: 1 list_id: 1 + index: 16 hex_color: f0f0f0 created: 1543626724 updated: 1543626724 @@ -201,12 +232,14 @@ text: 'task #32' created_by_id: 1 list_id: 3 + index: 1 created: 1543626724 updated: 1543626724 - id: 33 text: 'task #33 with percent done' created_by_id: 1 list_id: 1 + index: 17 percent_done: 0.5 created: 1543626724 updated: 1543626724 diff --git a/pkg/models/list_test.go b/pkg/models/list_test.go index 468038604..41baa2400 100644 --- a/pkg/models/list_test.go +++ b/pkg/models/list_test.go @@ -69,7 +69,7 @@ func TestList_CreateOrUpdate(t *testing.T) { list := List{ Title: "test", Description: "Lorem Ipsum", - Identifier: "test", + Identifier: "test1", NamespaceID: 1, } @@ -109,7 +109,7 @@ func TestList_CreateOrUpdate(t *testing.T) { list := List{ Title: "test", Description: "Lorem Ipsum", - Identifier: "test", + Identifier: "test1", NamespaceID: 1, } diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index dae3cfae3..e3cd925ca 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -55,6 +55,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 1, Text: "task #1", Description: "Lorem Ipsum", + Identifier: "test1-1", + Index: 1, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -73,6 +75,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { { ID: 29, Text: "task #29 with parent task (1)", + Index: 14, CreatedByID: 1, ListID: 1, Created: 1543626724, @@ -109,6 +112,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task2 := &Task{ ID: 2, Text: "task #2 done", + Identifier: "test1-2", + Index: 2, Done: true, CreatedByID: 1, CreatedBy: user1, @@ -130,6 +135,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task3 := &Task{ ID: 3, Text: "task #3 high prio", + Identifier: "test1-3", + Index: 3, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -141,6 +148,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task4 := &Task{ ID: 4, Text: "task #4 low prio", + Identifier: "test1-4", + Index: 4, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -152,6 +161,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task5 := &Task{ ID: 5, Text: "task #5 higher due date", + Identifier: "test1-5", + Index: 5, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -163,6 +174,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task6 := &Task{ ID: 6, Text: "task #6 lower due date", + Identifier: "test1-6", + Index: 6, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -174,6 +187,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task7 := &Task{ ID: 7, Text: "task #7 with start date", + Identifier: "test1-7", + Index: 7, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -185,6 +200,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task8 := &Task{ ID: 8, Text: "task #8 with end date", + Identifier: "test1-8", + Index: 8, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -196,6 +213,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task9 := &Task{ ID: 9, Text: "task #9 with start and end date", + Identifier: "test1-9", + Index: 9, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -208,6 +227,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task10 := &Task{ ID: 10, Text: "task #10 basic", + Identifier: "test1-10", + Index: 10, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -218,6 +239,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task11 := &Task{ ID: 11, Text: "task #11 basic", + Identifier: "test1-11", + Index: 11, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -228,6 +251,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task12 := &Task{ ID: 12, Text: "task #12 basic", + Identifier: "test1-12", + Index: 12, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -238,6 +263,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task15 := &Task{ ID: 15, Text: "task #15", + Identifier: "test6-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 6, @@ -248,6 +275,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task16 := &Task{ ID: 16, Text: "task #16", + Identifier: "test7-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 7, @@ -258,6 +287,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task17 := &Task{ ID: 17, Text: "task #17", + Identifier: "test8-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 8, @@ -268,6 +299,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task18 := &Task{ ID: 18, Text: "task #18", + Identifier: "test9-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 9, @@ -278,6 +311,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task19 := &Task{ ID: 19, Text: "task #19", + Identifier: "test10-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 10, @@ -288,6 +323,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task20 := &Task{ ID: 20, Text: "task #20", + Identifier: "test11-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 11, @@ -298,6 +335,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task21 := &Task{ ID: 21, Text: "task #21", + Identifier: "test12-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 12, @@ -308,6 +347,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task22 := &Task{ ID: 22, Text: "task #22", + Identifier: "test13-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 13, @@ -318,6 +359,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task23 := &Task{ ID: 23, Text: "task #23", + Identifier: "test14-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 14, @@ -328,6 +371,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task24 := &Task{ ID: 24, Text: "task #24", + Identifier: "test15-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 15, @@ -338,6 +383,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task25 := &Task{ ID: 25, Text: "task #25", + Identifier: "test16-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 16, @@ -348,6 +395,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task26 := &Task{ ID: 26, Text: "task #26", + Identifier: "test17-1", + Index: 1, CreatedByID: 6, CreatedBy: user6, ListID: 17, @@ -358,6 +407,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task27 := &Task{ ID: 27, Text: "task #27 with reminders", + Identifier: "test1-12", + Index: 12, CreatedByID: 1, CreatedBy: user1, RemindersUnix: []int64{1543626724, 1543626824}, @@ -369,6 +420,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task28 := &Task{ ID: 28, Text: "task #28 with repeat after", + Identifier: "test1-13", + Index: 13, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -380,6 +433,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task29 := &Task{ ID: 29, Text: "task #29 with parent task (1)", + Identifier: "test1-14", + Index: 14, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -389,6 +444,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { ID: 1, Text: "task #1", Description: "Lorem Ipsum", + Index: 1, CreatedByID: 1, ListID: 1, Created: 1543626724, @@ -402,6 +458,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task30 := &Task{ ID: 30, Text: "task #30 with assignees", + Identifier: "test1-15", + Index: 15, CreatedByID: 1, CreatedBy: user1, ListID: 1, @@ -416,6 +474,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31 := &Task{ ID: 31, Text: "task #31 with color", + Identifier: "test1-16", + Index: 16, HexColor: "f0f0f0", CreatedByID: 1, CreatedBy: user1, @@ -427,6 +487,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task32 := &Task{ ID: 32, Text: "task #32", + Identifier: "test3-1", + Index: 1, CreatedByID: 1, CreatedBy: user1, ListID: 3, @@ -437,6 +499,8 @@ func TestTaskCollection_ReadAll(t *testing.T) { task33 := &Task{ ID: 33, Text: "task #33 with percent done", + Identifier: "test1-17", + Index: 17, CreatedByID: 1, CreatedBy: user1, ListID: 1, diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index d4f47d02c..fddb844db 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -414,7 +414,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { task.RelatedTasks = make(RelatedTaskMap) // Build the task identifier from the list identifier and task index - task.Identifier = lists[task.ListID].Identifier + strconv.FormatInt(task.Index, 10) + task.Identifier = lists[task.ListID].Identifier + "-" + strconv.FormatInt(task.Index, 10) } // Get all related tasks -- 2.40.1 From 7595b16e296a20bc8fd9aed4a6b45a50ada50de8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 22:52:38 +0100 Subject: [PATCH 11/14] Swagger docs --- pkg/swagger/docs.go | 143 +++++++++++++++++++++++++++++++++------ pkg/swagger/swagger.json | 141 ++++++++++++++++++++++++++++++++------ pkg/swagger/swagger.yaml | 114 +++++++++++++++++++++++++------ 3 files changed, 336 insertions(+), 62 deletions(-) diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 2b852088e..6db5c7fda 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-07 21:20:14.32998445 +0100 CET m=+0.187124480 +// 2019-12-07 22:51:46.967330871 +0100 CET m=+0.166223609 package swagger @@ -407,7 +407,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns a team by its ID.", + "description": "Returns a list by its ID.", "consumes": [ "application/json" ], @@ -415,13 +415,13 @@ var doc = `{ "application/json" ], "tags": [ - "team" + "list" ], - "summary": "Gets one team", + "summary": "Gets one list", "parameters": [ { "type": "integer", - "description": "Team ID", + "description": "List ID", "name": "id", "in": "path", "required": true @@ -429,13 +429,13 @@ var doc = `{ ], "responses": { "200": { - "description": "The team", + "description": "The list", "schema": { - "$ref": "#/definitions/models.Team" + "$ref": "#/definitions/models.List" } }, "403": { - "description": "The user does not have access to the team", + "description": "The user does not have access to the list", "schema": { "$ref": "#/definitions/code.vikunja.io.web.HTTPError" } @@ -2649,7 +2649,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns one task by its ID", + "description": "Returns all tasks on any list the user has access to.", "consumes": [ "application/json" ], @@ -2659,27 +2659,53 @@ var doc = `{ "tags": [ "task" ], - "summary": "Get one task", + "summary": "Get tasks", "parameters": [ { "type": "integer", - "description": "The task ID", - "name": "ID", - "in": "path", - "required": true + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", + "in": "query" + }, + { + "type": "string", + "description": "Search tasks by task text.", + "name": "s", + "in": "query" + }, + { + "type": "string", + "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", + "name": "sort", + "in": "query" + }, + { + "type": "integer", + "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 current time.", + "name": "startdate", + "in": "query" + }, + { + "type": "integer", + "description": "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.", + "name": "enddate", + "in": "query" } ], "responses": { "200": { - "description": "The task", + "description": "The tasks", "schema": { - "$ref": "#/definitions/models.Task" - } - }, - "404": { - "description": "Task not found", - "schema": { - "$ref": "#/definitions/models.Message" + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } } }, "500": { @@ -2749,6 +2775,55 @@ var doc = `{ } } }, + "/tasks/{ID}": { + "get": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Returns one task by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "Get one task", + "parameters": [ + { + "type": "integer", + "description": "The task ID", + "name": "ID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The task", + "schema": { + "$ref": "#/definitions/models.Task" + } + }, + "404": { + "description": "Task not found", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "500": { + "description": "Internal error", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{id}": { "post": { "security": [ @@ -4452,6 +4527,14 @@ var doc = `{ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -4871,6 +4954,14 @@ var doc = `{ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -4979,6 +5070,14 @@ var doc = `{ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index 48f9cbe98..2164dff7c 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -389,7 +389,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns a team by its ID.", + "description": "Returns a list by its ID.", "consumes": [ "application/json" ], @@ -397,13 +397,13 @@ "application/json" ], "tags": [ - "team" + "list" ], - "summary": "Gets one team", + "summary": "Gets one list", "parameters": [ { "type": "integer", - "description": "Team ID", + "description": "List ID", "name": "id", "in": "path", "required": true @@ -411,13 +411,13 @@ ], "responses": { "200": { - "description": "The team", + "description": "The list", "schema": { - "$ref": "#/definitions/models.Team" + "$ref": "#/definitions/models.List" } }, "403": { - "description": "The user does not have access to the team", + "description": "The user does not have access to the list", "schema": { "$ref": "#/definitions/code.vikunja.io/web.HTTPError" } @@ -2631,7 +2631,7 @@ "JWTKeyAuth": [] } ], - "description": "Returns one task by its ID", + "description": "Returns all tasks on any list the user has access to.", "consumes": [ "application/json" ], @@ -2641,27 +2641,53 @@ "tags": [ "task" ], - "summary": "Get one task", + "summary": "Get tasks", "parameters": [ { "type": "integer", - "description": "The task ID", - "name": "ID", - "in": "path", - "required": true + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", + "in": "query" + }, + { + "type": "string", + "description": "Search tasks by task text.", + "name": "s", + "in": "query" + }, + { + "type": "string", + "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", + "name": "sort", + "in": "query" + }, + { + "type": "integer", + "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 current time.", + "name": "startdate", + "in": "query" + }, + { + "type": "integer", + "description": "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.", + "name": "enddate", + "in": "query" } ], "responses": { "200": { - "description": "The task", + "description": "The tasks", "schema": { - "$ref": "#/definitions/models.Task" - } - }, - "404": { - "description": "Task not found", - "schema": { - "$ref": "#/definitions/models.Message" + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } } }, "500": { @@ -2731,6 +2757,55 @@ } } }, + "/tasks/{ID}": { + "get": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Returns one task by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "Get one task", + "parameters": [ + { + "type": "integer", + "description": "The task ID", + "name": "ID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The task", + "schema": { + "$ref": "#/definitions/models.Task" + } + }, + "404": { + "description": "Task not found", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "500": { + "description": "Internal error", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{id}": { "post": { "security": [ @@ -4433,6 +4508,14 @@ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -4852,6 +4935,14 @@ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -4960,6 +5051,14 @@ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index a0c92ccce..e4a4a940f 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -84,6 +84,13 @@ definitions: id: description: The unique, numeric id of this task. type: integer + identifier: + description: The task identifier, based on the list identifier and the task's + index + type: string + index: + description: The task index, calculated per list + type: integer labels: description: An array of labels which are associated with this task. items: @@ -418,6 +425,13 @@ definitions: id: description: The unique, numeric id of this task. type: integer + identifier: + description: The task identifier, based on the list identifier and the + task's index + type: string + index: + description: The task index, calculated per list + type: integer labels: description: An array of labels which are associated with this task. items: @@ -505,6 +519,13 @@ definitions: id: description: The unique, numeric id of this task. type: integer + identifier: + description: The task identifier, based on the list identifier and the task's + index + type: string + index: + description: The task index, calculated per list + type: integer labels: description: An array of labels which are associated with this task. items: @@ -1138,9 +1159,9 @@ paths: get: consumes: - application/json - description: Returns a team by its ID. + description: Returns a list by its ID. parameters: - - description: Team ID + - description: List ID in: path name: id required: true @@ -1149,11 +1170,11 @@ paths: - application/json responses: "200": - description: The team + description: The list schema: - $ref: '#/definitions/models.Team' + $ref: '#/definitions/models.List' "403": - description: The user does not have access to the team + description: The user does not have access to the list schema: $ref: '#/definitions/code.vikunja.io/web.HTTPError' "500": @@ -1162,9 +1183,9 @@ paths: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Gets one team + summary: Gets one list tags: - - team + - list post: consumes: - application/json @@ -2592,6 +2613,37 @@ paths: summary: Get an auth token for a share tags: - sharing + /tasks/{ID}: + get: + consumes: + - application/json + description: Returns one task by its ID + parameters: + - description: The task ID + in: path + name: ID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: The task + schema: + $ref: '#/definitions/models.Task' + "404": + description: Task not found + schema: + $ref: '#/definitions/models.Message' + "500": + description: Internal error + schema: + $ref: '#/definitions/models.Message' + security: + - JWTKeyAuth: [] + summary: Get one task + tags: + - task /tasks/{id}: delete: description: Deletes a task from a list. This does not mean "mark it done". @@ -3246,31 +3298,55 @@ paths: get: consumes: - application/json - description: Returns one task by its ID + description: Returns all tasks on any list the user has access to. parameters: - - description: The task ID - in: path - name: ID - required: true + - description: The page number. Used for pagination. If not provided, the first + page of results is returned. + in: query + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page + type: integer + - description: Search tasks by task text. + in: query + name: s + type: string + - description: The sorting parameter. Possible values to sort by are priority, + prioritydesc, priorityasc, duedate, duedatedesc, duedateasc. + in: query + name: sort + 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 + current time. + in: query + name: startdate + type: integer + - description: 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. + in: query + name: enddate type: integer produces: - application/json responses: "200": - description: The task + description: The tasks schema: - $ref: '#/definitions/models.Task' - "404": - description: Task not found - schema: - $ref: '#/definitions/models.Message' + items: + $ref: '#/definitions/models.Task' + type: array "500": description: Internal error schema: $ref: '#/definitions/models.Message' security: - JWTKeyAuth: [] - summary: Get one task + summary: Get tasks tags: - task /tasks/bulk: -- 2.40.1 From 6f732fea1349059a359af5f99a2b87533c6d07af Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 22:54:11 +0100 Subject: [PATCH 12/14] Swagger docs --- pkg/swagger/docs.go | 135 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 120 insertions(+), 15 deletions(-) diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index b20677d13..80e202554 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-07 20:29:10.551783293 +0100 CET m=+0.172017440 +// 2019-12-07 22:54:02.661375666 +0100 CET m=+0.164990732 package swagger @@ -2649,7 +2649,7 @@ var doc = `{ "JWTKeyAuth": [] } ], - "description": "Returns one task by its ID", + "description": "Returns all tasks on any list the user has access to.", "consumes": [ "application/json" ], @@ -2659,27 +2659,53 @@ var doc = `{ "tags": [ "task" ], - "summary": "Get one task", + "summary": "Get tasks", "parameters": [ { "type": "integer", - "description": "The task ID", - "name": "ID", - "in": "path", - "required": true + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", + "in": "query" + }, + { + "type": "string", + "description": "Search tasks by task text.", + "name": "s", + "in": "query" + }, + { + "type": "string", + "description": "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc.", + "name": "sort", + "in": "query" + }, + { + "type": "integer", + "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 current time.", + "name": "startdate", + "in": "query" + }, + { + "type": "integer", + "description": "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.", + "name": "enddate", + "in": "query" } ], "responses": { "200": { - "description": "The task", + "description": "The tasks", "schema": { - "$ref": "#/definitions/models.Task" - } - }, - "404": { - "description": "Task not found", - "schema": { - "$ref": "#/definitions/models.Message" + "type": "array", + "items": { + "$ref": "#/definitions/models.Task" + } } }, "500": { @@ -2749,6 +2775,55 @@ var doc = `{ } } }, + "/tasks/{ID}": { + "get": { + "security": [ + { + "JWTKeyAuth": [] + } + ], + "description": "Returns one task by its ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "task" + ], + "summary": "Get one task", + "parameters": [ + { + "type": "integer", + "description": "The task ID", + "name": "ID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "The task", + "schema": { + "$ref": "#/definitions/models.Task" + } + }, + "404": { + "description": "Task not found", + "schema": { + "$ref": "#/definitions/models.Message" + } + }, + "500": { + "description": "Internal error", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/tasks/{id}": { "post": { "security": [ @@ -4481,6 +4556,14 @@ var doc = `{ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -4663,6 +4746,12 @@ var doc = `{ "description": "The unique, numeric id of this list.", "type": "integer" }, + "identifier": { + "description": "The unique list short identifier. Used to build task identifiers.", + "type": "string", + "maxLength": 10, + "minLength": 0 + }, "owner": { "description": "The user who created this list.", "type": "object", @@ -4894,6 +4983,14 @@ var doc = `{ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", @@ -5002,6 +5099,14 @@ var doc = `{ "description": "The unique, numeric id of this task.", "type": "integer" }, + "identifier": { + "description": "The task identifier, based on the list identifier and the task's index", + "type": "string" + }, + "index": { + "description": "The task index, calculated per list", + "type": "integer" + }, "labels": { "description": "An array of labels which are associated with this task.", "type": "array", -- 2.40.1 From 2b38539b47d2805c6cca71293e4af4b1224db5b8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 22:56:31 +0100 Subject: [PATCH 13/14] "Fix" gocyclo --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e05555d7b..7728f76c2 100644 --- a/Makefile +++ b/Makefile @@ -214,7 +214,7 @@ gocyclo-check: go get -u github.com/fzipp/gocyclo; \ go install $(GOFLAGS) github.com/fzipp/gocyclo; \ fi - for S in $(GOFILES); do gocyclo -over 23 $$S || exit 1; done; + for S in $(GOFILES); do gocyclo -over 24 $$S || exit 1; done; .PHONY: static-check static-check: -- 2.40.1 From 0db91ad5745416435890637321618db6f4ba22d4 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 7 Dec 2019 23:15:17 +0100 Subject: [PATCH 14/14] Fix integration tests --- pkg/integrations/task_collection_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index e9f3e497d..0acfc73e0 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -95,33 +95,33 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, 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}}]`) + 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,"identifier":"test1-17","index":17,"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,"identifier":"test1-4","index":4,"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,"identifier":"test1-3","index":3,"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_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,`) + 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,"identifier":"test1-3","index":3,"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_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}}]`) + 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,"identifier":"test1-17","index":17,"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,"identifier":"test1-4","index":4,"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,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) // should equal duedate asc t.Run("by duedate", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, 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}}]`) + 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,"identifier":"test1-6","index":6,"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,"identifier":"test1-5","index":5,"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_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"`) + 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,"identifier":"test1-5","index":5,"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_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}}]`) + 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,"identifier":"test1-6","index":6,"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,"identifier":"test1-5","index":5,"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) @@ -249,33 +249,33 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) 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}}]`) + 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,"identifier":"test1-17","index":17,"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,"identifier":"test1-4","index":4,"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,"identifier":"test1-3","index":3,"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_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,`) + 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,"identifier":"test1-3","index":3,"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_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}}]`) + 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,"identifier":"test1-17","index":17,"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,"identifier":"test1-4","index":4,"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,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) // should equal duedate asc t.Run("by duedate", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, 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}}]`) + 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,"identifier":"test1-6","index":6,"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,"identifier":"test1-5","index":5,"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_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"`) + 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,"identifier":"test1-5","index":5,"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_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}}]`) + 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,"identifier":"test1-6","index":6,"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,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`) }) t.Run("invalid parameter", func(t *testing.T) { // Invalid parameter should not sort at all -- 2.40.1