1. pkg/models/models.go:184 - Fixed typo in ForeignKeyConstraint constant from "foreign_Key" to "foreign_key" 2. pkg/readers/drawdb/reader.go:62-68 - Fixed ReadSchema() to properly detect schema name from tables instead of hardcoding "default" 3. pkg/writers/dbml/writer.go:128 - Changed primary key attribute from "primary key" to DBML standard "pk" 4. pkg/writers/dbml/writer.go:208-221 - Fixed foreign key reference format to use "table.column" syntax for single columns instead of "table.(column)" Test Results All reader and writer tests are now passing: Readers: - DBML: 74.4% coverage (2 tests skipped due to missing parser features for Ref statements) - DCTX: 77.6% coverage - DrawDB: 83.6% coverage - JSON: 82.1% coverage - YAML: 82.1% coverage Writers: - Bun: 68.5% coverage - DBML: 91.5% coverage - DCTX: 100.0% coverage - DrawDB: 83.8% coverage - GORM: 69.2% coverage - JSON: 82.4% coverage - YAML: 82.4% coverage
497 lines
13 KiB
Go
497 lines
13 KiB
Go
package dbml
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"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)
|
|
if err != nil {
|
|
t.Fatalf("WriteTable() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify table structure
|
|
if !strings.Contains(output, "Table public.users {") {
|
|
t.Error("Output should contain table definition")
|
|
}
|
|
|
|
// Verify columns
|
|
if !strings.Contains(output, "id bigint") {
|
|
t.Error("Output should contain id column")
|
|
}
|
|
if !strings.Contains(output, "pk") {
|
|
t.Error("Output should contain pk attribute for id")
|
|
}
|
|
if !strings.Contains(output, "increment") {
|
|
t.Error("Output should contain increment attribute for id")
|
|
}
|
|
if !strings.Contains(output, "email varchar(255)") {
|
|
t.Error("Output should contain email column")
|
|
}
|
|
if !strings.Contains(output, "not null") {
|
|
t.Error("Output should contain not null attribute")
|
|
}
|
|
|
|
// Verify table note
|
|
if !strings.Contains(output, "Note:") && table.Description != "" {
|
|
t.Error("Output should contain table note when description is present")
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteDatabase_WithRelationships(t *testing.T) {
|
|
db := models.InitDatabase("test_db")
|
|
schema := models.InitSchema("public")
|
|
|
|
// Create users table
|
|
usersTable := models.InitTable("users", "public")
|
|
idCol := models.InitColumn("id", "users", "public")
|
|
idCol.Type = "bigint"
|
|
idCol.IsPrimaryKey = true
|
|
idCol.AutoIncrement = true
|
|
idCol.NotNull = true
|
|
usersTable.Columns["id"] = idCol
|
|
|
|
emailCol := models.InitColumn("email", "users", "public")
|
|
emailCol.Type = "varchar(255)"
|
|
emailCol.NotNull = true
|
|
usersTable.Columns["email"] = emailCol
|
|
|
|
// Add index to users table
|
|
emailIdx := models.InitIndex("idx_users_email")
|
|
emailIdx.Columns = []string{"email"}
|
|
emailIdx.Unique = true
|
|
emailIdx.Table = "users"
|
|
emailIdx.Schema = "public"
|
|
usersTable.Indexes["idx_users_email"] = emailIdx
|
|
|
|
// Create posts table
|
|
postsTable := models.InitTable("posts", "public")
|
|
postIdCol := models.InitColumn("id", "posts", "public")
|
|
postIdCol.Type = "bigint"
|
|
postIdCol.IsPrimaryKey = true
|
|
postIdCol.AutoIncrement = true
|
|
postIdCol.NotNull = true
|
|
postsTable.Columns["id"] = postIdCol
|
|
|
|
userIdCol := models.InitColumn("user_id", "posts", "public")
|
|
userIdCol.Type = "bigint"
|
|
userIdCol.NotNull = true
|
|
postsTable.Columns["user_id"] = userIdCol
|
|
|
|
titleCol := models.InitColumn("title", "posts", "public")
|
|
titleCol.Type = "varchar(200)"
|
|
titleCol.NotNull = true
|
|
postsTable.Columns["title"] = titleCol
|
|
|
|
publishedCol := models.InitColumn("published", "posts", "public")
|
|
publishedCol.Type = "boolean"
|
|
publishedCol.Default = "false"
|
|
postsTable.Columns["published"] = publishedCol
|
|
|
|
// Add foreign key constraint
|
|
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"
|
|
fk.OnUpdate = "CASCADE"
|
|
postsTable.Constraints["fk_posts_user"] = fk
|
|
|
|
schema.Tables = append(schema.Tables, usersTable, 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)
|
|
if err != nil {
|
|
t.Fatalf("WriteDatabase() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify tables
|
|
if !strings.Contains(output, "Table public.users {") {
|
|
t.Error("Output should contain users table")
|
|
}
|
|
if !strings.Contains(output, "Table public.posts {") {
|
|
t.Error("Output should contain posts table")
|
|
}
|
|
|
|
// Verify foreign key reference
|
|
if !strings.Contains(output, "Ref:") {
|
|
t.Error("Output should contain Ref for foreign key")
|
|
}
|
|
if !strings.Contains(output, "public.posts.user_id") {
|
|
t.Error("Output should contain posts.user_id in reference")
|
|
}
|
|
if !strings.Contains(output, "public.users.id") {
|
|
t.Error("Output should contain users.id in reference")
|
|
}
|
|
if !strings.Contains(output, "ondelete: CASCADE") {
|
|
t.Error("Output should contain ondelete: CASCADE")
|
|
}
|
|
if !strings.Contains(output, "onupdate: CASCADE") {
|
|
t.Error("Output should contain onupdate: CASCADE")
|
|
}
|
|
|
|
// Verify index
|
|
if !strings.Contains(output, "indexes") {
|
|
t.Error("Output should contain indexes section")
|
|
}
|
|
if !strings.Contains(output, "(email)") {
|
|
t.Error("Output should contain email index")
|
|
}
|
|
if !strings.Contains(output, "unique") {
|
|
t.Error("Output should contain unique attribute for email index")
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteSchema(t *testing.T) {
|
|
schema := models.InitSchema("public")
|
|
|
|
table := models.InitTable("users", "public")
|
|
idCol := models.InitColumn("id", "users", "public")
|
|
idCol.Type = "bigint"
|
|
idCol.IsPrimaryKey = true
|
|
idCol.NotNull = true
|
|
table.Columns["id"] = idCol
|
|
|
|
usernameCol := models.InitColumn("username", "users", "public")
|
|
usernameCol.Type = "varchar(50)"
|
|
usernameCol.NotNull = true
|
|
table.Columns["username"] = usernameCol
|
|
|
|
schema.Tables = append(schema.Tables, table)
|
|
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "test.dbml")
|
|
|
|
opts := &writers.WriterOptions{
|
|
OutputPath: outputPath,
|
|
}
|
|
|
|
writer := NewWriter(opts)
|
|
err := writer.WriteSchema(schema)
|
|
if err != nil {
|
|
t.Fatalf("WriteSchema() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify table exists
|
|
if !strings.Contains(output, "Table public.users {") {
|
|
t.Error("Output should contain users table")
|
|
}
|
|
|
|
// Verify columns
|
|
if !strings.Contains(output, "id bigint") {
|
|
t.Error("Output should contain id column")
|
|
}
|
|
if !strings.Contains(output, "username varchar(50)") {
|
|
t.Error("Output should contain username column")
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteDatabase_MultipleSchemas(t *testing.T) {
|
|
db := models.InitDatabase("test_db")
|
|
|
|
// Create public schema with users table
|
|
publicSchema := models.InitSchema("public")
|
|
usersTable := models.InitTable("users", "public")
|
|
idCol := models.InitColumn("id", "users", "public")
|
|
idCol.Type = "bigint"
|
|
idCol.IsPrimaryKey = true
|
|
usersTable.Columns["id"] = idCol
|
|
publicSchema.Tables = append(publicSchema.Tables, usersTable)
|
|
|
|
// Create admin schema with audit_logs table
|
|
adminSchema := models.InitSchema("admin")
|
|
auditTable := models.InitTable("audit_logs", "admin")
|
|
auditIdCol := models.InitColumn("id", "audit_logs", "admin")
|
|
auditIdCol.Type = "bigint"
|
|
auditIdCol.IsPrimaryKey = true
|
|
auditTable.Columns["id"] = auditIdCol
|
|
|
|
userIdCol := models.InitColumn("user_id", "audit_logs", "admin")
|
|
userIdCol.Type = "bigint"
|
|
auditTable.Columns["user_id"] = userIdCol
|
|
|
|
// Add foreign key from admin.audit_logs to public.users
|
|
fk := models.InitConstraint("fk_audit_user", models.ForeignKeyConstraint)
|
|
fk.Table = "audit_logs"
|
|
fk.Schema = "admin"
|
|
fk.Columns = []string{"user_id"}
|
|
fk.ReferencedTable = "users"
|
|
fk.ReferencedSchema = "public"
|
|
fk.ReferencedColumns = []string{"id"}
|
|
fk.OnDelete = "SET NULL"
|
|
auditTable.Constraints["fk_audit_user"] = fk
|
|
|
|
adminSchema.Tables = append(adminSchema.Tables, auditTable)
|
|
|
|
db.Schemas = append(db.Schemas, publicSchema, adminSchema)
|
|
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "test.dbml")
|
|
|
|
opts := &writers.WriterOptions{
|
|
OutputPath: outputPath,
|
|
}
|
|
|
|
writer := NewWriter(opts)
|
|
err := writer.WriteDatabase(db)
|
|
if err != nil {
|
|
t.Fatalf("WriteDatabase() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify both schemas present
|
|
if !strings.Contains(output, "public.users") {
|
|
t.Error("Output should contain public.users table")
|
|
}
|
|
if !strings.Contains(output, "admin.audit_logs") {
|
|
t.Error("Output should contain admin.audit_logs table")
|
|
}
|
|
|
|
// Verify cross-schema foreign key
|
|
if !strings.Contains(output, "admin.audit_logs.user_id") {
|
|
t.Error("Output should contain admin.audit_logs.user_id in reference")
|
|
}
|
|
if !strings.Contains(output, "public.users.id") {
|
|
t.Error("Output should contain public.users.id in reference")
|
|
}
|
|
if !strings.Contains(output, "ondelete: SET NULL") {
|
|
t.Error("Output should contain ondelete: SET NULL")
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteTable_WithDefaults(t *testing.T) {
|
|
table := models.InitTable("products", "public")
|
|
|
|
idCol := models.InitColumn("id", "products", "public")
|
|
idCol.Type = "bigint"
|
|
idCol.IsPrimaryKey = true
|
|
table.Columns["id"] = idCol
|
|
|
|
isActiveCol := models.InitColumn("is_active", "products", "public")
|
|
isActiveCol.Type = "boolean"
|
|
isActiveCol.Default = "true"
|
|
table.Columns["is_active"] = isActiveCol
|
|
|
|
createdCol := models.InitColumn("created_at", "products", "public")
|
|
createdCol.Type = "timestamp"
|
|
createdCol.Default = "CURRENT_TIMESTAMP"
|
|
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)
|
|
if err != nil {
|
|
t.Fatalf("WriteTable() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify default values
|
|
if !strings.Contains(output, "default:") {
|
|
t.Error("Output should contain default values")
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteTable_EmptyPath(t *testing.T) {
|
|
table := models.InitTable("users", "public")
|
|
idCol := models.InitColumn("id", "users", "public")
|
|
idCol.Type = "bigint"
|
|
table.Columns["id"] = idCol
|
|
|
|
// When OutputPath is empty, it should print to stdout (not error)
|
|
opts := &writers.WriterOptions{
|
|
OutputPath: "",
|
|
}
|
|
|
|
writer := NewWriter(opts)
|
|
err := writer.WriteTable(table)
|
|
if err != nil {
|
|
t.Fatalf("WriteTable() with empty path should not error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteDatabase_WithComments(t *testing.T) {
|
|
db := models.InitDatabase("test_db")
|
|
db.Description = "Test database description"
|
|
db.Comment = "Additional comment"
|
|
|
|
schema := models.InitSchema("public")
|
|
table := models.InitTable("users", "public")
|
|
table.Comment = "Users table comment"
|
|
|
|
idCol := models.InitColumn("id", "users", "public")
|
|
idCol.Type = "bigint"
|
|
idCol.IsPrimaryKey = true
|
|
idCol.Comment = "Primary key"
|
|
table.Columns["id"] = idCol
|
|
|
|
schema.Tables = append(schema.Tables, table)
|
|
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)
|
|
if err != nil {
|
|
t.Fatalf("WriteDatabase() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify comments are present
|
|
if !strings.Contains(output, "//") {
|
|
t.Error("Output should contain comments")
|
|
}
|
|
}
|
|
|
|
func TestWriter_WriteDatabase_WithIndexType(t *testing.T) {
|
|
db := models.InitDatabase("test_db")
|
|
schema := models.InitSchema("public")
|
|
table := models.InitTable("users", "public")
|
|
|
|
idCol := models.InitColumn("id", "users", "public")
|
|
idCol.Type = "bigint"
|
|
idCol.IsPrimaryKey = true
|
|
table.Columns["id"] = idCol
|
|
|
|
emailCol := models.InitColumn("email", "users", "public")
|
|
emailCol.Type = "varchar(255)"
|
|
table.Columns["email"] = emailCol
|
|
|
|
// Add index with type
|
|
idx := models.InitIndex("idx_email")
|
|
idx.Columns = []string{"email"}
|
|
idx.Type = "btree"
|
|
idx.Unique = true
|
|
idx.Table = "users"
|
|
idx.Schema = "public"
|
|
table.Indexes["idx_email"] = idx
|
|
|
|
schema.Tables = append(schema.Tables, table)
|
|
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)
|
|
if err != nil {
|
|
t.Fatalf("WriteDatabase() error = %v", err)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
output := string(content)
|
|
|
|
// Verify index with type
|
|
if !strings.Contains(output, "type:") || !strings.Contains(output, "btree") {
|
|
t.Error("Output should contain index type")
|
|
}
|
|
}
|