test(pgsql, reflectutil): ✨ add comprehensive test coverage
All checks were successful
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.
This commit is contained in:
558
pkg/diff/diff_test.go
Normal file
558
pkg/diff/diff_test.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package diff
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||||
)
|
||||
|
||||
func TestCompareDatabases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source *models.Database
|
||||
target *models.Database
|
||||
want func(*DiffResult) bool
|
||||
}{
|
||||
{
|
||||
name: "identical databases",
|
||||
source: &models.Database{
|
||||
Name: "source",
|
||||
Schemas: []*models.Schema{},
|
||||
},
|
||||
target: &models.Database{
|
||||
Name: "target",
|
||||
Schemas: []*models.Schema{},
|
||||
},
|
||||
want: func(r *DiffResult) bool {
|
||||
return r.Source == "source" && r.Target == "target" &&
|
||||
len(r.Schemas.Missing) == 0 && len(r.Schemas.Extra) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "different schemas",
|
||||
source: &models.Database{
|
||||
Name: "source",
|
||||
Schemas: []*models.Schema{
|
||||
{Name: "public"},
|
||||
},
|
||||
},
|
||||
target: &models.Database{
|
||||
Name: "target",
|
||||
Schemas: []*models.Schema{},
|
||||
},
|
||||
want: func(r *DiffResult) bool {
|
||||
return len(r.Schemas.Missing) == 1 && r.Schemas.Missing[0].Name == "public"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := CompareDatabases(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("CompareDatabases() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareColumns(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source map[string]*models.Column
|
||||
target map[string]*models.Column
|
||||
want func(*ColumnDiff) bool
|
||||
}{
|
||||
{
|
||||
name: "identical columns",
|
||||
source: map[string]*models.Column{},
|
||||
target: map[string]*models.Column{},
|
||||
want: func(d *ColumnDiff) bool {
|
||||
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing column",
|
||||
source: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "integer"},
|
||||
},
|
||||
target: map[string]*models.Column{},
|
||||
want: func(d *ColumnDiff) bool {
|
||||
return len(d.Missing) == 1 && d.Missing[0].Name == "id"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra column",
|
||||
source: map[string]*models.Column{},
|
||||
target: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "integer"},
|
||||
},
|
||||
want: func(d *ColumnDiff) bool {
|
||||
return len(d.Extra) == 1 && d.Extra[0].Name == "id"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "modified column type",
|
||||
source: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "integer"},
|
||||
},
|
||||
target: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "bigint"},
|
||||
},
|
||||
want: func(d *ColumnDiff) bool {
|
||||
return len(d.Modified) == 1 && d.Modified[0].Name == "id" &&
|
||||
d.Modified[0].Changes["type"] != nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "modified column nullable",
|
||||
source: map[string]*models.Column{
|
||||
"name": {Name: "name", Type: "text", NotNull: true},
|
||||
},
|
||||
target: map[string]*models.Column{
|
||||
"name": {Name: "name", Type: "text", NotNull: false},
|
||||
},
|
||||
want: func(d *ColumnDiff) bool {
|
||||
return len(d.Modified) == 1 && d.Modified[0].Changes["not_null"] != nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "modified column length",
|
||||
source: map[string]*models.Column{
|
||||
"name": {Name: "name", Type: "varchar", Length: 100},
|
||||
},
|
||||
target: map[string]*models.Column{
|
||||
"name": {Name: "name", Type: "varchar", Length: 255},
|
||||
},
|
||||
want: func(d *ColumnDiff) bool {
|
||||
return len(d.Modified) == 1 && d.Modified[0].Changes["length"] != nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareColumns(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("compareColumns() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareColumnDetails(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source *models.Column
|
||||
target *models.Column
|
||||
want int // number of changes
|
||||
}{
|
||||
{
|
||||
name: "identical columns",
|
||||
source: &models.Column{Name: "id", Type: "integer"},
|
||||
target: &models.Column{Name: "id", Type: "integer"},
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "type change",
|
||||
source: &models.Column{Name: "id", Type: "integer"},
|
||||
target: &models.Column{Name: "id", Type: "bigint"},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "length change",
|
||||
source: &models.Column{Name: "name", Type: "varchar", Length: 100},
|
||||
target: &models.Column{Name: "name", Type: "varchar", Length: 255},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "precision change",
|
||||
source: &models.Column{Name: "price", Type: "numeric", Precision: 10},
|
||||
target: &models.Column{Name: "price", Type: "numeric", Precision: 12},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "scale change",
|
||||
source: &models.Column{Name: "price", Type: "numeric", Scale: 2},
|
||||
target: &models.Column{Name: "price", Type: "numeric", Scale: 4},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "not null change",
|
||||
source: &models.Column{Name: "name", Type: "text", NotNull: true},
|
||||
target: &models.Column{Name: "name", Type: "text", NotNull: false},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "auto increment change",
|
||||
source: &models.Column{Name: "id", Type: "integer", AutoIncrement: true},
|
||||
target: &models.Column{Name: "id", Type: "integer", AutoIncrement: false},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "primary key change",
|
||||
source: &models.Column{Name: "id", Type: "integer", IsPrimaryKey: true},
|
||||
target: &models.Column{Name: "id", Type: "integer", IsPrimaryKey: false},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "multiple changes",
|
||||
source: &models.Column{Name: "id", Type: "integer", NotNull: true, AutoIncrement: true},
|
||||
target: &models.Column{Name: "id", Type: "bigint", NotNull: false, AutoIncrement: false},
|
||||
want: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareColumnDetails(tt.source, tt.target)
|
||||
if len(got) != tt.want {
|
||||
t.Errorf("compareColumnDetails() = %d changes, want %d", len(got), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareIndexes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source map[string]*models.Index
|
||||
target map[string]*models.Index
|
||||
want func(*IndexDiff) bool
|
||||
}{
|
||||
{
|
||||
name: "identical indexes",
|
||||
source: map[string]*models.Index{},
|
||||
target: map[string]*models.Index{},
|
||||
want: func(d *IndexDiff) bool {
|
||||
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing index",
|
||||
source: map[string]*models.Index{
|
||||
"idx_name": {Name: "idx_name", Columns: []string{"name"}},
|
||||
},
|
||||
target: map[string]*models.Index{},
|
||||
want: func(d *IndexDiff) bool {
|
||||
return len(d.Missing) == 1 && d.Missing[0].Name == "idx_name"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra index",
|
||||
source: map[string]*models.Index{},
|
||||
target: map[string]*models.Index{
|
||||
"idx_name": {Name: "idx_name", Columns: []string{"name"}},
|
||||
},
|
||||
want: func(d *IndexDiff) bool {
|
||||
return len(d.Extra) == 1 && d.Extra[0].Name == "idx_name"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "modified index uniqueness",
|
||||
source: map[string]*models.Index{
|
||||
"idx_name": {Name: "idx_name", Columns: []string{"name"}, Unique: false},
|
||||
},
|
||||
target: map[string]*models.Index{
|
||||
"idx_name": {Name: "idx_name", Columns: []string{"name"}, Unique: true},
|
||||
},
|
||||
want: func(d *IndexDiff) bool {
|
||||
return len(d.Modified) == 1 && d.Modified[0].Name == "idx_name"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareIndexes(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("compareIndexes() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareConstraints(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source map[string]*models.Constraint
|
||||
target map[string]*models.Constraint
|
||||
want func(*ConstraintDiff) bool
|
||||
}{
|
||||
{
|
||||
name: "identical constraints",
|
||||
source: map[string]*models.Constraint{},
|
||||
target: map[string]*models.Constraint{},
|
||||
want: func(d *ConstraintDiff) bool {
|
||||
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing constraint",
|
||||
source: map[string]*models.Constraint{
|
||||
"pk_id": {Name: "pk_id", Type: "PRIMARY KEY", Columns: []string{"id"}},
|
||||
},
|
||||
target: map[string]*models.Constraint{},
|
||||
want: func(d *ConstraintDiff) bool {
|
||||
return len(d.Missing) == 1 && d.Missing[0].Name == "pk_id"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra constraint",
|
||||
source: map[string]*models.Constraint{},
|
||||
target: map[string]*models.Constraint{
|
||||
"pk_id": {Name: "pk_id", Type: "PRIMARY KEY", Columns: []string{"id"}},
|
||||
},
|
||||
want: func(d *ConstraintDiff) bool {
|
||||
return len(d.Extra) == 1 && d.Extra[0].Name == "pk_id"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareConstraints(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("compareConstraints() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareRelationships(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source map[string]*models.Relationship
|
||||
target map[string]*models.Relationship
|
||||
want func(*RelationshipDiff) bool
|
||||
}{
|
||||
{
|
||||
name: "identical relationships",
|
||||
source: map[string]*models.Relationship{},
|
||||
target: map[string]*models.Relationship{},
|
||||
want: func(d *RelationshipDiff) bool {
|
||||
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing relationship",
|
||||
source: map[string]*models.Relationship{
|
||||
"fk_user": {Name: "fk_user", Type: "FOREIGN KEY"},
|
||||
},
|
||||
target: map[string]*models.Relationship{},
|
||||
want: func(d *RelationshipDiff) bool {
|
||||
return len(d.Missing) == 1 && d.Missing[0].Name == "fk_user"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra relationship",
|
||||
source: map[string]*models.Relationship{},
|
||||
target: map[string]*models.Relationship{
|
||||
"fk_user": {Name: "fk_user", Type: "FOREIGN KEY"},
|
||||
},
|
||||
want: func(d *RelationshipDiff) bool {
|
||||
return len(d.Extra) == 1 && d.Extra[0].Name == "fk_user"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareRelationships(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("compareRelationships() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareTables(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source []*models.Table
|
||||
target []*models.Table
|
||||
want func(*TableDiff) bool
|
||||
}{
|
||||
{
|
||||
name: "identical tables",
|
||||
source: []*models.Table{},
|
||||
target: []*models.Table{},
|
||||
want: func(d *TableDiff) bool {
|
||||
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing table",
|
||||
source: []*models.Table{
|
||||
{Name: "users", Schema: "public"},
|
||||
},
|
||||
target: []*models.Table{},
|
||||
want: func(d *TableDiff) bool {
|
||||
return len(d.Missing) == 1 && d.Missing[0].Name == "users"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra table",
|
||||
source: []*models.Table{},
|
||||
target: []*models.Table{
|
||||
{Name: "users", Schema: "public"},
|
||||
},
|
||||
want: func(d *TableDiff) bool {
|
||||
return len(d.Extra) == 1 && d.Extra[0].Name == "users"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "modified table",
|
||||
source: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Schema: "public",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "integer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
target: []*models.Table{
|
||||
{
|
||||
Name: "users",
|
||||
Schema: "public",
|
||||
Columns: map[string]*models.Column{
|
||||
"id": {Name: "id", Type: "bigint"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: func(d *TableDiff) bool {
|
||||
return len(d.Modified) == 1 && d.Modified[0].Name == "users"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareTables(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("compareTables() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareSchemas(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source []*models.Schema
|
||||
target []*models.Schema
|
||||
want func(*SchemaDiff) bool
|
||||
}{
|
||||
{
|
||||
name: "identical schemas",
|
||||
source: []*models.Schema{},
|
||||
target: []*models.Schema{},
|
||||
want: func(d *SchemaDiff) bool {
|
||||
return len(d.Missing) == 0 && len(d.Extra) == 0 && len(d.Modified) == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing schema",
|
||||
source: []*models.Schema{
|
||||
{Name: "public"},
|
||||
},
|
||||
target: []*models.Schema{},
|
||||
want: func(d *SchemaDiff) bool {
|
||||
return len(d.Missing) == 1 && d.Missing[0].Name == "public"
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "extra schema",
|
||||
source: []*models.Schema{},
|
||||
target: []*models.Schema{
|
||||
{Name: "public"},
|
||||
},
|
||||
want: func(d *SchemaDiff) bool {
|
||||
return len(d.Extra) == 1 && d.Extra[0].Name == "public"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := compareSchemas(tt.source, tt.target)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("compareSchemas() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
v interface{}
|
||||
want bool
|
||||
}{
|
||||
{"empty ColumnDiff", &ColumnDiff{Missing: []*models.Column{}, Extra: []*models.Column{}, Modified: []*ColumnChange{}}, true},
|
||||
{"ColumnDiff with missing", &ColumnDiff{Missing: []*models.Column{{Name: "id"}}, Extra: []*models.Column{}, Modified: []*ColumnChange{}}, false},
|
||||
{"ColumnDiff with extra", &ColumnDiff{Missing: []*models.Column{}, Extra: []*models.Column{{Name: "id"}}, Modified: []*ColumnChange{}}, false},
|
||||
{"empty IndexDiff", &IndexDiff{Missing: []*models.Index{}, Extra: []*models.Index{}, Modified: []*IndexChange{}}, true},
|
||||
{"IndexDiff with missing", &IndexDiff{Missing: []*models.Index{{Name: "idx"}}, Extra: []*models.Index{}, Modified: []*IndexChange{}}, false},
|
||||
{"empty TableDiff", &TableDiff{Missing: []*models.Table{}, Extra: []*models.Table{}, Modified: []*TableChange{}}, true},
|
||||
{"TableDiff with extra", &TableDiff{Missing: []*models.Table{}, Extra: []*models.Table{{Name: "users"}}, Modified: []*TableChange{}}, false},
|
||||
{"empty ConstraintDiff", &ConstraintDiff{Missing: []*models.Constraint{}, Extra: []*models.Constraint{}, Modified: []*ConstraintChange{}}, true},
|
||||
{"empty RelationshipDiff", &RelationshipDiff{Missing: []*models.Relationship{}, Extra: []*models.Relationship{}, Modified: []*RelationshipChange{}}, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := isEmpty(tt.v)
|
||||
if got != tt.want {
|
||||
t.Errorf("isEmpty() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeSummary(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
result *DiffResult
|
||||
want func(*Summary) bool
|
||||
}{
|
||||
{
|
||||
name: "empty diff",
|
||||
result: &DiffResult{
|
||||
Schemas: &SchemaDiff{
|
||||
Missing: []*models.Schema{},
|
||||
Extra: []*models.Schema{},
|
||||
Modified: []*SchemaChange{},
|
||||
},
|
||||
},
|
||||
want: func(s *Summary) bool {
|
||||
return s.Schemas.Missing == 0 && s.Schemas.Extra == 0 && s.Schemas.Modified == 0
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "schemas with differences",
|
||||
result: &DiffResult{
|
||||
Schemas: &SchemaDiff{
|
||||
Missing: []*models.Schema{{Name: "schema1"}},
|
||||
Extra: []*models.Schema{{Name: "schema2"}, {Name: "schema3"}},
|
||||
Modified: []*SchemaChange{
|
||||
{Name: "public"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: func(s *Summary) bool {
|
||||
return s.Schemas.Missing == 1 && s.Schemas.Extra == 2 && s.Schemas.Modified == 1
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ComputeSummary(tt.result)
|
||||
if !tt.want(got) {
|
||||
t.Errorf("ComputeSummary() result doesn't match expectations")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
440
pkg/diff/formatters_test.go
Normal file
440
pkg/diff/formatters_test.go
Normal file
@@ -0,0 +1,440 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user