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

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