Files
relspecgo/pkg/writers/dbml/writer_test.go
T
warkanum b4f18f088b feat(pgsql): unique constraints on apply, matching unique index names, FK deferral
- migration writer now emits unique constraints (direct apply and live diff
  previously dropped them); idempotent via the existing DO block
- unique constraint adopts a same-named model unique index with
  ADD CONSTRAINT ... UNIQUE USING INDEX instead of building a second index
- dbml reader names unnamed unique indexes ukey_<table>_<cols> to match the
  unique constraint naming
- foreign key deferral: Constraint.Deferrable/InitiallyDeferred are *bool
  (nil = unspecified, never diffed); writers emit DEFERRABLE, DEFERRABLE
  INITIALLY DEFERRED and NOT DEFERRABLE; live reader reads condeferrable and
  condeferred; dbml reader/writer support deferrable and initially deferred
- tests and README/naming docs updated
2026-10-08 00:07:49 +02:00

202 lines
6.7 KiB
Go

package dbml
import (
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
)
func TestWriter_WriteTable(t *testing.T) {
table := models.InitTable("users", "public")
table.Description = "User accounts table"
idCol := models.InitColumn("id", "users", "public")
idCol.Type = "bigint"
idCol.IsPrimaryKey = true
idCol.AutoIncrement = true
idCol.NotNull = true
table.Columns["id"] = idCol
emailCol := models.InitColumn("email", "users", "public")
emailCol.Type = "varchar(255)"
emailCol.NotNull = true
table.Columns["email"] = emailCol
nameCol := models.InitColumn("name", "users", "public")
nameCol.Type = "varchar(100)"
nameCol.NotNull = false
table.Columns["name"] = nameCol
createdCol := models.InitColumn("created_at", "users", "public")
createdCol.Type = "timestamp"
createdCol.NotNull = true
createdCol.Default = "now()"
table.Columns["created_at"] = createdCol
tmpDir := t.TempDir()
outputPath := filepath.Join(tmpDir, "test.dbml")
opts := &writers.WriterOptions{
OutputPath: outputPath,
}
writer := NewWriter(opts)
err := writer.WriteTable(table)
assert.NoError(t, err)
content, err := os.ReadFile(outputPath)
assert.NoError(t, err)
output := string(content)
assert.Contains(t, output, "Table public.users {")
assert.Contains(t, output, "id bigint [pk, increment]")
assert.Contains(t, output, "email varchar(255) [not null]")
assert.Contains(t, output, "Note: 'User accounts table'")
}
func TestWriter_WriteTable_MultilineNote(t *testing.T) {
table := models.InitTable("cities", "info")
table.Description = "Cities and municipalities worldwide.\n\nSPATIAL:\nProximity queries use a GiST index."
outputPath := filepath.Join(t.TempDir(), "cities.dbml")
writer := NewWriter(&writers.WriterOptions{OutputPath: outputPath})
if err := writer.WriteTable(table); err != nil {
t.Fatalf("WriteTable() error = %v", err)
}
output, err := os.ReadFile(outputPath)
if err != nil {
t.Fatalf("failed to read generated DBML: %v", err)
}
assert.Contains(t, string(output), "Note: '''\nCities and municipalities worldwide.\n\nSPATIAL:\nProximity queries use a GiST index.\n '''")
}
func TestWriter_WriteDatabase_WithRelationships(t *testing.T) {
db := models.InitDatabase("test_db")
schema := models.InitSchema("public")
usersTable := models.InitTable("users", "public")
idCol := models.InitColumn("id", "users", "public")
idCol.Type = "bigint"
idCol.IsPrimaryKey = true
usersTable.Columns["id"] = idCol
emailIdx := models.InitIndex("idx_users_email", "users", "public")
emailIdx.Columns = []string{"email"}
emailIdx.Unique = true
usersTable.Indexes["idx_users_email"] = emailIdx
schema.Tables = append(schema.Tables, usersTable)
postsTable := models.InitTable("posts", "public")
postIdCol := models.InitColumn("id", "posts", "public")
postIdCol.Type = "bigint"
postsTable.Columns["id"] = postIdCol
userIdCol := models.InitColumn("user_id", "posts", "public")
userIdCol.Type = "bigint"
postsTable.Columns["user_id"] = userIdCol
fk := models.InitConstraint("fk_posts_user", models.ForeignKeyConstraint)
fk.Table = "posts"
fk.Schema = "public"
fk.Columns = []string{"user_id"}
fk.ReferencedTable = "users"
fk.ReferencedSchema = "public"
fk.ReferencedColumns = []string{"id"}
fk.OnDelete = "CASCADE"
postsTable.Constraints["fk_posts_user"] = fk
schema.Tables = append(schema.Tables, postsTable)
db.Schemas = append(db.Schemas, schema)
tmpDir := t.TempDir()
outputPath := filepath.Join(tmpDir, "test.dbml")
opts := &writers.WriterOptions{OutputPath: outputPath}
writer := NewWriter(opts)
err := writer.WriteDatabase(db)
assert.NoError(t, err)
content, err := os.ReadFile(outputPath)
assert.NoError(t, err)
output := string(content)
assert.Contains(t, output, "Table public.users {")
assert.Contains(t, output, "Table public.posts {")
assert.Contains(t, output, "Ref: public.posts.user_id > public.users.id [delete: CASCADE]")
assert.Contains(t, output, "(email) [unique, name: 'idx_users_email']")
}
func TestWriter_WriteDatabase_OneToOneRelationship(t *testing.T) {
db := models.InitDatabase("test_db")
schema := models.InitSchema("public")
usersTable := models.InitTable("users", "public")
idCol := models.InitColumn("id", "users", "public")
idCol.Type = "bigint"
idCol.IsPrimaryKey = true
usersTable.Columns["id"] = idCol
schema.Tables = append(schema.Tables, usersTable)
profilesTable := models.InitTable("profiles", "public")
profileIdCol := models.InitColumn("id", "profiles", "public")
profileIdCol.Type = "bigint"
profilesTable.Columns["id"] = profileIdCol
userIdCol := models.InitColumn("user_id", "profiles", "public")
userIdCol.Type = "bigint"
userIdCol.IsPrimaryKey = true // This makes it a one-to-one
profilesTable.Columns["user_id"] = userIdCol
fk := models.InitConstraint("fk_profiles_user", models.ForeignKeyConstraint)
fk.Table = "profiles"
fk.Schema = "public"
fk.Columns = []string{"user_id"}
fk.ReferencedTable = "users"
fk.ReferencedSchema = "public"
fk.ReferencedColumns = []string{"id"}
profilesTable.Constraints["fk_profiles_user"] = fk
schema.Tables = append(schema.Tables, profilesTable)
db.Schemas = append(db.Schemas, schema)
tmpDir := t.TempDir()
outputPath := filepath.Join(tmpDir, "test.dbml")
opts := &writers.WriterOptions{OutputPath: outputPath}
writer := NewWriter(opts)
err := writer.WriteDatabase(db)
assert.NoError(t, err)
content, err := os.ReadFile(outputPath)
assert.NoError(t, err)
output := string(content)
assert.Contains(t, output, "Ref: public.profiles.user_id - public.users.id")
}
func TestWriter_WriteDatabase_ForeignKeyDeferral(t *testing.T) {
yes := true
db := models.InitDatabase("test_db")
schema := models.InitSchema("public")
users := models.InitTable("users", "public")
users.Columns["id"] = models.InitColumn("id", "users", "public")
users.Columns["id"].Type = "bigint"
posts := models.InitTable("posts", "public")
posts.Columns["user_id"] = models.InitColumn("user_id", "posts", "public")
posts.Columns["user_id"].Type = "bigint"
fk := models.InitConstraint("fk_posts_user", models.ForeignKeyConstraint)
fk.Table, fk.Schema = "posts", "public"
fk.Columns = []string{"user_id"}
fk.ReferencedTable, fk.ReferencedSchema = "users", "public"
fk.ReferencedColumns = []string{"id"}
fk.Deferrable = &yes
fk.InitiallyDeferred = &yes
posts.Constraints[fk.Name] = fk
schema.Tables = append(schema.Tables, users, posts)
db.Schemas = append(db.Schemas, schema)
outputPath := filepath.Join(t.TempDir(), "test.dbml")
assert.NoError(t, NewWriter(&writers.WriterOptions{OutputPath: outputPath}).WriteDatabase(db))
content, err := os.ReadFile(outputPath)
assert.NoError(t, err)
assert.Contains(t, string(content), "Ref: public.posts.user_id > public.users.id [deferrable, initially deferred]")
}