Implements tests/_plans and previously deferred packages; updates plan README with new coverage numbers.
160 lines
4.8 KiB
Go
160 lines
4.8 KiB
Go
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)
|
|
}
|
|
}
|