mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
refactor(tests): enhance database handling with resettable tables and truncation
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
04c00fa7ed
commit
9dba0e1106
@ -31,6 +31,7 @@ import (
|
||||
"github.com/navidrome/navidrome/scanner"
|
||||
"github.com/navidrome/navidrome/server/events"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
"github.com/navidrome/navidrome/tests/harness"
|
||||
"github.com/navidrome/navidrome/utils/cache"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -65,8 +66,19 @@ var (
|
||||
// Windows. A suite-level tempdir plus an AfterSuite close avoids the lock conflict.
|
||||
var suiteDBTempDir string
|
||||
|
||||
// Migrating the schema costs ~400ms, so it runs once per suite and specs reset by truncating.
|
||||
var userTables []string
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
suiteDBTempDir = GinkgoT().TempDir()
|
||||
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.DbPath = filepath.Join(suiteDBTempDir, "artwork-resolution-e2e.db") + "?_journal_mode=WAL"
|
||||
conf.Server.DataFolder = conf.NewDir(GinkgoT().TempDir())
|
||||
db.Db().SetMaxOpenConns(1)
|
||||
db.Init(request.WithUser(context.Background(), model.User{ID: "admin-1", IsAdmin: true}))
|
||||
|
||||
userTables = harness.ResettableTables()
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
@ -86,10 +98,8 @@ func setupResolutionHarness() {
|
||||
conf.Server.EnableMediaFileCoverArt = true
|
||||
conf.Server.ArtworkWorkerConcurrency = 1
|
||||
|
||||
db.Db().SetMaxOpenConns(1)
|
||||
rctx = request.WithUser(GinkgoT().Context(), model.User{ID: "admin-1", UserName: "admin", IsAdmin: true})
|
||||
db.Init(rctx)
|
||||
DeferCleanup(func() { Expect(tests.ClearDB()).To(Succeed()) })
|
||||
harness.TruncateDB(userTables)
|
||||
|
||||
rds = &tests.MockDataStore{RealDS: persistence.New(db.Db())}
|
||||
|
||||
|
||||
@ -86,17 +86,13 @@ func SetupDB(ctx context.Context, users ...*model.User) *DB {
|
||||
return h
|
||||
}
|
||||
|
||||
// Restore reloads every table from the golden snapshot via ATTACH DATABASE — much faster than a
|
||||
// rescan. FTS shadow tables are skipped; they are kept in sync by their content tables' triggers.
|
||||
func (h *DB) Restore() {
|
||||
sqlDB := db.Db()
|
||||
_, err := sqlDB.Exec("PRAGMA foreign_keys = OFF")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = sqlDB.Exec("ATTACH DATABASE ? AS snapshot", h.SnapshotPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
rows, err := sqlDB.Query("SELECT name FROM main.sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts' AND name NOT LIKE '%_fts_%'")
|
||||
// ResettableTables lists the tables a per-spec reset may write. FTS shadow tables are excluded;
|
||||
// they are kept in sync by their content tables' triggers, and writing them directly corrupts the
|
||||
// index.
|
||||
func ResettableTables() []string {
|
||||
rows, err := db.Db().Query("SELECT name FROM main.sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '%_fts' AND name NOT LIKE '%_fts_%'")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer rows.Close()
|
||||
var tables []string
|
||||
for rows.Next() {
|
||||
var name string
|
||||
@ -104,12 +100,38 @@ func (h *DB) Restore() {
|
||||
tables = append(tables, name)
|
||||
}
|
||||
Expect(rows.Err()).ToNot(HaveOccurred())
|
||||
rows.Close()
|
||||
return tables
|
||||
}
|
||||
|
||||
// TruncateDB empties every resettable table, leaving the migrated schema in place. Suites whose
|
||||
// specs each build their own library use this instead of a golden snapshot, since re-migrating the
|
||||
// schema per spec costs ~400ms.
|
||||
func TruncateDB(tables []string) {
|
||||
sqlDB := db.Db()
|
||||
_, err := sqlDB.Exec("PRAGMA foreign_keys = OFF")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
defer func() { _, _ = sqlDB.Exec("PRAGMA foreign_keys = ON") }()
|
||||
|
||||
for _, table := range tables {
|
||||
// Table names come from sqlite_master, not user input.
|
||||
_, err = sqlDB.Exec(`DELETE FROM main."` + table + `"`) //nolint:gosec
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
// Restore reloads every table from the golden snapshot via ATTACH DATABASE — much faster than a
|
||||
// rescan.
|
||||
func (h *DB) Restore() {
|
||||
sqlDB := db.Db()
|
||||
_, err := sqlDB.Exec("PRAGMA foreign_keys = OFF")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = sqlDB.Exec("ATTACH DATABASE ? AS snapshot", h.SnapshotPath)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
for _, table := range ResettableTables() {
|
||||
// Table names come from sqlite_master, not user input.
|
||||
_, err = sqlDB.Exec(`DELETE FROM main."` + table + `"`) //nolint:gosec
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = sqlDB.Exec(`INSERT INTO main."` + table + `" SELECT * FROM snapshot."` + table + `"`) //nolint:gosec
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user