From a26037f83ae4f56cdc7f9c9985c7b6a7caafe5ee Mon Sep 17 00:00:00 2001 From: Sung <8265228+sungwoncho@users.noreply.github.com> Date: Sun, 28 Sep 2025 22:25:34 -0700 Subject: [PATCH] Add sync test (#678) --- .github/workflows/ci.yml | 4 + .gitignore | 1 + Makefile | 7 +- pkg/e2e/sync_test.go | 3867 ++++++++++++++++++++++++++++++++++++++ scripts/e2e/test.sh | 13 + 5 files changed, 3891 insertions(+), 1 deletion(-) create mode 100644 pkg/e2e/sync_test.go create mode 100755 scripts/e2e/test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e31ace2..02f8fa95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,3 +47,7 @@ jobs: - name: Test app run: | make test-api + + - name: Test e2e + run: | + make test-e2e diff --git a/.gitignore b/.gitignore index 9ed8a65d..c34f93b0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *.log node_modules /test +tmp diff --git a/Makefile b/Makefile index 71b829fc..09c3513f 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ endif .PHONY: install-js ## test -test: test-cli test-api +test: test-cli test-api test-e2e .PHONY: test test-cli: @@ -43,6 +43,11 @@ test-api: @(${currentDir}/scripts/server/test-local.sh) .PHONY: test-api +test-e2e: + @echo "==> running E2E test" + @(${currentDir}/scripts/e2e/test.sh) +.PHONY: test-e2e + test-selfhost: @echo "==> running a smoke test for self-hosting" diff --git a/pkg/e2e/sync_test.go b/pkg/e2e/sync_test.go new file mode 100644 index 00000000..68f0d4ee --- /dev/null +++ b/pkg/e2e/sync_test.go @@ -0,0 +1,3867 @@ +/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Dnote. If not, see . + */ + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/dnote/dnote/pkg/assert" + "github.com/dnote/dnote/pkg/cli/consts" + "github.com/dnote/dnote/pkg/cli/context" + cliDatabase "github.com/dnote/dnote/pkg/cli/database" + clitest "github.com/dnote/dnote/pkg/cli/testutils" + "github.com/dnote/dnote/pkg/clock" + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/config" + "github.com/dnote/dnote/pkg/server/controllers" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/mailer" + apitest "github.com/dnote/dnote/pkg/server/testutils" + "github.com/pkg/errors" +) + +var cliBinaryName string +var server *httptest.Server +var serverTime = time.Date(2017, time.March, 14, 21, 15, 0, 0, time.UTC) + +var tmpDirPath string +var dnoteCmdOpts clitest.RunDnoteCmdOptions +var paths context.Paths + +var testDir = "./tmp/.dnote" + +func init() { + tmpDirPath = fmt.Sprintf("%s/tmp", testDir) + cliBinaryName = fmt.Sprintf("%s/test/cli/test-cli", testDir) + dnoteCmdOpts = clitest.RunDnoteCmdOptions{ + Env: []string{ + fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpDirPath), + fmt.Sprintf("XDG_DATA_HOME=%s", tmpDirPath), + fmt.Sprintf("XDG_CACHE_HOME=%s", tmpDirPath), + }, + } + + paths = context.Paths{ + Data: tmpDirPath, + Cache: tmpDirPath, + Config: tmpDirPath, + } +} + +func clearTmp(t *testing.T) { + if err := os.RemoveAll(tmpDirPath); err != nil { + t.Fatal("cleaning tmp dir") + } +} + +func TestMain(m *testing.M) { + // Set up server database + apitest.InitTestDB() + + mockClock := clock.NewMock() + mockClock.SetNow(serverTime) + + var err error + server, err = controllers.NewServer(&app.App{ + Clock: mockClock, + EmailTemplates: mailer.Templates{}, + EmailBackend: &apitest.MockEmailbackendImplementation{}, + DB: apitest.DB, + Config: config.Config{ + WebURL: os.Getenv("WebURL"), + }, + }) + if err != nil { + panic(errors.Wrap(err, "initializing router")) + } + + defer server.Close() + + // Build binaries + apiEndpoint := fmt.Sprintf("%s/api", server.URL) + ldflags := fmt.Sprintf("-X main.apiEndpoint=%s", apiEndpoint) + + cmd := exec.Command("go", "build", "--tags", "fts5", "-o", cliBinaryName, "-ldflags", ldflags, "github.com/dnote/dnote/pkg/cli") + + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Run(); err != nil { + log.Print(errors.Wrap(err, "building a CLI binary").Error()) + log.Print(stderr.String()) + os.Exit(1) + } + + os.Exit(m.Run()) +} + +// helpers +func setupUser(t *testing.T, ctx *context.DnoteCtx) database.User { + user := apitest.SetupUserData() + apitest.SetupAccountData(user, "alice@example.com", "pass1234") + + // log in the user in CLI + session := apitest.SetupSession(t, user) + cliDatabase.MustExec(t, "inserting session_key", ctx.DB, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, session.Key) + cliDatabase.MustExec(t, "inserting session_key_expiry", ctx.DB, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, session.ExpiresAt.Unix()) + + return user +} + +func apiCreateBook(t *testing.T, user database.User, name, message string) string { + res := doHTTPReq(t, "POST", "/v3/books", fmt.Sprintf(`{"name": "%s"}`, name), message, user) + + var resp controllers.CreateBookResp + if err := json.NewDecoder(res.Body).Decode(&resp); err != nil { + t.Fatal(errors.Wrap(err, "decoding payload for adding book")) + return "" + } + + return resp.Book.UUID +} + +func apiPatchBook(t *testing.T, user database.User, uuid, payload, message string) { + doHTTPReq(t, "PATCH", fmt.Sprintf("/v3/books/%s", uuid), payload, message, user) +} + +func apiDeleteBook(t *testing.T, user database.User, uuid, message string) { + doHTTPReq(t, "DELETE", fmt.Sprintf("/v3/books/%s", uuid), "", message, user) +} + +func apiCreateNote(t *testing.T, user database.User, bookUUID, body, message string) string { + res := doHTTPReq(t, "POST", "/v3/notes", fmt.Sprintf(`{"book_uuid": "%s", "content": "%s"}`, bookUUID, body), message, user) + + var resp controllers.CreateNoteResp + if err := json.NewDecoder(res.Body).Decode(&resp); err != nil { + t.Fatal(errors.Wrap(err, "decoding payload for adding note")) + return "" + } + + return resp.Result.UUID +} + +func apiPatchNote(t *testing.T, user database.User, noteUUID, payload, message string) { + doHTTPReq(t, "PATCH", fmt.Sprintf("/v3/notes/%s", noteUUID), payload, message, user) +} + +func apiDeleteNote(t *testing.T, user database.User, noteUUID, message string) { + doHTTPReq(t, "DELETE", fmt.Sprintf("/v3/notes/%s", noteUUID), "", message, user) +} + +func doHTTPReq(t *testing.T, method, path, payload, message string, user database.User) *http.Response { + apiEndpoint := fmt.Sprintf("%s/api", server.URL) + endpoint := fmt.Sprintf("%s%s", apiEndpoint, path) + + req, err := http.NewRequest(method, endpoint, strings.NewReader(payload)) + if err != nil { + panic(errors.Wrap(err, "constructing http request")) + } + + res := apitest.HTTPAuthDo(t, req, user) + if res.StatusCode >= 400 { + bs, err := ioutil.ReadAll(res.Body) + if err != nil { + panic(errors.Wrap(err, "parsing response body for error")) + } + + t.Errorf("%s. HTTP status %d. Message: %s", message, res.StatusCode, string(bs)) + } + + return res +} + +type setupFunc func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string +type assertFunc func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) + +func testSyncCmd(t *testing.T, fullSync bool, setup setupFunc, assert assertFunc) { + // clean up + apitest.ClearData(apitest.DB) + defer apitest.ClearData(apitest.DB) + + clearTmp(t) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + + user := setupUser(t, &ctx) + ids := setup(t, ctx, user) + + if fullSync { + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + } else { + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + } + + assert(t, ctx, user, ids) +} + +type systemState struct { + clientNoteCount int + clientBookCount int + clientLastMaxUSN int + clientLastSyncAt int64 + serverNoteCount int + serverBookCount int + serverUserMaxUSN int +} + +// checkState compares the state of the client and the server with the given system state +func checkState(t *testing.T, ctx context.DnoteCtx, user database.User, expected systemState) { + serverDB := apitest.DB + clientDB := ctx.DB + + var clientBookCount, clientNoteCount int + cliDatabase.MustScan(t, "counting client notes", clientDB.QueryRow("SELECT count(*) FROM notes"), &clientNoteCount) + cliDatabase.MustScan(t, "counting client books", clientDB.QueryRow("SELECT count(*) FROM books"), &clientBookCount) + assert.Equal(t, clientNoteCount, expected.clientNoteCount, "client note count mismatch") + assert.Equal(t, clientBookCount, expected.clientBookCount, "client book count mismatch") + + var clientLastMaxUSN int + var clientLastSyncAt int64 + cliDatabase.MustScan(t, "finding system last_max_usn", clientDB.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastMaxUSN), &clientLastMaxUSN) + cliDatabase.MustScan(t, "finding system last_sync_at", clientDB.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastSyncAt), &clientLastSyncAt) + assert.Equal(t, clientLastMaxUSN, expected.clientLastMaxUSN, "client last_max_usn mismatch") + assert.Equal(t, clientLastSyncAt, expected.clientLastSyncAt, "client last_sync_at mismatch") + + var serverBookCount, serverNoteCount int + apitest.MustExec(t, serverDB.Model(&database.Note{}).Count(&serverNoteCount), "counting server notes") + apitest.MustExec(t, serverDB.Model(&database.Book{}).Count(&serverBookCount), "counting api notes") + assert.Equal(t, serverNoteCount, expected.serverNoteCount, "server note count mismatch") + assert.Equal(t, serverBookCount, expected.serverBookCount, "server book count mismatch") + var serverUser database.User + apitest.MustExec(t, serverDB.Where("id = ?", user.ID).First(&serverUser), "finding user") + assert.Equal(t, serverUser.MaxUSN, expected.serverUserMaxUSN, "user max_usn mismatch") +} + +// tests +func TestSync_Empty(t *testing.T) { + setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + return map[string]string{} + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + // Test + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) { + apiDB := apitest.DB + apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User) { + apiDB := apitest.DB + cliDB := ctx.DB + + // test client + checkState(t, ctx, user, 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, apiDB.Model(&database.Note{}).Where("uuid = ?", cliNote1JS.UUID).First(&apiNote1JS), "getting js1 note") + apitest.MustExec(t, apiDB.Model(&database.Note{}).Where("uuid = ?", cliNote2JS.UUID).First(&apiNote2JS), "getting js2 note") + apitest.MustExec(t, apiDB.Model(&database.Note{}).Where("uuid = ?", cliNote1CSS.UUID).First(&apiNote1CSS), "getting css1 note") + apitest.MustExec(t, apiDB.Model(&database.Book{}).Where("uuid = ?", cliBookJS.UUID).First(&apiBookJS), "getting js book") + apitest.MustExec(t, apiDB.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) { + clearTmp(t) + defer apitest.ClearData(apitest.DB) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + user := setupUser(t, &ctx) + setup(t, ctx, user) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + assert(t, ctx, user) + }) + + t.Run("fullSync", func(t *testing.T) { + clearTmp(t) + defer apitest.ClearData(apitest.DB) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + user := setupUser(t, &ctx) + setup(t, ctx, user) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + + assert(t, ctx, user) + }) + }) + + t.Run("cli to api with edit and delete", func(t *testing.T) { + setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) { + apiDB := apitest.DB + apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js3") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css2") + + var nid, nid2 string + cliDB := ctx.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, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js3-edited") + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "css", nid2) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css3") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css4") + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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) { + clearTmp(t) + defer apitest.ClearData(apitest.DB) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + user := setupUser(t, &ctx) + setup(t, ctx, user) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + assert(t, ctx, user) + }) + + t.Run("fullSync", func(t *testing.T) { + clearTmp(t) + defer apitest.ClearData(apitest.DB) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + user := setupUser(t, &ctx) + setup(t, ctx, user) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + + assert(t, ctx, user) + }) + }) + + t.Run("api to cli", func(t *testing.T) { + setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + apiDB := apitest.DB + + apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") + + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, user, cssBookUUID, "css1", "adding css note 1") + jsNote2UUID := apiCreateNote(t, user, jsBookUUID, "js2", "adding js note 2") + cssNote2UUID := apiCreateNote(t, user, cssBookUUID, "css2", "adding css note 2") + linuxBookUUID := apiCreateBook(t, user, "linux", "adding linux book") + linuxNote1UUID := apiCreateNote(t, user, linuxBookUUID, "linux1", "adding linux note 1") + apiPatchNote(t, user, jsNote2UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, linuxBookUUID), "moving js note 2 to linux") + apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + cssNote3UUID := apiCreateNote(t, user, cssBookUUID, "css3", "adding css note 3") + bashBookUUID := apiCreateBook(t, user, "bash", "adding bash book") + bashNote1UUID := apiCreateNote(t, user, bashBookUUID, "bash1", "adding bash note 1") + + // delete the linux book and its two notes + apiDeleteBook(t, user, linuxBookUUID, "deleting linux book") + + apiPatchNote(t, user, cssNote2UUID, fmt.Sprintf(`{"content": "%s"}`, "css2-edited"), "editing css 2 body") + bashNote2UUID := apiCreateNote(t, user, bashBookUUID, "bash2", "adding bash note 2") + linuxBook2UUID := apiCreateBook(t, user, "linux", "adding new linux book") + linux2Note1UUID := apiCreateNote(t, user, linuxBookUUID, "linux-new-1", "adding linux note 1") + apiDeleteBook(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, user, cssBookUUID, "css1", "adding css note 1") + jsNote2UUID := apiCreateNote(t, user, jsBookUUID, "js2", "adding js note 2") + cssNote2UUID := apiCreateNote(t, user, cssBookUUID, "css2", "adding css note 2") + linuxBookUUID := apiCreateBook(t, user, "linux", "adding linux book") + linuxNote1UUID := apiCreateNote(t, user, linuxBookUUID, "linux1", "adding linux note 1") + apiPatchNote(t, user, jsNote2UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, linuxBookUUID), "moving js note 2 to linux") + apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + cssNote3UUID := apiCreateNote(t, user, cssBookUUID, "css3", "adding css note 3") + bashBookUUID := apiCreateBook(t, user, "bash", "adding bash book") + bashNote1UUID := apiCreateNote(t, user, bashBookUUID, "bash1", "adding bash note 1") + apiDeleteBook(t, user, linuxBookUUID, "deleting linux book") + apiPatchNote(t, user, cssNote2UUID, fmt.Sprintf(`{"content": "%s"}`, "css2-edited"), "editing css 2 body") + bashNote2UUID := apiCreateNote(t, user, bashBookUUID, "bash2", "adding bash note 2") + linuxBook2UUID := apiCreateBook(t, user, "linux", "adding new linux book") + linux2Note1UUID := apiCreateNote(t, user, linuxBookUUID, "linux-new-1", "adding linux note 1") + apiDeleteBook(t, user, jsBookUUID, "deleting js book") + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js3") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js4") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms2") + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "algorithms") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css4") + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, user, cssBookUUID, "css1", "adding css note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "math", "-c", "math1") + + var nid string + cliDB := ctx.DB + cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "math1"), &nid) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "math", nid, "-c", "math1-edited") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + scssBookUUID := apiCreateBook(t, user, "scss", "adding a scss book") + apiPatchNote(t, 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, user, n1UUID, fmt.Sprintf(`{"content": "%s", "public": true}`, "math1-edited"), "editing math1 note") + + cssNote2UUID := apiCreateNote(t, user, cssBookUUID, "css2", "adding css note 2") + apiDeleteBook(t, user, cssBookUUID, "deleting css book") + + bashBookUUID := apiCreateBook(t, user, "bash", "adding a bash book") + algorithmsBookUUID := apiCreateBook(t, user, "algorithms", "adding a algorithms book") + + // 4. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js3") + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + apiDB := apitest.DB + cliDB := ctx.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + goBookUUID := apiCreateBook(t, user, "go", "adding a go book") + goNote1UUID := apiCreateNote(t, user, goBookUUID, "go1", "adding go note 1") + + // 4. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "html", "-c", "html1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + "goBookUUID": goBookUUID, + "goNote1UUID": goNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + + return map[string]string{} + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js", nid) + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-new-label"), "editing js book") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteBook(t, user, jsBookUUID, "deleting js book") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiPatchNote(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, user, "css", "adding css book") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiPatchNote(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteBook(t, user, jsBookUUID, "deleting js book") + + // 4. on cli + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteNote(t, 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.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js", nid) + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + apiDB := apitest.DB + cliDB := ctx.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 4. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, user, cssBookUUID, "css1", "adding css note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + "cssBookUUID": cssBookUUID, + "cssNote1UUID": cssNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 4. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited-from-client") + + // 3. on server + apiPatchNote(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + resolvedBody := "<<<<<<< Local\njs1-edited-from-client\n=======\njs1-edited-from-server\n>>>>>>> Server\n" + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js", nid) + + // 3. on server + apiPatchNote(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, user, "css", "adding css book") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, 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.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js", nid) + + // 3. on server + apiPatchNote(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteNote(t, 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, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + + // 4. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + cliDB := ctx.DB + + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiDeleteBook(t, 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, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js") + + // 3. on server + apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js1 note") + + // 4. on cli + clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirm, cliBinaryName, "remove", "js") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") + + // 3. on server + apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited-client") + + // 3. on server + apiPatchBook(t, 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, ctx context.DnoteCtx, user database.User, ids map[string]string) { + // In this case, server's change wins and overwrites that of client's + + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + cssBookUUID := apiCreateBook(t, user, "css", "adding a css book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "1", "-b", "css") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "cssBookUUID": cssBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + cssBookUUID := apiCreateBook(t, user, "css", "adding a css book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") + + // 3. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "1", "-b", "css") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "cssBookUUID": cssBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + cssBookUUID := apiCreateBook(t, user, "css", "adding a css book") + linuxBookUUID := apiCreateBook(t, user, "linux", "adding a linux book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") + + // 3. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "1", "-b", "linux") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "cssBookUUID": cssBookUUID, + "jsNote1UUID": jsNote1UUID, + "linuxBookUUID": linuxBookUUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + expectedNote1JSBody := `<<<<<<< Local +Moved to the book linux +======= +Moved to the book css +>>>>>>> Server + +js1` + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + + cliDB := ctx.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, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-b", "css") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + // 1. on server + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + // 2. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + + // 3. on server + cssBookUUID := apiCreateBook(t, user, "css", "adding a css book") + + // 3. on cli + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + + var nid string + cliDatabase.MustScan(t, "getting id of note to edit", ctx.DB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", nid, "-b", "css") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "cssBookUUID": cssBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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, ctx context.DnoteCtx, user database.User) map[string]string { + jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + + return map[string]string{ + "jsBookUUID": jsBookUUID, + "jsNote1UUID": jsNote1UUID, + } + } + + assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + cliDB := ctx.DB + apiDB := apitest.DB + + checkState(t, ctx, user, 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) { + // clean up + os.RemoveAll(tmpDirPath) + apitest.ClearData(apitest.DB) + defer apitest.ClearData(apitest.DB) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + user := setupUser(t, &ctx) + ids := setup(t, ctx, user) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + assert(t, ctx, user, ids) + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + assert(t, ctx, user, ids) + }) + + t.Run("fullSync then stepSync", func(t *testing.T) { + // clean up + os.RemoveAll(tmpDirPath) + apitest.ClearData(apitest.DB) + defer apitest.ClearData(apitest.DB) + + ctx := context.InitTestCtx(t, paths, nil) + defer context.TeardownTestCtx(t, ctx) + + user := setupUser(t, &ctx) + ids := setup(t, ctx, user) + + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + assert(t, ctx, user, ids) + clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + assert(t, ctx, user, ids) + }) + }) +} diff --git a/scripts/e2e/test.sh b/scripts/e2e/test.sh new file mode 100755 index 00000000..240d5c97 --- /dev/null +++ b/scripts/e2e/test.sh @@ -0,0 +1,13 @@ +#!/bin/bash +set -eux + +dir=$(dirname "${BASH_SOURCE[0]}") +basePath=$(realpath "$dir/../../") + +set -a +source "$basePath/pkg/server/.env.test" +set +a + +pushd "$basePath"/pkg/e2e +go test --tags "fts5" ./... -p 1 +popd