Files
go-omni/database/migrate/migrate_test.go

100 lines
3.1 KiB
Go

package migrate
import (
"context"
"database/sql"
"git.yuntuo.hk/k2safe/go-omni/telemetry"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"io/fs"
"os"
"testing"
"testing/fstest"
"time"
)
func TestSnapshotBoundsAndIdentity(t *testing.T) {
source := fstest.MapFS{"0001_createItems.up.sql": &fstest.MapFile{Data: []byte("SELECT 1;")}}
f, manifest, err := Snapshot(source, ".")
if err != nil || len(manifest) != 1 {
t.Fatal(manifest, err)
}
source["0001_createItems.up.sql"].Data[0] = 'x'
raw, err := fs.ReadFile(f, "0001_createItems.up.sql")
if err != nil || string(raw) != "SELECT 1;" {
t.Fatal(string(raw), err)
}
for _, files := range []fstest.MapFS{{"0001_bad_name.up.sql": &fstest.MapFile{Data: []byte("x")}}, {"0001_a.up.sql": &fstest.MapFile{Data: []byte("x")}, "1_b.up.sql": &fstest.MapFile{Data: []byte("x")}}, {"0001_a.down.sql": &fstest.MapFile{Data: []byte("x")}}, {"0001_a.up.sql": &fstest.MapFile{Data: make([]byte, (1<<20)+1)}}} {
if _, _, err = Snapshot(files, "."); err == nil {
t.Fatal("invalid migration accepted")
}
}
}
func TestMySQLIntegration(t *testing.T) {
dsn := os.Getenv("GO_OMNI_MIGRATE_TEST_DSN")
if dsn == "" {
t.Skip("requires disposable MySQL database")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
files := fstest.MapFS{"0001_createItems.up.sql": &fstest.MapFile{Data: []byte("CREATE TABLE testItems (id BIGINT NOT NULL PRIMARY KEY, value VARCHAR(64) NOT NULL);")}, "0002_seedItems.up.sql": &fstest.MapFile{Data: []byte("INSERT INTO testItems (id,value) VALUES (1,'original');")}}
runner, err := NewMySQL(ctx, dsn, files, ".")
if err != nil {
t.Fatal(err)
}
if err = runner.Up(ctx); err != nil {
t.Fatal(err)
}
if err = runner.Up(ctx); err != nil {
t.Fatal("repeat", err)
}
v, dirty, err := runner.Version()
if err != nil || v != 2 || dirty {
t.Fatal(v, dirty, err)
}
runner.Close()
db, err := sql.Open("mysql", dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()
exporter := tracetest.NewInMemoryExporter()
tr, _ := telemetry.New(telemetry.Config{Service: "dbTest", SampleRatio: 1, Exporter: exporter})
defer tr.Close()
instrumented := telemetry.SQL{DB: db, Runtime: tr}
if _, err = instrumented.ExecContext(ctx, "UPDATE testItems SET value=? WHERE id=1", "secret parameter"); err != nil {
t.Fatal(err)
}
rows, err := instrumented.QueryContext(ctx, "SELECT value FROM testItems WHERE id=1")
if err != nil {
t.Fatal(err)
}
if !rows.Next() {
t.Fatal("missing row")
}
var value string
if err = rows.Scan(&value); err != nil || value != "secret parameter" {
t.Fatal(value, err)
}
rows.Close()
tr.Flush(ctx)
if len(exporter.GetSpans()) != 2 {
t.Fatal("SQL spans missing")
}
files["0003_failMigration.up.sql"] = &fstest.MapFile{Data: []byte("INSERT INTO missingTable VALUES (1);")}
runner, err = NewMySQL(ctx, dsn, files, ".")
if err != nil {
t.Fatal(err)
}
defer runner.Close()
if err = runner.Up(ctx); err == nil {
t.Fatal("bad migration passed")
}
v, dirty, err = runner.Version()
if err != nil || v != 3 || !dirty {
t.Fatal(v, dirty, err)
}
if err = runner.Up(ctx); err == nil {
t.Fatal("dirty migration auto-cleared")
}
}