Bugs Fixed
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
This commit is contained in:
540
pkg/writers/drawdb/writer_test.go
Normal file
540
pkg/writers/drawdb/writer_test.go
Normal file
@@ -0,0 +1,540 @@
|
||||
package drawdb
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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
|
||||
idCol.Comment = "Primary key"
|
||||
table.Columns["id"] = idCol
|
||||
|
||||
emailCol := models.InitColumn("email", "users", "public")
|
||||
emailCol.Type = "varchar(255)"
|
||||
emailCol.NotNull = true
|
||||
emailCol.Comment = "User email"
|
||||
table.Columns["email"] = emailCol
|
||||
|
||||
nameCol := models.InitColumn("name", "users", "public")
|
||||
nameCol.Type = "varchar(100)"
|
||||
table.Columns["name"] = nameCol
|
||||
|
||||
// Add index
|
||||
emailIdx := models.InitIndex("idx_users_email")
|
||||
emailIdx.Columns = []string{"email"}
|
||||
emailIdx.Unique = true
|
||||
emailIdx.Table = "users"
|
||||
emailIdx.Schema = "public"
|
||||
table.Indexes["idx_users_email"] = emailIdx
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
outputPath := filepath.Join(tmpDir, "test.json")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var result DrawDBSchema
|
||||
if err := json.Unmarshal(content, &result); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Tables) != 1 {
|
||||
t.Fatalf("Expected 1 table, got %d", len(result.Tables))
|
||||
}
|
||||
|
||||
drawTable := result.Tables[0]
|
||||
if drawTable.Name != "users" {
|
||||
t.Errorf("Expected table name 'users', got '%s'", drawTable.Name)
|
||||
}
|
||||
|
||||
if drawTable.Schema != "public" {
|
||||
t.Errorf("Expected schema 'public', got '%s'", drawTable.Schema)
|
||||
}
|
||||
|
||||
if drawTable.Comment != "User accounts table" {
|
||||
t.Errorf("Expected comment 'User accounts table', got '%s'", drawTable.Comment)
|
||||
}
|
||||
|
||||
if len(drawTable.Fields) != 3 {
|
||||
t.Errorf("Expected 3 fields, got %d", len(drawTable.Fields))
|
||||
}
|
||||
|
||||
// Verify id field
|
||||
var idField *DrawDBField
|
||||
for _, field := range drawTable.Fields {
|
||||
if field.Name == "id" {
|
||||
idField = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if idField == nil {
|
||||
t.Fatal("Field 'id' not found")
|
||||
}
|
||||
|
||||
if !idField.Primary {
|
||||
t.Error("Field 'id' should be primary")
|
||||
}
|
||||
|
||||
if !idField.NotNull {
|
||||
t.Error("Field 'id' should be not null")
|
||||
}
|
||||
|
||||
if !idField.Increment {
|
||||
t.Error("Field 'id' should be auto-increment")
|
||||
}
|
||||
|
||||
if idField.Comment != "Primary key" {
|
||||
t.Errorf("Expected id comment 'Primary key', got '%s'", idField.Comment)
|
||||
}
|
||||
|
||||
// Verify email field
|
||||
var emailField *DrawDBField
|
||||
for _, field := range drawTable.Fields {
|
||||
if field.Name == "email" {
|
||||
emailField = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if emailField == nil {
|
||||
t.Fatal("Field 'email' not found")
|
||||
}
|
||||
|
||||
if !emailField.NotNull {
|
||||
t.Error("Field 'email' should be not null")
|
||||
}
|
||||
|
||||
if emailField.Comment != "User email" {
|
||||
t.Errorf("Expected email comment 'User email', got '%s'", emailField.Comment)
|
||||
}
|
||||
|
||||
// Verify index
|
||||
if len(drawTable.Indexes) != 1 {
|
||||
t.Errorf("Expected 1 index, got %d", len(drawTable.Indexes))
|
||||
}
|
||||
|
||||
idx := drawTable.Indexes[0]
|
||||
if idx.Name != "idx_users_email" {
|
||||
t.Errorf("Expected index name 'idx_users_email', got '%s'", idx.Name)
|
||||
}
|
||||
|
||||
if !idx.Unique {
|
||||
t.Error("Email index should be unique")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriter_WriteDatabase_WithRelationships(t *testing.T) {
|
||||
db := models.InitDatabase("test_db")
|
||||
schema := models.InitSchema("public")
|
||||
|
||||
// Create users table
|
||||
usersTable := models.InitTable("users", "public")
|
||||
usersTable.Description = "User accounts"
|
||||
|
||||
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
|
||||
|
||||
// Create posts table
|
||||
postsTable := models.InitTable("posts", "public")
|
||||
postsTable.Description = "Blog posts"
|
||||
|
||||
postIdCol := models.InitColumn("id", "posts", "public")
|
||||
postIdCol.Type = "bigint"
|
||||
postIdCol.IsPrimaryKey = 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.json")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var result DrawDBSchema
|
||||
if err := json.Unmarshal(content, &result); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Tables) != 2 {
|
||||
t.Fatalf("Expected 2 tables, got %d", len(result.Tables))
|
||||
}
|
||||
|
||||
// Verify tables
|
||||
var usersTableResult, postsTableResult *DrawDBTable
|
||||
for _, table := range result.Tables {
|
||||
if table.Name == "users" {
|
||||
usersTableResult = table
|
||||
} else if table.Name == "posts" {
|
||||
postsTableResult = table
|
||||
}
|
||||
}
|
||||
|
||||
if usersTableResult == nil {
|
||||
t.Fatal("Users table not found")
|
||||
}
|
||||
|
||||
if postsTableResult == nil {
|
||||
t.Fatal("Posts table not found")
|
||||
}
|
||||
|
||||
// Verify relationship
|
||||
if len(result.Relationships) != 1 {
|
||||
t.Fatalf("Expected 1 relationship, got %d", len(result.Relationships))
|
||||
}
|
||||
|
||||
rel := result.Relationships[0]
|
||||
if rel.Name != "fk_posts_user" {
|
||||
t.Errorf("Expected relationship name 'fk_posts_user', got '%s'", rel.Name)
|
||||
}
|
||||
|
||||
// Verify relationship references correct tables
|
||||
if rel.StartTableID != postsTableResult.ID {
|
||||
t.Error("Relationship should start from posts table")
|
||||
}
|
||||
|
||||
if rel.EndTableID != usersTableResult.ID {
|
||||
t.Error("Relationship should end at users table")
|
||||
}
|
||||
|
||||
if rel.DeleteConstraint != "CASCADE" {
|
||||
t.Errorf("Expected DELETE constraint 'CASCADE', got '%s'", rel.DeleteConstraint)
|
||||
}
|
||||
|
||||
if rel.UpdateConstraint != "CASCADE" {
|
||||
t.Errorf("Expected UPDATE constraint 'CASCADE', got '%s'", rel.UpdateConstraint)
|
||||
}
|
||||
|
||||
// Verify published column default
|
||||
var publishedField *DrawDBField
|
||||
for _, field := range postsTableResult.Fields {
|
||||
if field.Name == "published" {
|
||||
publishedField = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if publishedField == nil {
|
||||
t.Fatal("Field 'published' not found")
|
||||
}
|
||||
|
||||
if publishedField.Default != "false" {
|
||||
t.Errorf("Expected published default 'false', got '%s'", publishedField.Default)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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.json")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var result DrawDBSchema
|
||||
if err := json.Unmarshal(content, &result); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Tables) != 1 {
|
||||
t.Fatalf("Expected 1 table, got %d", len(result.Tables))
|
||||
}
|
||||
|
||||
drawTable := result.Tables[0]
|
||||
if drawTable.Name != "users" {
|
||||
t.Errorf("Expected table name 'users', got '%s'", drawTable.Name)
|
||||
}
|
||||
|
||||
if len(drawTable.Fields) != 2 {
|
||||
t.Errorf("Expected 2 fields, got %d", len(drawTable.Fields))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriter_WriteDatabase_MultipleConstraints(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
|
||||
usersTable.Columns["id"] = idCol
|
||||
|
||||
// Create posts table
|
||||
postsTable := models.InitTable("posts", "public")
|
||||
postIdCol := models.InitColumn("id", "posts", "public")
|
||||
postIdCol.Type = "bigint"
|
||||
postIdCol.IsPrimaryKey = true
|
||||
postsTable.Columns["id"] = postIdCol
|
||||
|
||||
userIdCol := models.InitColumn("user_id", "posts", "public")
|
||||
userIdCol.Type = "bigint"
|
||||
postsTable.Columns["user_id"] = userIdCol
|
||||
|
||||
// Create comments table with two foreign keys
|
||||
commentsTable := models.InitTable("comments", "public")
|
||||
commentIdCol := models.InitColumn("id", "comments", "public")
|
||||
commentIdCol.Type = "bigint"
|
||||
commentIdCol.IsPrimaryKey = true
|
||||
commentsTable.Columns["id"] = commentIdCol
|
||||
|
||||
commentPostIdCol := models.InitColumn("post_id", "comments", "public")
|
||||
commentPostIdCol.Type = "bigint"
|
||||
commentPostIdCol.NotNull = true
|
||||
commentsTable.Columns["post_id"] = commentPostIdCol
|
||||
|
||||
commentUserIdCol := models.InitColumn("user_id", "comments", "public")
|
||||
commentUserIdCol.Type = "bigint"
|
||||
commentUserIdCol.NotNull = true
|
||||
commentsTable.Columns["user_id"] = commentUserIdCol
|
||||
|
||||
// Add foreign key to posts
|
||||
fkPost := models.InitConstraint("fk_comments_post", models.ForeignKeyConstraint)
|
||||
fkPost.Table = "comments"
|
||||
fkPost.Schema = "public"
|
||||
fkPost.Columns = []string{"post_id"}
|
||||
fkPost.ReferencedTable = "posts"
|
||||
fkPost.ReferencedSchema = "public"
|
||||
fkPost.ReferencedColumns = []string{"id"}
|
||||
fkPost.OnDelete = "CASCADE"
|
||||
commentsTable.Constraints["fk_comments_post"] = fkPost
|
||||
|
||||
// Add foreign key to users
|
||||
fkUser := models.InitConstraint("fk_comments_user", models.ForeignKeyConstraint)
|
||||
fkUser.Table = "comments"
|
||||
fkUser.Schema = "public"
|
||||
fkUser.Columns = []string{"user_id"}
|
||||
fkUser.ReferencedTable = "users"
|
||||
fkUser.ReferencedSchema = "public"
|
||||
fkUser.ReferencedColumns = []string{"id"}
|
||||
fkUser.OnDelete = "SET NULL"
|
||||
commentsTable.Constraints["fk_comments_user"] = fkUser
|
||||
|
||||
schema.Tables = append(schema.Tables, usersTable, postsTable, commentsTable)
|
||||
db.Schemas = append(db.Schemas, schema)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
outputPath := filepath.Join(tmpDir, "test.json")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
var result DrawDBSchema
|
||||
if err := json.Unmarshal(content, &result); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
if len(result.Tables) != 3 {
|
||||
t.Fatalf("Expected 3 tables, got %d", len(result.Tables))
|
||||
}
|
||||
|
||||
// Verify 2 relationships from comments table
|
||||
if len(result.Relationships) != 2 {
|
||||
t.Fatalf("Expected 2 relationships, got %d", len(result.Relationships))
|
||||
}
|
||||
|
||||
// Verify different delete constraints
|
||||
cascadeFound := false
|
||||
setNullFound := false
|
||||
|
||||
for _, rel := range result.Relationships {
|
||||
if rel.DeleteConstraint == "CASCADE" {
|
||||
cascadeFound = true
|
||||
}
|
||||
if rel.DeleteConstraint == "SET NULL" {
|
||||
setNullFound = true
|
||||
}
|
||||
}
|
||||
|
||||
if !cascadeFound {
|
||||
t.Error("Expected at least one CASCADE delete constraint")
|
||||
}
|
||||
|
||||
if !setNullFound {
|
||||
t.Error("Expected at least one SET NULL delete constraint")
|
||||
}
|
||||
}
|
||||
|
||||
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 TestGetPrimaryKey(t *testing.T) {
|
||||
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"
|
||||
table.Columns["email"] = emailCol
|
||||
|
||||
pk := table.GetPrimaryKey()
|
||||
if pk == nil {
|
||||
t.Fatal("Expected primary key, got nil")
|
||||
}
|
||||
|
||||
if pk.Name != "id" {
|
||||
t.Errorf("Expected primary key name 'id', got '%s'", pk.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetForeignKeys(t *testing.T) {
|
||||
table := models.InitTable("posts", "public")
|
||||
|
||||
idCol := models.InitColumn("id", "posts", "public")
|
||||
idCol.Type = "bigint"
|
||||
idCol.IsPrimaryKey = true
|
||||
table.Columns["id"] = idCol
|
||||
|
||||
userIdCol := models.InitColumn("user_id", "posts", "public")
|
||||
userIdCol.Type = "bigint"
|
||||
table.Columns["user_id"] = userIdCol
|
||||
|
||||
// Add foreign key constraint
|
||||
fk := models.InitConstraint("fk_posts_user", models.ForeignKeyConstraint)
|
||||
fk.Columns = []string{"user_id"}
|
||||
fk.ReferencedTable = "users"
|
||||
fk.ReferencedColumns = []string{"id"}
|
||||
table.Constraints["fk_posts_user"] = fk
|
||||
|
||||
fks := table.GetForeignKeys()
|
||||
if len(fks) != 1 {
|
||||
t.Errorf("Expected 1 foreign key, got %d", len(fks))
|
||||
}
|
||||
|
||||
if fks[0].Type != models.ForeignKeyConstraint {
|
||||
t.Error("Expected foreign key constraint type")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user