// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-2021 Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public Licensee as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public Licensee for more details. // // You should have received a copy of the GNU Affero General Public Licensee // along with this program. If not, see . package todoist import ( "os" "strconv" "testing" "time" "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/models" "github.com/stretchr/testify/assert" "gopkg.in/d4l3k/messagediff.v1" ) func TestConvertTodoistToVikunja(t *testing.T) { config.InitConfig() time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z") assert.NoError(t, err) time1 = time1.In(config.GetTimeZone()) time3, err := time.Parse(time.RFC3339Nano, "2014-10-21T08:25:05Z") assert.NoError(t, err) time3 = time3.In(config.GetTimeZone()) dueTime, err := time.Parse(time.RFC3339Nano, "2020-05-31T23:59:00Z") assert.NoError(t, err) dueTime = dueTime.In(config.GetTimeZone()) dueTimeWithTime, err := time.Parse(time.RFC3339Nano, "2021-01-31T19:00:00Z") assert.NoError(t, err) dueTimeWithTime = dueTimeWithTime.In(config.GetTimeZone()) nilTime, err := time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00Z") assert.NoError(t, err) exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg") assert.NoError(t, err) makeTestItem := func(id, projectId int64, hasDueDate, hasLabels, done bool) *item { item := &item{ ID: id, UserID: 1855589, ProjectID: projectId, Content: "Task" + strconv.FormatInt(id, 10), Priority: 1, ParentID: 0, ChildOrder: 1, DateAdded: time1, DateCompleted: nilTime, } if done { item.Checked = 1 item.DateCompleted = time3 } if hasLabels { item.Labels = []int64{ 80000, 80001, 80002, 80003, } } if hasDueDate { item.Due = &dueDate{ Date: "2020-05-31", Timezone: nil, IsRecurring: false, } } return item } testSync := &sync{ Projects: []*project{ { ID: 396936926, Name: "Project1", Color: 30, ChildOrder: 1, Collapsed: 0, Shared: false, IsDeleted: 0, IsArchived: 0, IsFavorite: 0, }, { ID: 396936927, Name: "Project2", Color: 37, ChildOrder: 1, Collapsed: 0, Shared: false, IsDeleted: 0, IsArchived: 0, IsFavorite: 0, }, { ID: 396936928, Name: "Project3 - Archived", Color: 37, ChildOrder: 1, Collapsed: 0, Shared: false, IsDeleted: 0, IsArchived: 1, IsFavorite: 0, }, }, Items: []*item{ makeTestItem(400000000, 396936926, false, false, false), makeTestItem(400000001, 396936926, false, false, false), makeTestItem(400000002, 396936926, false, false, false), makeTestItem(400000003, 396936926, true, true, true), makeTestItem(400000004, 396936926, false, true, false), makeTestItem(400000005, 396936926, true, false, true), makeTestItem(400000006, 396936926, true, false, true), { ID: 400000110, UserID: 1855589, ProjectID: 396936926, Content: "Task with parent", Priority: 2, ParentID: 400000006, ChildOrder: 1, Checked: 0, DateAdded: time1, }, { ID: 400000106, UserID: 1855589, ProjectID: 396936926, Content: "Task400000106", Priority: 1, ParentID: 0, ChildOrder: 1, DateAdded: time1, Checked: 1, DateCompleted: time3, Due: &dueDate{ Date: "2021-01-31T19:00:00Z", Timezone: nil, IsRecurring: false, }, Labels: []int64{ 80000, 80001, 80002, 80003, }, }, makeTestItem(400000107, 396936926, false, false, true), makeTestItem(400000108, 396936926, false, false, true), { ID: 400000109, UserID: 1855589, ProjectID: 396936926, Content: "Task400000109", Priority: 1, ChildOrder: 1, Checked: 1, DateAdded: time1, DateCompleted: time3, SectionID: 1234, }, makeTestItem(400000007, 396936927, true, false, false), makeTestItem(400000008, 396936927, true, false, false), makeTestItem(400000009, 396936927, false, false, false), makeTestItem(400000010, 396936927, false, false, true), makeTestItem(400000101, 396936927, false, false, false), makeTestItem(400000102, 396936927, true, true, false), makeTestItem(400000103, 396936927, false, true, false), makeTestItem(400000104, 396936927, false, true, false), makeTestItem(400000105, 396936927, true, true, false), makeTestItem(400000111, 396936928, false, false, true), }, Labels: []*label{ { ID: 80000, Name: "Label1", Color: 30, }, { ID: 80001, Name: "Label2", Color: 31, }, { ID: 80002, Name: "Label3", Color: 32, }, { ID: 80003, Name: "Label4", Color: 33, }, }, Notes: []*note{ { ID: 101476, PostedUID: 1855589, ItemID: 400000000, Content: "Lorem Ipsum dolor sit amet", Posted: time1, }, { ID: 101477, PostedUID: 1855589, ItemID: 400000001, Content: "Lorem Ipsum dolor sit amet", Posted: time1, }, { ID: 101478, PostedUID: 1855589, ItemID: 400000003, Content: "Lorem Ipsum dolor sit amet", Posted: time1, }, { ID: 101479, PostedUID: 1855589, ItemID: 400000010, Content: "Lorem Ipsum dolor sit amet", Posted: time1, }, { ID: 101480, PostedUID: 1855589, ItemID: 400000101, Content: "Lorem Ipsum dolor sit amet", FileAttachment: &fileAttachment{ FileName: "file.md", FileType: "text/plain", FileSize: 12345, FileURL: "https://vikunja.io/testimage.jpg", // Using an image which we are hosting, so it'll still be up UploadState: "completed", }, Posted: time1, }, }, ProjectNotes: []*projectNote{ { ID: 102000, Content: "Lorem Ipsum dolor sit amet", ProjectID: 396936926, Posted: time3, PostedUID: 1855589, }, { ID: 102001, Content: "Lorem Ipsum dolor sit amet 2", ProjectID: 396936926, Posted: time3, PostedUID: 1855589, }, { ID: 102002, Content: "Lorem Ipsum dolor sit amet 3", ProjectID: 396936926, Posted: time3, PostedUID: 1855589, }, { ID: 102003, Content: "Lorem Ipsum dolor sit amet 4", ProjectID: 396936927, Posted: time3, PostedUID: 1855589, }, { ID: 102004, Content: "Lorem Ipsum dolor sit amet 5", ProjectID: 396936927, Posted: time3, PostedUID: 1855589, }, }, Reminders: []*reminder{ { ID: 103000, ItemID: 400000000, Due: &dueDate{ Date: "2020-06-15", IsRecurring: false, }, MmOffset: 180, }, { ID: 103001, ItemID: 400000000, Due: &dueDate{ Date: "2020-06-16T07:00:00", IsRecurring: false, }, }, { ID: 103002, ItemID: 400000002, Due: &dueDate{ Date: "2020-07-15T07:00:00Z", IsRecurring: true, }, }, { ID: 103003, ItemID: 400000003, Due: &dueDate{ Date: "2020-06-15T07:00:00", IsRecurring: false, }, }, { ID: 103004, ItemID: 400000005, Due: &dueDate{ Date: "2020-06-15T07:00:00", IsRecurring: false, }, }, { ID: 103006, ItemID: 400000009, Due: &dueDate{ Date: "2020-06-15T07:00:00", IsRecurring: false, }, }, }, Sections: []*section{ { ID: 1234, Name: "Some Bucket", ProjectID: 396936926, }, }, } vikunjaLabels := []*models.Label{ { Title: "Label1", HexColor: todoistColors[30], }, { Title: "Label2", HexColor: todoistColors[31], }, { Title: "Label3", HexColor: todoistColors[32], }, { Title: "Label4", HexColor: todoistColors[33], }, } expectedHierachie := []*models.NamespaceWithListsAndTasks{ { Namespace: models.Namespace{ Title: "Migrated from todoist", }, Lists: []*models.ListWithTasksAndBuckets{ { List: models.List{ Title: "Project1", Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3", HexColor: todoistColors[30], }, Buckets: []*models.Bucket{ { ID: 1234, Title: "Some Bucket", }, }, Tasks: []*models.TaskWithComments{ { Task: models.Task{ Title: "Task400000000", Description: "Lorem Ipsum dolor sit amet", Done: false, Created: time1, Reminders: []time.Time{ time.Date(2020, time.June, 15, 23, 59, 0, 0, time.UTC).In(config.GetTimeZone()), time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()), }, }, }, { Task: models.Task{ Title: "Task400000001", Description: "Lorem Ipsum dolor sit amet", Done: false, Created: time1, }, }, { Task: models.Task{ Title: "Task400000002", Done: false, Created: time1, Reminders: []time.Time{ time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()), }, }, }, { Task: models.Task{ Title: "Task400000003", Description: "Lorem Ipsum dolor sit amet", Done: true, DueDate: dueTime, Created: time1, DoneAt: time3, Labels: vikunjaLabels, Reminders: []time.Time{ time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()), }, }, }, { Task: models.Task{ Title: "Task400000004", Done: false, Created: time1, Labels: vikunjaLabels, }, }, { Task: models.Task{ Title: "Task400000005", Done: true, DueDate: dueTime, Created: time1, DoneAt: time3, Reminders: []time.Time{ time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()), }, }, }, { Task: models.Task{ Title: "Task400000006", Done: true, DueDate: dueTime, Created: time1, DoneAt: time3, RelatedTasks: map[models.RelationKind][]*models.Task{ models.RelationKindSubtask: { { Title: "Task with parent", Done: false, Priority: 2, Created: time1, DoneAt: nilTime, }, }, }, }, }, { Task: models.Task{ Title: "Task400000106", Done: true, DueDate: dueTimeWithTime, Created: time1, DoneAt: time3, Labels: vikunjaLabels, }, }, { Task: models.Task{ Title: "Task400000107", Done: true, Created: time1, DoneAt: time3, }, }, { Task: models.Task{ Title: "Task400000108", Done: true, Created: time1, DoneAt: time3, }, }, { Task: models.Task{ Title: "Task400000109", Done: true, Created: time1, DoneAt: time3, BucketID: 1234, }, }, }, }, { List: models.List{ Title: "Project2", Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5", HexColor: todoistColors[37], }, Tasks: []*models.TaskWithComments{ { Task: models.Task{ Title: "Task400000007", Done: false, DueDate: dueTime, Created: time1, }, }, { Task: models.Task{ Title: "Task400000008", Done: false, DueDate: dueTime, Created: time1, }, }, { Task: models.Task{ Title: "Task400000009", Done: false, Created: time1, Reminders: []time.Time{ time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()), }, }, }, { Task: models.Task{ Title: "Task400000010", Description: "Lorem Ipsum dolor sit amet", Done: true, Created: time1, DoneAt: time3, }, }, { Task: models.Task{ Title: "Task400000101", Description: "Lorem Ipsum dolor sit amet", Done: false, Created: time1, Attachments: []*models.TaskAttachment{ { File: &files.File{ Name: "file.md", Mime: "text/plain", Size: 12345, Created: time1, FileContent: exampleFile, }, Created: time1, }, }, }, }, { Task: models.Task{ Title: "Task400000102", Done: false, DueDate: dueTime, Created: time1, Labels: vikunjaLabels, }, }, { Task: models.Task{ Title: "Task400000103", Done: false, Created: time1, Labels: vikunjaLabels, }, }, { Task: models.Task{ Title: "Task400000104", Done: false, Created: time1, Labels: vikunjaLabels, }, }, { Task: models.Task{ Title: "Task400000105", Done: false, DueDate: dueTime, Created: time1, Labels: vikunjaLabels, }, }, }, }, { List: models.List{ Title: "Project3 - Archived", HexColor: todoistColors[37], IsArchived: true, }, Tasks: []*models.TaskWithComments{ { Task: models.Task{ Title: "Task400000111", Done: true, Created: time1, DoneAt: time3, }, }, }, }, }, }, } doneItems := make(map[int64]*doneItem) hierachie, err := convertTodoistToVikunja(testSync, doneItems) assert.NoError(t, err) assert.NotNil(t, hierachie) if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal { t.Errorf("converted todoist data = %v, want %v, diff: %v", hierachie, expectedHierachie, diff) } }