Write migration and test

This commit is contained in:
Sung 2025-10-25 11:59:46 -07:00
commit 4fe23fe996
21 changed files with 1435 additions and 1185 deletions

View file

@ -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

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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")

View file

@ -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)

View file

@ -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,

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
// 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
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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")
}

View file

@ -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{

View file

@ -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)

View file

@ -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;

View file

@ -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!")
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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")

View file

@ -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")

View file

@ -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,
}

View file

@ -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")

View file

@ -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()

View file

@ -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")

File diff suppressed because it is too large Load diff

View file

@ -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()

View file

@ -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