check / check (push) Waiting to run
SQLite keeps the foreign keys setting per connection, and the server turned it on once with a PRAGMA. A query cancelled while it runs makes the pool close its only connection and open a new one, which then ran without foreign keys until a restart. connect now adds `_pragma=foreign_keys(1)` to the database URL. The driver runs every `_pragma` parameter on each connection it opens, so this also holds for a `DBURL` without `_pragma` parameters. The new test cancels a query on a file database, then checks that the tables are still there and that foreign keys are on. Model: opus-5-5
170 lines
3.7 KiB
Go
170 lines
3.7 KiB
Go
package db_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"go.uber.org/fx/fxtest"
|
|
"sneak.berlin/go/neoirc/internal/config"
|
|
"sneak.berlin/go/neoirc/internal/db"
|
|
"sneak.berlin/go/neoirc/internal/globals"
|
|
"sneak.berlin/go/neoirc/internal/logger"
|
|
)
|
|
|
|
const wantBusyTimeoutMS = 5000
|
|
|
|
const (
|
|
// endlessQuery counts the rows of a recursive query that never
|
|
// stops, so it runs until its context ends.
|
|
endlessQuery = `WITH RECURSIVE n(x) AS
|
|
(SELECT 1 UNION ALL SELECT x + 1 FROM n)
|
|
SELECT COUNT(*) FROM n`
|
|
|
|
endlessQueryTimeout = 100 * time.Millisecond
|
|
)
|
|
|
|
// TestDefaultDBURLSetsWALAndBusyTimeout opens a file database
|
|
// with the default URL's parameters the way the server does,
|
|
// and checks the journal mode and busy timeout it ends up with.
|
|
func TestDefaultDBURLSetsWALAndBusyTimeout(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, params, _ := strings.Cut(config.DefaultDBURL, "?")
|
|
dbURL := "file://" + filepath.Join(t.TempDir(), "state.db") +
|
|
"?" + params
|
|
|
|
lifecycle := fxtest.NewLifecycle(t)
|
|
|
|
log, err := logger.New(lifecycle, logger.Params{
|
|
Globals: &globals.Globals{},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
database, err := db.New(lifecycle, db.Params{
|
|
Logger: log,
|
|
Config: &config.Config{DBURL: dbURL},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
lifecycle.RequireStart()
|
|
t.Cleanup(lifecycle.RequireStop)
|
|
|
|
var journalMode string
|
|
|
|
err = database.GetDB().QueryRowContext(
|
|
t.Context(), "PRAGMA journal_mode",
|
|
).Scan(&journalMode)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if journalMode != "wal" {
|
|
t.Errorf("journal_mode = %q, want %q", journalMode, "wal")
|
|
}
|
|
|
|
var busyTimeout int
|
|
|
|
err = database.GetDB().QueryRowContext(
|
|
t.Context(), "PRAGMA busy_timeout",
|
|
).Scan(&busyTimeout)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if busyTimeout != wantBusyTimeoutMS {
|
|
t.Errorf(
|
|
"busy_timeout = %d, want %d",
|
|
busyTimeout, wantBusyTimeoutMS,
|
|
)
|
|
}
|
|
}
|
|
|
|
// TestForeignKeysOnAfterCancelledQuery cancels a query while it
|
|
// runs, which makes the pool close its only connection and open a
|
|
// new one. It checks that the file database keeps its tables and
|
|
// that the new connection has foreign keys on.
|
|
func TestForeignKeysOnAfterCancelledQuery(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, defaultParams, _ := strings.Cut(config.DefaultDBURL, "?")
|
|
|
|
urlParams := map[string]string{
|
|
"default parameters": "?" + defaultParams,
|
|
"no parameters": "",
|
|
}
|
|
|
|
for name, params := range urlParams {
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dbURL := "file://" +
|
|
filepath.Join(t.TempDir(), "state.db") + params
|
|
|
|
lifecycle := fxtest.NewLifecycle(t)
|
|
|
|
log, err := logger.New(lifecycle, logger.Params{
|
|
Globals: &globals.Globals{},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
database, err := db.New(lifecycle, db.Params{
|
|
Logger: log,
|
|
Config: &config.Config{DBURL: dbURL},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
lifecycle.RequireStart()
|
|
t.Cleanup(lifecycle.RequireStop)
|
|
|
|
ctx, cancel := context.WithTimeout(
|
|
t.Context(), endlessQueryTimeout,
|
|
)
|
|
defer cancel()
|
|
|
|
_, err = database.GetDB().ExecContext(ctx, endlessQuery)
|
|
if !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("endless query: err = %v", err)
|
|
}
|
|
|
|
var migrations int
|
|
|
|
err = database.GetDB().QueryRowContext(
|
|
t.Context(),
|
|
"SELECT COUNT(*) FROM schema_migrations",
|
|
).Scan(&migrations)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if migrations == 0 {
|
|
t.Error("schema_migrations is empty")
|
|
}
|
|
|
|
var foreignKeys int
|
|
|
|
err = database.GetDB().QueryRowContext(
|
|
t.Context(), "PRAGMA foreign_keys",
|
|
).Scan(&foreignKeys)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if foreignKeys != 1 {
|
|
t.Errorf("foreign_keys = %d, want 1", foreignKeys)
|
|
}
|
|
})
|
|
}
|
|
}
|