diff --git a/internal/handlers/api_test.go b/internal/handlers/api_test.go index b6635a8..594ef40 100644 --- a/internal/handlers/api_test.go +++ b/internal/handlers/api_test.go @@ -6,7 +6,10 @@ package handlers_test import ( "bytes" + "context" + "database/sql" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -22,6 +25,7 @@ import ( "go.uber.org/fx" "go.uber.org/fx/fxtest" "golang.org/x/crypto/bcrypt" + "modernc.org/sqlite" "sneak.berlin/go/neoirc/internal/broker" "sneak.berlin/go/neoirc/internal/config" "sneak.berlin/go/neoirc/internal/db" @@ -37,9 +41,76 @@ import ( "sneak.berlin/go/neoirc/pkg/irc" ) +// templateDBURL names the in-memory database TestMain migrates once +// and every test copies; it lasts while TestMain holds it open. +const templateDBURL = "file:template?mode=memory&cache=shared" + func TestMain(m *testing.M) { db.SetBcryptCost(bcrypt.MinCost) - os.Exit(m.Run()) + + template, err := sql.Open("sqlite", templateDBURL) + if err != nil { + panic(err) + } + + err = db.NewTestDatabaseFromConn(template). + RunMigrations(context.Background()) + if err != nil { + panic(err) + } + + code := m.Run() + + _ = template.Close() + + os.Exit(code) +} + +// copyTemplateDB creates the in-memory database dbURL as a copy of +// the migrated template, so a test does not migrate the schema +// again. The copy lasts until the test ends. +func copyTemplateDB(t *testing.T, dbURL string) { + t.Helper() + + database, err := sql.Open("sqlite", dbURL) + if err != nil { + t.Fatalf("open test db: %v", err) + } + + t.Cleanup(func() { _ = database.Close() }) + + conn, err := database.Conn(t.Context()) + if err != nil { + t.Fatalf("connect test db: %v", err) + } + + defer func() { _ = conn.Close() }() + + err = conn.Raw(func(driverConn any) error { + restorer, ok := driverConn.(interface { + NewRestore(srcURI string) (*sqlite.Backup, error) + }) + if !ok { + return errors.ErrUnsupported + } + + restore, restoreErr := restorer.NewRestore(templateDBURL) + if restoreErr != nil { + return fmt.Errorf("start restore: %w", restoreErr) + } + + _, restoreErr = restore.Step(-1) + if restoreErr != nil { + _ = restore.Finish() + + return fmt.Errorf("restore: %w", restoreErr) + } + + return restore.Finish() + }) + if err != nil { + t.Fatalf("copy template db: %v", err) + } } const ( @@ -84,6 +155,7 @@ func newTestServerWith( "file:test_%p?mode=memory&cache=shared", t, ) + copyTemplateDB(t, dbURL) var srv *server.Server @@ -109,6 +181,8 @@ func newTestServerWith( cfg.DBURL = dbURL cfg.Port = 0 + // One line: each line is a database write per new session. + cfg.MOTD = "neoirc test server" cfg.HashcashBits = hashcashBits cfg.OperName = operName cfg.OperPassword = operPassword