package mysql import ( "os" "path/filepath" "strings" "testing" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" ) func shopDB() *models.Database { s := models.InitSchema("shop") t := models.InitTable("users", "shop") add := func(name, typ string, mod func(*models.Column)) { c := models.InitColumn(name, "users", "shop") c.Type = typ if mod != nil { mod(c) } t.Columns[name] = c } add("id", "int", func(c *models.Column) { c.IsPrimaryKey, c.NotNull, c.AutoIncrement = true, true, true }) add("email", "string", func(c *models.Column) { c.Length, c.NotNull = 255, true }) add("nick", "string", nil) add("age", "int", func(c *models.Column) { c.Default = 18 }) add("active", "boolean", func(c *models.Column) { c.Default = true }) add("zeta", "string", nil) add("alpha", "string", nil) for _, name := range []string{"uq_b", "uq_a", "uq_c"} { u := models.InitConstraint(name, models.UniqueConstraint) u.Columns = []string{"email"} t.Constraints[name] = u } s.Tables = append(s.Tables, t) db := models.InitDatabase("shop") db.Schemas = append(db.Schemas, s) return db } func writeFile(t *testing.T, db *models.Database) string { t.Helper() out := filepath.Join(t.TempDir(), "out.sql") if err := NewWriter(&writers.WriterOptions{OutputPath: out}).WriteDatabase(db); err != nil { t.Fatal(err) } b, err := os.ReadFile(out) if err != nil { t.Fatal(err) } return string(b) } func TestWriteDatabase_ToFile(t *testing.T) { got := writeFile(t, shopDB()) for _, want := range []string{ "-- Database: shop", "CREATE TABLE IF NOT EXISTS `shop`.`users`", "`id` ", "AUTO_INCREMENT", "`email` VARCHAR(255) NOT NULL", "DEFAULT 18", "PRIMARY KEY (`id`)", "CONSTRAINT `uq_a` UNIQUE (`email`)", "ENGINE=InnoDB", } { if !strings.Contains(got, want) { t.Errorf("missing %q\n%s", want, got) } } } func TestWriteDatabase_Deterministic(t *testing.T) { first := writeFile(t, shopDB()) for i := 0; i < 30; i++ { if got := writeFile(t, shopDB()); got != first { t.Fatalf("output differs on run %d:\n--- first\n%s\n--- got\n%s", i, first, got) } } } func TestWriteDatabase_UniqueConstraintsSorted(t *testing.T) { got := writeFile(t, shopDB()) a, b, c := strings.Index(got, "`uq_a`"), strings.Index(got, "`uq_b`"), strings.Index(got, "`uq_c`") if !(a < b && b < c) { t.Errorf("unique constraints must be sorted by name: %d %d %d", a, b, c) } } func TestWriteSchemaAndTable_UseOutputPath(t *testing.T) { db := shopDB() dir := t.TempDir() sOut := filepath.Join(dir, "s.sql") if err := NewWriter(&writers.WriterOptions{OutputPath: sOut}).WriteSchema(db.Schemas[0]); err != nil { t.Fatal(err) } if b, _ := os.ReadFile(sOut); !strings.Contains(string(b), "CREATE TABLE IF NOT EXISTS `shop`.`users`") { t.Errorf("schema output:\n%s", b) } tOut := filepath.Join(dir, "t.sql") if err := NewWriter(&writers.WriterOptions{OutputPath: tOut}).WriteTable(db.Schemas[0].Tables[0]); err != nil { t.Fatal(err) } if b, _ := os.ReadFile(tOut); !strings.Contains(string(b), "CREATE TABLE IF NOT EXISTS `users`") { t.Errorf("table output (unqualified name):\n%s", b) } } func TestWriteSchema_WithoutWriterDoesNotPanic(t *testing.T) { defer func() { if r := recover(); r != nil { t.Fatalf("panic: %v", r) } }() s := shopDB().Schemas[0] s.Tables = nil // nothing to print to stdout if err := NewWriter(&writers.WriterOptions{}).WriteSchema(s); err != nil { t.Fatal(err) } } func TestWriteDatabase_Errors(t *testing.T) { if err := NewWriter(nil).WriteDatabase(shopDB()); err == nil || !strings.Contains(err.Error(), "options are required") { t.Errorf("nil options: %v", err) } bad := filepath.Join(t.TempDir(), "missing", "x.sql") if err := NewWriter(&writers.WriterOptions{OutputPath: bad}).WriteDatabase(shopDB()); err == nil { t.Error("bad output path must fail") } } func TestWriteDatabase_ConnectionFailure(t *testing.T) { opts := &writers.WriterOptions{Metadata: map[string]any{ "connection_string": "u:p@tcp(127.0.0.1:1)/none?timeout=1s", }} if err := NewWriter(opts).WriteDatabase(shopDB()); err == nil || !strings.Contains(err.Error(), "failed to execute SQL") { t.Errorf("got %v", err) } } func TestQuoteHelpers(t *testing.T) { if got := quote("a`b"); got != "`a``b`" { t.Errorf("quote: %q", got) } if got := quoted([]string{"a", "b"}); got != "`a`, `b`" { t.Errorf("quoted: %q", got) } if got := quoted(nil); got != "" { t.Errorf("quoted(nil): %q", got) } } func TestPrimaryKeyConstraintOverridesColumnFlags(t *testing.T) { db := shopDB() tbl := db.Schemas[0].Tables[0] pk := models.InitConstraint("pk", models.PrimaryKeyConstraint) pk.Columns = []string{"email", "id"} tbl.Constraints["pk"] = pk if got := writeFile(t, db); !strings.Contains(got, "PRIMARY KEY (`email`, `id`)") { t.Errorf("composite pk:\n%s", got) } }