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") } }