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") } }