/* Copyright 2025 Dnote Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package sync import ( "fmt" "os" "testing" "github.com/dnote/dnote/pkg/assert" cliDatabase "github.com/dnote/dnote/pkg/cli/database" "github.com/dnote/dnote/pkg/cli/testutils" clitest "github.com/dnote/dnote/pkg/cli/testutils" "github.com/dnote/dnote/pkg/server/database" apitest "github.com/dnote/dnote/pkg/server/testutils" ) func TestSync_Empty(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { return map[string]string{} } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { // Test checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 0, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 0, serverBookCount: 0, serverUserMaxUSN: 0, }) } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) } func TestSync_oneway(t *testing.T) { t.Run("cli to api only", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) { apitest.MustExec(t, env.ServerDB.Model(&user).Update("max_usn", 0), "updating user max_usn") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") } assert := func(t *testing.T, env testEnv, user database.User) { cliDB := env.DB // test client checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 3, clientBookCount: 2, clientLastMaxUSN: 5, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 3, serverBookCount: 2, serverUserMaxUSN: 5, }) var cliBookJS, cliBookCSS cliDatabase.Book var cliNote1JS, cliNote2JS, cliNote1CSS cliDatabase.Note cliDatabase.MustScan(t, "finding cli book js", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cli book css", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote2JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliNote2JS.UUID, &cliNote2JS.Body, &cliNote2JS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body, &cliNote1CSS.USN) // assert on usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliNote2JS.USN, 0, "cliNote2JS USN mismatch") assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS USN mismatch") assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote2JS.Body, "js2", "cliNote2JS Body mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote2JS.Deleted, false, "cliNote2JS Deleted mismatch") assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") // test server var apiBookJS, apiBookCSS database.Book var apiNote1JS, apiNote2JS, apiNote1CSS database.Note apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Where("uuid = ?", cliNote1JS.UUID).First(&apiNote1JS), "getting js1 note") apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Where("uuid = ?", cliNote2JS.UUID).First(&apiNote2JS), "getting js2 note") apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Where("uuid = ?", cliNote1CSS.UUID).First(&apiNote1CSS), "getting css1 note") apitest.MustExec(t, env.ServerDB.Model(&database.Book{}).Where("uuid = ?", cliBookJS.UUID).First(&apiBookJS), "getting js book") apitest.MustExec(t, env.ServerDB.Model(&database.Book{}).Where("uuid = ?", cliBookCSS.UUID).First(&apiBookCSS), "getting css book") // assert usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote2JS.USN, 0, "apiNote2JS usn mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS usn mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS usn mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS usn mismatch") // client must have generated uuids assert.NotEqual(t, apiNote1JS.UUID, "", "apiNote1JS UUID mismatch") assert.NotEqual(t, apiNote2JS.UUID, "", "apiNote2JS UUID mismatch") assert.NotEqual(t, apiNote1CSS.UUID, "", "apiNote1CSS UUID mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote2JS.Deleted, false, "apiNote2JS Deleted mismatch") assert.Equal(t, apiNote1CSS.Deleted, false, "apiNote1CSS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") // assert on body and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote2JS.Body, "js2", "apiNote2JS Body mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") } t.Run("stepSync", func(t *testing.T) { env := setupTestEnv(t) user := setupUserAndLogin(t, env) setup(t, env, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") assert(t, env, user) }) t.Run("fullSync", func(t *testing.T) { env := setupTestEnv(t) user := setupUserAndLogin(t, env) setup(t, env, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") assert(t, env, user) }) }) t.Run("cli to api with edit and delete", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) { apiDB := env.ServerDB apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js3") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css2") var nid, nid2 string cliDB := env.DB cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js3"), &nid) cliDatabase.MustScan(t, "getting id of note to delete", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "css2"), &nid2) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js3-edited") clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "css", nid2) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css3") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css4") } assert := func(t *testing.T, env testEnv, user database.User) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 6, clientBookCount: 2, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 6, serverBookCount: 2, serverUserMaxUSN: 8, }) // test cli var cliN1, cliN2, cliN3, cliN4, cliN5, cliN6 cliDatabase.Note var cliB1, cliB2 cliDatabase.Book cliDatabase.MustScan(t, "finding cliN1", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliN1.UUID, &cliN1.Body, &cliN1.USN) cliDatabase.MustScan(t, "finding cliN2", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliN2.UUID, &cliN2.Body, &cliN2.USN) cliDatabase.MustScan(t, "finding cliN3", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js3-edited"), &cliN3.UUID, &cliN3.Body, &cliN3.USN) cliDatabase.MustScan(t, "finding cliN4", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliN4.UUID, &cliN4.Body, &cliN4.USN) cliDatabase.MustScan(t, "finding cliN5", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css3"), &cliN5.UUID, &cliN5.Body, &cliN5.USN) cliDatabase.MustScan(t, "finding cliN6", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css4"), &cliN6.UUID, &cliN6.Body, &cliN6.USN) cliDatabase.MustScan(t, "finding cliB1", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliB1.UUID, &cliB1.Label, &cliB1.USN) cliDatabase.MustScan(t, "finding cliB2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliB2.UUID, &cliB2.Label, &cliB2.USN) // assert on usn assert.NotEqual(t, cliN1.USN, 0, "cliN1 USN mismatch") assert.NotEqual(t, cliN2.USN, 0, "cliN2 USN mismatch") assert.NotEqual(t, cliN3.USN, 0, "cliN3 USN mismatch") assert.NotEqual(t, cliN4.USN, 0, "cliN4 USN mismatch") assert.NotEqual(t, cliN5.USN, 0, "cliN5 USN mismatch") assert.NotEqual(t, cliN6.USN, 0, "cliN6 USN mismatch") assert.NotEqual(t, cliB1.USN, 0, "cliB1 USN mismatch") assert.NotEqual(t, cliB2.USN, 0, "cliB2 USN mismatch") // assert on bodys and labels assert.Equal(t, cliN1.Body, "js1", "cliN1 Body mismatch") assert.Equal(t, cliN2.Body, "js2", "cliN2 Body mismatch") assert.Equal(t, cliN3.Body, "js3-edited", "cliN3 Body mismatch") assert.Equal(t, cliN4.Body, "css1", "cliN4 Body mismatch") assert.Equal(t, cliN5.Body, "css3", "cliN5 Body mismatch") assert.Equal(t, cliN6.Body, "css4", "cliN6 Body mismatch") assert.Equal(t, cliB1.Label, "js", "cliB1 Label mismatch") assert.Equal(t, cliB2.Label, "css", "cliB2 Label mismatch") // assert on deleted assert.Equal(t, cliN1.Deleted, false, "cliN1 Deleted mismatch") assert.Equal(t, cliN2.Deleted, false, "cliN2 Deleted mismatch") assert.Equal(t, cliN3.Deleted, false, "cliN3 Deleted mismatch") assert.Equal(t, cliN4.Deleted, false, "cliN4 Deleted mismatch") assert.Equal(t, cliN5.Deleted, false, "cliN5 Deleted mismatch") assert.Equal(t, cliN6.Deleted, false, "cliN6 Deleted mismatch") assert.Equal(t, cliB1.Deleted, false, "cliB1 Deleted mismatch") assert.Equal(t, cliB2.Deleted, false, "cliB2 Deleted mismatch") // test api var apiN1, apiN2, apiN3, apiN4, apiN5, apiN6 database.Note var apiB1, apiB2 database.Book apitest.MustExec(t, apiDB.Where("uuid = ?", cliN1.UUID).First(&apiN1), "finding apiN1") apitest.MustExec(t, apiDB.Where("uuid = ?", cliN2.UUID).First(&apiN2), "finding apiN2") apitest.MustExec(t, apiDB.Where("uuid = ?", cliN3.UUID).First(&apiN3), "finding apiN3") apitest.MustExec(t, apiDB.Where("uuid = ?", cliN4.UUID).First(&apiN4), "finding apiN4") apitest.MustExec(t, apiDB.Where("uuid = ?", cliN5.UUID).First(&apiN5), "finding apiN5") apitest.MustExec(t, apiDB.Where("uuid = ?", cliN6.UUID).First(&apiN6), "finding apiN6") apitest.MustExec(t, apiDB.Where("uuid = ?", cliB1.UUID).First(&apiB1), "finding apiB1") apitest.MustExec(t, apiDB.Where("uuid = ?", cliB2.UUID).First(&apiB2), "finding apiB2") // assert on usn assert.NotEqual(t, apiN1.USN, 0, "apiN1 usn mismatch") assert.NotEqual(t, apiN2.USN, 0, "apiN2 usn mismatch") assert.NotEqual(t, apiN3.USN, 0, "apiN3 usn mismatch") assert.NotEqual(t, apiN4.USN, 0, "apiN4 usn mismatch") assert.NotEqual(t, apiN5.USN, 0, "apiN5 usn mismatch") assert.NotEqual(t, apiN6.USN, 0, "apiN6 usn mismatch") assert.NotEqual(t, apiB1.USN, 0, "apiB1 usn mismatch") assert.NotEqual(t, apiB2.USN, 0, "apiB2 usn mismatch") // client must have generated uuids assert.NotEqual(t, apiN1.UUID, "", "apiN1 UUID mismatch") assert.NotEqual(t, apiN2.UUID, "", "apiN2 UUID mismatch") assert.NotEqual(t, apiN3.UUID, "", "apiN3 UUID mismatch") assert.NotEqual(t, apiN4.UUID, "", "apiN4 UUID mismatch") assert.NotEqual(t, apiN5.UUID, "", "apiN5 UUID mismatch") assert.NotEqual(t, apiN6.UUID, "", "apiN6 UUID mismatch") assert.NotEqual(t, apiB1.UUID, "", "apiB1 UUID mismatch") assert.NotEqual(t, apiB2.UUID, "", "apiB2 UUID mismatch") // assert on deleted assert.Equal(t, apiN1.Deleted, false, "apiN1 Deleted mismatch") assert.Equal(t, apiN2.Deleted, false, "apiN2 Deleted mismatch") assert.Equal(t, apiN3.Deleted, false, "apiN3 Deleted mismatch") assert.Equal(t, apiN4.Deleted, false, "apiN4 Deleted mismatch") assert.Equal(t, apiN5.Deleted, false, "apiN5 Deleted mismatch") assert.Equal(t, apiN6.Deleted, false, "apiN6 Deleted mismatch") assert.Equal(t, apiB1.Deleted, false, "apiB1 Deleted mismatch") assert.Equal(t, apiB2.Deleted, false, "apiB2 Deleted mismatch") // assert on body and labels assert.Equal(t, apiN1.Body, "js1", "apiN1 Body mismatch") assert.Equal(t, apiN2.Body, "js2", "apiN2 Body mismatch") assert.Equal(t, apiN3.Body, "js3-edited", "apiN3 Body mismatch") assert.Equal(t, apiN4.Body, "css1", "apiN4 Body mismatch") assert.Equal(t, apiN5.Body, "css3", "apiN5 Body mismatch") assert.Equal(t, apiN6.Body, "css4", "apiN6 Body mismatch") assert.Equal(t, apiB1.Label, "js", "apiB1 Label mismatch") assert.Equal(t, apiB2.Label, "css", "apiB2 Label mismatch") } t.Run("stepSync", func(t *testing.T) { env := setupTestEnv(t) user := setupUserAndLogin(t, env) setup(t, env, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") assert(t, env, user) }) t.Run("fullSync", func(t *testing.T) { env := setupTestEnv(t) user := setupUserAndLogin(t, env) setup(t, env, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") assert(t, env, user) }) }) t.Run("api to cli", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { apiDB := env.ServerDB apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") jsNote2UUID := apiCreateNote(t, env, user, jsBookUUID, "js2", "adding js note 2") cssNote2UUID := apiCreateNote(t, env, user, cssBookUUID, "css2", "adding css note 2") linuxBookUUID := apiCreateBook(t, env, user, "linux", "adding linux book") linuxNote1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux1", "adding linux note 1") apiPatchNote(t, env, user, jsNote2UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, linuxBookUUID), "moving js note 2 to linux") apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") cssNote3UUID := apiCreateNote(t, env, user, cssBookUUID, "css3", "adding css note 3") bashBookUUID := apiCreateBook(t, env, user, "bash", "adding bash book") bashNote1UUID := apiCreateNote(t, env, user, bashBookUUID, "bash1", "adding bash note 1") // delete the linux book and its two notes apiDeleteBook(t, env, user, linuxBookUUID, "deleting linux book") apiPatchNote(t, env, user, cssNote2UUID, fmt.Sprintf(`{"content": "%s"}`, "css2-edited"), "editing css 2 body") bashNote2UUID := apiCreateNote(t, env, user, bashBookUUID, "bash2", "adding bash note 2") linuxBook2UUID := apiCreateBook(t, env, user, "linux", "adding new linux book") linux2Note1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux-new-1", "adding linux note 1") apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "jsNote2UUID": jsNote2UUID, "cssBookUUID": cssBookUUID, "cssNote1UUID": cssNote1UUID, "cssNote2UUID": cssNote2UUID, "cssNote3UUID": cssNote3UUID, "linuxBookUUID": linuxBookUUID, "linuxNote1UUID": linuxNote1UUID, "bashBookUUID": bashBookUUID, "bashNote1UUID": bashNote1UUID, "bashNote2UUID": bashNote2UUID, "linuxBook2UUID": linuxBook2UUID, "linux2Note1UUID": linux2Note1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 6, clientBookCount: 3, clientLastMaxUSN: 21, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 9, serverBookCount: 5, serverUserMaxUSN: 21, }) // test server var apiNote1JS, apiNote2JS, apiNote1CSS, apiNote2CSS, apiNote3CSS, apiNote1Bash, apiNote2Bash, apiNote1Linux, apiNote2Linux, apiNote1LinuxDup database.Note var apiBookJS, apiBookCSS, apiBookBash, apiBookLinux, apiBookLinuxDup database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding api js note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote2UUID"]).First(&apiNote2JS), "finding api js note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote1UUID"]).First(&apiNote1CSS), "finding api css note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote2UUID"]).First(&apiNote2CSS), "finding api css note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote3UUID"]).First(&apiNote3CSS), "finding api css note 3") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxNote1UUID"]).First(&apiNote1Linux), "finding api linux note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote2UUID"]).First(&apiNote2Linux), "finding api linux note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashNote1UUID"]).First(&apiNote1Bash), "finding api bash note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashNote2UUID"]).First(&apiNote2Bash), "finding api bash note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linux2Note1UUID"]).First(&apiNote1LinuxDup), "finding api linux 2 note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding api js book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding api css book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashBookUUID"]).First(&apiBookBash), "finding api bash book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxBookUUID"]).First(&apiBookLinux), "finding api linux book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxBook2UUID"]).First(&apiBookLinuxDup), "finding api linux book 2") // assert on server Label assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote2JS.USN, 0, "apiNote2JS USN mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS USN mismatch") assert.NotEqual(t, apiNote2CSS.USN, 0, "apiNote2CSS USN mismatch") assert.NotEqual(t, apiNote3CSS.USN, 0, "apiNote3CSS USN mismatch") assert.NotEqual(t, apiNote1Linux.USN, 0, "apiNote1Linux USN mismatch") assert.NotEqual(t, apiNote2Linux.USN, 0, "apiNote2Linux USN mismatch") assert.NotEqual(t, apiNote1Bash.USN, 0, "apiNote1Bash USN mismatch") assert.NotEqual(t, apiNote2Bash.USN, 0, "apiNote2Bash USN mismatch") assert.NotEqual(t, apiNote1LinuxDup.USN, 0, "apiNote1LinuxDup USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apibookJS USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apibookCSS USN mismatch") assert.NotEqual(t, apiBookBash.USN, 0, "apibookBash USN mismatch") assert.NotEqual(t, apiBookLinux.USN, 0, "apibookLinux USN mismatch") assert.NotEqual(t, apiBookLinuxDup.USN, 0, "apiBookLinuxDup USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote2JS.Body, "", "apiNote2JS Body mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiNote2CSS.Body, "css2-edited", "apiNote2CSS Body mismatch") assert.Equal(t, apiNote3CSS.Body, "css3", "apiNote3CSS Body mismatch") assert.Equal(t, apiNote1Linux.Body, "", "apiNote1Linux Body mismatch") assert.Equal(t, apiNote2Linux.Body, "", "apiNote2Linux Body mismatch") assert.Equal(t, apiNote1Bash.Body, "bash1", "apiNote1Bash Body mismatch") assert.Equal(t, apiNote2Bash.Body, "bash2", "apiNote2Bash Body mismatch") assert.Equal(t, apiNote1LinuxDup.Body, "linux-new-1", "apiNote1LinuxDup Body mismatch") assert.Equal(t, apiBookJS.Label, "", "apibookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apibookCSS Label mismatch") assert.Equal(t, apiBookBash.Label, "bash", "apibookBash Label mismatch") assert.Equal(t, apiBookLinux.Label, "", "apibookLinux Label mismatch") assert.Equal(t, apiBookLinuxDup.Label, "linux", "apiBookLinuxDup Label mismatch") // assert on uuids assert.NotEqual(t, apiNote1JS.UUID, "", "apiNote1JS UUID mismatch") assert.NotEqual(t, apiNote2JS.UUID, "", "apiNote2JS UUID mismatch") assert.NotEqual(t, apiNote1CSS.UUID, "", "apiNote1CSS UUID mismatch") assert.NotEqual(t, apiNote2CSS.UUID, "", "apiNote2CSS UUID mismatch") assert.NotEqual(t, apiNote3CSS.UUID, "", "apiNote3CSS UUID mismatch") assert.NotEqual(t, apiNote1Linux.UUID, "", "apiNote1Linux UUID mismatch") assert.NotEqual(t, apiNote2Linux.UUID, "", "apiNote2Linux UUID mismatch") assert.NotEqual(t, apiNote1Bash.UUID, "", "apiNote1Bash UUID mismatch") assert.NotEqual(t, apiNote2Bash.UUID, "", "apiNote2Bash UUID mismatch") assert.NotEqual(t, apiNote2Bash.UUID, "", "apiNote2Bash UUID mismatch") assert.NotEqual(t, apiBookJS.UUID, "", "apibookJS UUID mismatch") assert.NotEqual(t, apiBookCSS.UUID, "", "apibookCSS UUID mismatch") assert.NotEqual(t, apiBookBash.UUID, "", "apibookBash UUID mismatch") assert.NotEqual(t, apiBookLinux.UUID, "", "apibookLinux UUID mismatch") assert.NotEqual(t, apiBookLinuxDup.UUID, "", "apiBookLinuxDup UUID mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote2JS.Deleted, true, "apiNote2JS Deleted mismatch") assert.Equal(t, apiNote1CSS.Deleted, false, "apiNote1CSS Deleted mismatch") assert.Equal(t, apiNote2CSS.Deleted, false, "apiNote2CSS Deleted mismatch") assert.Equal(t, apiNote3CSS.Deleted, false, "apiNote3CSS Deleted mismatch") assert.Equal(t, apiNote1Linux.Deleted, true, "apiNote1Linux Deleted mismatch") assert.Equal(t, apiNote2Linux.Deleted, true, "apiNote2Linux Deleted mismatch") assert.Equal(t, apiNote1Bash.Deleted, false, "apiNote1Bash Deleted mismatch") assert.Equal(t, apiNote2Bash.Deleted, false, "apiNote2Bash Deleted mismatch") assert.Equal(t, apiNote2Bash.Deleted, false, "apiNote2Bash Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, true, "apibookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apibookCSS Deleted mismatch") assert.Equal(t, apiBookBash.Deleted, false, "apibookBash Deleted mismatch") assert.Equal(t, apiBookLinux.Deleted, true, "apibookLinux Deleted mismatch") assert.Equal(t, apiBookLinuxDup.Deleted, false, "apiBookLinuxDup Deleted mismatch") // test client var cliBookCSS, cliBookBash, cliBookLinux cliDatabase.Book var cliNote1CSS, cliNote2CSS, cliNote3CSS, cliNote1Bash, cliNote2Bash, cliNote1Linux cliDatabase.Note cliDatabase.MustScan(t, "finding cli book css", cliDB.QueryRow("SELECT label FROM books WHERE uuid = ?", ids["cssBookUUID"]), &cliBookCSS.Label) cliDatabase.MustScan(t, "finding cli book bash", cliDB.QueryRow("SELECT label FROM books WHERE uuid = ?", ids["bashBookUUID"]), &cliBookBash.Label) cliDatabase.MustScan(t, "finding cli book linux2", cliDB.QueryRow("SELECT label FROM books WHERE uuid = ?", ids["linuxBook2UUID"]), &cliBookLinux.Label) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", apiNote1CSS.UUID), &cliNote1CSS.Body, &cliNote1CSS.USN) cliDatabase.MustScan(t, "finding cliNote2CSS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", apiNote2CSS.UUID), &cliNote2CSS.Body, &cliNote2CSS.USN) cliDatabase.MustScan(t, "finding cliNote3CSS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", apiNote3CSS.UUID), &cliNote3CSS.Body, &cliNote3CSS.USN) cliDatabase.MustScan(t, "finding cliNote1Bash", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", apiNote1Bash.UUID), &cliNote1Bash.Body, &cliNote1Bash.USN) cliDatabase.MustScan(t, "finding cliNote2Bash", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", apiNote2Bash.UUID), &cliNote2Bash.Body, &cliNote2Bash.USN) cliDatabase.MustScan(t, "finding cliNote2Bash", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", apiNote1LinuxDup.UUID), &cliNote1Linux.Body, &cliNote1Linux.USN) // assert on usn assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS usn mismatch") assert.NotEqual(t, cliNote2CSS.USN, 0, "cliNote2CSS usn mismatch") assert.NotEqual(t, cliNote3CSS.USN, 0, "cliNote3CSS usn mismatch") assert.NotEqual(t, cliNote1Bash.USN, 0, "cliNote1Bash usn mismatch") assert.NotEqual(t, cliNote2Bash.USN, 0, "cliNote2Bash usn mismatch") assert.NotEqual(t, cliNote1Linux.USN, 0, "cliNote1Linux usn mismatch") // assert on bodys and labels assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliNote2CSS.Body, "css2-edited", "cliNote2CSS Body mismatch") assert.Equal(t, cliNote3CSS.Body, "css3", "cliNote3CSS Body mismatch") assert.Equal(t, cliNote1Bash.Body, "bash1", "cliNote1Bash Body mismatch") assert.Equal(t, cliNote2Bash.Body, "bash2", "cliNote2Bash Body mismatch") assert.Equal(t, cliNote1Linux.Body, "linux-new-1", "cliNote1Linux Body mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") assert.Equal(t, cliBookBash.Label, "bash", "cliBookBash Label mismatch") assert.Equal(t, cliBookLinux.Label, "linux", "cliBookLinux Label mismatch") // assert on deleted assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliNote2CSS.Deleted, false, "cliNote2CSS Deleted mismatch") assert.Equal(t, cliNote3CSS.Deleted, false, "cliNote3CSS Deleted mismatch") assert.Equal(t, cliNote1Bash.Deleted, false, "cliNote1Bash Deleted mismatch") assert.Equal(t, cliNote2Bash.Deleted, false, "cliNote2Bash Deleted mismatch") assert.Equal(t, cliNote1Linux.Deleted, false, "cliNote1Linux Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") assert.Equal(t, cliBookBash.Deleted, false, "cliBookBash Deleted mismatch") assert.Equal(t, cliBookLinux.Deleted, false, "cliBookLinux Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) } func TestSync_twoway(t *testing.T) { t.Run("once", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") jsNote2UUID := apiCreateNote(t, env, user, jsBookUUID, "js2", "adding js note 2") cssNote2UUID := apiCreateNote(t, env, user, cssBookUUID, "css2", "adding css note 2") linuxBookUUID := apiCreateBook(t, env, user, "linux", "adding linux book") linuxNote1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux1", "adding linux note 1") apiPatchNote(t, env, user, jsNote2UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, linuxBookUUID), "moving js note 2 to linux") apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") cssNote3UUID := apiCreateNote(t, env, user, cssBookUUID, "css3", "adding css note 3") bashBookUUID := apiCreateBook(t, env, user, "bash", "adding bash book") bashNote1UUID := apiCreateNote(t, env, user, bashBookUUID, "bash1", "adding bash note 1") apiDeleteBook(t, env, user, linuxBookUUID, "deleting linux book") apiPatchNote(t, env, user, cssNote2UUID, fmt.Sprintf(`{"content": "%s"}`, "css2-edited"), "editing css 2 body") bashNote2UUID := apiCreateNote(t, env, user, bashBookUUID, "bash2", "adding bash note 2") linuxBook2UUID := apiCreateBook(t, env, user, "linux", "adding new linux book") linux2Note1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux-new-1", "adding linux note 1") apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js3") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js4") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms2") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "math", "-c", "math1") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js3"), &nid) clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "algorithms") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css4") clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "jsNote2UUID": jsNote2UUID, "cssBookUUID": cssBookUUID, "cssNote1UUID": cssNote1UUID, "cssNote2UUID": cssNote2UUID, "cssNote3UUID": cssNote3UUID, "linuxBookUUID": linuxBookUUID, "linuxNote1UUID": linuxNote1UUID, "bashBookUUID": bashBookUUID, "bashNote1UUID": bashNote1UUID, "bashNote2UUID": bashNote2UUID, "linuxBook2UUID": linuxBook2UUID, "linux2Note1UUID": linux2Note1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 9, clientBookCount: 6, clientLastMaxUSN: 27, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 12, serverBookCount: 8, serverUserMaxUSN: 27, }) // test client var cliNote1CSS, cliNote2CSS, cliNote3CSS, cliNote1CSS2, cliNote1Bash, cliNote2Bash, cliNote1Linux, cliNote1Math, cliNote1JS cliDatabase.Note var cliBookCSS, cliBookCSS2, cliBookBash, cliBookLinux, cliBookMath, cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body, &cliNote1CSS.USN) cliDatabase.MustScan(t, "finding cliNote2CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css2-edited"), &cliNote2CSS.UUID, &cliNote2CSS.Body, &cliNote2CSS.USN) cliDatabase.MustScan(t, "finding cliNote3CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css3"), &cliNote3CSS.UUID, &cliNote3CSS.Body, &cliNote3CSS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS2", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css4"), &cliNote1CSS2.UUID, &cliNote1CSS2.Body, &cliNote1CSS2.USN) cliDatabase.MustScan(t, "finding cliNote1Bash", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "bash1"), &cliNote1Bash.UUID, &cliNote1Bash.Body, &cliNote1Bash.USN) cliDatabase.MustScan(t, "finding cliNote2Bash", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "bash2"), &cliNote2Bash.UUID, &cliNote2Bash.Body, &cliNote2Bash.USN) cliDatabase.MustScan(t, "finding cliNote1Linux", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "linux-new-1"), &cliNote1Linux.UUID, &cliNote1Linux.Body, &cliNote1Linux.USN) cliDatabase.MustScan(t, "finding cliNote1Math", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "math1"), &cliNote1Math.UUID, &cliNote1Math.Body, &cliNote1Math.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js4"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliBookCSS2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) cliDatabase.MustScan(t, "finding cliBookBash", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "bash"), &cliBookBash.UUID, &cliBookBash.Label, &cliBookBash.USN) cliDatabase.MustScan(t, "finding cliBookLinux", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "linux"), &cliBookLinux.UUID, &cliBookLinux.Label, &cliBookLinux.USN) cliDatabase.MustScan(t, "finding cliBookMath", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "math"), &cliBookMath.UUID, &cliBookMath.Label, &cliBookMath.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS USN mismatch") assert.NotEqual(t, cliNote2CSS.USN, 0, "cliNote2CSS USN mismatch") assert.NotEqual(t, cliNote3CSS.USN, 0, "cliNote3CSS USN mismatch") assert.NotEqual(t, cliNote1CSS2.USN, 0, "cliNote1CSS2 USN mismatch") assert.NotEqual(t, cliNote1Bash.USN, 0, "cliNote1Bash USN mismatch") assert.NotEqual(t, cliNote2Bash.USN, 0, "cliNote2Bash USN mismatch") assert.NotEqual(t, cliNote1Linux.USN, 0, "cliNote1Linux USN mismatch") assert.NotEqual(t, cliNote1Math.USN, 0, "cliNote1Math USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliBookCSS2.USN, 0, "cliBookCSS2 USN mismatch") assert.NotEqual(t, cliBookBash.USN, 0, "cliBookBash USN mismatch") assert.NotEqual(t, cliBookMath.USN, 0, "cliBookMath USN mismatch") assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliNote2CSS.Body, "css2-edited", "cliNote2CSS Body mismatch") assert.Equal(t, cliNote3CSS.Body, "css3", "cliNote3CSS Body mismatch") assert.Equal(t, cliNote1CSS2.Body, "css4", "cliNote1CSS2 Body mismatch") assert.Equal(t, cliNote1Bash.Body, "bash1", "cliNote1Bash Body mismatch") assert.Equal(t, cliNote2Bash.Body, "bash2", "cliNote2Bash Body mismatch") assert.Equal(t, cliNote1Linux.Body, "linux-new-1", "cliNote1Linux Body mismatch") assert.Equal(t, cliNote1Math.Body, "math1", "cliNote1Math Body mismatch") assert.Equal(t, cliNote1JS.Body, "js4", "cliNote1JS Body mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") assert.Equal(t, cliBookCSS2.Label, "css_2", "cliBookCSS2 Label mismatch") assert.Equal(t, cliBookBash.Label, "bash", "cliBookBash Label mismatch") assert.Equal(t, cliBookMath.Label, "math", "cliBookMath Label mismatch") assert.Equal(t, cliBookLinux.Label, "linux", "cliBookLinux Label mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliNote2CSS.Deleted, false, "cliNote2CSS Deleted mismatch") assert.Equal(t, cliNote3CSS.Deleted, false, "cliNote3CSS Deleted mismatch") assert.Equal(t, cliNote1CSS2.Deleted, false, "cliNote1CSS2 Deleted mismatch") assert.Equal(t, cliNote1Bash.Deleted, false, "cliNote1Bash Deleted mismatch") assert.Equal(t, cliNote2Bash.Deleted, false, "cliNote2Bash Deleted mismatch") assert.Equal(t, cliNote1Linux.Deleted, false, "cliNote1Linux Deleted mismatch") assert.Equal(t, cliNote1Math.Deleted, false, "cliNote1Math Deleted mismatch") assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") assert.Equal(t, cliBookCSS2.Deleted, false, "cliBookCSS2 Deleted mismatch") assert.Equal(t, cliBookBash.Deleted, false, "cliBookBash Deleted mismatch") assert.Equal(t, cliBookMath.Deleted, false, "cliBookMath Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // test server var apiNote1JS, apiNote1CSS, apiNote2CSS, apiNote3CSS, apiNote1Linux, apiNote2Linux, apiNote1Bash, apiNote2Bash, apiNote1LinuxDup, apiNote1CSS2, apiNote1Math, apiNote1JS2 database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding api js note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote1UUID"]).First(&apiNote1CSS), "finding api css note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote2UUID"]).First(&apiNote2CSS), "finding api css note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote3UUID"]).First(&apiNote3CSS), "finding api css note 3") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxNote1UUID"]).First(&apiNote1Linux), "finding api linux note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote2UUID"]).First(&apiNote2Linux), "finding api linux note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashNote1UUID"]).First(&apiNote1Bash), "finding api bash note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashNote2UUID"]).First(&apiNote2Bash), "finding api bash note 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linux2Note1UUID"]).First(&apiNote1LinuxDup), "finding api linux 2 note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1CSS2.UUID).First(&apiNote1CSS2), "finding apiNote1CSS2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1Math.UUID).First(&apiNote1Math), "finding apiNote1Math") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1JS.UUID).First(&apiNote1JS2), "finding apiNote1JS2") var apiBookJS, apiBookCSS, apiBookLinux, apiBookBash, apiBookLinuxDup, apiBookCSS2, apiBookMath, apiBookJS2 database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding api js book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding api css book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashBookUUID"]).First(&apiBookBash), "finding api bash book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxBookUUID"]).First(&apiBookLinux), "finding api linux book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxBook2UUID"]).First(&apiBookLinuxDup), "finding api linux book 2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookCSS2.UUID).First(&apiBookCSS2), "finding apiBookCSS2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookMath.UUID).First(&apiBookMath), "finding apiBookMath") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookJS.UUID).First(&apiBookJS2), "finding apiBookJS2") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS usn mismatch") assert.NotEqual(t, apiNote2CSS.USN, 0, "apiNote2CSS usn mismatch") assert.NotEqual(t, apiNote3CSS.USN, 0, "apiNote3CSS usn mismatch") assert.NotEqual(t, apiNote1Linux.USN, 0, "apiNote1Linux usn mismatch") assert.NotEqual(t, apiNote2Linux.USN, 0, "apiNote2Linux usn mismatch") assert.NotEqual(t, apiNote1Bash.USN, 0, "apiNote1Bash usn mismatch") assert.NotEqual(t, apiNote2Bash.USN, 0, "apiNote2Bash usn mismatch") assert.NotEqual(t, apiNote1LinuxDup.USN, 0, "apiNote1LinuxDup usn mismatch") assert.NotEqual(t, apiNote1CSS2.USN, 0, "apiNoteCSS2 usn mismatch") assert.NotEqual(t, apiNote1Math.USN, 0, "apiNote1Math usn mismatch") assert.NotEqual(t, apiNote1JS2.USN, 0, "apiNote1JS2 usn mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS usn mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS usn mismatch") assert.NotEqual(t, apiBookLinux.USN, 0, "apiBookLinux usn mismatch") assert.NotEqual(t, apiBookBash.USN, 0, "apiBookBash usn mismatch") assert.NotEqual(t, apiBookLinuxDup.USN, 0, "apiBookLinuxDup usn mismatch") assert.NotEqual(t, apiBookCSS2.USN, 0, "apiBookCSS2 usn mismatch") assert.NotEqual(t, apiBookMath.USN, 0, "apiBookMath usn mismatch") assert.NotEqual(t, apiBookJS2.USN, 0, "apiBookJS2 usn mismatch") // assert on note bodys assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiNote2CSS.Body, "css2-edited", "apiNote2CSS Body mismatch") assert.Equal(t, apiNote3CSS.Body, "css3", "apiNote3CSS Body mismatch") assert.Equal(t, apiNote1Linux.Body, "", "apiNote1Linux Body mismatch") assert.Equal(t, apiNote2Linux.Body, "", "apiNote2Linux Body mismatch") assert.Equal(t, apiNote1Bash.Body, "bash1", "apiNote1Bash Body mismatch") assert.Equal(t, apiNote2Bash.Body, "bash2", "apiNote2Bash Body mismatch") assert.Equal(t, apiNote1LinuxDup.Body, "linux-new-1", "apiNote1LinuxDup Body mismatch") assert.Equal(t, apiNote1CSS2.Body, "css4", "apiNote1CSS2 Body mismatch") assert.Equal(t, apiNote1Math.Body, "math1", "apiNote1Math Body mismatch") assert.Equal(t, apiNote1JS2.Body, "js4", "apiNote1JS2 Body mismatch") // client must have generated uuids assert.NotEqual(t, apiNote1CSS2.UUID, "", "apiNote1CSS2 uuid mismatch") assert.NotEqual(t, apiNote1Math.UUID, "", "apiNote1Math uuid mismatch") assert.NotEqual(t, apiNote1JS2.UUID, "", "apiNote1JS2 uuid mismatch") // assert on labels assert.Equal(t, apiBookJS.Label, "", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") assert.Equal(t, apiBookLinux.Label, "", "apiBookLinux Label mismatch") assert.Equal(t, apiBookBash.Label, "bash", "apiBookBash Label mismatch") assert.Equal(t, apiBookLinuxDup.Label, "linux", "apiBookLinuxDup Label mismatch") assert.Equal(t, apiBookCSS2.Label, "css_2", "apiBookCSS2 Label mismatch") assert.Equal(t, apiBookMath.Label, "math", "apiBookMath Label mismatch") assert.Equal(t, apiBookJS2.Label, "js", "apiBookJS2 Label mismatch") // assert on note deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote1CSS.Deleted, false, "apiNote1CSS Deleted mismatch") assert.Equal(t, apiNote2CSS.Deleted, false, "apiNote2CSS Deleted mismatch") assert.Equal(t, apiNote3CSS.Deleted, false, "apiNote3CSS Deleted mismatch") assert.Equal(t, apiNote1Linux.Deleted, true, "apiNote1Linux Deleted mismatch") assert.Equal(t, apiNote2Linux.Deleted, true, "apiNote2Linux Deleted mismatch") assert.Equal(t, apiNote1Bash.Deleted, false, "apiNote1Bash Deleted mismatch") assert.Equal(t, apiNote2Bash.Deleted, false, "apiNote2Bash Deleted mismatch") assert.Equal(t, apiNote1LinuxDup.Deleted, false, "apiNote1LinuxDup Deleted mismatch") assert.Equal(t, apiNote1CSS2.Deleted, false, "apiNote1CSS2 Deleted mismatch") assert.Equal(t, apiNote1Math.Deleted, false, "apiNote1Math Deleted mismatch") assert.Equal(t, apiNote1JS2.Deleted, false, "apiNote1JS2 Deleted mismatch") // assert on book deleted assert.Equal(t, apiBookJS.Deleted, true, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") assert.Equal(t, apiBookLinux.Deleted, true, "apiBookLinux Deleted mismatch") assert.Equal(t, apiBookBash.Deleted, false, "apiBookBash Deleted mismatch") assert.Equal(t, apiBookLinuxDup.Deleted, false, "apiBookLinuxDup Deleted mismatch") assert.Equal(t, apiBookCSS2.Deleted, false, "apiBookCSS2 Deleted mismatch") assert.Equal(t, apiBookMath.Deleted, false, "apiBookMath Deleted mismatch") assert.Equal(t, apiBookJS2.Deleted, false, "apiBookJS2 Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("twice", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "math", "-c", "math1") var nid string cliDB := env.DB cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "math1"), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "math", nid, "-c", "math1-edited") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server scssBookUUID := apiCreateBook(t, env, user, "scss", "adding a scss book") apiPatchNote(t, env, user, cssNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, scssBookUUID), "moving css note 1 to scss") var n1UUID string cliDatabase.MustScan(t, "getting math1-edited note UUID", cliDB.QueryRow("SELECT uuid FROM notes WHERE body = ?", "math1-edited"), &n1UUID) apiPatchNote(t, env, user, n1UUID, fmt.Sprintf(`{"content": "%s", "public": true}`, "math1-edited"), "editing math1 note") cssNote2UUID := apiCreateNote(t, env, user, cssBookUUID, "css2", "adding css note 2") apiDeleteBook(t, env, user, cssBookUUID, "deleting css book") bashBookUUID := apiCreateBook(t, env, user, "bash", "adding a bash book") algorithmsBookUUID := apiCreateBook(t, env, user, "algorithms", "adding a algorithms book") // 4. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js3") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "cssBookUUID": cssBookUUID, "scssBookUUID": scssBookUUID, "cssNote1UUID": cssNote1UUID, "cssNote2UUID": cssNote2UUID, "bashBookUUID": bashBookUUID, "algorithmsBookUUID": algorithmsBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { apiDB := env.ServerDB cliDB := env.DB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 5, clientBookCount: 6, clientLastMaxUSN: 17, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 6, serverBookCount: 7, serverUserMaxUSN: 17, }) // test client var cliNote1JS, cliNote2JS, cliNote1SCSS, cliNote1Math, cliNote1Alg2 cliDatabase.Note var cliBookJS, cliBookSCSS, cliBookMath, cliBookBash, cliBookAlg, cliBookAlg2 cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote2JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js3"), &cliNote2JS.UUID, &cliNote2JS.Body, &cliNote2JS.USN) cliDatabase.MustScan(t, "finding cliNote1SCSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNote1SCSS.UUID, &cliNote1SCSS.Body, &cliNote1SCSS.USN) cliDatabase.MustScan(t, "finding cliNote1Math", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "math1-edited"), &cliNote1Math.UUID, &cliNote1Math.Body, &cliNote1Math.USN) cliDatabase.MustScan(t, "finding cliNote1Alg2", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "algorithms1"), &cliNote1Alg2.UUID, &cliNote1Alg2.Body, &cliNote1Alg2.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookSCSS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "scss"), &cliBookSCSS.UUID, &cliBookSCSS.Label, &cliBookSCSS.USN) cliDatabase.MustScan(t, "finding cliBookMath", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "math"), &cliBookMath.UUID, &cliBookMath.Label, &cliBookMath.USN) cliDatabase.MustScan(t, "finding cliBookBash", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "bash"), &cliBookBash.UUID, &cliBookBash.Label, &cliBookBash.USN) cliDatabase.MustScan(t, "finding cliBookAlg", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "algorithms"), &cliBookAlg.UUID, &cliBookAlg.Label, &cliBookAlg.USN) cliDatabase.MustScan(t, "finding cliBookAlg2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "algorithms_2"), &cliBookAlg2.UUID, &cliBookAlg2.Label, &cliBookAlg2.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliNote2JS.USN, 0, "cliNote2JS USN mismatch") assert.NotEqual(t, cliNote1SCSS.USN, 0, "cliNote1SCSS USN mismatch") assert.NotEqual(t, cliNote1Math.USN, 0, "cliNote1Math USN mismatch") assert.NotEqual(t, cliNote1Alg2.USN, 0, "cliNote1Alg2 USN mismatch") assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookSCSS.USN, 0, "cliBookSCSS USN mismatch") assert.NotEqual(t, cliBookMath.USN, 0, "cliBookMath USN mismatch") assert.NotEqual(t, cliBookBash.USN, 0, "cliBookBash USN mismatch") assert.NotEqual(t, cliBookAlg.USN, 0, "cliBookAlg USN mismatch") assert.NotEqual(t, cliBookAlg2.USN, 0, "cliBookAlg2 USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote2JS.Body, "js3", "cliNote2JS Body mismatch") assert.Equal(t, cliNote1SCSS.Body, "css1", "cliNote1SCSS Body mismatch") assert.Equal(t, cliNote1Math.Body, "math1-edited", "cliNote1Math Body mismatch") assert.Equal(t, cliNote1Alg2.Body, "algorithms1", "cliNote1Alg2 Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookSCSS.Label, "scss", "cliBookSCSS Label mismatch") assert.Equal(t, cliBookMath.Label, "math", "cliBookMath Label mismatch") assert.Equal(t, cliBookBash.Label, "bash", "cliBookBash Label mismatch") assert.Equal(t, cliBookAlg.Label, "algorithms", "cliBookAlg Label mismatch") assert.Equal(t, cliBookAlg2.Label, "algorithms_2", "cliBookAlg2 Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote2JS.Deleted, false, "cliNote2JS Deleted mismatch") assert.Equal(t, cliNote1SCSS.Deleted, false, "cliNote1SCSS Deleted mismatch") assert.Equal(t, cliNote1Math.Deleted, false, "cliNote1Math Deleted mismatch") assert.Equal(t, cliNote1Alg2.Deleted, false, "cliNote1Alg2 Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookSCSS.Deleted, false, "cliBookSCSS Deleted mismatch") assert.Equal(t, cliBookMath.Deleted, false, "cliBookMath Deleted mismatch") assert.Equal(t, cliBookBash.Deleted, false, "cliBookBash Deleted mismatch") assert.Equal(t, cliBookAlg.Deleted, false, "cliBookAlg Deleted mismatch") assert.Equal(t, cliBookAlg2.Deleted, false, "cliBookAlg2 Deleted mismatch") // test server var apiNote1JS, apiNote2JS, apiNote1SCSS, apiNote2CSS, apiNote1Math, apiNote1Alg database.Note var apiBookJS, apiBookCSS, apiBookSCSS, apiBookMath, apiBookBash, apiBookAlg, apiBookAlg2 database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote2UUID"]).First(&apiNote2CSS), "finding apiNote2CSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote2JS.UUID).First(&apiNote2JS), "finding apiNote2JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote1UUID"]).First(&apiNote1SCSS), "finding apiNote1SCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1Math.UUID).First(&apiNote1Math), "finding apiNote1Math") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1Alg2.UUID).First(&apiNote1Alg), "finding apiNote1Alg") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["bashBookUUID"]).First(&apiBookBash), "finding apiBookBash") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["scssBookUUID"]).First(&apiBookSCSS), "finding apiBookSCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["algorithmsBookUUID"]).First(&apiBookAlg), "finding apiBookAlg") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookAlg2.UUID).First(&apiBookAlg2), "finding apiBookAlg2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookMath.UUID).First(&apiBookMath), "finding apiBookMath") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote2JS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote1SCSS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote2CSS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote1Math.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote1Alg.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBook1Alg usn mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS usn mismatch") assert.NotEqual(t, apiBookSCSS.USN, 0, "apibookSCSS usn mismatch") assert.NotEqual(t, apiBookMath.USN, 0, "apiBookMath usn mismatch") assert.NotEqual(t, apiBookBash.USN, 0, "apiBookBash usn mismatch") assert.NotEqual(t, apiBookAlg.USN, 0, "apiBookAlg usn mismatch") assert.NotEqual(t, apiBookAlg2.USN, 0, "apiBookAlg2 usn mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote2JS.Body, "js3", "apiNote2JS Body mismatch") assert.Equal(t, apiNote1SCSS.Body, "css1", "apiNote1SCSS Body mismatch") assert.Equal(t, apiNote2CSS.Body, "", "apiNote2CSS Body mismatch") assert.Equal(t, apiNote1Math.Body, "math1-edited", "apiNote1Math Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "", "apiBookCSS Label mismatch") assert.Equal(t, apiBookSCSS.Label, "scss", "apiBookSCSS Label mismatch") assert.Equal(t, apiBookMath.Label, "math", "apiBookMath Label mismatch") assert.Equal(t, apiBookBash.Label, "bash", "apiBookBash Label mismatch") assert.Equal(t, apiBookAlg.Label, "algorithms", "apiBookAlg Label mismatch") assert.Equal(t, apiBookAlg2.Label, "algorithms_2", "apiBookAlg2 Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote2JS.Deleted, false, "apiNote2JS Deleted mismatch") assert.Equal(t, apiNote1SCSS.Deleted, false, "apiNote1SCSS Deleted mismatch") assert.Equal(t, apiNote2CSS.Deleted, true, "apiNote2CSS Deleted mismatch") assert.Equal(t, apiNote1Math.Deleted, false, "apiNote1Math Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, true, "apiBookCSS Deleted mismatch") assert.Equal(t, apiBookSCSS.Deleted, false, "apiBookSCSS Deleted mismatch") assert.Equal(t, apiBookMath.Deleted, false, "apiBookMath Deleted mismatch") assert.Equal(t, apiBookBash.Deleted, false, "apiBookBash Deleted mismatch") assert.Equal(t, apiBookAlg.Deleted, false, "apiBookAlg Deleted mismatch") assert.Equal(t, apiBookAlg2.Deleted, false, "apiBookAlg2 Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("three times", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server goBookUUID := apiCreateBook(t, env, user, "go", "adding a go book") goNote1UUID := apiCreateNote(t, env, user, goBookUUID, "go1", "adding go note 1") // 4. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "html", "-c", "html1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "goBookUUID": goBookUUID, "goNote1UUID": goNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 4, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 4, serverUserMaxUSN: 8, }) // test client var cliNote1JS, cliNote1CSS, cliNote1Go, cliNote1HTML cliDatabase.Note var cliBookJS, cliBookCSS, cliBookGo, cliBookHTML cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body, &cliNote1CSS.USN) cliDatabase.MustScan(t, "finding cliNote1Go", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "go1"), &cliNote1Go.UUID, &cliNote1Go.Body, &cliNote1Go.USN) cliDatabase.MustScan(t, "finding cliNote1HTML", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "html1"), &cliNote1HTML.UUID, &cliNote1HTML.Body, &cliNote1HTML.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliBookGo", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "go"), &cliBookGo.UUID, &cliBookGo.Label, &cliBookGo.USN) cliDatabase.MustScan(t, "finding cliBookHTML", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "html"), &cliBookHTML.UUID, &cliBookHTML.Label, &cliBookHTML.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS USN mismatch") assert.NotEqual(t, cliNote1Go.USN, 0, "cliNote1Go USN mismatch") assert.NotEqual(t, cliNote1HTML.USN, 0, "cliNote1HTML USN mismatch") assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliBookGo.USN, 0, "cliBookGo USN mismatch") assert.NotEqual(t, cliBookHTML.USN, 0, "cliBookHTML USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliNote1Go.Body, "go1", "cliNote1Go Body mismatch") assert.Equal(t, cliNote1HTML.Body, "html1", "cliNote1HTML Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") assert.Equal(t, cliBookGo.Label, "go", "cliBookGo Label mismatch") assert.Equal(t, cliBookHTML.Label, "html", "cliBookHTML Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliNote1Go.Deleted, false, "cliNote1Go Deleted mismatch") assert.Equal(t, cliNote1HTML.Deleted, false, "cliNote1HTML Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") assert.Equal(t, cliBookGo.Deleted, false, "cliBookGo Deleted mismatch") assert.Equal(t, cliBookHTML.Deleted, false, "cliBookHTML Deleted mismatch") // test server var apiNote1JS, apiNote1CSS, apiNote1Go, apiNote1HTML database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["goNote1UUID"]).First(&apiNote1Go), "finding apiNote1Go") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1CSS.UUID).First(&apiNote1CSS), "finding apiNote1CSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1HTML.UUID).First(&apiNote1HTML), "finding apiNote1HTML") var apiBookJS, apiBookCSS, apiBookGo, apiBookHTML database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["goBookUUID"]).First(&apiBookGo), "finding apiBookGo") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookCSS.UUID).First(&apiBookCSS), "finding apiBookCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookHTML.UUID).First(&apiBookHTML), "finding apiBookHTML") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS USN mismatch") assert.NotEqual(t, apiNote1Go.USN, 0, "apiNote1Go USN mismatch") assert.NotEqual(t, apiNote1HTML.USN, 0, "apiNote1HTM USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookGo.USN, 0, "apiBookGo USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") assert.NotEqual(t, apiBookHTML.USN, 0, "apiBookHTML USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiNote1Go.Body, "go1", "apiNote1Go Body mismatch") assert.Equal(t, apiNote1HTML.Body, "html1", "apiNote1HTM Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookGo.Label, "go", "apiBookGo Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") assert.Equal(t, apiBookHTML.Label, "html", "apiBookHTML Label mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) } func TestSync(t *testing.T) { t.Run("client adds a book and a note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") return map[string]string{} } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 2, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 2, }) // test client // assert on bodys and labels var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1JS.UUID).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookJS.UUID).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client deletes a book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 5, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 5, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, true, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client deletes a note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 3, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE label = ?", "js"), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client edits a note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 3, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1-edited", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1-edited", "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client edits a book by renaming it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 3, }) // test client var cliBookJS cliDatabase.Book var cliNote1JS cliDatabase.Note cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, book_uuid, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.BookUUID, &cliNote1JS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookJS.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js-edited", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js-edited", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server adds a book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") return map[string]string{ "jsBookUUID": jsBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 1, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 0, serverBookCount: 1, serverUserMaxUSN: 1, }) // test server var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client // assert on bodys and labels var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE label = ?", "js"), &cliBookJS.Label, &cliBookJS.USN) assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server edits a book by renaming it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-new-label"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 2, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 0, serverBookCount: 1, serverUserMaxUSN: 2, }) // test server var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiBookJS.Label, "js-new-label", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client // assert on bodys and labels var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) assert.Equal(t, cliBookJS.Label, "js-new-label", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server deletes a book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") return map[string]string{ "jsBookUUID": jsBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 2, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 0, serverBookCount: 1, serverUserMaxUSN: 2, }) // test server var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiBookJS.Label, "", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiBookJS.Deleted, true, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server adds a note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 2, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 2, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server edits a note body", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 3, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1-edited", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1-edited", "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server moves a note to another book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "cssBookUUID": cssBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 2, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS, apiBookCSS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["cssBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS, cliBookCSS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["cssBookUUID"]), &cliBookCSS.Label, &cliBookCSS.USN) // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server deletes a note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 3, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // assert on bodys and labels assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server deletes the same book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") // 4. on cli clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 6, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 6, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, true, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server deletes the same note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") // 4. on cli var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { apiDB := env.ServerDB cliDB := env.DB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server and client adds a note with same body", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 4. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 1, serverUserMaxUSN: 3, }) // test client var cliNote1JS, cliNote2JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote2JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ? and uuid != ?", "js1", ids["jsNote1UUID"]), &cliNote2JS.UUID, &cliNote2JS.Body, &cliNote2JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote2JS.Body, "js1", "cliNote2JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote2JS.Deleted, false, "cliNote2JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // test server var apiNote1JS, apiNote2JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote2JS.UUID).First(&apiNote2JS), "finding apiNote2JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote2JS.USN, 0, "apiNote2JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote2JS.Body, "js1", "apiNote2JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server and client adds a book with same label", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 2, serverUserMaxUSN: 4, }) // test client var cliNote1JS, cliNote1JS2 cliDatabase.Note var cliBookJS, cliBookJS2 cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote1JS2", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ? AND uuid !=?", "js1", ids["jsNote1UUID"]), &cliNote1JS2.UUID, &cliNote1JS2.Body, &cliNote1JS2.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookJS2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS2.Body, "js1", "cliNote1JS2 Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookJS2.Label, "js_2", "cliBookJS2 Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote1JS2.Deleted, false, "cliNote1JS2 Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookJS2.Deleted, false, "cliBookJS2 Deleted mismatch") // test server var apiNote1JS, apiNote1JS2 database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1JS2.UUID).First(&apiNote1JS2), "finding apiNote1JS2") var apiBookJS, apiBookJS2 database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookJS2.UUID).First(&apiBookJS2), "finding apiBookJS2") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote1JS2.USN, 0, "apiNote1JS2 USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookJS2.USN, 0, "apiBookJS2 USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS2.Body, "js1", "apiNote1JS2 Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookJS2.Label, "js_2", "apiBookJS2 Label mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server and client adds two sets of books with same labels", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "cssBookUUID": cssBookUUID, "cssNote1UUID": cssNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 4, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 4, serverUserMaxUSN: 8, }) // test client var cliNote1JS, cliNote1JS2, cliNote1CSS, cliNote1CSS2 cliDatabase.Note var cliBookJS, cliBookJS2, cliBookCSS, cliBookCSS2 cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote1JS2", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ? AND uuid != ?", "js1", ids["jsNote1UUID"]), &cliNote1JS2.UUID, &cliNote1JS2.Body, &cliNote1JS2.USN) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE uuid = ?", ids["cssNote1UUID"]), &cliNote1CSS.UUID, &cliNote1CSS.Body, &cliNote1CSS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS2", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ? AND uuid != ?", "css1", ids["cssNote1UUID"]), &cliNote1CSS2.UUID, &cliNote1CSS2.Body, &cliNote1CSS2.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookJS2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliBookCSS2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS2.Body, "js1", "cliNote1JS2 Body mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliNote1CSS2.Body, "css1", "cliNote1CSS2 Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookJS2.Label, "js_2", "cliBookJS2 Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") assert.Equal(t, cliBookCSS2.Label, "css_2", "cliBookCSS2 Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote1JS2.Deleted, false, "cliNote1JS2 Deleted mismatch") assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliNote1CSS2.Deleted, false, "cliNote1CSS2 Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookJS2.Deleted, false, "cliBookJS2 Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") assert.Equal(t, cliBookCSS2.Deleted, false, "cliBookCSS2 Deleted mismatch") // test server var apiNote1JS, apiNote1JS2, apiNote1CSS, apiNote1CSS2 database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1JS2.UUID).First(&apiNote1JS2), "finding apiNote1JS2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssNote1UUID"]).First(&apiNote1CSS), "finding apiNote1CSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1CSS2.UUID).First(&apiNote1CSS2), "finding apiNote1CSS2") var apiBookJS, apiBookJS2, apiBookCSS, apiBookCSS2 database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookJS2.UUID).First(&apiBookJS2), "finding apiBookJS2") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookCSS2.UUID).First(&apiBookCSS2), "finding apiBookCSS2") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote1JS2.USN, 0, "apiNote1JS2 USN mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS USN mismatch") assert.NotEqual(t, apiNote1CSS2.USN, 0, "apiNote1CSS2 USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookJS2.USN, 0, "apiBookJS2 USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") assert.NotEqual(t, apiBookCSS2.USN, 0, "apiBookCSS2 USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS2.Body, "js1", "apiNote1JS2 Body mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS2 Body mismatch") assert.Equal(t, apiNote1CSS2.Body, "css1", "apiNote1CSS2 Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookJS2.Label, "js_2", "apiBookJS2 Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") assert.Equal(t, apiBookCSS2.Label, "css_2", "apiBookCSS2 Label mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server and client adds notes to the same book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 4. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 1, serverUserMaxUSN: 3, }) // test client var cliNote1JS, cliNote2JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote2JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliNote2JS.UUID, &cliNote2JS.Body, &cliNote2JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote2JS.Body, "js2", "cliNote2JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote2JS.Deleted, false, "cliNote2JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // test server var apiNote1JS, apiNote2JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote2JS.UUID).First(&apiNote2JS), "finding apiNote2JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote2JS.USN, 0, "apiNote2JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote2JS.Body, "js2", "apiNote2JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server and client adds a book with the same label and notes in it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 2, serverUserMaxUSN: 4, }) // test client var cliNote1JS, cliNote2JS cliDatabase.Note var cliBookJS, cliBookJS2 cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote2JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliNote2JS.UUID, &cliNote2JS.Body, &cliNote2JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookJS2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote2JS.Body, "js2", "cliNote2JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookJS2.Label, "js_2", "cliBookJS2 Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote2JS.Deleted, false, "cliNote2JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookJS2.Deleted, false, "cliBookJS2 Deleted mismatch") // test server var apiNote1JS, apiNote2JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote2JS.UUID).First(&apiNote2JS), "finding apiNote2JS") var apiBookJS, apiBookJS2 database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookJS2.UUID).First(&apiBookJS2), "finding apiBookJS2") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote2JS.USN, 0, "apiNote2JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookJS2.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote2JS.Body, "js2", "apiNote2JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookJS2.Label, "js_2", "apiBookJS2 USN mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server edits bodys of the same note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited-from-client") // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited-from-server"), "editing js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB resolvedBody := "<<<<<<< Local\njs1-edited-from-client\n=======\njs1-edited-from-server\n>>>>>>> Server\n" checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, resolvedBody, "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, resolvedBody, "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("clients deletes a note and server edits its body", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 3, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1-edited", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1-edited", "cliNote1JS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("clients deletes a note and server moves it to another book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, "cssBookUUID": cssBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 2, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS, apiBookCSS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["cssBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS, cliBookCSS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["cssBookUUID"]), &cliBookCSS.Label, &cliBookCSS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, ids["cssNote1UUID"], "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server deletes a note and client edits it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") // 4. on cli var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1-edited", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, book_uuid, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.BookUUID, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1-edited", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, ids["jsBookUUID"], "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server deletes a book and client edits it by renaming it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") // 4. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js-edited", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, cliBookJS.Label, "js-edited", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("server deletes a book and client edits a note in it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { cliDB := env.DB // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") // 4. on cli var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 6, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 6, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1-edited", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliNote1JS cliDatabase.Note var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT body, book_uuid, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.Body, &cliNote1JS.BookUUID, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1-edited", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, ids["jsBookUUID"], "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client deletes a book and server edits it by renaming it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") // 3. on server apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 5, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 5, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js-edited", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") // test client var cliBookJS cliDatabase.Book cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.Label, &cliBookJS.USN) // test usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, cliBookJS.Label, "js-edited", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client deletes a book and server edits a note in it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js1 note") // 4. on cli clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 6, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 6, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["jsBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, true, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, true, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server edit a book by renaming it to a same name", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") // 3. on server apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 4, }) // test client var cliBookJS cliDatabase.Book var cliNote1JS cliDatabase.Note cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, book_uuid, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.BookUUID, &cliNote1JS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookJS.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js-edited", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js-edited", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server edit a book by renaming it to different names", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited-client") // 3. on server apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited-server"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { // In this case, server's change wins and overwrites that of client's cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 1, serverUserMaxUSN: 4, }) // test client var cliBookJS cliDatabase.Book var cliNote1JS cliDatabase.Note cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE uuid = ?", ids["jsBookUUID"]), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, book_uuid, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.BookUUID, &cliNote1JS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookJS.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js-edited-server", "cliBookJS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiBookJS.Label, "js-edited-server", "apiBookJS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client moves a note", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "1", "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, "cssBookUUID": cssBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 2, serverUserMaxUSN: 4, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS, apiBookCSS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["cssBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") // test client var cliBookJS, cliBookCSS cliDatabase.Book var cliNote1JS cliDatabase.Note cliDatabase.MustScan(t, "finding cli book js", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cli book css", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, book_uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.BookUUID, &cliNote1JS.Body, &cliNote1JS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookCSS.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") assert.Equal(t, cliBookCSS.Dirty, false, "cliBookCSS Dirty mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server each moves a note to a same book", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") // 3. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "1", "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, "cssBookUUID": cssBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 5, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 2, serverUserMaxUSN: 5, }) // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS, apiBookCSS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, ids["cssBookUUID"], "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") // test client var cliBookJS, cliBookCSS cliDatabase.Book var cliNote1JS cliDatabase.Note cliDatabase.MustScan(t, "finding cli book js", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cli book css", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, book_uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.BookUUID, &cliNote1JS.Body, &cliNote1JS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookCSS.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") assert.Equal(t, cliBookCSS.Dirty, false, "cliBookCSS Dirty mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client and server each moves a note to different books", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") linuxBookUUID := apiCreateBook(t, env, user, "linux", "adding a linux book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") // 3. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "1", "-b", "linux") return map[string]string{ "jsBookUUID": jsBookUUID, "cssBookUUID": cssBookUUID, "jsNote1UUID": jsNote1UUID, "linuxBookUUID": linuxBookUUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB expectedNote1JSBody := `<<<<<<< Local Moved to the book linux ======= Moved to the book css >>>>>>> Server js1` checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 4, clientLastMaxUSN: 7, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 1, serverBookCount: 4, serverUserMaxUSN: 7, }) // test client var cliBookJS, cliBookCSS, cliBookLinux, cliBookConflicts cliDatabase.Book var cliNote1JS cliDatabase.Note cliDatabase.MustScan(t, "finding cli book js", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cli book css", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliBookLinux", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "linux"), &cliBookLinux.UUID, &cliBookLinux.Label, &cliBookLinux.USN) cliDatabase.MustScan(t, "finding cliBookConflicts", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "conflicts"), &cliBookConflicts.UUID, &cliBookConflicts.Label, &cliBookConflicts.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, book_uuid, body, usn FROM notes WHERE uuid = ?", ids["jsNote1UUID"]), &cliNote1JS.UUID, &cliNote1JS.BookUUID, &cliNote1JS.Body, &cliNote1JS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliBookLinux.USN, 0, "cliBookLinux USN mismatch") assert.NotEqual(t, cliBookConflicts.USN, 0, "cliBookConflicts USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, expectedNote1JSBody, "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookConflicts.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") assert.Equal(t, cliBookLinux.Label, "linux", "cliBookLinux Label mismatch") assert.Equal(t, cliBookConflicts.Label, "conflicts", "cliBookConflicts Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") assert.Equal(t, cliBookLinux.Deleted, false, "cliBookLinux Deleted mismatch") assert.Equal(t, cliBookConflicts.Deleted, false, "cliBookConflicts Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") assert.Equal(t, cliBookCSS.Dirty, false, "cliBookCSS Dirty mismatch") assert.Equal(t, cliBookLinux.Dirty, false, "cliBookLinux Dirty mismatch") assert.Equal(t, cliBookConflicts.Dirty, false, "cliBookConflicts Dirty mismatch") // test server var apiNote1JS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") var apiBookJS, apiBookCSS, apiBookLinux, apiBookConflicts database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["linuxBookUUID"]).First(&apiBookLinux), "finding apiBookLinux") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookConflicts.UUID).First(&apiBookConflicts), "finding apiBookConflicts") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") assert.NotEqual(t, apiBookConflicts.USN, 0, "apiBookConflicts USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, expectedNote1JSBody, "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, apiBookConflicts.UUID, "apiNote1JS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") assert.Equal(t, apiBookLinux.Label, "linux", "apiBookLinux Label mismatch") assert.Equal(t, apiBookConflicts.Label, "conflicts", "apiBookConflicts Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") assert.Equal(t, apiBookLinux.Deleted, false, "apiBookLinux Deleted mismatch") assert.Equal(t, apiBookConflicts.Deleted, false, "apiBookConflicts Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client adds a new book and moves a note into it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") cliDB := env.DB var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 5, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 2, serverUserMaxUSN: 5, }) // test client var cliBookJS, cliBookCSS cliDatabase.Book var cliNote1JS, cliNote1CSS cliDatabase.Note cliDatabase.MustScan(t, "finding cli book js", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cli book css", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, book_uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.BookUUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, book_uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.BookUUID, &cliNote1CSS.Body, &cliNote1CSS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookCSS.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliNote1CSS.BookUUID, cliBookCSS.UUID, "cliNote1CSS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliNote1CSS.Dirty, false, "cliNote1CSS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") assert.Equal(t, cliBookCSS.Dirty, false, "cliBookCSS Dirty mismatch") // test server var apiNote1JS, apiNote1CSS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1CSS.UUID).First(&apiNote1CSS), "finding apiNote1CSS") var apiBookJS, apiBookCSS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookCSS.UUID).First(&apiBookCSS), "finding apiBookCSS") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, apiBookCSS.UUID, "apiNote1JS BookUUID mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiNote1CSS.BookUUID, apiBookCSS.UUID, "apiNote1CSS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote1CSS.Deleted, false, "apiNote1CSS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) t.Run("client adds a duplicate book and moves a note into it", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") // 3. on cli clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") var nid string cliDatabase.MustScan(t, "getting id of note to edit", env.DB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", nid, "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, "cssBookUUID": cssBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 3, clientLastMaxUSN: 6, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 3, serverUserMaxUSN: 6, }) // test client var cliBookJS, cliBookCSS, cliBookCSS2 cliDatabase.Book var cliNote1JS, cliNote1CSS cliDatabase.Note cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding cliBookCSS2", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, book_uuid, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.BookUUID, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, body, book_uuid, usn FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body, &cliNote1CSS.BookUUID, &cliNote1CSS.USN) // assert on usn assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") assert.NotEqual(t, cliBookCSS2.USN, 0, "cliBookCSS2 USN mismatch") assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1JS.BookUUID, cliBookCSS2.UUID, "cliNote1JS BookUUID mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliNote1CSS.BookUUID, cliBookCSS2.UUID, "cliNote1CSS BookUUID mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") assert.Equal(t, cliBookCSS2.Label, "css_2", "cliBookCSS2 Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") assert.Equal(t, cliBookCSS2.Deleted, false, "cliBookCSS2 Deleted mismatch") // assert on dirty assert.Equal(t, cliNote1JS.Dirty, false, "cliNote1JS Dirty mismatch") assert.Equal(t, cliNote1CSS.Dirty, false, "cliNote1CSS Dirty mismatch") assert.Equal(t, cliBookJS.Dirty, false, "cliBookJS Dirty mismatch") assert.Equal(t, cliBookCSS.Dirty, false, "cliBookCSS Dirty mismatch") assert.Equal(t, cliBookCSS2.Dirty, false, "cliBookCSS2 Dirty mismatch") // test server var apiNote1JS, apiNote1CSS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding apiNote1JS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1CSS.UUID).First(&apiNote1CSS), "finding apiNote1CSS") var apiBookJS, apiBookCSS, apiBookCSS2 database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding apiBookJS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["cssBookUUID"]).First(&apiBookCSS), "finding apiBookCSS") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookCSS2.UUID).First(&apiBookCSS2), "finding apiBookCSS2") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS USN mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS USN mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS USN mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS USN mismatch") assert.NotEqual(t, apiBookCSS2.USN, 0, "apiBookCSS2 USN mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1JS.BookUUID, apiBookCSS2.UUID, "apiNote1JS BookUUID mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiNote1CSS.BookUUID, apiBookCSS2.UUID, "apiNote1CSS BookUUID mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") assert.Equal(t, apiBookCSS2.Label, "css_2", "apiBookCSS2 Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote1CSS.Deleted, false, "apiNote1CSS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") assert.Equal(t, apiBookCSS2.Deleted, false, "apiBookCSS2 Deleted mismatch") } testSyncCmd(t, false, setup, assert) testSyncCmd(t, true, setup, assert) }) } func TestFullSync(t *testing.T) { t.Run("consecutively with stepSync", func(t *testing.T) { setup := func(t *testing.T, env testEnv, user database.User) map[string]string { jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") return map[string]string{ "jsBookUUID": jsBookUUID, "jsNote1UUID": jsNote1UUID, } } assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { cliDB := env.DB apiDB := env.ServerDB checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 2, serverUserMaxUSN: 4, }) // test client var cliNote1JS, cliNote1CSS cliDatabase.Note var cliBookJS, cliBookCSS cliDatabase.Book cliDatabase.MustScan(t, "finding cliNote1JS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body, &cliNote1JS.USN) cliDatabase.MustScan(t, "finding cliNote1CSS", cliDB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body, &cliNote1CSS.USN) cliDatabase.MustScan(t, "finding cliBookJS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding cliBookCSS", cliDB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) // test usn assert.NotEqual(t, cliNote1JS.USN, 0, "cliNote1JS USN mismatch") assert.NotEqual(t, cliNote1CSS.USN, 0, "cliNote1CSS USN mismatch") assert.NotEqual(t, cliBookJS.USN, 0, "cliBookJS USN mismatch") assert.NotEqual(t, cliBookCSS.USN, 0, "cliBookCSS USN mismatch") // assert on bodys and labels assert.Equal(t, cliNote1JS.Body, "js1", "cliNote1JS Body mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "cliNote1CSS Body mismatch") assert.Equal(t, cliBookJS.Label, "js", "cliBookJS Label mismatch") assert.Equal(t, cliBookCSS.Label, "css", "cliBookCSS Label mismatch") // assert on deleted assert.Equal(t, cliNote1JS.Deleted, false, "cliNote1JS Deleted mismatch") assert.Equal(t, cliNote1CSS.Deleted, false, "cliNote1CSS Deleted mismatch") assert.Equal(t, cliBookJS.Deleted, false, "cliBookJS Deleted mismatch") assert.Equal(t, cliBookCSS.Deleted, false, "cliBookCSS Deleted mismatch") // test server var apiNote1JS, apiNote1CSS database.Note apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsNote1UUID"]).First(&apiNote1JS), "finding api js note 1") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliNote1CSS.UUID).First(&apiNote1CSS), "finding api css note 1") var apiBookJS, apiBookCSS database.Book apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, ids["jsBookUUID"]).First(&apiBookJS), "finding api js book") apitest.MustExec(t, apiDB.Where("user_id = ? AND uuid = ?", user.ID, cliBookCSS.UUID).First(&apiBookCSS), "finding api css book") // assert on usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS usn mismatch") assert.NotEqual(t, apiNote1CSS.USN, 0, "apiNote1CSS usn mismatch") assert.NotEqual(t, apiBookJS.USN, 0, "apiBookJS usn mismatch") assert.NotEqual(t, apiBookCSS.USN, 0, "apiBookCSS usn mismatch") // assert on bodys and labels assert.Equal(t, apiNote1JS.Body, "js1", "apiNote1JS Body mismatch") assert.Equal(t, apiNote1CSS.Body, "css1", "apiNote1CSS Body mismatch") assert.Equal(t, apiBookJS.Label, "js", "apiBookJS Label mismatch") assert.Equal(t, apiBookCSS.Label, "css", "apiBookCSS Label mismatch") // assert on deleted assert.Equal(t, apiNote1JS.Deleted, false, "apiNote1JS Deleted mismatch") assert.Equal(t, apiNote1CSS.Deleted, false, "apiNote1CSS Deleted mismatch") assert.Equal(t, apiBookJS.Deleted, false, "apiBookJS Deleted mismatch") assert.Equal(t, apiBookCSS.Deleted, false, "apiBookCSS Deleted mismatch") } t.Run("stepSync then fullSync", func(t *testing.T) { env := setupTestEnv(t) user := setupUserAndLogin(t, env) ids := setup(t, env, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") assert(t, env, user, ids) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") assert(t, env, user, ids) }) t.Run("fullSync then stepSync", func(t *testing.T) { env := setupTestEnv(t) user := setupUserAndLogin(t, env) ids := setup(t, env, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") assert(t, env, user, ids) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") assert(t, env, user, ids) }) }) } func TestSync_FreshClientConcurrent(t *testing.T) { // Test the core issue: Fresh client (never synced, lastMaxUSN=0) syncing to a server // that already has data uploaded by another client. // // Scenario: // 1. Client A creates local notes (never synced, lastMaxUSN=0, lastSyncAt=0) // 2. Client B uploads same book names to server first // 3. Client A syncs // // Expected: Client A should pull server data first, detect duplicate book names, // rename local books to avoid conflicts (js→js_2), then upload successfully. env := setupTestEnv(t) user := setupUserAndLogin(t, env) // Client A: Create local data (never sync) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") // Client B: Upload same book names to server via API jsBookUUID := apiCreateBook(t, env, user, "js", "client B creating js book") cssBookUUID := apiCreateBook(t, env, user, "css", "client B creating css book") apiCreateNote(t, env, user, jsBookUUID, "js2", "client B note") apiCreateNote(t, env, user, cssBookUUID, "css2", "client B note") // Client A syncs - should handle the conflict gracefully // Expected: pulls server data, renames local books to js_2/css_2, uploads successfully clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify: Should have 4 books and 4 notes on both client and server // USN breakdown: 2 books + 2 notes from Client B (USN 1-4), then 2 books + 2 notes from Client A (USN 5-8) checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 4, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 4, serverUserMaxUSN: 8, }) // Verify server has all 4 books with correct names var svrBookJS, svrBookCSS, svrBookJS2, svrBookCSS2 database.Book apitest.MustExec(t, env.ServerDB.Where("label = ?", "js").First(&svrBookJS), "finding server book 'js'") apitest.MustExec(t, env.ServerDB.Where("label = ?", "css").First(&svrBookCSS), "finding server book 'css'") apitest.MustExec(t, env.ServerDB.Where("label = ?", "js_2").First(&svrBookJS2), "finding server book 'js_2'") apitest.MustExec(t, env.ServerDB.Where("label = ?", "css_2").First(&svrBookCSS2), "finding server book 'css_2'") assert.Equal(t, svrBookJS.Label, "js", "server should have book 'js' (Client B)") assert.Equal(t, svrBookCSS.Label, "css", "server should have book 'css' (Client B)") assert.Equal(t, svrBookJS2.Label, "js_2", "server should have book 'js_2' (Client A renamed)") assert.Equal(t, svrBookCSS2.Label, "css_2", "server should have book 'css_2' (Client A renamed)") // Verify server has all 4 notes with correct content var svrNoteJS1, svrNoteJS2, svrNoteCSS1, svrNoteCSS2 database.Note apitest.MustExec(t, env.ServerDB.Where("body = ?", "js1").First(&svrNoteJS1), "finding server note 'js1'") apitest.MustExec(t, env.ServerDB.Where("body = ?", "js2").First(&svrNoteJS2), "finding server note 'js2'") apitest.MustExec(t, env.ServerDB.Where("body = ?", "css1").First(&svrNoteCSS1), "finding server note 'css1'") apitest.MustExec(t, env.ServerDB.Where("body = ?", "css2").First(&svrNoteCSS2), "finding server note 'css2'") assert.Equal(t, svrNoteJS1.BookUUID, svrBookJS2.UUID, "note 'js1' should belong to book 'js_2' (Client A)") assert.Equal(t, svrNoteJS2.BookUUID, svrBookJS.UUID, "note 'js2' should belong to book 'js' (Client B)") assert.Equal(t, svrNoteCSS1.BookUUID, svrBookCSS2.UUID, "note 'css1' should belong to book 'css_2' (Client A)") assert.Equal(t, svrNoteCSS2.BookUUID, svrBookCSS.UUID, "note 'css2' should belong to book 'css' (Client B)") // Verify client has all 4 books var cliBookJS, cliBookCSS, cliBookJS2, cliBookCSS2 cliDatabase.Book cliDatabase.MustScan(t, "finding client book 'js'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) cliDatabase.MustScan(t, "finding client book 'css'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) cliDatabase.MustScan(t, "finding client book 'js_2'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) cliDatabase.MustScan(t, "finding client book 'css_2'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) // Verify client UUIDs match server assert.Equal(t, cliBookJS.UUID, svrBookJS.UUID, "client 'js' UUID should match server") assert.Equal(t, cliBookCSS.UUID, svrBookCSS.UUID, "client 'css' UUID should match server") assert.Equal(t, cliBookJS2.UUID, svrBookJS2.UUID, "client 'js_2' UUID should match server") assert.Equal(t, cliBookCSS2.UUID, svrBookCSS2.UUID, "client 'css_2' UUID should match server") // Verify all books have non-zero USN (synced successfully) assert.NotEqual(t, cliBookJS.USN, 0, "client 'js' should have non-zero USN") assert.NotEqual(t, cliBookCSS.USN, 0, "client 'css' should have non-zero USN") assert.NotEqual(t, cliBookJS2.USN, 0, "client 'js_2' should have non-zero USN") assert.NotEqual(t, cliBookCSS2.USN, 0, "client 'css_2' should have non-zero USN") // Verify client has all 4 notes var cliNoteJS1, cliNoteJS2, cliNoteCSS1, cliNoteCSS2 cliDatabase.Note cliDatabase.MustScan(t, "finding client note 'js1'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNoteJS1.UUID, &cliNoteJS1.Body, &cliNoteJS1.USN) cliDatabase.MustScan(t, "finding client note 'js2'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliNoteJS2.UUID, &cliNoteJS2.Body, &cliNoteJS2.USN) cliDatabase.MustScan(t, "finding client note 'css1'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNoteCSS1.UUID, &cliNoteCSS1.Body, &cliNoteCSS1.USN) cliDatabase.MustScan(t, "finding client note 'css2'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css2"), &cliNoteCSS2.UUID, &cliNoteCSS2.Body, &cliNoteCSS2.USN) // Verify client note UUIDs match server assert.Equal(t, cliNoteJS1.UUID, svrNoteJS1.UUID, "client note 'js1' UUID should match server") assert.Equal(t, cliNoteJS2.UUID, svrNoteJS2.UUID, "client note 'js2' UUID should match server") assert.Equal(t, cliNoteCSS1.UUID, svrNoteCSS1.UUID, "client note 'css1' UUID should match server") assert.Equal(t, cliNoteCSS2.UUID, svrNoteCSS2.UUID, "client note 'css2' UUID should match server") // Verify all notes have non-zero USN (synced successfully) assert.NotEqual(t, cliNoteJS1.USN, 0, "client note 'js1' should have non-zero USN") assert.NotEqual(t, cliNoteJS2.USN, 0, "client note 'js2' should have non-zero USN") assert.NotEqual(t, cliNoteCSS1.USN, 0, "client note 'css1' should have non-zero USN") assert.NotEqual(t, cliNoteCSS2.USN, 0, "client note 'css2' should have non-zero USN") } // TestSync_ConvergeSameBookNames tests that two clients don't enter an infinite sync loop if they // try to sync books with the same names. Books shouldn't get marked dirty when re-downloaded from server. func TestSync_ConvergeSameBookNames(t *testing.T) { env := setupTestEnv(t) tmpDir := t.TempDir() // Setup two separate client databases client1DB := fmt.Sprintf("%s/client1.db", tmpDir) client2DB := fmt.Sprintf("%s/client2.db", tmpDir) defer os.Remove(client1DB) defer os.Remove(client2DB) // Set up sessions user := setupUser(t, env) db1 := testutils.MustOpenDatabase(t, client1DB) db2 := testutils.MustOpenDatabase(t, client2DB) defer db1.Close() defer db2.Close() // Client 1: First sync to empty server clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "add", "testbook", "-c", "client1 note1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "add", "anotherbook", "-c", "client1 note2") login(t, db1, env.ServerDB, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") checkState(t, db1, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 2, serverBookCount: 2, serverUserMaxUSN: 4, }) // Client 2: Sync (downloads client 1's data, adds own notes) ===== clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "add", "testbook", "-c", "client2 note1") clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "add", "anotherbook", "-c", "client2 note2") login(t, db2, env.ServerDB, user) clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "sync") // Verify state after client2 sync checkState(t, db2, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 2, serverUserMaxUSN: 8, }) // Client 1: Sync again. It downloads client2's changes (2 extra notes). clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") // Verify MaxUSN did not increase (client1 should only download, not upload) // Client1 still has: 2 original books + 4 notes (2 own + 2 from client2) checkState(t, db1, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 2, serverUserMaxUSN: 8, }) // Verify no infinite loop: alternate syncing // Both clients should be able to sync without any changes (MaxUSN stays at 8) for range 3 { // Client 2 syncs clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "sync") // Verify client2 state unchanged checkState(t, db2, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 2, serverUserMaxUSN: 8, }) // Client 1 syncs clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") // Verify client1 state unchanged checkState(t, db1, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, clientLastSyncAt: serverTime.Unix(), serverNoteCount: 4, serverBookCount: 2, serverUserMaxUSN: 8, }) } }