From 4fe23fe9966ecfb23232aa8b00245dedfe79243f Mon Sep 17 00:00:00 2001 From: Sung Date: Sat, 25 Oct 2025 11:59:46 -0700 Subject: [PATCH] Write migration and test --- Makefile | 11 +- pkg/cli/cmd/sync/main_test.go | 35 - pkg/cli/cmd/sync/sync_test.go | 131 +- pkg/cli/context/testutils.go | 68 +- pkg/cli/database/cmd/generate-schema.go | 160 ++ pkg/cli/database/cmd/generate-schema_test.go | 76 + pkg/cli/database/models_test.go | 36 +- pkg/cli/database/queries_test.go | 40 +- pkg/cli/database/schema.sql | 36 + pkg/cli/database/test_embed.go | 27 + pkg/cli/database/testutils.go | 119 +- pkg/cli/infra/init.go | 1 + pkg/cli/infra/init_test.go | 8 +- pkg/cli/main_test.go | 101 +- pkg/cli/migrate/legacy_test.go | 13 +- pkg/cli/migrate/migrate_test.go | 155 +- pkg/cli/testutils/main.go | 11 +- pkg/cli/ui/editor_test.go | 21 +- pkg/e2e/sync_test.go | 1538 +++++++++--------- pkg/server/testutils/main.go | 20 - scripts/cli/test.sh | 7 +- 21 files changed, 1432 insertions(+), 1182 deletions(-) delete mode 100644 pkg/cli/cmd/sync/main_test.go create mode 100644 pkg/cli/database/cmd/generate-schema.go create mode 100644 pkg/cli/database/cmd/generate-schema_test.go create mode 100644 pkg/cli/database/schema.sql create mode 100644 pkg/cli/database/test_embed.go diff --git a/Makefile b/Makefile index 5037c408..75d50f41 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ endif test: test-cli test-api test-e2e .PHONY: test -test-cli: +test-cli: generate-cli-schema @echo "==> running CLI test" @(${currentDir}/scripts/cli/test.sh) .PHONY: test-cli @@ -76,7 +76,14 @@ endif @(cd ${currentDir}/host/docker && ./build.sh $(version) $(platform)) .PHONY: build-server-docker -build-cli: +generate-cli-schema: + @echo "==> generating CLI database schema" + @mkdir -p pkg/cli/database + @touch pkg/cli/database/schema.sql + @go run -tags fts5 pkg/cli/database/cmd/generate-schema.go +.PHONY: generate-cli-schema + +build-cli: generate-cli-schema ifeq ($(debug), true) @echo "==> building cli in dev mode" @${currentDir}/scripts/cli/dev.sh diff --git a/pkg/cli/cmd/sync/main_test.go b/pkg/cli/cmd/sync/main_test.go deleted file mode 100644 index e7a620b0..00000000 --- a/pkg/cli/cmd/sync/main_test.go +++ /dev/null @@ -1,35 +0,0 @@ -/* 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 sync - -import ( - "github.com/dnote/dnote/pkg/cli/context" - "path/filepath" -) - -var testDir = "../../tmp" - -var paths context.Paths = context.Paths{ - Home: testDir, - Cache: testDir, - Config: testDir, - Data: testDir, -} - -var dbPath = filepath.Join(testDir, "test.db") diff --git a/pkg/cli/cmd/sync/sync_test.go b/pkg/cli/cmd/sync/sync_test.go index 34f0c9df..aff1ae5e 100644 --- a/pkg/cli/cmd/sync/sync_test.go +++ b/pkg/cli/cmd/sync/sync_test.go @@ -36,6 +36,17 @@ import ( "github.com/pkg/errors" ) +// getTestPaths creates unique test paths for parallel test execution +func getTestPaths(t *testing.T) context.Paths { + testDir := t.TempDir() + return context.Paths{ + Home: testDir, + Cache: testDir, + Config: testDir, + Data: testDir, + } +} + func TestProcessFragments(t *testing.T) { fragments := []client.SyncFragment{ { @@ -106,8 +117,7 @@ func TestProcessFragments(t *testing.T) { func TestGetLastSyncAt(t *testing.T) { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "setting up last_sync_at", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemLastSyncAt, 1541108743) // exec @@ -129,8 +139,7 @@ func TestGetLastSyncAt(t *testing.T) { func TestGetLastMaxUSN(t *testing.T) { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "setting up last_max_usn", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemLastMaxUSN, 20001) // exec @@ -176,8 +185,7 @@ func TestResolveLabel(t *testing.T) { for idx, tc := range testCases { func() { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, fmt.Sprintf("inserting book for test case %d", idx), db, "INSERT INTO books (uuid, label) VALUES (?, ?)", "b1-uuid", "js") database.MustExec(t, fmt.Sprintf("inserting book for test case %d", idx), db, "INSERT INTO books (uuid, label) VALUES (?, ?)", "b2-uuid", "css_2") @@ -206,8 +214,7 @@ func TestResolveLabel(t *testing.T) { func TestSyncDeleteNote(t *testing.T) { t.Run("exists on server only", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) // execute tx, err := db.Begin() @@ -235,8 +242,7 @@ func TestSyncDeleteNote(t *testing.T) { b1UUID := testutils.MustGenerateUUID(t) // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1 for test case %d", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") database.MustExec(t, "inserting n1 for test case %d", db, "INSERT INTO notes (uuid, book_uuid, usn, body, added_on, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", "n1-uuid", b1UUID, 10, "n1 body", 1541108743, false, true) @@ -305,8 +311,7 @@ func TestSyncDeleteNote(t *testing.T) { b1UUID := testutils.MustGenerateUUID(t) // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1 for test case %d", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") database.MustExec(t, "inserting n1 for test case %d", db, "INSERT INTO notes (uuid, book_uuid, usn, body, added_on, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", "n1-uuid", b1UUID, 10, "n1 body", 1541108743, false, false) @@ -361,8 +366,7 @@ func TestSyncDeleteNote(t *testing.T) { func TestSyncDeleteBook(t *testing.T) { t.Run("exists on server only", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1 for test case %d", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", "b1-uuid", "b1-label") var b1 database.Book @@ -406,8 +410,7 @@ func TestSyncDeleteBook(t *testing.T) { b1UUID := testutils.MustGenerateUUID(t) // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1 for test case %d", db, "INSERT INTO books (uuid, label, usn, dirty) VALUES (?, ?, ?, ?)", b1UUID, "b1-label", 12, true) database.MustExec(t, "inserting n1 for test case %d", db, "INSERT INTO notes (uuid, book_uuid, usn, body, added_on, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", "n1-uuid", b1UUID, 10, "n1 body", 1541108743, false, true) @@ -472,8 +475,7 @@ func TestSyncDeleteBook(t *testing.T) { b2UUID := testutils.MustGenerateUUID(t) // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1 for test case %d", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") database.MustExec(t, "inserting n1 for test case %d", db, "INSERT INTO notes (uuid, book_uuid, usn, body, added_on, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", "n1-uuid", b1UUID, 10, "n1 body", 1541108743, false, false) @@ -538,8 +540,7 @@ func TestSyncDeleteBook(t *testing.T) { b1UUID := testutils.MustGenerateUUID(t) // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1 for test case %d", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") database.MustExec(t, "inserting n1 for test case %d", db, "INSERT INTO notes (uuid, book_uuid, usn, body, added_on, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", "n1-uuid", b1UUID, 10, "n1 body", 1541108743, false, true) @@ -590,8 +591,7 @@ func TestSyncDeleteBook(t *testing.T) { func TestFullSyncNote(t *testing.T) { t.Run("exists on server only", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") @@ -822,8 +822,7 @@ n1 body edited for idx, tc := range testCases { func() { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, fmt.Sprintf("inserting b1 for test case %d", idx), db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") database.MustExec(t, fmt.Sprintf("inserting b2 for test case %d", idx), db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b2UUID, "b2-label") @@ -884,8 +883,7 @@ n1 body edited func TestFullSyncBook(t *testing.T) { t.Run("exists on server only", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", b1UUID, 555, "b1-label", true, false) @@ -1023,8 +1021,7 @@ func TestFullSyncBook(t *testing.T) { for idx, tc := range testCases { func() { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, fmt.Sprintf("inserting book for test case %d", idx), db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", b1UUID, tc.clientUSN, tc.clientLabel, tc.clientDirty, tc.clientDeleted) @@ -1076,8 +1073,7 @@ func TestFullSyncBook(t *testing.T) { func TestStepSyncNote(t *testing.T) { t.Run("exists on server only", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") @@ -1234,8 +1230,7 @@ n1 body edited for idx, tc := range testCases { func() { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, fmt.Sprintf("inserting b1 for test case %d", idx), db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1-label") database.MustExec(t, fmt.Sprintf("inserting b2 for test case %d", idx), db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b2UUID, "b2-label") @@ -1296,8 +1291,7 @@ n1 body edited func TestStepSyncBook(t *testing.T) { t.Run("exists on server only", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", b1UUID, 555, "b1-label", true, false) @@ -1419,8 +1413,7 @@ func TestStepSyncBook(t *testing.T) { for idx, tc := range testCases { func() { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, fmt.Sprintf("inserting book for test case %d", idx), db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", b1UUID, tc.clientUSN, tc.clientLabel, tc.clientDirty, tc.clientDeleted) @@ -1483,8 +1476,7 @@ func TestStepSyncBook(t *testing.T) { func TestMergeBook(t *testing.T) { t.Run("insert, no duplicates", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) // test tx, err := db.Begin() @@ -1527,8 +1519,7 @@ func TestMergeBook(t *testing.T) { t.Run("insert, 1 duplicate", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b1-uuid", 1, "foo", false, false) // test @@ -1579,8 +1570,7 @@ func TestMergeBook(t *testing.T) { t.Run("insert, 3 duplicates", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b1-uuid", 1, "foo", false, false) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b2-uuid", 2, "foo_2", true, false) @@ -1648,8 +1638,7 @@ func TestMergeBook(t *testing.T) { t.Run("update, no duplicates", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) // test tx, err := db.Begin() @@ -1695,8 +1684,7 @@ func TestMergeBook(t *testing.T) { t.Run("update, 1 duplicate", func(t *testing.T) { // set up - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b1-uuid", 1, "foo", false, false) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b2-uuid", 2, "bar", false, false) @@ -1749,8 +1737,7 @@ func TestMergeBook(t *testing.T) { t.Run("update, 3 duplicate", func(t *testing.T) { // set uj - db := database.InitTestDB(t, dbPath, nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b1-uuid", 1, "foo", false, false) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, usn, label, dirty, deleted) VALUES (?, ?, ?, ?, ?)", "b2-uuid", 2, "bar", false, false) @@ -1820,11 +1807,8 @@ func TestMergeBook(t *testing.T) { func TestSaveServerState(t *testing.T) { // set up - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - testutils.Login(t, &ctx) - - db := ctx.DB + db := database.InitTestMemoryDB(t) + testutils.LoginDB(t, db) database.MustExec(t, "inserting last synced at", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemLastSyncAt, int64(1231108742)) database.MustExec(t, "inserting last max usn", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemLastMaxUSN, 8) @@ -1864,7 +1848,7 @@ func TestSaveServerState(t *testing.T) { // are updated accordingly based on the server response. func TestSendBooks(t *testing.T) { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -1900,7 +1884,7 @@ func TestSendBooks(t *testing.T) { var updatesUUIDs []string var deletedUUIDs []string - // fire up a test server. It decrypts the payload for test purposes. + // fire up a test server ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() == "/v3/books" && r.Method == "POST" { var payload client.CreateBookPayload @@ -1967,7 +1951,7 @@ func TestSendBooks(t *testing.T) { // test - // First, decrypt data so that they can be asserted + // First, sort data so that they can be asserted sort.SliceStable(createdLabels, func(i, j int) bool { return strings.Compare(createdLabels[i], createdLabels[j]) < 0 }) @@ -2097,7 +2081,7 @@ func TestSendBooks_isBehind(t *testing.T) { for idx, tc := range testCases { func() { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) ctx.APIEndpoint = ts.URL defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -2145,7 +2129,7 @@ func TestSendBooks_isBehind(t *testing.T) { for idx, tc := range testCases { func() { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) ctx.APIEndpoint = ts.URL defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -2193,7 +2177,7 @@ func TestSendBooks_isBehind(t *testing.T) { for idx, tc := range testCases { func() { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) ctx.APIEndpoint = ts.URL defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -2228,7 +2212,7 @@ func TestSendBooks_isBehind(t *testing.T) { // uuid from the incoming data. func TestSendNotes(t *testing.T) { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -2264,7 +2248,7 @@ func TestSendNotes(t *testing.T) { var updatedUUIDs []string var deletedUUIDs []string - // fire up a test server. It decrypts the payload for test purposes. + // fire up a test server ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() == "/v3/notes" && r.Method == "POST" { var payload client.CreateNotePayload @@ -2381,7 +2365,7 @@ func TestSendNotes(t *testing.T) { func TestSendNotes_addedOn(t *testing.T) { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -2393,7 +2377,7 @@ func TestSendNotes_addedOn(t *testing.T) { b1UUID := "b1-uuid" database.MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, usn, body, added_on, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?)", "n1-uuid", b1UUID, 0, "n1-body", 1541108743, false, true) - // fire up a test server. It decrypts the payload for test purposes. + // fire up a test server ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.String() == "/v3/notes" && r.Method == "POST" { resp := client.CreateNoteResp{ @@ -2513,7 +2497,7 @@ func TestSendNotes_isBehind(t *testing.T) { for idx, tc := range testCases { func() { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) ctx.APIEndpoint = ts.URL @@ -2562,7 +2546,7 @@ func TestSendNotes_isBehind(t *testing.T) { for idx, tc := range testCases { func() { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) ctx.APIEndpoint = ts.URL @@ -2611,7 +2595,7 @@ func TestSendNotes_isBehind(t *testing.T) { for idx, tc := range testCases { func() { // set up - ctx := context.InitTestCtx(t, paths, nil) + ctx := context.InitTestCtx(t, getTestPaths(t)) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) ctx.APIEndpoint = ts.URL @@ -2777,8 +2761,7 @@ n1 body edited for idx, tc := range testCases { func() { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, fmt.Sprintf("inserting b1 for test case %d", idx), db, "INSERT INTO books (uuid, label, usn, dirty) VALUES (?, ?, ?, ?)", b1UUID, "b1-label", 5, false) database.MustExec(t, fmt.Sprintf("inserting b2 for test case %d", idx), db, "INSERT INTO books (uuid, label, usn, dirty) VALUES (?, ?, ?, ?)", b2UUID, "b2-label", 6, false) @@ -2859,8 +2842,7 @@ n1 body edited func TestCheckBookPristine(t *testing.T) { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) database.MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, dirty) VALUES (?, ?, ?, ?)", "b1-uuid", "b1-label", 5, false) database.MustExec(t, "inserting b2", db, "INSERT INTO books (uuid, label, usn, dirty) VALUES (?, ?, ?, ?)", "b2-uuid", "b2-label", 6, false) @@ -3033,8 +3015,7 @@ func TestCheckBookInList(t *testing.T) { func TestCleanLocalNotes(t *testing.T) { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) list := syncList{ Notes: map[string]client.SyncFragNote{ @@ -3105,8 +3086,7 @@ func TestCleanLocalNotes(t *testing.T) { func TestCleanLocalBooks(t *testing.T) { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) list := syncList{ Notes: map[string]client.SyncFragNote{ @@ -3173,8 +3153,7 @@ func TestCleanLocalBooks(t *testing.T) { func TestPrepareEmptyServerSync(t *testing.T) { // set up - db := database.InitTestDB(t, "../../tmp/.dnote", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) // Setup: local has synced data (usn > 0, dirty = false) and some deleted items database.MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", "b1-uuid", "b1-label", 5, false, false) diff --git a/pkg/cli/context/testutils.go b/pkg/cli/context/testutils.go index 8453dd3a..b7b99fd0 100644 --- a/pkg/cli/context/testutils.go +++ b/pkg/cli/context/testutils.go @@ -19,8 +19,8 @@ package context import ( - "fmt" "os" + "path/filepath" "testing" "github.com/dnote/dnote/pkg/cli/consts" @@ -29,11 +29,69 @@ import ( "github.com/pkg/errors" ) -// InitTestCtx initializes a test context -func InitTestCtx(t *testing.T, paths Paths, dbOpts *database.TestDBOptions) DnoteCtx { - dbPath := fmt.Sprintf("%s/%s/%s", paths.Data, consts.DnoteDirName, consts.DnoteDBFileName) +// createTestDirectories creates test directories for the given paths +func createTestDirectories(t *testing.T, paths Paths) { + if paths.Config != "" { + configDir := filepath.Join(paths.Config, consts.DnoteDirName) + if err := os.MkdirAll(configDir, 0755); err != nil { + t.Fatal(errors.Wrap(err, "creating test config directory")) + } + } + if paths.Data != "" { + dataDir := filepath.Join(paths.Data, consts.DnoteDirName) + if err := os.MkdirAll(dataDir, 0755); err != nil { + t.Fatal(errors.Wrap(err, "creating test data directory")) + } + } + if paths.Cache != "" { + cacheDir := filepath.Join(paths.Cache, consts.DnoteDirName) + if err := os.MkdirAll(cacheDir, 0755); err != nil { + t.Fatal(errors.Wrap(err, "creating test cache directory")) + } + } +} - db := database.InitTestDB(t, dbPath, dbOpts) +// InitTestCtx initializes a test context with an in-memory database +func InitTestCtx(t *testing.T, paths Paths) DnoteCtx { + db := database.InitTestMemoryDB(t) + createTestDirectories(t, paths) + + return DnoteCtx{ + DB: db, + Paths: paths, + Clock: clock.NewMock(), // Use a mock clock to test times + } +} + +// InitTestCtxWithDB initializes a test context with the provided database. +// Used when you need full control over database initialization (e.g. migration tests). +func InitTestCtxWithDB(t *testing.T, paths Paths, db *database.DB) DnoteCtx { + createTestDirectories(t, paths) + + return DnoteCtx{ + DB: db, + Paths: paths, + Clock: clock.NewMock(), // Use a mock clock to test times + } +} + +// InitTestCtxWithFileDB initializes a test context with a file-based database +// at the expected XDG path. This is used for e2e tests that spawn CLI processes +// which need to access the database file. +func InitTestCtxWithFileDB(t *testing.T, paths Paths) DnoteCtx { + createTestDirectories(t, paths) + + dbPath := filepath.Join(paths.Data, consts.DnoteDirName, consts.DnoteDBFileName) + db, err := database.Open(dbPath) + if err != nil { + t.Fatal(errors.Wrap(err, "opening database")) + } + + if _, err := db.Exec(database.GetDefaultSchemaSQL()); err != nil { + t.Fatal(errors.Wrap(err, "running schema sql")) + } + + database.MarkMigrationComplete(t, db) return DnoteCtx{ DB: db, diff --git a/pkg/cli/database/cmd/generate-schema.go b/pkg/cli/database/cmd/generate-schema.go new file mode 100644 index 00000000..4bedaeaa --- /dev/null +++ b/pkg/cli/database/cmd/generate-schema.go @@ -0,0 +1,160 @@ +/* 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 . + */ + +// generate-schema creates a schema.sql file +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/dnote/dnote/pkg/cli/config" + "github.com/dnote/dnote/pkg/cli/context" + "github.com/dnote/dnote/pkg/cli/database" + "github.com/dnote/dnote/pkg/cli/infra" + "github.com/dnote/dnote/pkg/cli/migrate" +) + +func main() { + tmpDir, err := os.MkdirTemp("", "dnote-schema-gen-*") + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } + defer os.RemoveAll(tmpDir) + + schemaPath := filepath.Join("pkg", "cli", "database", "schema.sql") + + if err := run(tmpDir, schemaPath); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +func run(tmpDir, outputPath string) error { + schema, err := generateSchema(tmpDir) + if err != nil { + return err + } + + if err := os.WriteFile(outputPath, []byte(schema), 0644); err != nil { + return fmt.Errorf("writing schema file: %w", err) + } + + fmt.Printf("Schema generated successfully at %s\n", outputPath) + return nil +} + +// generateSchema creates a fresh database, runs all migrations, and extracts the schema +func generateSchema(tmpDir string) (string, error) { + // Create dnote directory structure in temp dir + dnoteDir := filepath.Join(tmpDir, "dnote") + if err := os.MkdirAll(dnoteDir, 0755); err != nil { + return "", fmt.Errorf("creating dnote dir: %w", err) + } + + // Use a file-based database + dbPath := filepath.Join(tmpDir, "schema.db") + + // Create context + ctx := context.DnoteCtx{ + Paths: context.Paths{ + Home: tmpDir, + Config: tmpDir, + Data: tmpDir, + Cache: tmpDir, + }, + Version: "schema-gen", + } + + // Open database + db, err := database.Open(dbPath) + if err != nil { + return "", fmt.Errorf("opening database: %w", err) + } + defer db.Close() + ctx.DB = db + + // Initialize database with base tables + if err := infra.InitDB(ctx); err != nil { + return "", fmt.Errorf("initializing database: %w", err) + } + + // Initialize system data + if err := infra.InitSystem(ctx); err != nil { + return "", fmt.Errorf("initializing system: %w", err) + } + + // Create minimal config file + if err := config.Write(ctx, config.Config{}); err != nil { + return "", fmt.Errorf("writing initial config: %w", err) + } + + // Run all local migrations + if err := migrate.Run(ctx, migrate.LocalSequence, migrate.LocalMode); err != nil { + return "", fmt.Errorf("running migrations: %w", err) + } + + // Extract schema before closing database + schema, err := extractSchema(db) + if err != nil { + return "", fmt.Errorf("extracting schema: %w", err) + } + + return schema, nil +} + +// extractSchema extracts the complete schema by querying sqlite_master +func extractSchema(db *database.DB) (string, error) { + // Query sqlite_master for all schema objects, excluding FTS shadow tables + // FTS shadow tables are internal tables automatically created by FTS virtual tables + rows, err := db.Conn.Query(`SELECT sql FROM sqlite_master + WHERE sql IS NOT NULL + AND name NOT LIKE 'sqlite_%' + AND (type != 'table' + OR (type = 'table' AND name NOT IN ( + SELECT m1.name FROM sqlite_master m1 + JOIN sqlite_master m2 ON m1.name LIKE m2.name || '_%' + WHERE m2.type = 'table' AND m2.sql LIKE '%VIRTUAL TABLE%' + )))`) + if err != nil { + return "", fmt.Errorf("querying sqlite_master: %w", err) + } + defer rows.Close() + + var schemas []string + for rows.Next() { + var sql string + if err := rows.Scan(&sql); err != nil { + return "", fmt.Errorf("scanning row: %w", err) + } + schemas = append(schemas, sql) + } + + if err := rows.Err(); err != nil { + return "", fmt.Errorf("iterating rows: %w", err) + } + + // Add autogenerated header comment + header := `-- This is the final state of the CLI database after all migrations. +-- Auto-generated by generate-schema.go. Do not edit manually. +` + return header + strings.Join(schemas, ";\n") + ";\n", nil +} diff --git a/pkg/cli/database/cmd/generate-schema_test.go b/pkg/cli/database/cmd/generate-schema_test.go new file mode 100644 index 00000000..acc46654 --- /dev/null +++ b/pkg/cli/database/cmd/generate-schema_test.go @@ -0,0 +1,76 @@ +/* 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 ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/dnote/dnote/pkg/assert" +) + +func TestRun(t *testing.T) { + tmpDir := t.TempDir() + outputPath := filepath.Join(tmpDir, "schema.sql") + + // Run the function + if err := run(tmpDir, outputPath); err != nil { + t.Fatalf("run() failed: %v", err) + } + + // Verify schema.sql was created + content, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("reading schema.sql: %v", err) + } + + schema := string(content) + + // Verify it has the header + assert.Equal(t, strings.HasPrefix(schema, "-- This is the final state"), true, "schema.sql should have header comment") + + // Verify schema contains expected tables + expectedTables := []string{ + "CREATE TABLE books", + "CREATE TABLE system", + "CREATE TABLE \"notes\"", + "CREATE VIRTUAL TABLE note_fts", + } + + for _, expected := range expectedTables { + assert.Equal(t, strings.Contains(schema, expected), true, fmt.Sprintf("schema should contain %s", expected)) + } + + // Verify schema contains triggers + expectedTriggers := []string{ + "CREATE TRIGGER notes_after_insert", + "CREATE TRIGGER notes_after_delete", + "CREATE TRIGGER notes_after_update", + } + + for _, expected := range expectedTriggers { + assert.Equal(t, strings.Contains(schema, expected), true, fmt.Sprintf("schema should contain %s", expected)) + } + + // Verify schema does not contain sqlite internal tables + assert.Equal(t, strings.Contains(schema, "sqlite_sequence"), false, "schema should not contain sqlite_sequence") +} diff --git a/pkg/cli/database/models_test.go b/pkg/cli/database/models_test.go index 80bc98b2..28bc2212 100644 --- a/pkg/cli/database/models_test.go +++ b/pkg/cli/database/models_test.go @@ -109,8 +109,8 @@ func TestNoteInsert(t *testing.T) { for idx, tc := range testCases { func() { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() n := Note{ UUID: tc.uuid, @@ -243,8 +243,8 @@ func TestNoteUpdate(t *testing.T) { for idx, tc := range testCases { func() { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() n1 := Note{ UUID: tc.uuid, @@ -335,8 +335,8 @@ func TestNoteUpdateUUID(t *testing.T) { for idx, tc := range testCases { t.Run(fmt.Sprintf("testCase%d", idx), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() n1 := Note{ UUID: "n1-uuid", @@ -390,8 +390,8 @@ func TestNoteUpdateUUID(t *testing.T) { func TestNoteExpunge(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() n1 := Note{ UUID: "n1-uuid", @@ -513,8 +513,8 @@ func TestBookInsert(t *testing.T) { for idx, tc := range testCases { func() { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() b := Book{ UUID: tc.uuid, @@ -594,8 +594,8 @@ func TestBookUpdate(t *testing.T) { for idx, tc := range testCases { func() { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() b1 := Book{ UUID: "b1-uuid", @@ -673,8 +673,8 @@ func TestBookUpdateUUID(t *testing.T) { t.Run(fmt.Sprintf("testCase%d", idx), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() b1 := Book{ UUID: "b1-uuid", @@ -724,8 +724,8 @@ func TestBookUpdateUUID(t *testing.T) { func TestBookExpunge(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() b1 := Book{ UUID: "b1-uuid", @@ -779,8 +779,8 @@ func TestBookExpunge(t *testing.T) { // TestNoteFTS tests that note full text search indices stay in sync with the notes after insert, update and delete func TestNoteFTS(t *testing.T) { // set up - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() // execute - insert n := Note{ diff --git a/pkg/cli/database/queries_test.go b/pkg/cli/database/queries_test.go index c5e055bf..d3c2793e 100644 --- a/pkg/cli/database/queries_test.go +++ b/pkg/cli/database/queries_test.go @@ -47,8 +47,8 @@ func TestInsertSystem(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("insert %s %s", tc.key, tc.val), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() // execute tx, err := db.Begin() @@ -95,8 +95,8 @@ func TestUpsertSystem(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("insert %s %s", tc.key, tc.val), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "baz", "quz") @@ -134,8 +134,8 @@ func TestUpsertSystem(t *testing.T) { func TestGetSystem(t *testing.T) { t.Run(fmt.Sprintf("get string value"), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() // execute MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", "bar") @@ -157,8 +157,8 @@ func TestGetSystem(t *testing.T) { t.Run(fmt.Sprintf("get int64 value"), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() // execute MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", 1234) @@ -198,8 +198,8 @@ func TestUpdateSystem(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("update %s %s", tc.key, tc.val), func(t *testing.T) { // Setup - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "foo", "fuz") MustExec(t, "inserting a system configuration", db, "INSERT INTO system (key, value) VALUES (?, ?)", "baz", "quz") @@ -238,8 +238,8 @@ func TestUpdateSystem(t *testing.T) { func TestGetActiveNote(t *testing.T) { t.Run("not deleted", func(t *testing.T) { // set up - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() n1UUID := "n1-uuid" MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, edited_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", n1UUID, "b1-uuid", "n1 content", 1542058875, 1542058876, 1, false, true) @@ -267,8 +267,8 @@ func TestGetActiveNote(t *testing.T) { t.Run("deleted", func(t *testing.T) { // set up - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() n1UUID := "n1-uuid" MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, edited_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", n1UUID, "b1-uuid", "n1 content", 1542058875, 1542058876, 1, true, true) @@ -291,8 +291,8 @@ func TestGetActiveNote(t *testing.T) { func TestUpdateNoteContent(t *testing.T) { // set up - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() uuid := "n1-uuid" MustExec(t, "inserting n1", db, "INSERT INTO notes (uuid, book_uuid, body, added_on, edited_on, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", uuid, "b1-uuid", "n1 content", 1542058875, 0, 1, false, false) @@ -323,8 +323,8 @@ func TestUpdateNoteContent(t *testing.T) { func TestUpdateNoteBook(t *testing.T) { // set up - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() b1UUID := "b1-uuid" b2UUID := "b2-uuid" @@ -360,8 +360,8 @@ func TestUpdateNoteBook(t *testing.T) { func TestUpdateBookName(t *testing.T) { // set up - db := InitTestDB(t, "../tmp/dnote-test.db", nil) - defer TeardownTestDB(t, db) + db := InitTestMemoryDB(t) + defer db.Close() b1UUID := "b1-uuid" MustExec(t, "inserting b1", db, "INSERT INTO books (uuid, label, usn, deleted, dirty) VALUES (?, ?, ?, ?, ?)", b1UUID, "b1-label", 8, false, false) diff --git a/pkg/cli/database/schema.sql b/pkg/cli/database/schema.sql new file mode 100644 index 00000000..655148c8 --- /dev/null +++ b/pkg/cli/database/schema.sql @@ -0,0 +1,36 @@ +-- This is the final state of the CLI database after all migrations. +-- Auto-generated by generate-schema.go. Do not edit manually. +CREATE TABLE books + ( + uuid text PRIMARY KEY, + label text NOT NULL + , dirty bool DEFAULT false, usn int DEFAULT 0 NOT NULL, deleted bool DEFAULT false); +CREATE TABLE system + ( + key string NOT NULL, + value text NOT NULL + ); +CREATE UNIQUE INDEX idx_books_label ON books(label); +CREATE UNIQUE INDEX idx_books_uuid ON books(uuid); +CREATE TABLE "notes" + ( + uuid text NOT NULL, + book_uuid text NOT NULL, + body text NOT NULL, + added_on integer NOT NULL, + edited_on integer DEFAULT 0, + dirty bool DEFAULT false, + usn int DEFAULT 0 NOT NULL, + deleted bool DEFAULT false + ); +CREATE VIRTUAL TABLE note_fts USING fts5(content=notes, body, tokenize="porter unicode61 categories 'L* N* Co Ps Pe'"); +CREATE TRIGGER notes_after_insert AFTER INSERT ON notes BEGIN + INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body); + END; +CREATE TRIGGER notes_after_delete AFTER DELETE ON notes BEGIN + INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body); + END; +CREATE TRIGGER notes_after_update AFTER UPDATE ON notes BEGIN + INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body); + INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body); + END; diff --git a/pkg/cli/database/test_embed.go b/pkg/cli/database/test_embed.go new file mode 100644 index 00000000..c2fe08e9 --- /dev/null +++ b/pkg/cli/database/test_embed.go @@ -0,0 +1,27 @@ +package database + +import ( + "testing" +) + +func TestSchemaEmbed(t *testing.T) { + db := InitTestMemoryDB(t) + defer db.Close() + + // Try to insert a book to verify schema is loaded + _, err := db.Exec("INSERT INTO books (uuid, label) VALUES (?, ?)", "test-uuid", "test-label") + if err != nil { + t.Fatalf("Failed to insert into books: %v", err) + } + + // Verify it was inserted + var label string + err = db.QueryRow("SELECT label FROM books WHERE uuid = ?", "test-uuid").Scan(&label) + if err != nil { + t.Fatalf("Failed to query books: %v", err) + } + if label != "test-label" { + t.Fatalf("Expected label 'test-label', got '%s'", label) + } + t.Log("Schema embed test passed!") +} diff --git a/pkg/cli/database/testutils.go b/pkg/cli/database/testutils.go index b21fcfb6..fb631c62 100644 --- a/pkg/cli/database/testutils.go +++ b/pkg/cli/database/testutils.go @@ -20,6 +20,7 @@ package database import ( "database/sql" + _ "embed" "fmt" "os" "path/filepath" @@ -30,55 +31,13 @@ import ( "github.com/pkg/errors" ) -var defaultSchemaSQL = `CREATE TABLE books - ( - uuid text PRIMARY KEY, - label text NOT NULL - , dirty bool DEFAULT false, usn int DEFAULT 0 NOT NULL, deleted bool DEFAULT false); -CREATE TABLE system - ( - key string NOT NULL, - value text NOT NULL - ); -CREATE UNIQUE INDEX idx_books_label ON books(label); -CREATE UNIQUE INDEX idx_books_uuid ON books(uuid); -CREATE TABLE IF NOT EXISTS "notes" - ( - uuid text NOT NULL, - book_uuid text NOT NULL, - body text NOT NULL, - added_on integer NOT NULL, - edited_on integer DEFAULT 0, - dirty bool DEFAULT false, - usn int DEFAULT 0 NOT NULL, - deleted bool DEFAULT false - ); -CREATE VIRTUAL TABLE note_fts USING fts5(content=notes, body, tokenize="porter unicode61 categories 'L* N* Co Ps Pe'") -/* note_fts(body) */; -CREATE TABLE IF NOT EXISTS 'note_fts_data'(id INTEGER PRIMARY KEY, block BLOB); -CREATE TABLE IF NOT EXISTS 'note_fts_idx'(segid, term, pgno, PRIMARY KEY(segid, term)) WITHOUT ROWID; -CREATE TABLE IF NOT EXISTS 'note_fts_docsize'(id INTEGER PRIMARY KEY, sz BLOB); -CREATE TABLE IF NOT EXISTS 'note_fts_config'(k PRIMARY KEY, v) WITHOUT ROWID; -CREATE TRIGGER notes_after_insert AFTER INSERT ON notes BEGIN - INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body); - END; -CREATE TRIGGER notes_after_delete AFTER DELETE ON notes BEGIN - INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body); - END; -CREATE TRIGGER notes_after_update AFTER UPDATE ON notes BEGIN - INSERT INTO note_fts(note_fts, rowid, body) VALUES ('delete', old.rowid, old.body); - INSERT INTO note_fts(rowid, body) VALUES (new.rowid, new.body); - END; -CREATE TABLE actions - ( - uuid text PRIMARY KEY, - schema integer NOT NULL, - type text NOT NULL, - data text NOT NULL, - timestamp integer NOT NULL - ); -CREATE UNIQUE INDEX idx_notes_uuid ON notes(uuid); -CREATE INDEX idx_notes_book_uuid ON notes(book_uuid);` +//go:embed schema.sql +var defaultSchemaSQL string + +// GetDefaultSchemaSQL returns the default schema SQL for tests +func GetDefaultSchemaSQL() string { + return defaultSchemaSQL +} // MustScan scans the given row and fails a test in case of any errors func MustScan(t *testing.T, message string, row *sql.Row, args ...interface{}) { @@ -98,29 +57,50 @@ func MustExec(t *testing.T, message string, db *DB, query string, args ...interf return result } -// TestDBOptions contains options for test database -type TestDBOptions struct { - SchemaSQLPath string - SkipMigration bool +// InitTestMemoryDB initializes an in-memory test database with the default schema. +func InitTestMemoryDB(t *testing.T) *DB { + db := InitTestMemoryDBRaw(t, "") + MarkMigrationComplete(t, db) + return db } -// InitTestDB initializes a test database and opens connection to it -func InitTestDB(t *testing.T, dbPath string, options *TestDBOptions) *DB { +// InitTestFileDB initializes a file-based test database with the default schema. +func InitTestFileDB(t *testing.T) (*DB, string) { + dbPath := filepath.Join(t.TempDir(), "dnote.db") + + return InitTestFileDBRaw(t, dbPath), dbPath +} + +// InitTestFileDBRaw initializes a file-based test database at the specified path with the default schema. +func InitTestFileDBRaw(t *testing.T, dbPath string) *DB { db, err := Open(dbPath) if err != nil { - t.Fatal(errors.Wrap(err, "opening database connection")) + t.Fatal(errors.Wrap(err, "opening database")) } - dir, _ := filepath.Split(dbPath) - err = os.MkdirAll(dir, 0777) + if _, err := db.Exec(defaultSchemaSQL); err != nil { + t.Fatal(errors.Wrap(err, "running schema sql")) + } + + MarkMigrationComplete(t, db) + + return db +} + +// InitTestMemoryDBRaw initializes an in-memory test database without marking migrations complete. +// If schemaPath is empty, uses the default schema. Used for migration testing. +func InitTestMemoryDBRaw(t *testing.T, schemaPath string) *DB { + uuid := mustGenerateTestUUID(t) + dbName := fmt.Sprintf("file:%s?mode=memory&cache=shared", uuid) + + db, err := Open(dbName) if err != nil { - t.Fatal(errors.Wrap(err, "creating the directory for test database file")) + t.Fatal(errors.Wrap(err, "opening in-memory database")) } var schemaSQL string - if options != nil && options.SchemaSQLPath != "" { - b := utils.ReadFileAbs(options.SchemaSQLPath) - schemaSQL = string(b) + if schemaPath != "" { + schemaSQL = string(utils.ReadFileAbs(schemaPath)) } else { schemaSQL = defaultSchemaSQL } @@ -129,10 +109,6 @@ func InitTestDB(t *testing.T, dbPath string, options *TestDBOptions) *DB { t.Fatal(errors.Wrap(err, "running schema sql")) } - if options == nil || !options.SkipMigration { - MarkMigrationComplete(t, db) - } - return db } @@ -161,10 +137,19 @@ func OpenTestDB(t *testing.T, dnoteDir string) *DB { // MarkMigrationComplete marks all migrations as complete in the database func MarkMigrationComplete(t *testing.T, db *DB) { - if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", consts.SystemSchema, 12); err != nil { + if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", consts.SystemSchema, 14); err != nil { t.Fatal(errors.Wrap(err, "inserting schema")) } if _, err := db.Exec("INSERT INTO system (key, value) VALUES (? , ?);", consts.SystemRemoteSchema, 1); err != nil { t.Fatal(errors.Wrap(err, "inserting remote schema")) } } + +// mustGenerateTestUUID generates a UUID for test databases and fails the test on error +func mustGenerateTestUUID(t *testing.T) string { + uuid, err := utils.GenerateUUID() + if err != nil { + t.Fatal(errors.Wrap(err, "generating UUID for test database")) + } + return uuid +} diff --git a/pkg/cli/infra/init.go b/pkg/cli/infra/init.go index 4bbaae6e..fbff4267 100644 --- a/pkg/cli/infra/init.go +++ b/pkg/cli/infra/init.go @@ -329,6 +329,7 @@ func getEditorCommand() string { return ret } +// initDir creates a directory if it doesn't exist func initDir(path string) error { ok, err := utils.FileExists(path) if err != nil { diff --git a/pkg/cli/infra/init_test.go b/pkg/cli/infra/init_test.go index 546baab0..401294d5 100644 --- a/pkg/cli/infra/init_test.go +++ b/pkg/cli/infra/init_test.go @@ -32,8 +32,8 @@ import ( func TestInitSystemKV(t *testing.T) { // Setup - db := database.InitTestDB(t, "../tmp/dnote-test.db", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) + defer db.Close() var originalCount int database.MustScan(t, "counting system configs", db.QueryRow("SELECT count(*) FROM system"), &originalCount) @@ -64,8 +64,8 @@ func TestInitSystemKV(t *testing.T) { func TestInitSystemKV_existing(t *testing.T) { // Setup - db := database.InitTestDB(t, "../tmp/dnote-test.db", nil) - defer database.TeardownTestDB(t, db) + db := database.InitTestMemoryDB(t) + defer db.Close() database.MustExec(t, "inserting a system config", db, "INSERT INTO system (key, value) VALUES (?, ?)", "testKey", "testVal") diff --git a/pkg/cli/main_test.go b/pkg/cli/main_test.go index 63a95d87..660a5e35 100644 --- a/pkg/cli/main_test.go +++ b/pkg/cli/main_test.go @@ -35,14 +35,17 @@ import ( var binaryName = "test-dnote" -var testDir = "./tmp/.dnote" - -var opts = testutils.RunDnoteCmdOptions{ - Env: []string{ - fmt.Sprintf("XDG_CONFIG_HOME=%s", testDir), - fmt.Sprintf("XDG_DATA_HOME=%s", testDir), - fmt.Sprintf("XDG_CACHE_HOME=%s", testDir), - }, +// setupTestEnv creates a unique test directory for parallel test execution +func setupTestEnv(t *testing.T) (string, testutils.RunDnoteCmdOptions) { + testDir := t.TempDir() + opts := testutils.RunDnoteCmdOptions{ + Env: []string{ + fmt.Sprintf("XDG_CONFIG_HOME=%s", testDir), + fmt.Sprintf("XDG_DATA_HOME=%s", testDir), + fmt.Sprintf("XDG_CACHE_HOME=%s", testDir), + }, + } + return testDir, opts } func TestMain(m *testing.M) { @@ -55,10 +58,11 @@ func TestMain(m *testing.M) { } func TestInit(t *testing.T) { + testDir, opts := setupTestEnv(t) + // Execute // run an arbitrary command "view" due to https://github.com/spf13/cobra/issues/1056 testutils.RunDnoteCmd(t, opts, binaryName, "view") - defer testutils.RemoveDir(t, testDir) db := database.OpenTestDB(t, testDir) @@ -107,12 +111,12 @@ func TestInit(t *testing.T) { func TestAddNote(t *testing.T) { t.Run("new book", func(t *testing.T) { + testDir, opts := setupTestEnv(t) + // Set up and execute testutils.RunDnoteCmd(t, opts, binaryName, "add", "js", "-c", "foo") testutils.MustWaitDnoteCmd(t, opts, testutils.UserContent, binaryName, "add", "js") - defer testutils.RemoveDir(t, testDir) - db := database.OpenTestDB(t, testDir) // Test @@ -138,13 +142,15 @@ func TestAddNote(t *testing.T) { }) t.Run("existing book", func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup3(t, db) // Execute - testutils.RunDnoteCmd(t, opts, binaryName, "add", "js", "-c", "foo") - defer testutils.RemoveDir(t, testDir) + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "add", "js", "-c", "foo") // Test @@ -179,13 +185,15 @@ func TestAddNote(t *testing.T) { func TestEditNote(t *testing.T) { t.Run("content flag", func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup4(t, db) // Execute - testutils.RunDnoteCmd(t, opts, binaryName, "edit", "2", "-c", "foo bar") - defer testutils.RemoveDir(t, testDir) + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "edit", "2", "-c", "foo bar") // Test var noteCount, bookCount int @@ -212,13 +220,15 @@ func TestEditNote(t *testing.T) { }) t.Run("book flag", func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup5(t, db) // Execute - testutils.RunDnoteCmd(t, opts, binaryName, "edit", "2", "-b", "linux") - defer testutils.RemoveDir(t, testDir) + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "edit", "2", "-b", "linux") // Test var noteCount, bookCount int @@ -246,13 +256,15 @@ func TestEditNote(t *testing.T) { }) t.Run("book flag and content flag", func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup5(t, db) // Execute - testutils.RunDnoteCmd(t, opts, binaryName, "edit", "2", "-b", "linux", "-c", "n2 body updated") - defer testutils.RemoveDir(t, testDir) + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "edit", "2", "-b", "linux", "-c", "n2 body updated") // Test var noteCount, bookCount int @@ -282,13 +294,15 @@ func TestEditNote(t *testing.T) { func TestEditBook(t *testing.T) { t.Run("name flag", func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup1(t, db) // Execute - testutils.RunDnoteCmd(t, opts, binaryName, "edit", "js", "-n", "js-edited") - defer testutils.RemoveDir(t, testDir) + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "edit", "js", "-n", "js-edited") // Test var noteCount, bookCount int @@ -341,17 +355,19 @@ func TestRemoveNote(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("--yes=%t", tc.yesFlag), func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup2(t, db) // Execute if tc.yesFlag { - testutils.RunDnoteCmd(t, opts, binaryName, "remove", "-y", "1") + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "remove", "-y", "1") } else { - testutils.MustWaitDnoteCmd(t, opts, testutils.ConfirmRemoveNote, binaryName, "remove", "1") + testutils.MustWaitDnoteCmd(t, opts, testutils.ConfirmRemoveNote, binaryName, "--dbPath", dbPath, "remove", "1") } - defer testutils.RemoveDir(t, testDir) // Test var noteCount, bookCount, jsNoteCount, linuxNoteCount int @@ -428,19 +444,20 @@ func TestRemoveBook(t *testing.T) { for _, tc := range testCases { t.Run(fmt.Sprintf("--yes=%t", tc.yesFlag), func(t *testing.T) { + _, opts := setupTestEnv(t) + // Setup - db := database.InitTestDB(t, fmt.Sprintf("%s/%s/%s", testDir, consts.DnoteDirName, consts.DnoteDBFileName), nil) + db, dbPath := database.InitTestFileDB(t) + defer database.TeardownTestDB(t, db) testutils.Setup2(t, db) // Execute if tc.yesFlag { - testutils.RunDnoteCmd(t, opts, binaryName, "remove", "-y", "js") + testutils.RunDnoteCmd(t, opts, binaryName, "--dbPath", dbPath, "remove", "-y", "js") } else { - testutils.MustWaitDnoteCmd(t, opts, testutils.ConfirmRemoveBook, binaryName, "remove", "js") + testutils.MustWaitDnoteCmd(t, opts, testutils.ConfirmRemoveBook, binaryName, "--dbPath", dbPath, "remove", "js") } - defer testutils.RemoveDir(t, testDir) - // Test var noteCount, bookCount, jsNoteCount, linuxNoteCount int database.MustScan(t, "counting books", db.QueryRow("SELECT count(*) FROM books"), &bookCount) @@ -537,17 +554,9 @@ func TestDBPathFlag(t *testing.T) { } // Setup - use two different custom database paths - customDBPath1 := "./tmp/custom-test1.db" - customDBPath2 := "./tmp/custom-test2.db" - defer testutils.RemoveDir(t, "./tmp") - - customOpts := testutils.RunDnoteCmdOptions{ - Env: []string{ - fmt.Sprintf("XDG_CONFIG_HOME=%s", testDir), - fmt.Sprintf("XDG_DATA_HOME=%s", testDir), - fmt.Sprintf("XDG_CACHE_HOME=%s", testDir), - }, - } + testDir, customOpts := setupTestEnv(t) + customDBPath1 := fmt.Sprintf("%s/custom-test1.db", testDir) + customDBPath2 := fmt.Sprintf("%s/custom-test2.db", testDir) // Execute - add different notes to each database testutils.RunDnoteCmd(t, customOpts, binaryName, "--dbPath", customDBPath1, "add", "db1-book", "-c", "content in db1") diff --git a/pkg/cli/migrate/legacy_test.go b/pkg/cli/migrate/legacy_test.go index 00ebb7d7..cdc5baf5 100644 --- a/pkg/cli/migrate/legacy_test.go +++ b/pkg/cli/migrate/legacy_test.go @@ -353,14 +353,19 @@ func TestMigrateToV7(t *testing.T) { } func TestMigrateToV8(t *testing.T) { - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-1-pre-schema.sql", SkipMigration: true} - db := database.InitTestDB(t, "../tmp/.dnote/dnote-test.db", &opts) + tmpDir := t.TempDir() + dnoteDir := tmpDir + "/.dnote" + if err := os.MkdirAll(dnoteDir, 0755); err != nil { + t.Fatal(errors.Wrap(err, "creating legacy dnote directory")) + } + + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-1-pre-schema.sql") defer database.TeardownTestDB(t, db) ctx := context.DnoteCtx{ Paths: context.Paths{ - Home: "../tmp", - LegacyDnote: "../tmp/.dnote", + Home: tmpDir, + LegacyDnote: dnoteDir, }, DB: db, } diff --git a/pkg/cli/migrate/migrate_test.go b/pkg/cli/migrate/migrate_test.go index b87c5b15..315530de 100644 --- a/pkg/cli/migrate/migrate_test.go +++ b/pkg/cli/migrate/migrate_test.go @@ -38,14 +38,18 @@ import ( "github.com/pkg/errors" ) -var paths context.Paths = context.Paths{ - Home: "../../tmp", - Cache: "../../tmp", - Config: "../../tmp", - Data: "../../tmp", +func getTestPaths(t *testing.T) context.Paths { + tmpDir := t.TempDir() + return context.Paths{ + Home: tmpDir, + Cache: tmpDir, + Config: tmpDir, + Data: tmpDir, + } } func TestExecute_bump_schema(t *testing.T) { + paths := getTestPaths(t) testCases := []struct { schemaKey string }{ @@ -60,12 +64,10 @@ func TestExecute_bump_schema(t *testing.T) { for _, tc := range testCases { func() { // set up - opts := database.TestDBOptions{SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - database.MustExec(t, "inserting a schema", db, "INSERT INTO system (key, value) VALUES (?, ?)", tc.schemaKey, 8) m1 := migration{ @@ -100,6 +102,7 @@ func TestExecute_bump_schema(t *testing.T) { } func TestRun_nonfresh(t *testing.T) { + paths := getTestPaths(t) testCases := []struct { mode int schemaKey string @@ -117,11 +120,9 @@ func TestRun_nonfresh(t *testing.T) { for _, tc := range testCases { func() { // set up - opts := database.TestDBOptions{SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - - db := ctx.DB database.MustExec(t, "inserting a schema", db, "INSERT INTO system (key, value) VALUES (?, ?)", tc.schemaKey, 2) database.MustExec(t, "creating a temporary table for testing", db, "CREATE TABLE migrate_run_test ( name string )") @@ -180,6 +181,7 @@ func TestRun_nonfresh(t *testing.T) { } func TestRun_fresh(t *testing.T) { + paths := getTestPaths(t) testCases := []struct { mode int schemaKey string @@ -197,12 +199,10 @@ func TestRun_fresh(t *testing.T) { for _, tc := range testCases { func() { // set up - opts := database.TestDBOptions{SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - database.MustExec(t, "creating a temporary table for testing", db, "CREATE TABLE migrate_run_test ( name string )") @@ -254,6 +254,7 @@ func TestRun_fresh(t *testing.T) { } func TestRun_up_to_date(t *testing.T) { + paths := getTestPaths(t) testCases := []struct { mode int schemaKey string @@ -271,12 +272,10 @@ func TestRun_up_to_date(t *testing.T) { for _, tc := range testCases { func() { // set up - opts := database.TestDBOptions{SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - database.MustExec(t, "creating a temporary table for testing", db, "CREATE TABLE migrate_run_test ( name string )") @@ -325,12 +324,11 @@ func TestRun_up_to_date(t *testing.T) { } func TestLocalMigration1(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-1-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-1-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - - db := ctx.DB data := testutils.MustMarshalJSON(t, actions.AddBookDataV1{BookName: "js"}) a1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting action", db, @@ -403,12 +401,11 @@ func TestLocalMigration1(t *testing.T) { } func TestLocalMigration2(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-1-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-1-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - - db := ctx.DB c1 := "note 1 - v1" c2 := "note 1 - v2" css := "css" @@ -490,12 +487,11 @@ func TestLocalMigration2(t *testing.T) { } func TestLocalMigration3(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-1-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-1-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - - db := ctx.DB data := testutils.MustMarshalJSON(t, actions.AddNoteDataV2{NoteUUID: "note-1-uuid", BookName: "js", Content: "note 1", Public: false}) a1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting action", db, @@ -565,13 +561,12 @@ func TestLocalMigration3(t *testing.T) { } func TestLocalMigration4(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-1-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-1-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting css book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "css") n1UUID := testutils.MustGenerateUUID(t) @@ -609,13 +604,12 @@ func TestLocalMigration4(t *testing.T) { } func TestLocalMigration5(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-5-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-5-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting css book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "css") b2UUID := testutils.MustGenerateUUID(t) @@ -671,13 +665,12 @@ func TestLocalMigration5(t *testing.T) { } func TestLocalMigration6(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-5-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-5-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - data := testutils.MustMarshalJSON(t, actions.AddBookDataV1{BookName: "js"}) a1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting action", db, @@ -704,13 +697,12 @@ func TestLocalMigration6(t *testing.T) { } func TestLocalMigration7_trash(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-7-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-7-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting trash book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "trash") @@ -737,13 +729,12 @@ func TestLocalMigration7_trash(t *testing.T) { } func TestLocalMigration7_conflicts(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-7-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-7-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "conflicts") @@ -770,13 +761,12 @@ func TestLocalMigration7_conflicts(t *testing.T) { } func TestLocalMigration7_conflicts_dup(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-7-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-7-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "conflicts") b2UUID := testutils.MustGenerateUUID(t) @@ -808,13 +798,12 @@ func TestLocalMigration7_conflicts_dup(t *testing.T) { } func TestLocalMigration8(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-8-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-8-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1") @@ -874,13 +863,12 @@ func TestLocalMigration8(t *testing.T) { } func TestLocalMigration9(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-9-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-9-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1") @@ -920,13 +908,12 @@ func TestLocalMigration9(t *testing.T) { } func TestLocalMigration10(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-10-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-10-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book ", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "123") b2UUID := testutils.MustGenerateUUID(t) @@ -992,13 +979,12 @@ func TestLocalMigration10(t *testing.T) { } func TestLocalMigration11(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-11-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-11-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book 1", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "foo") b2UUID := testutils.MustGenerateUUID(t) @@ -1072,9 +1058,10 @@ func TestLocalMigration11(t *testing.T) { } func TestLocalMigration12(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-12-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-12-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) data := []byte("editor: vim") @@ -1109,9 +1096,10 @@ func TestLocalMigration12(t *testing.T) { } func TestLocalMigration13(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-12-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-12-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) data := []byte("editor: vim\napiEndpoint: https://test.com/api") @@ -1151,13 +1139,12 @@ func TestLocalMigration13(t *testing.T) { } func TestLocalMigration14(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/local-14-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/local-14-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) - db := ctx.DB - b1UUID := testutils.MustGenerateUUID(t) database.MustExec(t, "inserting book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", b1UUID, "b1") @@ -1201,9 +1188,10 @@ func TestLocalMigration14(t *testing.T) { } func TestRemoteMigration1(t *testing.T) { + paths := getTestPaths(t) // set up - opts := database.TestDBOptions{SchemaSQLPath: "./fixtures/remote-1-pre-schema.sql", SkipMigration: true} - ctx := context.InitTestCtx(t, paths, &opts) + db := database.InitTestMemoryDBRaw(t, "./fixtures/remote-1-pre-schema.sql") + ctx := context.InitTestCtxWithDB(t, paths, db) defer context.TeardownTestCtx(t, ctx) testutils.Login(t, &ctx) @@ -1244,7 +1232,6 @@ func TestRemoteMigration1(t *testing.T) { ctx.APIEndpoint = server.URL - db := ctx.DB database.MustExec(t, "inserting js book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", JSBookUUID, "js") database.MustExec(t, "inserting css book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", CSSBookUUID, "css") database.MustExec(t, "inserting linux book", db, "INSERT INTO books (uuid, label) VALUES (?, ?)", linuxBookUUID, "linux") diff --git a/pkg/cli/testutils/main.go b/pkg/cli/testutils/main.go index 6c6caa64..cefa6323 100644 --- a/pkg/cli/testutils/main.go +++ b/pkg/cli/testutils/main.go @@ -48,12 +48,15 @@ const ( // Timeout for waiting for prompts in tests const promptTimeout = 10 * time.Second -// Login simulates a logged in user by inserting credentials in the local database -func Login(t *testing.T, ctx *context.DnoteCtx) { - db := ctx.DB - +// LoginDB sets up login credentials in the database for tests +func LoginDB(t *testing.T, db *database.DB) { database.MustExec(t, "inserting sessionKey", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, "someSessionKey") database.MustExec(t, "inserting sessionKeyExpiry", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, time.Now().Add(24*time.Hour).Unix()) +} + +// Login simulates a logged in user by inserting credentials in the local database +func Login(t *testing.T, ctx *context.DnoteCtx) { + LoginDB(t, ctx.DB) ctx.SessionKey = "someSessionKey" ctx.SessionKeyExpiry = time.Now().Add(24 * time.Hour).Unix() diff --git a/pkg/cli/ui/editor_test.go b/pkg/cli/ui/editor_test.go index c2834a09..f359e1ee 100644 --- a/pkg/cli/ui/editor_test.go +++ b/pkg/cli/ui/editor_test.go @@ -30,10 +30,11 @@ import ( func TestGetTmpContentPath(t *testing.T) { t.Run("no collision", func(t *testing.T) { + tmpDir := t.TempDir() ctx := context.InitTestCtx(t, context.Paths{ - Data: "../tmp", - Cache: "../tmp", - }, nil) + Data: tmpDir, + Cache: tmpDir, + }) defer context.TeardownTestCtx(t, ctx) res, err := GetTmpContentPath(ctx) @@ -47,10 +48,11 @@ func TestGetTmpContentPath(t *testing.T) { t.Run("one existing session", func(t *testing.T) { // set up + tmpDir := t.TempDir() ctx := context.InitTestCtx(t, context.Paths{ - Data: "../tmp2", - Cache: "../tmp2", - }, nil) + Data: tmpDir, + Cache: tmpDir, + }) defer context.TeardownTestCtx(t, ctx) p := fmt.Sprintf("%s/%s", ctx.Paths.Cache, "DNOTE_TMPCONTENT_0.md") @@ -71,10 +73,11 @@ func TestGetTmpContentPath(t *testing.T) { t.Run("two existing sessions", func(t *testing.T) { // set up + tmpDir := t.TempDir() ctx := context.InitTestCtx(t, context.Paths{ - Data: "../tmp3", - Cache: "../tmp3", - }, nil) + Data: tmpDir, + Cache: tmpDir, + }) defer context.TeardownTestCtx(t, ctx) p1 := fmt.Sprintf("%s/%s", ctx.Paths.Cache, "DNOTE_TMPCONTENT_0.md") diff --git a/pkg/e2e/sync_test.go b/pkg/e2e/sync_test.go index 73ea3b31..605cc761 100644 --- a/pkg/e2e/sync_test.go +++ b/pkg/e2e/sync_test.go @@ -28,13 +28,13 @@ import ( "net/http/httptest" "os" "os/exec" + "path/filepath" "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" "github.com/dnote/dnote/pkg/cli/testutils" clitest "github.com/dnote/dnote/pkg/cli/testutils" @@ -49,37 +49,59 @@ import ( ) var cliBinaryName string -var server *httptest.Server -var serverDb *gorm.DB 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{ +} + +// testEnv holds the test environment for a single test +type testEnv struct { + DB *cliDatabase.DB + CmdOpts clitest.RunDnoteCmdOptions + Server *httptest.Server + ServerDB *gorm.DB + TmpDir string +} + +// setupTestEnv creates an isolated test environment with its own database and temp directory +func setupTestEnv(t *testing.T) testEnv { + tmpDir := t.TempDir() + + // Create .dnote directory + dnoteDir := filepath.Join(tmpDir, consts.DnoteDirName) + if err := os.MkdirAll(dnoteDir, 0755); err != nil { + t.Fatal(errors.Wrap(err, "creating dnote directory")) + } + + // Create database at the expected path + dbPath := filepath.Join(dnoteDir, consts.DnoteDBFileName) + db := cliDatabase.InitTestFileDBRaw(t, dbPath) + + // Create server + server, serverDB := setupNewServer(t, tmpDir) + + // Create config file with this server's endpoint + apiEndpoint := fmt.Sprintf("%s/api", server.URL) + updateConfigAPIEndpoint(t, tmpDir, apiEndpoint) + + // Create command options with XDG paths pointing to temp dir + cmdOpts := 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), + fmt.Sprintf("XDG_CONFIG_HOME=%s", tmpDir), + fmt.Sprintf("XDG_DATA_HOME=%s", tmpDir), + fmt.Sprintf("XDG_CACHE_HOME=%s", tmpDir), }, } - 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") + return testEnv{ + DB: db, + CmdOpts: cmdOpts, + Server: server, + ServerDB: serverDB, + TmpDir: tmpDir, } } @@ -104,23 +126,48 @@ func setupTestServer(dbPath string, serverTime time.Time) (*httptest.Server, *go return server, db, nil } -func TestMain(m *testing.M) { - // Set up server database - use file-based DB for e2e tests - dbPath := fmt.Sprintf("%s/server.db", testDir) - - var err error - server, serverDb, err = setupTestServer(dbPath, serverTime) +// setupNewServer creates a new server and returns the server and database. +// This is useful when a test needs to switch to a new empty server. +func setupNewServer(t *testing.T, tmpDir string) (*httptest.Server, *gorm.DB) { + // Create new server with its own database (unique name to avoid conflicts) + serverDBPath := filepath.Join(tmpDir, fmt.Sprintf("server-%d.db", time.Now().UnixNano())) + server, serverDB, err := setupTestServer(serverDBPath, serverTime) if err != nil { - panic(err) + t.Fatal(errors.Wrap(err, "setting up new test server")) } + t.Cleanup(func() { server.Close() }) - defer server.Close() + return server, serverDB +} - // Build binaries - apiEndpoint := fmt.Sprintf("%s/api", server.URL) - ldflags := fmt.Sprintf("-X main.apiEndpoint=%s", apiEndpoint) +// updateConfigAPIEndpoint updates the config file with the given API endpoint +func updateConfigAPIEndpoint(t *testing.T, tmpDir string, apiEndpoint string) { + dnoteDir := filepath.Join(tmpDir, consts.DnoteDirName) + configPath := filepath.Join(dnoteDir, consts.ConfigFilename) + configContent := fmt.Sprintf("apiEndpoint: %s\n", apiEndpoint) + if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { + t.Fatal(errors.Wrap(err, "writing config file")) + } +} - cmd := exec.Command("go", "build", "--tags", "fts5", "-o", cliBinaryName, "-ldflags", ldflags, "github.com/dnote/dnote/pkg/cli") +// switchToEmptyServer closes the current server and creates a new empty server, +// updating the config file to point to it. +func switchToEmptyServer(t *testing.T, env *testEnv) { + // Close old server + env.Server.Close() + + // Create new empty server with unique database file + env.Server, env.ServerDB = setupNewServer(t, t.TempDir()) + + // Update config file to point to new server + apiEndpoint := fmt.Sprintf("%s/api", env.Server.URL) + updateConfigAPIEndpoint(t, env.TmpDir, apiEndpoint) +} + +func TestMain(m *testing.M) { + // Build CLI binary without hardcoded API endpoint + // Each test will create its own server and config file + cmd := exec.Command("go", "build", "--tags", "fts5", "-o", cliBinaryName, "github.com/dnote/dnote/pkg/cli") var stderr bytes.Buffer cmd.Stderr = &stderr @@ -135,29 +182,29 @@ func TestMain(m *testing.M) { } // helpers -func setupUser(t *testing.T, db *cliDatabase.DB) database.User { - user := apitest.SetupUserData(serverDb, "alice@example.com", "pass1234") +func setupUser(t *testing.T, env testEnv) database.User { + user := apitest.SetupUserData(env.ServerDB, "alice@example.com", "pass1234") return user } -func setupUserAndLogin(t *testing.T, db *cliDatabase.DB) database.User { - user := setupUser(t, db) - login(t, db, user) +func setupUserAndLogin(t *testing.T, env testEnv) database.User { + user := setupUser(t, env) + login(t, env.DB, env.ServerDB, user) return user } // log in the user in CLI -func login(t *testing.T, db *cliDatabase.DB, user database.User) { - session := apitest.SetupSession(serverDb, user) +func login(t *testing.T, db *cliDatabase.DB, serverDB *gorm.DB, user database.User) { + session := apitest.SetupSession(serverDB, user) cliDatabase.MustExec(t, "inserting session_key", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, session.Key) cliDatabase.MustExec(t, "inserting session_key_expiry", db, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, session.ExpiresAt.Unix()) } -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) +func apiCreateBook(t *testing.T, env testEnv, user database.User, name, message string) string { + res := doHTTPReq(t, env, "POST", "/v3/books", fmt.Sprintf(`{"name": "%s"}`, name), message, user) var resp controllers.CreateBookResp if err := json.NewDecoder(res.Body).Decode(&resp); err != nil { @@ -168,16 +215,16 @@ func apiCreateBook(t *testing.T, user database.User, name, message string) strin 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 apiPatchBook(t *testing.T, env testEnv, user database.User, uuid, payload, message string) { + doHTTPReq(t, env, "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 apiDeleteBook(t *testing.T, env testEnv, user database.User, uuid, message string) { + doHTTPReq(t, env, "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) +func apiCreateNote(t *testing.T, env testEnv, user database.User, bookUUID, body, message string) string { + res := doHTTPReq(t, env, "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 { @@ -188,16 +235,16 @@ func apiCreateNote(t *testing.T, user database.User, bookUUID, body, message str 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 apiPatchNote(t *testing.T, env testEnv, user database.User, noteUUID, payload, message string) { + doHTTPReq(t, env, "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 apiDeleteNote(t *testing.T, env testEnv, user database.User, noteUUID, message string) { + doHTTPReq(t, env, "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) +func doHTTPReq(t *testing.T, env testEnv, method, path, payload, message string, user database.User) *http.Response { + apiEndpoint := fmt.Sprintf("%s/api", env.Server.URL) endpoint := fmt.Sprintf("%s%s", apiEndpoint, path) req, err := http.NewRequest(method, endpoint, strings.NewReader(payload)) @@ -205,7 +252,7 @@ func doHTTPReq(t *testing.T, method, path, payload, message string, user databas panic(errors.Wrap(err, "constructing http request")) } - res := apitest.HTTPAuthDo(t, serverDb, req, user) + res := apitest.HTTPAuthDo(t, env.ServerDB, req, user) if res.StatusCode >= 400 { bs, err := io.ReadAll(res.Body) if err != nil { @@ -218,29 +265,22 @@ func doHTTPReq(t *testing.T, method, path, payload, message string, user databas 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) +type setupFunc func(t *testing.T, env testEnv, user database.User) map[string]string +type assertFunc func(t *testing.T, env testEnv, user database.User, ids map[string]string) func testSyncCmd(t *testing.T, fullSync bool, setup setupFunc, assert assertFunc) { - // clean up - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) + env := setupTestEnv(t) - clearTmp(t) - - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - - user := setupUserAndLogin(t, ctx.DB) - ids := setup(t, ctx, user) + user := setupUserAndLogin(t, env) + ids := setup(t, env, user) if fullSync { - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") } else { - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") } - assert(t, ctx, user, ids) + assert(t, env, user, ids) } type systemState struct { @@ -254,11 +294,7 @@ type systemState struct { } // 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) { - checkStateWithDB(t, ctx.DB, user, serverDb, expected) -} - -func checkStateWithDB(t *testing.T, clientDB *cliDatabase.DB, user database.User, serverDB *gorm.DB, expected systemState) { +func checkState(t *testing.T, clientDB *cliDatabase.DB, user database.User, serverDB *gorm.DB, expected systemState) { 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) @@ -284,13 +320,13 @@ func checkStateWithDB(t *testing.T, clientDB *cliDatabase.DB, user database.User // tests func TestSync_Empty(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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) { + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { // Test - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 0, @@ -307,19 +343,19 @@ func TestSync_Empty(t *testing.T) { 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) { - apitest.MustExec(t, serverDb.Model(&user).Update("max_usn", 0), "updating user max_usn") + setup := func(t *testing.T, env testEnv, user database.User) { + apitest.MustExec(t, env.ServerDB.Model(&user).Update("max_usn", 0), "updating user max_usn") - clitest.RunDnoteCmd(t, 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, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User) { - cliDB := ctx.DB + assert := func(t *testing.T, env testEnv, user database.User) { + cliDB := env.DB // test client - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 3, clientBookCount: 2, clientLastMaxUSN: 5, @@ -359,11 +395,11 @@ func TestSync_oneway(t *testing.T) { // test server var apiBookJS, apiBookCSS database.Book var apiNote1JS, apiNote2JS, apiNote1CSS database.Note - apitest.MustExec(t, serverDb.Model(&database.Note{}).Where("uuid = ?", cliNote1JS.UUID).First(&apiNote1JS), "getting js1 note") - apitest.MustExec(t, serverDb.Model(&database.Note{}).Where("uuid = ?", cliNote2JS.UUID).First(&apiNote2JS), "getting js2 note") - apitest.MustExec(t, serverDb.Model(&database.Note{}).Where("uuid = ?", cliNote1CSS.UUID).First(&apiNote1CSS), "getting css1 note") - apitest.MustExec(t, serverDb.Model(&database.Book{}).Where("uuid = ?", cliBookJS.UUID).First(&apiBookJS), "getting js book") - apitest.MustExec(t, serverDb.Model(&database.Book{}).Where("uuid = ?", cliBookCSS.UUID).First(&apiBookCSS), "getting css book") + apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Where("uuid = ?", cliNote1JS.UUID).First(&apiNote1JS), "getting js1 note") + apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Where("uuid = ?", cliNote2JS.UUID).First(&apiNote2JS), "getting js2 note") + apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Where("uuid = ?", cliNote1CSS.UUID).First(&apiNote1CSS), "getting css1 note") + apitest.MustExec(t, env.ServerDB.Model(&database.Book{}).Where("uuid = ?", cliBookJS.UUID).First(&apiBookJS), "getting js book") + apitest.MustExec(t, env.ServerDB.Model(&database.Book{}).Where("uuid = ?", cliBookCSS.UUID).First(&apiBookCSS), "getting css book") // assert usn assert.NotEqual(t, apiNote1JS.USN, 0, "apiNote1JS usn mismatch") @@ -390,62 +426,56 @@ func TestSync_oneway(t *testing.T) { } t.Run("stepSync", func(t *testing.T) { - clearTmp(t) - defer apitest.ClearData(serverDb) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - user := setupUserAndLogin(t, ctx.DB) - setup(t, ctx, user) + env := setupTestEnv(t) + user := setupUserAndLogin(t, env) + setup(t, env, user) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") - assert(t, ctx, user) + assert(t, env, user) }) t.Run("fullSync", func(t *testing.T) { - clearTmp(t) - defer apitest.ClearData(serverDb) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - user := setupUserAndLogin(t, ctx.DB) - setup(t, ctx, user) + env := setupTestEnv(t) + user := setupUserAndLogin(t, env) + setup(t, env, user) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") - assert(t, ctx, user) + assert(t, env, 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 := serverDb + setup := func(t *testing.T, env testEnv, user database.User) { + apiDB := env.ServerDB apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") - clitest.RunDnoteCmd(t, 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") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js3") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css2") var nid, nid2 string - cliDB := ctx.DB + cliDB := env.DB cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js3"), &nid) cliDatabase.MustScan(t, "getting id of note to delete", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "css2"), &nid2) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js3-edited") - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "css", nid2) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js3-edited") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "css", nid2) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css3") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css4") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css3") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css4") } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 6, clientBookCount: 2, clientLastMaxUSN: 8, @@ -546,62 +576,56 @@ func TestSync_oneway(t *testing.T) { } t.Run("stepSync", func(t *testing.T) { - clearTmp(t) - defer apitest.ClearData(serverDb) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - user := setupUserAndLogin(t, ctx.DB) - setup(t, ctx, user) + env := setupTestEnv(t) + user := setupUserAndLogin(t, env) + setup(t, env, user) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") - assert(t, ctx, user) + assert(t, env, user) }) t.Run("fullSync", func(t *testing.T) { - clearTmp(t) - defer apitest.ClearData(serverDb) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - user := setupUserAndLogin(t, ctx.DB) - setup(t, ctx, user) + env := setupTestEnv(t) + user := setupUserAndLogin(t, env) + setup(t, env, user) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "-f") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") - assert(t, ctx, user) + assert(t, env, 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 := serverDb + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + apiDB := env.ServerDB apitest.MustExec(t, apiDB.Model(&user).Update("max_usn", 0), "updating user max_usn") - jsBookUUID := apiCreateBook(t, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") + jsNote2UUID := apiCreateNote(t, env, user, jsBookUUID, "js2", "adding js note 2") + cssNote2UUID := apiCreateNote(t, env, user, cssBookUUID, "css2", "adding css note 2") + linuxBookUUID := apiCreateBook(t, env, user, "linux", "adding linux book") + linuxNote1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux1", "adding linux note 1") + apiPatchNote(t, env, user, jsNote2UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, linuxBookUUID), "moving js note 2 to linux") + apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") + cssNote3UUID := apiCreateNote(t, env, user, cssBookUUID, "css3", "adding css note 3") + bashBookUUID := apiCreateBook(t, env, user, "bash", "adding bash book") + bashNote1UUID := apiCreateNote(t, env, user, bashBookUUID, "bash1", "adding bash note 1") // delete the linux book and its two notes - apiDeleteBook(t, user, linuxBookUUID, "deleting linux book") + apiDeleteBook(t, env, 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") + apiPatchNote(t, env, user, cssNote2UUID, fmt.Sprintf(`{"content": "%s"}`, "css2-edited"), "editing css 2 body") + bashNote2UUID := apiCreateNote(t, env, user, bashBookUUID, "bash2", "adding bash note 2") + linuxBook2UUID := apiCreateBook(t, env, user, "linux", "adding new linux book") + linux2Note1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux-new-1", "adding linux note 1") + apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -621,11 +645,11 @@ func TestSync_oneway(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 6, clientBookCount: 3, clientLastMaxUSN: 21, @@ -768,41 +792,41 @@ func TestSync_oneway(t *testing.T) { 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") + jsNote2UUID := apiCreateNote(t, env, user, jsBookUUID, "js2", "adding js note 2") + cssNote2UUID := apiCreateNote(t, env, user, cssBookUUID, "css2", "adding css note 2") + linuxBookUUID := apiCreateBook(t, env, user, "linux", "adding linux book") + linuxNote1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux1", "adding linux note 1") + apiPatchNote(t, env, user, jsNote2UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, linuxBookUUID), "moving js note 2 to linux") + apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") + cssNote3UUID := apiCreateNote(t, env, user, cssBookUUID, "css3", "adding css note 3") + bashBookUUID := apiCreateBook(t, env, user, "bash", "adding bash book") + bashNote1UUID := apiCreateNote(t, env, user, bashBookUUID, "bash1", "adding bash note 1") + apiDeleteBook(t, env, user, linuxBookUUID, "deleting linux book") + apiPatchNote(t, env, user, cssNote2UUID, fmt.Sprintf(`{"content": "%s"}`, "css2-edited"), "editing css 2 body") + bashNote2UUID := apiCreateNote(t, env, user, bashBookUUID, "bash2", "adding bash note 2") + linuxBook2UUID := apiCreateBook(t, env, user, "linux", "adding new linux book") + linux2Note1UUID := apiCreateNote(t, env, user, linuxBookUUID, "linux-new-1", "adding linux note 1") + apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") - clitest.RunDnoteCmd(t, 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") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js3") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js4") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms2") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "math", "-c", "math1") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js3"), &nid) - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "algorithms") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css4") - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "algorithms") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css4") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) return map[string]string{ "jsBookUUID": jsBookUUID, @@ -822,11 +846,11 @@ func TestSync_twoway(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 9, clientBookCount: 6, clientLastMaxUSN: 27, @@ -1002,43 +1026,43 @@ func TestSync_twoway(t *testing.T) { }) t.Run("twice", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "math", "-c", "math1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "math", "-c", "math1") var nid string - cliDB := ctx.DB + cliDB := env.DB cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "math1"), &nid) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "math", nid, "-c", "math1-edited") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "math", nid, "-c", "math1-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - scssBookUUID := apiCreateBook(t, user, "scss", "adding a scss book") - apiPatchNote(t, user, cssNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, scssBookUUID), "moving css note 1 to scss") + scssBookUUID := apiCreateBook(t, env, user, "scss", "adding a scss book") + apiPatchNote(t, env, user, cssNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, scssBookUUID), "moving css note 1 to scss") var n1UUID string cliDatabase.MustScan(t, "getting math1-edited note UUID", cliDB.QueryRow("SELECT uuid FROM notes WHERE body = ?", "math1-edited"), &n1UUID) - apiPatchNote(t, user, n1UUID, fmt.Sprintf(`{"content": "%s", "public": true}`, "math1-edited"), "editing math1 note") + apiPatchNote(t, env, 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") + cssNote2UUID := apiCreateNote(t, env, user, cssBookUUID, "css2", "adding css note 2") + apiDeleteBook(t, env, user, cssBookUUID, "deleting css book") - bashBookUUID := apiCreateBook(t, user, "bash", "adding a bash book") - algorithmsBookUUID := apiCreateBook(t, user, "algorithms", "adding a algorithms book") + bashBookUUID := apiCreateBook(t, env, user, "bash", "adding a bash book") + algorithmsBookUUID := apiCreateBook(t, env, user, "algorithms", "adding a algorithms book") // 4. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js3") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js3") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "algorithms", "-c", "algorithms1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1052,11 +1076,11 @@ func TestSync_twoway(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - apiDB := serverDb - cliDB := ctx.DB + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + apiDB := env.ServerDB + cliDB := env.DB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 5, clientBookCount: 6, clientLastMaxUSN: 17, @@ -1182,21 +1206,21 @@ func TestSync_twoway(t *testing.T) { }) t.Run("three times", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - goBookUUID := apiCreateBook(t, user, "go", "adding a go book") - goNote1UUID := apiCreateNote(t, user, goBookUUID, "go1", "adding go note 1") + goBookUUID := apiCreateBook(t, env, user, "go", "adding a go book") + goNote1UUID := apiCreateNote(t, env, user, goBookUUID, "go1", "adding go note 1") // 4. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "html", "-c", "html1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "html", "-c", "html1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1206,11 +1230,11 @@ func TestSync_twoway(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 4, clientLastMaxUSN: 8, @@ -1299,17 +1323,17 @@ func TestSync_twoway(t *testing.T) { 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") + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") return map[string]string{} } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 2, @@ -1353,14 +1377,14 @@ func TestSync(t *testing.T) { }) t.Run("client deletes a book", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1368,10 +1392,10 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 5, @@ -1403,18 +1427,18 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1422,11 +1446,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 3, @@ -1469,19 +1493,19 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1489,11 +1513,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, @@ -1540,14 +1564,14 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1555,11 +1579,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, @@ -1611,19 +1635,19 @@ func TestSync(t *testing.T) { }) 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") + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") return map[string]string{ "jsBookUUID": jsBookUUID, } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 1, @@ -1658,26 +1682,26 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-new-label"), "editing js book") + apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-new-label"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 2, @@ -1712,25 +1736,25 @@ func TestSync(t *testing.T) { }) t.Run("server deletes a book", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteBook(t, user, jsBookUUID, "deleting js book") + apiDeleteBook(t, env, 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 := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 2, @@ -1757,10 +1781,10 @@ func TestSync(t *testing.T) { }) t.Run("server adds a note", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1768,11 +1792,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 2, @@ -1817,16 +1841,16 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js note 1") + apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1834,11 +1858,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, @@ -1883,17 +1907,17 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") + apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1902,11 +1926,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 4, @@ -1958,16 +1982,16 @@ func TestSync(t *testing.T) { }) t.Run("server deletes a note", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -1975,11 +1999,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 3, @@ -2020,19 +2044,19 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteBook(t, user, jsBookUUID, "deleting js book") + apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") // 4. on cli - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2040,10 +2064,10 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 6, @@ -2076,23 +2100,23 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") // 4. on cli var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2100,11 +2124,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - apiDB := serverDb - cliDB := ctx.DB + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + apiDB := env.ServerDB + cliDB := env.DB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 4, @@ -2148,19 +2172,19 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsNote1UUID := apiCreateNote(t, env, 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") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2168,11 +2192,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 1, clientLastMaxUSN: 3, @@ -2222,13 +2246,13 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2236,11 +2260,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -2297,16 +2321,16 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") + cssNote1UUID := apiCreateNote(t, env, user, cssBookUUID, "css1", "adding css note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2316,11 +2340,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 4, clientLastMaxUSN: 8, @@ -2402,19 +2426,19 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsNote1UUID := apiCreateNote(t, env, 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") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2422,11 +2446,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 1, clientLastMaxUSN: 3, @@ -2476,13 +2500,13 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js2") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js2") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2490,11 +2514,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -2550,22 +2574,22 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited-from-client") + clitest.RunDnoteCmd(t, env.CmdOpts, 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") + apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited-from-server"), "editing js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2573,13 +2597,13 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB resolvedBody := "<<<<<<< Local\njs1-edited-from-client\n=======\njs1-edited-from-server\n>>>>>>> Server\n" - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, @@ -2626,21 +2650,21 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js note 1") + apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js note 1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2648,11 +2672,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 3, @@ -2699,22 +2723,22 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding css book") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") var nid string cliDatabase.MustScan(t, "getting id of note to remove", cliDB.QueryRow("SELECT rowid FROM notes WHERE uuid = ?", jsNote1UUID), &nid) - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveNote, cliBinaryName, "remove", "js", nid) // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") + apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2723,11 +2747,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 4, @@ -2783,23 +2807,23 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") // 4. on cli var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2807,11 +2831,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, @@ -2860,19 +2884,19 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteNote(t, user, jsNote1UUID, "deleting js note 1") + apiDeleteNote(t, env, user, jsNote1UUID, "deleting js note 1") // 4. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2880,11 +2904,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 4, @@ -2928,23 +2952,23 @@ func TestSync(t *testing.T) { }) 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 + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + cliDB := env.DB // 1. on server - jsBookUUID := apiCreateBook(t, user, "js", "adding a js book") - jsNote1UUID := apiCreateNote(t, user, jsBookUUID, "js1", "adding js note 1") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiDeleteBook(t, user, jsBookUUID, "deleting js book") + apiDeleteBook(t, env, user, jsBookUUID, "deleting js book") // 4. on cli var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-c", "js1-edited") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -2952,11 +2976,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 6, @@ -3005,17 +3029,17 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") // 3. on server - apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") + apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3023,11 +3047,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 1, clientLastMaxUSN: 5, @@ -3071,19 +3095,19 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js1 note") + apiPatchNote(t, env, user, jsNote1UUID, fmt.Sprintf(`{"content": "%s"}`, "js1-edited"), "editing js1 note") // 4. on cli - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.ConfirmRemoveBook, cliBinaryName, "remove", "js") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3091,10 +3115,10 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 0, clientBookCount: 0, clientLastMaxUSN: 6, @@ -3127,17 +3151,17 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited") // 3. on server - apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") + apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3145,11 +3169,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, @@ -3201,17 +3225,17 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited-client") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", "-n", "js-edited-client") // 3. on server - apiPatchBook(t, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited-server"), "editing js book") + apiPatchBook(t, env, user, jsBookUUID, fmt.Sprintf(`{"name": "%s"}`, "js-edited-server"), "editing js book") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3219,13 +3243,13 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { // In this case, server's change wins and overwrites that of client's - cliDB := ctx.DB - apiDB := serverDb + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 1, clientLastMaxUSN: 4, @@ -3277,17 +3301,17 @@ func TestSync(t *testing.T) { }) t.Run("client moves a note", func(t *testing.T) { - setup := func(t *testing.T, ctx context.DnoteCtx, user database.User) map[string]string { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "1", "-b", "css") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "1", "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3296,11 +3320,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 4, @@ -3361,20 +3385,20 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") + apiPatchNote(t, env, 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") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "1", "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3383,11 +3407,11 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 2, clientLastMaxUSN: 5, @@ -3448,21 +3472,21 @@ func TestSync(t *testing.T) { }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") + linuxBookUUID := apiCreateBook(t, env, user, "linux", "adding a linux book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - apiPatchNote(t, user, jsNote1UUID, fmt.Sprintf(`{"book_uuid": "%s"}`, cssBookUUID), "moving js note 1 to css book") + apiPatchNote(t, env, 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") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "1", "-b", "linux") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3472,9 +3496,9 @@ func TestSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB expectedNote1JSBody := `<<<<<<< Local Moved to the book linux @@ -3484,7 +3508,7 @@ Moved to the book css js1` - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 1, clientBookCount: 4, clientLastMaxUSN: 7, @@ -3563,20 +3587,20 @@ js1` }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") - cliDB := ctx.DB + cliDB := env.DB var nid string cliDatabase.MustScan(t, "getting id of note to edit", cliDB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "edit", "js", nid, "-b", "css") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", "js", nid, "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3584,11 +3608,11 @@ js1` } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 5, @@ -3661,23 +3685,23 @@ js1` }) 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 { + setup := func(t *testing.T, env testEnv, 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") + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") // 2. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // 3. on server - cssBookUUID := apiCreateBook(t, user, "css", "adding a css book") + cssBookUUID := apiCreateBook(t, env, user, "css", "adding a css book") // 3. on cli - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, 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") + cliDatabase.MustScan(t, "getting id of note to edit", env.DB.QueryRow("SELECT rowid FROM notes WHERE body = ?", "js1"), &nid) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "edit", nid, "-b", "css") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3686,11 +3710,11 @@ js1` } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 3, clientLastMaxUSN: 6, @@ -3774,11 +3798,11 @@ js1` 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") + setup := func(t *testing.T, env testEnv, user database.User) map[string]string { + jsBookUUID := apiCreateBook(t, env, user, "js", "adding a js book") + jsNote1UUID := apiCreateNote(t, env, user, jsBookUUID, "js1", "adding js note 1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") return map[string]string{ "jsBookUUID": jsBookUUID, @@ -3786,11 +3810,11 @@ func TestFullSync(t *testing.T) { } } - assert := func(t *testing.T, ctx context.DnoteCtx, user database.User, ids map[string]string) { - cliDB := ctx.DB - apiDB := serverDb + assert := func(t *testing.T, env testEnv, user database.User, ids map[string]string) { + cliDB := env.DB + apiDB := env.ServerDB - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -3850,38 +3874,26 @@ func TestFullSync(t *testing.T) { } t.Run("stepSync then fullSync", func(t *testing.T) { - // clean up - os.RemoveAll(tmpDirPath) - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) + env := setupTestEnv(t) + user := setupUserAndLogin(t, env) + ids := setup(t, env, user) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - user := setupUserAndLogin(t, ctx.DB) - 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) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + assert(t, env, user, ids) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") + assert(t, env, user, ids) }) t.Run("fullSync then stepSync", func(t *testing.T) { - // clean up - os.RemoveAll(tmpDirPath) - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) + env := setupTestEnv(t) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) + user := setupUserAndLogin(t, env) + ids := setup(t, env, user) - user := setupUserAndLogin(t, ctx.DB) - 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) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "-f") + assert(t, env, user, ids) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") + assert(t, env, user, ids) }) }) } @@ -3891,24 +3903,17 @@ func TestSync_EmptyServer(t *testing.T) { // Test server data loss/wipe scenario (disaster recovery): // Verify empty server detection works when the server loses all its data - // clean up - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) + env := setupTestEnv(t) - clearTmp(t) - - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - - user := setupUserAndLogin(t, ctx.DB) + user := setupUserAndLogin(t, env) // Step 1: Create local data and sync to server - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify sync succeeded - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -3918,17 +3923,18 @@ func TestSync_EmptyServer(t *testing.T) { serverUserMaxUSN: 4, }) - // Step 2: Clear all server data to simulate switching to a completely new empty server - apitest.ClearData(serverDb) - // Recreate user and session (simulating a new server) - user = setupUserAndLogin(t, ctx.DB) + // Step 2: Switch to a completely new empty server + switchToEmptyServer(t, &env) + + // Recreate user and session on new server + user = setupUserAndLogin(t, env) // Step 3: Sync again - should detect empty server and prompt user // User confirms with "y" - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirmEmptyServerSync, cliBinaryName, "sync") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.UserConfirmEmptyServerSync, cliBinaryName, "sync") // Step 4: Verify data was uploaded to the empty server - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -3941,10 +3947,10 @@ func TestSync_EmptyServer(t *testing.T) { // Verify the content is correct on both client and server var cliNote1JS, cliNote1CSS cliDatabase.Note var cliBookJS, cliBookCSS cliDatabase.Book - cliDatabase.MustScan(t, "finding cliNote1JS", ctx.DB.QueryRow("SELECT uuid, body FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body) - cliDatabase.MustScan(t, "finding cliNote1CSS", ctx.DB.QueryRow("SELECT uuid, body FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body) - cliDatabase.MustScan(t, "finding cliBookJS", ctx.DB.QueryRow("SELECT uuid, label FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label) - cliDatabase.MustScan(t, "finding cliBookCSS", ctx.DB.QueryRow("SELECT uuid, label FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label) + cliDatabase.MustScan(t, "finding cliNote1JS", env.DB.QueryRow("SELECT uuid, body FROM notes WHERE body = ?", "js1"), &cliNote1JS.UUID, &cliNote1JS.Body) + cliDatabase.MustScan(t, "finding cliNote1CSS", env.DB.QueryRow("SELECT uuid, body FROM notes WHERE body = ?", "css1"), &cliNote1CSS.UUID, &cliNote1CSS.Body) + cliDatabase.MustScan(t, "finding cliBookJS", env.DB.QueryRow("SELECT uuid, label FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label) + cliDatabase.MustScan(t, "finding cliBookCSS", env.DB.QueryRow("SELECT uuid, label FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label) assert.Equal(t, cliNote1JS.Body, "js1", "js note body mismatch") assert.Equal(t, cliNote1CSS.Body, "css1", "css note body mismatch") @@ -3954,10 +3960,10 @@ func TestSync_EmptyServer(t *testing.T) { // Verify on server side var serverNoteJS, serverNoteCSS database.Note var serverBookJS, serverBookCSS database.Book - apitest.MustExec(t, serverDb.Where("body = ?", "js1").First(&serverNoteJS), "finding server note js1") - apitest.MustExec(t, serverDb.Where("body = ?", "css1").First(&serverNoteCSS), "finding server note css1") - apitest.MustExec(t, serverDb.Where("label = ?", "js").First(&serverBookJS), "finding server book js") - apitest.MustExec(t, serverDb.Where("label = ?", "css").First(&serverBookCSS), "finding server book css") + apitest.MustExec(t, env.ServerDB.Where("body = ?", "js1").First(&serverNoteJS), "finding server note js1") + apitest.MustExec(t, env.ServerDB.Where("body = ?", "css1").First(&serverNoteCSS), "finding server note css1") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "js").First(&serverBookJS), "finding server book js") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "css").First(&serverBookCSS), "finding server book css") assert.Equal(t, serverNoteJS.Body, "js1", "server js note body mismatch") assert.Equal(t, serverNoteCSS.Body, "css1", "server css note body mismatch") @@ -3966,24 +3972,17 @@ func TestSync_EmptyServer(t *testing.T) { }) t.Run("user cancels empty server prompt", func(t *testing.T) { - // clean up - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) + env := setupTestEnv(t) - clearTmp(t) - - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - - user := setupUserAndLogin(t, ctx.DB) + user := setupUserAndLogin(t, env) // Step 1: Create local data and sync to server - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify initial sync succeeded - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -3993,12 +3992,12 @@ func TestSync_EmptyServer(t *testing.T) { serverUserMaxUSN: 4, }) - // Step 2: Clear all server data - apitest.ClearData(serverDb) - user = setupUserAndLogin(t, ctx.DB) + // Step 2: Switch to empty server + switchToEmptyServer(t, &env) + user = setupUserAndLogin(t, env) // Step 3: Sync again but user cancels with "n" - output, err := clitest.WaitDnoteCmd(t, dnoteCmdOpts, clitest.UserCancelEmptyServerSync, cliBinaryName, "sync") + output, err := clitest.WaitDnoteCmd(t, env.CmdOpts, clitest.UserCancelEmptyServerSync, cliBinaryName, "sync") if err == nil { t.Fatal("Expected sync to fail when user cancels, but it succeeded") } @@ -4009,7 +4008,7 @@ func TestSync_EmptyServer(t *testing.T) { } // Step 4: Verify local state unchanged (transaction rolled back) - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4022,8 +4021,8 @@ func TestSync_EmptyServer(t *testing.T) { // Verify items still have original USN and dirty=false var book cliDatabase.Book var note cliDatabase.Note - cliDatabase.MustScan(t, "checking book state", ctx.DB.QueryRow("SELECT usn, dirty FROM books WHERE label = ?", "js"), &book.USN, &book.Dirty) - cliDatabase.MustScan(t, "checking note state", ctx.DB.QueryRow("SELECT usn, dirty FROM notes WHERE body = ?", "js1"), ¬e.USN, ¬e.Dirty) + cliDatabase.MustScan(t, "checking book state", env.DB.QueryRow("SELECT usn, dirty FROM books WHERE label = ?", "js"), &book.USN, &book.Dirty) + cliDatabase.MustScan(t, "checking note state", env.DB.QueryRow("SELECT usn, dirty FROM notes WHERE body = ?", "js1"), ¬e.USN, ¬e.Dirty) assert.NotEqual(t, book.USN, 0, "book USN should not be reset") assert.NotEqual(t, note.USN, 0, "note USN should not be reset") @@ -4035,24 +4034,17 @@ func TestSync_EmptyServer(t *testing.T) { // Test edge case: Server MaxUSN=0, local MaxUSN>0, but all items are deleted=true // Should NOT prompt because there's nothing to upload - // clean up - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) + env := setupTestEnv(t) - clearTmp(t) - - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - - user := setupUserAndLogin(t, ctx.DB) + user := setupUserAndLogin(t, env) // Step 1: Create local data and sync to server - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify initial sync succeeded - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4063,35 +4055,35 @@ func TestSync_EmptyServer(t *testing.T) { }) // Step 2: Delete all local notes and books (mark as deleted) - cliDatabase.MustExec(t, "marking all books deleted", ctx.DB, "UPDATE books SET deleted = 1") - cliDatabase.MustExec(t, "marking all notes deleted", ctx.DB, "UPDATE notes SET deleted = 1") + cliDatabase.MustExec(t, "marking all books deleted", env.DB, "UPDATE books SET deleted = 1") + cliDatabase.MustExec(t, "marking all notes deleted", env.DB, "UPDATE notes SET deleted = 1") - // Step 3: Clear server data to simulate switching to empty server - apitest.ClearData(serverDb) - user = setupUserAndLogin(t, ctx.DB) + // Step 3: Switch to empty server + switchToEmptyServer(t, &env) + user = setupUserAndLogin(t, env) // Step 4: Sync - should NOT prompt because bookCount=0 and noteCount=0 (counting only deleted=0) // This should complete without user interaction - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify no data was uploaded (server still empty, but client still has deleted items) // Check server is empty var serverNoteCount, serverBookCount int64 - apitest.MustExec(t, serverDb.Model(&database.Note{}).Count(&serverNoteCount), "counting server notes") - apitest.MustExec(t, serverDb.Model(&database.Book{}).Count(&serverBookCount), "counting server books") + apitest.MustExec(t, env.ServerDB.Model(&database.Note{}).Count(&serverNoteCount), "counting server notes") + apitest.MustExec(t, env.ServerDB.Model(&database.Book{}).Count(&serverBookCount), "counting server books") assert.Equal(t, serverNoteCount, int64(0), "server should have no notes") assert.Equal(t, serverBookCount, int64(0), "server should have no books") // Check client still has the deleted items locally var clientNoteCount, clientBookCount int - cliDatabase.MustScan(t, "counting client notes", ctx.DB.QueryRow("SELECT count(*) FROM notes WHERE deleted = 1"), &clientNoteCount) - cliDatabase.MustScan(t, "counting client books", ctx.DB.QueryRow("SELECT count(*) FROM books WHERE deleted = 1"), &clientBookCount) + cliDatabase.MustScan(t, "counting client notes", env.DB.QueryRow("SELECT count(*) FROM notes WHERE deleted = 1"), &clientNoteCount) + cliDatabase.MustScan(t, "counting client books", env.DB.QueryRow("SELECT count(*) FROM books WHERE deleted = 1"), &clientBookCount) assert.Equal(t, clientNoteCount, 2, "client should still have 2 deleted notes") assert.Equal(t, clientBookCount, 2, "client should still have 2 deleted books") // Verify lastMaxUSN was reset to 0 var lastMaxUSN int - cliDatabase.MustScan(t, "getting lastMaxUSN", ctx.DB.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastMaxUSN), &lastMaxUSN) + cliDatabase.MustScan(t, "getting lastMaxUSN", env.DB.QueryRow("SELECT value FROM system WHERE key = ?", consts.SystemLastMaxUSN), &lastMaxUSN) assert.Equal(t, lastMaxUSN, 0, "lastMaxUSN should be reset to 0") }) @@ -4113,23 +4105,17 @@ func TestSync_EmptyServer(t *testing.T) { // 4. Retrying sendChanges to upload the renamed books // - Result: Both clients' data is preserved (4 books total) - // Clean up - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) - clearTmp(t) + env := setupTestEnv(t) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - - user := setupUserAndLogin(t, ctx.DB) + user := setupUserAndLogin(t, env) // Step 1: Create local data and sync to establish lastMaxUSN > 0 - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify initial sync succeeded - checkState(t, ctx, user, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4139,9 +4125,11 @@ func TestSync_EmptyServer(t *testing.T) { serverUserMaxUSN: 4, }) - // Step 2: Clear server to simulate switching to empty server - apitest.ClearData(serverDb) - user = setupUserAndLogin(t, ctx.DB) + // Step 2: Switch to new empty server to simulate switching to empty server + switchToEmptyServer(t, &env) + + // Create user on new server and login + user = setupUserAndLogin(t, env) // Step 3: Trigger sync which will detect empty server and prompt user // Inside the callback (before confirming), we simulate Client B uploading via API. @@ -4154,10 +4142,10 @@ func TestSync_EmptyServer(t *testing.T) { // Now Client B uploads the same data via API (after Client A got the sync state from the server // but before its sync decision) // This creates the race condition: Client A thinks server is empty, but Client B uploads data - jsBookUUID := apiCreateBook(t, user, "js", "client B creating js book") - cssBookUUID := apiCreateBook(t, user, "css", "client B creating css book") - apiCreateNote(t, user, jsBookUUID, "js1", "client B creating js note") - apiCreateNote(t, user, cssBookUUID, "css1", "client B creating css note") + jsBookUUID := apiCreateBook(t, env, user, "js", "client B creating js book") + cssBookUUID := apiCreateBook(t, env, user, "css", "client B creating css book") + apiCreateNote(t, env, user, jsBookUUID, "js1", "client B creating js note") + apiCreateNote(t, env, user, cssBookUUID, "css1", "client B creating css note") // Now user confirms if _, err := io.WriteString(stdin, "y\n"); err != nil { @@ -4174,10 +4162,10 @@ func TestSync_EmptyServer(t *testing.T) { // - mergeBook renames Client A's books to js_2, css_2 // - Renamed books are uploaded // - Both clients' data is preserved. - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, raceCallback, cliBinaryName, "sync") + clitest.MustWaitDnoteCmd(t, env.CmdOpts, raceCallback, cliBinaryName, "sync") // Verify final state - both clients' data preserved - checkStateWithDB(t, ctx.DB, user, serverDb, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, // Both clients' notes clientBookCount: 4, // js, css, js_2, css_2 clientLastMaxUSN: 8, // 4 from Client B + 4 from Client A's renamed books/notes @@ -4189,10 +4177,10 @@ func TestSync_EmptyServer(t *testing.T) { // Verify server has both clients' books var svrBookJS, svrBookCSS, svrBookJS2, svrBookCSS2 database.Book - apitest.MustExec(t, serverDb.Where("label = ?", "js").First(&svrBookJS), "finding server book 'js'") - apitest.MustExec(t, serverDb.Where("label = ?", "css").First(&svrBookCSS), "finding server book 'css'") - apitest.MustExec(t, serverDb.Where("label = ?", "js_2").First(&svrBookJS2), "finding server book 'js_2'") - apitest.MustExec(t, serverDb.Where("label = ?", "css_2").First(&svrBookCSS2), "finding server book 'css_2'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "js").First(&svrBookJS), "finding server book 'js'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "css").First(&svrBookCSS), "finding server book 'css'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "js_2").First(&svrBookJS2), "finding server book 'js_2'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "css_2").First(&svrBookCSS2), "finding server book 'css_2'") assert.Equal(t, svrBookJS.Label, "js", "server should have book 'js' (Client B)") assert.Equal(t, svrBookCSS.Label, "css", "server should have book 'css' (Client B)") @@ -4201,10 +4189,10 @@ func TestSync_EmptyServer(t *testing.T) { // Verify client has all books var cliBookJS, cliBookCSS, cliBookJS2, cliBookCSS2 cliDatabase.Book - cliDatabase.MustScan(t, "finding client book 'js'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) - cliDatabase.MustScan(t, "finding client book 'css'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) - cliDatabase.MustScan(t, "finding client book 'js_2'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) - cliDatabase.MustScan(t, "finding client book 'css_2'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) + cliDatabase.MustScan(t, "finding client book 'js'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) + cliDatabase.MustScan(t, "finding client book 'css'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) + cliDatabase.MustScan(t, "finding client book 'js_2'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) + cliDatabase.MustScan(t, "finding client book 'css_2'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) // Verify client UUIDs match server assert.Equal(t, cliBookJS.UUID, svrBookJS.UUID, "client 'js' UUID should match server") @@ -4225,17 +4213,13 @@ func TestSync_EmptyServer(t *testing.T) { // 2. No false detection when switching back to non-empty servers // 3. Both servers maintain independent state across multiple switches - // Clean up - clearTmp(t) - - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) + env := setupTestEnv(t) // Create Server A with its own database dbPathA := fmt.Sprintf("%s/serverA.db", testDir) defer os.Remove(dbPathA) - serverA, serverDbA, err := setupTestServer(dbPathA, serverTime) + serverA, serverDBA, err := setupTestServer(dbPathA, serverTime) if err != nil { t.Fatal(errors.Wrap(err, "setting up server A")) } @@ -4245,7 +4229,7 @@ func TestSync_EmptyServer(t *testing.T) { dbPathB := fmt.Sprintf("%s/serverB.db", testDir) defer os.Remove(dbPathB) - serverB, serverDbB, err := setupTestServer(dbPathB, serverTime) + serverB, serverDBB, err := setupTestServer(dbPathB, serverTime) if err != nil { t.Fatal(errors.Wrap(err, "setting up server B")) } @@ -4254,17 +4238,17 @@ func TestSync_EmptyServer(t *testing.T) { // Step 1: Set up user on Server A and sync apiEndpointA := fmt.Sprintf("%s/api", serverA.URL) - userA := apitest.SetupUserData(serverDbA, "alice@example.com", "pass1234") - sessionA := apitest.SetupSession(serverDbA, userA) - cliDatabase.MustExec(t, "inserting session_key", ctx.DB, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, sessionA.Key) - cliDatabase.MustExec(t, "inserting session_key_expiry", ctx.DB, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, sessionA.ExpiresAt.Unix()) + userA := apitest.SetupUserData(serverDBA, "alice@example.com", "pass1234") + sessionA := apitest.SetupSession(serverDBA, userA) + cliDatabase.MustExec(t, "inserting session_key", env.DB, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKey, sessionA.Key) + cliDatabase.MustExec(t, "inserting session_key_expiry", env.DB, "INSERT INTO system (key, value) VALUES (?, ?)", consts.SystemSessionKeyExpiry, sessionA.ExpiresAt.Unix()) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "--apiEndpoint", apiEndpointA) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "--apiEndpoint", apiEndpointA) // Verify sync to Server A succeeded - checkStateWithDB(t, ctx.DB, userA, serverDbA, systemState{ + checkState(t, env.DB, userA, serverDBA, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4278,16 +4262,16 @@ func TestSync_EmptyServer(t *testing.T) { apiEndpointB := fmt.Sprintf("%s/api", serverB.URL) // Set up user on Server B - userB := apitest.SetupUserData(serverDbB, "alice@example.com", "pass1234") - sessionB := apitest.SetupSession(serverDbB, userB) - cliDatabase.MustExec(t, "updating session_key for B", ctx.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.Key, consts.SystemSessionKey) - cliDatabase.MustExec(t, "updating session_key_expiry for B", ctx.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.ExpiresAt.Unix(), consts.SystemSessionKeyExpiry) + userB := apitest.SetupUserData(serverDBB, "alice@example.com", "pass1234") + sessionB := apitest.SetupSession(serverDBB, userB) + cliDatabase.MustExec(t, "updating session_key for B", env.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.Key, consts.SystemSessionKey) + cliDatabase.MustExec(t, "updating session_key_expiry for B", env.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.ExpiresAt.Unix(), consts.SystemSessionKeyExpiry) // Should detect empty server and prompt - clitest.MustWaitDnoteCmd(t, dnoteCmdOpts, clitest.UserConfirmEmptyServerSync, cliBinaryName, "sync", "--apiEndpoint", apiEndpointB) + clitest.MustWaitDnoteCmd(t, env.CmdOpts, clitest.UserConfirmEmptyServerSync, cliBinaryName, "sync", "--apiEndpoint", apiEndpointB) // Verify Server B now has data - checkStateWithDB(t, ctx.DB, userB, serverDbB, systemState{ + checkState(t, env.DB, userB, serverDBB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4298,14 +4282,14 @@ func TestSync_EmptyServer(t *testing.T) { }) // Step 3: Switch back to Server A and sync - cliDatabase.MustExec(t, "updating session_key back to A", ctx.DB, "UPDATE system SET value = ? WHERE key = ?", sessionA.Key, consts.SystemSessionKey) - cliDatabase.MustExec(t, "updating session_key_expiry back to A", ctx.DB, "UPDATE system SET value = ? WHERE key = ?", sessionA.ExpiresAt.Unix(), consts.SystemSessionKeyExpiry) + cliDatabase.MustExec(t, "updating session_key back to A", env.DB, "UPDATE system SET value = ? WHERE key = ?", sessionA.Key, consts.SystemSessionKey) + cliDatabase.MustExec(t, "updating session_key_expiry back to A", env.DB, "UPDATE system SET value = ? WHERE key = ?", sessionA.ExpiresAt.Unix(), consts.SystemSessionKeyExpiry) // Should NOT trigger empty server detection (Server A has MaxUSN > 0) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "--apiEndpoint", apiEndpointA) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "--apiEndpoint", apiEndpointA) // Verify Server A still has its data - checkStateWithDB(t, ctx.DB, userA, serverDbA, systemState{ + checkState(t, env.DB, userA, serverDBA, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4316,14 +4300,14 @@ func TestSync_EmptyServer(t *testing.T) { }) // Step 4: Switch back to Server B and sync again - cliDatabase.MustExec(t, "updating session_key back to B", ctx.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.Key, consts.SystemSessionKey) - cliDatabase.MustExec(t, "updating session_key_expiry back to B", ctx.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.ExpiresAt.Unix(), consts.SystemSessionKeyExpiry) + cliDatabase.MustExec(t, "updating session_key back to B", env.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.Key, consts.SystemSessionKey) + cliDatabase.MustExec(t, "updating session_key_expiry back to B", env.DB, "UPDATE system SET value = ? WHERE key = ?", sessionB.ExpiresAt.Unix(), consts.SystemSessionKeyExpiry) // Should NOT trigger empty server detection (Server B now has MaxUSN > 0 from Step 2) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync", "--apiEndpoint", apiEndpointB) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync", "--apiEndpoint", apiEndpointB) // Verify both servers maintain independent state - checkStateWithDB(t, ctx.DB, userB, serverDbB, systemState{ + checkState(t, env.DB, userB, serverDBB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4347,33 +4331,27 @@ func TestSync_FreshClientConcurrent(t *testing.T) { // Expected: Client A should pull server data first, detect duplicate book names, // rename local books to avoid conflicts (js→js_2), then upload successfully. - // Clean up - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) - clearTmp(t) + env := setupTestEnv(t) - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) - - user := setupUserAndLogin(t, ctx.DB) + user := setupUserAndLogin(t, env) // Client A: Create local data (never sync) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "js", "-c", "js1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "add", "css", "-c", "css1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "js", "-c", "js1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "add", "css", "-c", "css1") // Client B: Upload same book names to server via API - jsBookUUID := apiCreateBook(t, user, "js", "client B creating js book") - cssBookUUID := apiCreateBook(t, user, "css", "client B creating css book") - apiCreateNote(t, user, jsBookUUID, "js2", "client B note") - apiCreateNote(t, user, cssBookUUID, "css2", "client B note") + jsBookUUID := apiCreateBook(t, env, user, "js", "client B creating js book") + cssBookUUID := apiCreateBook(t, env, user, "css", "client B creating css book") + apiCreateNote(t, env, user, jsBookUUID, "js2", "client B note") + apiCreateNote(t, env, user, cssBookUUID, "css2", "client B note") // Client A syncs - should handle the conflict gracefully // Expected: pulls server data, renames local books to js_2/css_2, uploads successfully - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "sync") // Verify: Should have 4 books and 4 notes on both client and server // USN breakdown: 2 books + 2 notes from Client B (USN 1-4), then 2 books + 2 notes from Client A (USN 5-8) - checkStateWithDB(t, ctx.DB, user, serverDb, systemState{ + checkState(t, env.DB, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 4, clientLastMaxUSN: 8, @@ -4385,10 +4363,10 @@ func TestSync_FreshClientConcurrent(t *testing.T) { // Verify server has all 4 books with correct names var svrBookJS, svrBookCSS, svrBookJS2, svrBookCSS2 database.Book - apitest.MustExec(t, serverDb.Where("label = ?", "js").First(&svrBookJS), "finding server book 'js'") - apitest.MustExec(t, serverDb.Where("label = ?", "css").First(&svrBookCSS), "finding server book 'css'") - apitest.MustExec(t, serverDb.Where("label = ?", "js_2").First(&svrBookJS2), "finding server book 'js_2'") - apitest.MustExec(t, serverDb.Where("label = ?", "css_2").First(&svrBookCSS2), "finding server book 'css_2'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "js").First(&svrBookJS), "finding server book 'js'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "css").First(&svrBookCSS), "finding server book 'css'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "js_2").First(&svrBookJS2), "finding server book 'js_2'") + apitest.MustExec(t, env.ServerDB.Where("label = ?", "css_2").First(&svrBookCSS2), "finding server book 'css_2'") assert.Equal(t, svrBookJS.Label, "js", "server should have book 'js' (Client B)") assert.Equal(t, svrBookCSS.Label, "css", "server should have book 'css' (Client B)") @@ -4397,10 +4375,10 @@ func TestSync_FreshClientConcurrent(t *testing.T) { // Verify server has all 4 notes with correct content var svrNoteJS1, svrNoteJS2, svrNoteCSS1, svrNoteCSS2 database.Note - apitest.MustExec(t, serverDb.Where("body = ?", "js1").First(&svrNoteJS1), "finding server note 'js1'") - apitest.MustExec(t, serverDb.Where("body = ?", "js2").First(&svrNoteJS2), "finding server note 'js2'") - apitest.MustExec(t, serverDb.Where("body = ?", "css1").First(&svrNoteCSS1), "finding server note 'css1'") - apitest.MustExec(t, serverDb.Where("body = ?", "css2").First(&svrNoteCSS2), "finding server note 'css2'") + apitest.MustExec(t, env.ServerDB.Where("body = ?", "js1").First(&svrNoteJS1), "finding server note 'js1'") + apitest.MustExec(t, env.ServerDB.Where("body = ?", "js2").First(&svrNoteJS2), "finding server note 'js2'") + apitest.MustExec(t, env.ServerDB.Where("body = ?", "css1").First(&svrNoteCSS1), "finding server note 'css1'") + apitest.MustExec(t, env.ServerDB.Where("body = ?", "css2").First(&svrNoteCSS2), "finding server note 'css2'") assert.Equal(t, svrNoteJS1.BookUUID, svrBookJS2.UUID, "note 'js1' should belong to book 'js_2' (Client A)") assert.Equal(t, svrNoteJS2.BookUUID, svrBookJS.UUID, "note 'js2' should belong to book 'js' (Client B)") @@ -4409,10 +4387,10 @@ func TestSync_FreshClientConcurrent(t *testing.T) { // Verify client has all 4 books var cliBookJS, cliBookCSS, cliBookJS2, cliBookCSS2 cliDatabase.Book - cliDatabase.MustScan(t, "finding client book 'js'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) - cliDatabase.MustScan(t, "finding client book 'css'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) - cliDatabase.MustScan(t, "finding client book 'js_2'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) - cliDatabase.MustScan(t, "finding client book 'css_2'", ctx.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) + cliDatabase.MustScan(t, "finding client book 'js'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js"), &cliBookJS.UUID, &cliBookJS.Label, &cliBookJS.USN) + cliDatabase.MustScan(t, "finding client book 'css'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css"), &cliBookCSS.UUID, &cliBookCSS.Label, &cliBookCSS.USN) + cliDatabase.MustScan(t, "finding client book 'js_2'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "js_2"), &cliBookJS2.UUID, &cliBookJS2.Label, &cliBookJS2.USN) + cliDatabase.MustScan(t, "finding client book 'css_2'", env.DB.QueryRow("SELECT uuid, label, usn FROM books WHERE label = ?", "css_2"), &cliBookCSS2.UUID, &cliBookCSS2.Label, &cliBookCSS2.USN) // Verify client UUIDs match server assert.Equal(t, cliBookJS.UUID, svrBookJS.UUID, "client 'js' UUID should match server") @@ -4428,10 +4406,10 @@ func TestSync_FreshClientConcurrent(t *testing.T) { // Verify client has all 4 notes var cliNoteJS1, cliNoteJS2, cliNoteCSS1, cliNoteCSS2 cliDatabase.Note - cliDatabase.MustScan(t, "finding client note 'js1'", ctx.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNoteJS1.UUID, &cliNoteJS1.Body, &cliNoteJS1.USN) - cliDatabase.MustScan(t, "finding client note 'js2'", ctx.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliNoteJS2.UUID, &cliNoteJS2.Body, &cliNoteJS2.USN) - cliDatabase.MustScan(t, "finding client note 'css1'", ctx.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNoteCSS1.UUID, &cliNoteCSS1.Body, &cliNoteCSS1.USN) - cliDatabase.MustScan(t, "finding client note 'css2'", ctx.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css2"), &cliNoteCSS2.UUID, &cliNoteCSS2.Body, &cliNoteCSS2.USN) + cliDatabase.MustScan(t, "finding client note 'js1'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js1"), &cliNoteJS1.UUID, &cliNoteJS1.Body, &cliNoteJS1.USN) + cliDatabase.MustScan(t, "finding client note 'js2'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "js2"), &cliNoteJS2.UUID, &cliNoteJS2.Body, &cliNoteJS2.USN) + cliDatabase.MustScan(t, "finding client note 'css1'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css1"), &cliNoteCSS1.UUID, &cliNoteCSS1.Body, &cliNoteCSS1.USN) + cliDatabase.MustScan(t, "finding client note 'css2'", env.DB.QueryRow("SELECT uuid, body, usn FROM notes WHERE body = ?", "css2"), &cliNoteCSS2.UUID, &cliNoteCSS2.Body, &cliNoteCSS2.USN) // Verify client note UUIDs match server assert.Equal(t, cliNoteJS1.UUID, svrNoteJS1.UUID, "client note 'js1' UUID should match server") @@ -4449,34 +4427,28 @@ func TestSync_FreshClientConcurrent(t *testing.T) { // TestSync_ConvergeSameBookNames tests that two clients don't enter an infinite sync loop if they // try to sync books with the same names. Books shouldn't get marked dirty when re-downloaded from server. func TestSync_ConvergeSameBookNames(t *testing.T) { - // Clean up and prepare server - apitest.ClearData(serverDb) - defer apitest.ClearData(serverDb) - - clearTmp(t) - - ctx := context.InitTestCtx(t, paths, nil) - defer context.TeardownTestCtx(t, ctx) + env := setupTestEnv(t) + tmpDir := t.TempDir() // Setup two separate client databases - client1DB := fmt.Sprintf("%s/client1.db", tmpDirPath) - client2DB := fmt.Sprintf("%s/client2.db", tmpDirPath) + client1DB := fmt.Sprintf("%s/client1.db", tmpDir) + client2DB := fmt.Sprintf("%s/client2.db", tmpDir) defer os.Remove(client1DB) defer os.Remove(client2DB) // Set up sessions - user := setupUser(t, ctx.DB) + user := setupUser(t, env) db1 := testutils.MustOpenDatabase(t, client1DB) db2 := testutils.MustOpenDatabase(t, client2DB) defer db1.Close() defer db2.Close() // Client 1: First sync to empty server - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client1DB, "add", "testbook", "-c", "client1 note1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client1DB, "add", "anotherbook", "-c", "client1 note2") - login(t, db1, user) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") - checkStateWithDB(t, db1, user, serverDb, systemState{ + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "add", "testbook", "-c", "client1 note1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "add", "anotherbook", "-c", "client1 note2") + login(t, db1, env.ServerDB, user) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") + checkState(t, db1, user, env.ServerDB, systemState{ clientNoteCount: 2, clientBookCount: 2, clientLastMaxUSN: 4, @@ -4487,12 +4459,12 @@ func TestSync_ConvergeSameBookNames(t *testing.T) { }) // Client 2: Sync (downloads client 1's data, adds own notes) ===== - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client2DB, "add", "testbook", "-c", "client2 note1") - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client2DB, "add", "anotherbook", "-c", "client2 note2") - login(t, db2, user) - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client2DB, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "add", "testbook", "-c", "client2 note1") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "add", "anotherbook", "-c", "client2 note2") + login(t, db2, env.ServerDB, user) + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "sync") // Verify state after client2 sync - checkStateWithDB(t, db2, user, serverDb, systemState{ + checkState(t, db2, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, @@ -4503,11 +4475,11 @@ func TestSync_ConvergeSameBookNames(t *testing.T) { }) // Client 1: Sync again. It downloads client2's changes (2 extra notes). - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") // Verify MaxUSN did not increase (client1 should only download, not upload) // Client1 still has: 2 original books + 4 notes (2 own + 2 from client2) - checkStateWithDB(t, db1, user, serverDb, systemState{ + checkState(t, db1, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, @@ -4521,10 +4493,10 @@ func TestSync_ConvergeSameBookNames(t *testing.T) { // Both clients should be able to sync without any changes (MaxUSN stays at 8) for range 3 { // Client 2 syncs - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client2DB, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client2DB, "sync") // Verify client2 state unchanged - checkStateWithDB(t, db2, user, serverDb, systemState{ + checkState(t, db2, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, @@ -4535,10 +4507,10 @@ func TestSync_ConvergeSameBookNames(t *testing.T) { }) // Client 1 syncs - clitest.RunDnoteCmd(t, dnoteCmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") + clitest.RunDnoteCmd(t, env.CmdOpts, cliBinaryName, "--dbPath", client1DB, "sync") // Verify client1 state unchanged - checkStateWithDB(t, db1, user, serverDb, systemState{ + checkState(t, db1, user, env.ServerDB, systemState{ clientNoteCount: 4, clientBookCount: 2, clientLastMaxUSN: 8, diff --git a/pkg/server/testutils/main.go b/pkg/server/testutils/main.go index 45c547d0..6b7c21b6 100644 --- a/pkg/server/testutils/main.go +++ b/pkg/server/testutils/main.go @@ -67,26 +67,6 @@ func InitMemoryDB(t *testing.T) *gorm.DB { return db } -// ClearData deletes all records from the database -func ClearData(db *gorm.DB) { - // Delete in order: child tables first, parent tables last - if err := db.Where("1 = 1").Delete(&database.Note{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear notes")) - } - if err := db.Where("1 = 1").Delete(&database.Book{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear books")) - } - if err := db.Where("1 = 1").Delete(&database.Token{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear tokens")) - } - if err := db.Where("1 = 1").Delete(&database.Session{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear sessions")) - } - if err := db.Where("1 = 1").Delete(&database.User{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear users")) - } -} - // MustUUID generates a UUID and fails the test on error func MustUUID(t *testing.T) string { uuid, err := helpers.GenUUID() diff --git a/scripts/cli/test.sh b/scripts/cli/test.sh index e66db37a..42e0ad86 100755 --- a/scripts/cli/test.sh +++ b/scripts/cli/test.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash -# test.sh runs test files sequentially -# https://stackoverflow.com/questions/23715302/go-how-to-run-tests-for-multiple-packages +# test.sh runs tests for CLI packages set -eux dir=$(dirname "${BASH_SOURCE[0]}") @@ -8,7 +7,5 @@ pushd "$dir/../../pkg/cli" # clear tmp dir in case not properly torn down rm -rf "./tmp" -go test -a ./... \ - -p 1\ - --tags "fts5" +go test ./... --tags "fts5" popd