package merge import ( "testing" "git.warky.dev/wdevs/relspecgo/pkg/models" ) func TestMergeDatabases_NilInputs(t *testing.T) { result := MergeDatabases(nil, nil, nil) if result == nil { t.Fatal("Expected non-nil result") } if result.SchemasAdded != 0 { t.Errorf("Expected 0 schemas added, got %d", result.SchemasAdded) } } func TestMergeDatabases_NewSchema(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ {Name: "public"}, }, } source := &models.Database{ Schemas: []*models.Schema{ {Name: "auth"}, }, } result := MergeDatabases(target, source, nil) if result.SchemasAdded != 1 { t.Errorf("Expected 1 schema added, got %d", result.SchemasAdded) } if len(target.Schemas) != 2 { t.Errorf("Expected 2 schemas in target, got %d", len(target.Schemas)) } } func TestMergeDatabases_ExistingSchema(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ {Name: "public"}, }, } source := &models.Database{ Schemas: []*models.Schema{ {Name: "public"}, }, } result := MergeDatabases(target, source, nil) if result.SchemasAdded != 0 { t.Errorf("Expected 0 schemas added, got %d", result.SchemasAdded) } if len(target.Schemas) != 1 { t.Errorf("Expected 1 schema in target, got %d", len(target.Schemas)) } } func TestMergeTables_NewTable(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "posts", Schema: "public", Columns: map[string]*models.Column{}, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.TablesAdded != 1 { t.Errorf("Expected 1 table added, got %d", result.TablesAdded) } if len(target.Schemas[0].Tables) != 2 { t.Errorf("Expected 2 tables in target schema, got %d", len(target.Schemas[0].Tables)) } } func TestMergeColumns_NewColumn(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{ "id": {Name: "id", Type: "int"}, }, }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{ "email": {Name: "email", Type: "varchar"}, }, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.ColumnsAdded != 1 { t.Errorf("Expected 1 column added, got %d", result.ColumnsAdded) } if len(target.Schemas[0].Tables[0].Columns) != 2 { t.Errorf("Expected 2 columns in target table, got %d", len(target.Schemas[0].Tables[0].Columns)) } } func TestMergeConstraints_NewConstraint(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Constraints: map[string]*models.Constraint{}, }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Constraints: map[string]*models.Constraint{ "ukey_users_email": { Type: models.UniqueConstraint, Columns: []string{"email"}, Name: "ukey_users_email", }, }, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.ConstraintsAdded != 1 { t.Errorf("Expected 1 constraint added, got %d", result.ConstraintsAdded) } if len(target.Schemas[0].Tables[0].Constraints) != 1 { t.Errorf("Expected 1 constraint in target table, got %d", len(target.Schemas[0].Tables[0].Constraints)) } } func TestMergeConstraints_NilConstraintsMap(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Constraints: nil, // Nil map }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Constraints: map[string]*models.Constraint{ "ukey_users_email": { Type: models.UniqueConstraint, Columns: []string{"email"}, Name: "ukey_users_email", }, }, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.ConstraintsAdded != 1 { t.Errorf("Expected 1 constraint added, got %d", result.ConstraintsAdded) } if target.Schemas[0].Tables[0].Constraints == nil { t.Error("Expected constraints map to be initialized") } if len(target.Schemas[0].Tables[0].Constraints) != 1 { t.Errorf("Expected 1 constraint in target table, got %d", len(target.Schemas[0].Tables[0].Constraints)) } } func TestMergeIndexes_NewIndex(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Indexes: map[string]*models.Index{}, }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Indexes: map[string]*models.Index{ "idx_users_email": { Name: "idx_users_email", Columns: []string{"email"}, }, }, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.IndexesAdded != 1 { t.Errorf("Expected 1 index added, got %d", result.IndexesAdded) } if len(target.Schemas[0].Tables[0].Indexes) != 1 { t.Errorf("Expected 1 index in target table, got %d", len(target.Schemas[0].Tables[0].Indexes)) } } func TestMergeIndexes_NilIndexesMap(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Indexes: nil, // Nil map }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, Indexes: map[string]*models.Index{ "idx_users_email": { Name: "idx_users_email", Columns: []string{"email"}, }, }, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.IndexesAdded != 1 { t.Errorf("Expected 1 index added, got %d", result.IndexesAdded) } if target.Schemas[0].Tables[0].Indexes == nil { t.Error("Expected indexes map to be initialized") } if len(target.Schemas[0].Tables[0].Indexes) != 1 { t.Errorf("Expected 1 index in target table, got %d", len(target.Schemas[0].Tables[0].Indexes)) } } func TestMergeOptions_SkipTableNames(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{}, }, }, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "migrations", Schema: "public", Columns: map[string]*models.Column{}, }, }, }, }, } opts := &MergeOptions{ SkipTableNames: map[string]bool{ "migrations": true, }, } result := MergeDatabases(target, source, opts) if result.TablesAdded != 0 { t.Errorf("Expected 0 tables added (skipped), got %d", result.TablesAdded) } if len(target.Schemas[0].Tables) != 1 { t.Errorf("Expected 1 table in target schema, got %d", len(target.Schemas[0].Tables)) } } func TestMergeViews_NewView(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Views: []*models.View{}, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Views: []*models.View{ { Name: "user_summary", Schema: "public", Definition: "SELECT * FROM users", }, }, }, }, } result := MergeDatabases(target, source, nil) if result.ViewsAdded != 1 { t.Errorf("Expected 1 view added, got %d", result.ViewsAdded) } if len(target.Schemas[0].Views) != 1 { t.Errorf("Expected 1 view in target schema, got %d", len(target.Schemas[0].Views)) } } func TestMergeEnums_NewEnum(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Enums: []*models.Enum{}, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Enums: []*models.Enum{ { Name: "user_role", Schema: "public", Values: []string{"admin", "user"}, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.EnumsAdded != 1 { t.Errorf("Expected 1 enum added, got %d", result.EnumsAdded) } if len(target.Schemas[0].Enums) != 1 { t.Errorf("Expected 1 enum in target schema, got %d", len(target.Schemas[0].Enums)) } } func TestMergeDomains_NewDomain(t *testing.T) { target := &models.Database{ Domains: []*models.Domain{}, } source := &models.Database{ Domains: []*models.Domain{ { Name: "auth", Description: "Authentication domain", }, }, } result := MergeDatabases(target, source, nil) if result.DomainsAdded != 1 { t.Errorf("Expected 1 domain added, got %d", result.DomainsAdded) } if len(target.Domains) != 1 { t.Errorf("Expected 1 domain in target, got %d", len(target.Domains)) } } func TestMergeRelations_NewRelation(t *testing.T) { target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Relations: []*models.Relationship{}, }, }, } source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Relations: []*models.Relationship{ { Name: "fk_posts_user", Type: models.OneToMany, FromTable: "posts", FromColumns: []string{"user_id"}, ToTable: "users", ToColumns: []string{"id"}, }, }, }, }, } result := MergeDatabases(target, source, nil) if result.RelationsAdded != 1 { t.Errorf("Expected 1 relation added, got %d", result.RelationsAdded) } if len(target.Schemas[0].Relations) != 1 { t.Errorf("Expected 1 relation in target schema, got %d", len(target.Schemas[0].Relations)) } } func TestGetMergeSummary(t *testing.T) { result := &MergeResult{ SchemasAdded: 1, TablesAdded: 2, ColumnsAdded: 5, ConstraintsAdded: 3, IndexesAdded: 2, ViewsAdded: 1, } summary := GetMergeSummary(result) if summary == "" { t.Error("Expected non-empty summary") } if len(summary) < 50 { t.Errorf("Summary seems too short: %s", summary) } } func TestGetMergeSummary_Nil(t *testing.T) { summary := GetMergeSummary(nil) if summary == "" { t.Error("Expected non-empty summary for nil result") } } func TestComplexMerge(t *testing.T) { // Target with existing structure target := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{ "id": {Name: "id", Type: "int"}, }, Constraints: map[string]*models.Constraint{}, Indexes: map[string]*models.Index{}, }, }, }, }, } // Source with new columns, constraints, and indexes source := &models.Database{ Schemas: []*models.Schema{ { Name: "public", Tables: []*models.Table{ { Name: "users", Schema: "public", Columns: map[string]*models.Column{ "email": {Name: "email", Type: "varchar"}, "guid": {Name: "guid", Type: "uuid"}, }, Constraints: map[string]*models.Constraint{ "ukey_users_email": { Type: models.UniqueConstraint, Columns: []string{"email"}, Name: "ukey_users_email", }, "ukey_users_guid": { Type: models.UniqueConstraint, Columns: []string{"guid"}, Name: "ukey_users_guid", }, }, Indexes: map[string]*models.Index{ "idx_users_email": { Name: "idx_users_email", Columns: []string{"email"}, }, }, }, }, }, }, } result := MergeDatabases(target, source, nil) // Verify counts if result.ColumnsAdded != 2 { t.Errorf("Expected 2 columns added, got %d", result.ColumnsAdded) } if result.ConstraintsAdded != 2 { t.Errorf("Expected 2 constraints added, got %d", result.ConstraintsAdded) } if result.IndexesAdded != 1 { t.Errorf("Expected 1 index added, got %d", result.IndexesAdded) } // Verify target has merged data table := target.Schemas[0].Tables[0] if len(table.Columns) != 3 { t.Errorf("Expected 3 columns in merged table, got %d", len(table.Columns)) } if len(table.Constraints) != 2 { t.Errorf("Expected 2 constraints in merged table, got %d", len(table.Constraints)) } if len(table.Indexes) != 1 { t.Errorf("Expected 1 index in merged table, got %d", len(table.Indexes)) } // Verify specific constraint if _, exists := table.Constraints["ukey_users_guid"]; !exists { t.Error("Expected ukey_users_guid constraint to exist") } }