All checks were successful
* Introduce tests for PostgreSQL data types and keywords. * Implement tests for reflect utility functions. * Ensure consistency and correctness of type conversions and keyword mappings. * Validate behavior for various edge cases and input types.
441 lines
9.4 KiB
Go
441 lines
9.4 KiB
Go
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{
|
|
"<!DOCTYPE html>",
|
|
"<title>Database Diff Report</title>",
|
|
"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{
|
|
"<!DOCTYPE html>",
|
|
"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)
|
|
}
|
|
})
|
|
}
|
|
}
|