package diff import ( "bytes" "encoding/json" "strings" "testing" "git.warky.dev/wdevs/relspecgo/pkg/models" ) func TestFormatDiff(t *testing.T) { result := &DiffResult{ Source: "source_db", Target: "target_db", Schemas: &SchemaDiff{ Missing: []*models.Schema{}, Extra: []*models.Schema{}, Modified: []*SchemaChange{}, }, } tests := []struct { name string format OutputFormat wantErr bool }{ {"summary format", FormatSummary, false}, {"json format", FormatJSON, false}, {"html format", FormatHTML, false}, {"invalid format", OutputFormat("invalid"), true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var buf bytes.Buffer err := FormatDiff(result, tt.format, &buf) if (err != nil) != tt.wantErr { t.Errorf("FormatDiff() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && buf.Len() == 0 { t.Error("FormatDiff() produced empty output") } }) } } func TestFormatSummary(t *testing.T) { tests := []struct { name string result *DiffResult wantStr []string // strings that should appear in output }{ { name: "no differences", result: &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Missing: []*models.Schema{}, Extra: []*models.Schema{}, Modified: []*SchemaChange{}, }, }, wantStr: []string{"source", "target", "No differences found"}, }, { name: "with schema differences", result: &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Missing: []*models.Schema{{Name: "schema1"}}, Extra: []*models.Schema{{Name: "schema2"}}, Modified: []*SchemaChange{ {Name: "public"}, }, }, }, wantStr: []string{"Schemas:", "Missing: 1", "Extra: 1", "Modified: 1"}, }, { name: "with table differences", result: &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Modified: []*SchemaChange{ { Name: "public", Tables: &TableDiff{ Missing: []*models.Table{{Name: "users"}}, Extra: []*models.Table{{Name: "posts"}}, Modified: []*TableChange{ {Name: "comments", Schema: "public"}, }, }, }, }, }, }, wantStr: []string{"Tables:", "Missing: 1", "Extra: 1", "Modified: 1"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var buf bytes.Buffer err := formatSummary(tt.result, &buf) if err != nil { t.Errorf("formatSummary() error = %v", err) return } output := buf.String() for _, want := range tt.wantStr { if !strings.Contains(output, want) { t.Errorf("formatSummary() output doesn't contain %q\nGot: %s", want, output) } } }) } } func TestFormatJSON(t *testing.T) { result := &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Missing: []*models.Schema{{Name: "schema1"}}, Extra: []*models.Schema{}, Modified: []*SchemaChange{}, }, } var buf bytes.Buffer err := formatJSON(result, &buf) if err != nil { t.Errorf("formatJSON() error = %v", err) return } // Check if output is valid JSON var decoded DiffResult if err := json.Unmarshal(buf.Bytes(), &decoded); err != nil { t.Errorf("formatJSON() produced invalid JSON: %v", err) } // Check basic structure if decoded.Source != "source" { t.Errorf("formatJSON() source = %v, want %v", decoded.Source, "source") } if decoded.Target != "target" { t.Errorf("formatJSON() target = %v, want %v", decoded.Target, "target") } if len(decoded.Schemas.Missing) != 1 { t.Errorf("formatJSON() missing schemas = %v, want 1", len(decoded.Schemas.Missing)) } } func TestFormatHTML(t *testing.T) { tests := []struct { name string result *DiffResult wantStr []string // HTML elements/content that should appear }{ { name: "basic HTML structure", result: &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Missing: []*models.Schema{}, Extra: []*models.Schema{}, Modified: []*SchemaChange{}, }, }, wantStr: []string{ "", "Database Diff Report", "source", "target", }, }, { name: "with schema differences", result: &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Missing: []*models.Schema{{Name: "missing_schema"}}, Extra: []*models.Schema{{Name: "extra_schema"}}, Modified: []*SchemaChange{}, }, }, wantStr: []string{ "", "missing_schema", "extra_schema", "MISSING", "EXTRA", }, }, { name: "with table modifications", result: &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Modified: []*SchemaChange{ { Name: "public", Tables: &TableDiff{ Modified: []*TableChange{ { Name: "users", Schema: "public", Columns: &ColumnDiff{ Missing: []*models.Column{{Name: "email", Type: "text"}}, }, }, }, }, }, }, }, }, wantStr: []string{ "public", "users", "email", "text", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var buf bytes.Buffer err := formatHTML(tt.result, &buf) if err != nil { t.Errorf("formatHTML() error = %v", err) return } output := buf.String() for _, want := range tt.wantStr { if !strings.Contains(output, want) { t.Errorf("formatHTML() output doesn't contain %q", want) } } }) } } func TestFormatSummaryWithColumns(t *testing.T) { result := &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Modified: []*SchemaChange{ { Name: "public", Tables: &TableDiff{ Modified: []*TableChange{ { Name: "users", Schema: "public", Columns: &ColumnDiff{ Missing: []*models.Column{{Name: "email"}}, Extra: []*models.Column{{Name: "phone"}, {Name: "address"}}, Modified: []*ColumnChange{ {Name: "name"}, }, }, }, }, }, }, }, }, } var buf bytes.Buffer err := formatSummary(result, &buf) if err != nil { t.Errorf("formatSummary() error = %v", err) return } output := buf.String() wantStrings := []string{ "Columns:", "Missing: 1", "Extra: 2", "Modified: 1", } for _, want := range wantStrings { if !strings.Contains(output, want) { t.Errorf("formatSummary() output doesn't contain %q\nGot: %s", want, output) } } } func TestFormatSummaryWithIndexes(t *testing.T) { result := &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Modified: []*SchemaChange{ { Name: "public", Tables: &TableDiff{ Modified: []*TableChange{ { Name: "users", Schema: "public", Indexes: &IndexDiff{ Missing: []*models.Index{{Name: "idx_email"}}, Extra: []*models.Index{{Name: "idx_phone"}}, Modified: []*IndexChange{{Name: "idx_name"}}, }, }, }, }, }, }, }, } var buf bytes.Buffer err := formatSummary(result, &buf) if err != nil { t.Errorf("formatSummary() error = %v", err) return } output := buf.String() if !strings.Contains(output, "Indexes:") { t.Error("formatSummary() output doesn't contain Indexes section") } if !strings.Contains(output, "Missing: 1") { t.Error("formatSummary() output doesn't contain correct missing count") } } func TestFormatSummaryWithConstraints(t *testing.T) { result := &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Modified: []*SchemaChange{ { Name: "public", Tables: &TableDiff{ Modified: []*TableChange{ { Name: "users", Schema: "public", Constraints: &ConstraintDiff{ Missing: []*models.Constraint{{Name: "pk_users", Type: "PRIMARY KEY"}}, Extra: []*models.Constraint{{Name: "fk_users_roles", Type: "FOREIGN KEY"}}, }, }, }, }, }, }, }, } var buf bytes.Buffer err := formatSummary(result, &buf) if err != nil { t.Errorf("formatSummary() error = %v", err) return } output := buf.String() if !strings.Contains(output, "Constraints:") { t.Error("formatSummary() output doesn't contain Constraints section") } } func TestFormatJSONIndentation(t *testing.T) { result := &DiffResult{ Source: "source", Target: "target", Schemas: &SchemaDiff{ Missing: []*models.Schema{{Name: "test"}}, }, } var buf bytes.Buffer err := formatJSON(result, &buf) if err != nil { t.Errorf("formatJSON() error = %v", err) return } // Check that JSON is indented (has newlines and spaces) output := buf.String() if !strings.Contains(output, "\n") { t.Error("formatJSON() should produce indented JSON with newlines") } if !strings.Contains(output, " ") { t.Error("formatJSON() should produce indented JSON with spaces") } } func TestOutputFormatConstants(t *testing.T) { tests := []struct { name string format OutputFormat want string }{ {"summary constant", FormatSummary, "summary"}, {"json constant", FormatJSON, "json"}, {"html constant", FormatHTML, "html"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if string(tt.format) != tt.want { t.Errorf("OutputFormat %v = %v, want %v", tt.name, tt.format, tt.want) } }) } }