354 lines
13 KiB
Go
354 lines
13 KiB
Go
package integration
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"testing"
|
||
|
||
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
||
"git.warky.dev/wdevs/relspecgo/pkg/readers"
|
||
bunreader "git.warky.dev/wdevs/relspecgo/pkg/readers/bun"
|
||
gormreader "git.warky.dev/wdevs/relspecgo/pkg/readers/gorm"
|
||
yamlreader "git.warky.dev/wdevs/relspecgo/pkg/readers/yaml"
|
||
"git.warky.dev/wdevs/relspecgo/pkg/writers"
|
||
bunwriter "git.warky.dev/wdevs/relspecgo/pkg/writers/bun"
|
||
gormwriter "git.warky.dev/wdevs/relspecgo/pkg/writers/gorm"
|
||
yamlwriter "git.warky.dev/wdevs/relspecgo/pkg/writers/yaml"
|
||
"github.com/stretchr/testify/require"
|
||
"gopkg.in/yaml.v3"
|
||
)
|
||
|
||
// ComparisonResults holds the results of database comparison
|
||
type ComparisonResults struct {
|
||
Schemas int
|
||
Tables int
|
||
Columns int
|
||
OriginalIndexes int
|
||
RoundtripIndexes int
|
||
OriginalConstraints int
|
||
RoundtripConstraints int
|
||
}
|
||
|
||
// countDatabaseStats counts tables, indexes, and constraints in a database
|
||
func countDatabaseStats(db *models.Database) (tables, indexes, constraints int) {
|
||
for _, schema := range db.Schemas {
|
||
tables += len(schema.Tables)
|
||
for _, table := range schema.Tables {
|
||
indexes += len(table.Indexes)
|
||
constraints += len(table.Constraints)
|
||
}
|
||
}
|
||
return
|
||
}
|
||
|
||
// compareDatabases performs comprehensive comparison between two databases
|
||
func compareDatabases(t *testing.T, db1, db2 *models.Database, ormName string) ComparisonResults {
|
||
t.Helper()
|
||
|
||
results := ComparisonResults{}
|
||
|
||
// Compare high-level structure
|
||
t.Log(" Comparing high-level structure...")
|
||
require.Equal(t, len(db1.Schemas), len(db2.Schemas), "Schema count should match")
|
||
results.Schemas = len(db1.Schemas)
|
||
|
||
// Count totals
|
||
tables1, indexes1, constraints1 := countDatabaseStats(db1)
|
||
_, indexes2, constraints2 := countDatabaseStats(db2)
|
||
|
||
results.OriginalIndexes = indexes1
|
||
results.RoundtripIndexes = indexes2
|
||
results.OriginalConstraints = constraints1
|
||
results.RoundtripConstraints = constraints2
|
||
|
||
// Compare schemas and tables
|
||
for i, schema1 := range db1.Schemas {
|
||
if i >= len(db2.Schemas) {
|
||
t.Errorf("Schema index %d out of bounds in second database", i)
|
||
continue
|
||
}
|
||
schema2 := db2.Schemas[i]
|
||
|
||
require.Equal(t, schema1.Name, schema2.Name, "Schema names should match")
|
||
require.Equal(t, len(schema1.Tables), len(schema2.Tables),
|
||
"Table count in schema '%s' should match", schema1.Name)
|
||
|
||
// Compare tables
|
||
for j, table1 := range schema1.Tables {
|
||
if j >= len(schema2.Tables) {
|
||
t.Errorf("Table index %d out of bounds in schema '%s'", j, schema1.Name)
|
||
continue
|
||
}
|
||
table2 := schema2.Tables[j]
|
||
|
||
require.Equal(t, table1.Name, table2.Name,
|
||
"Table names should match in schema '%s'", schema1.Name)
|
||
|
||
// Compare column count
|
||
require.Equal(t, len(table1.Columns), len(table2.Columns),
|
||
"Column count in table '%s.%s' should match", schema1.Name, table1.Name)
|
||
|
||
results.Columns += len(table1.Columns)
|
||
|
||
// Compare each column
|
||
for colName, col1 := range table1.Columns {
|
||
col2, ok := table2.Columns[colName]
|
||
if !ok {
|
||
t.Errorf("Column '%s' missing from roundtrip table '%s.%s'",
|
||
colName, schema1.Name, table1.Name)
|
||
continue
|
||
}
|
||
|
||
// Compare key column properties
|
||
require.Equal(t, col1.Name, col2.Name,
|
||
"Column name mismatch in '%s.%s.%s'", schema1.Name, table1.Name, colName)
|
||
require.Equal(t, col1.Type, col2.Type,
|
||
"Column type mismatch in '%s.%s.%s'", schema1.Name, table1.Name, colName)
|
||
require.Equal(t, col1.Length, col2.Length,
|
||
"Column length mismatch in '%s.%s.%s'", schema1.Name, table1.Name, colName)
|
||
require.Equal(t, col1.IsPrimaryKey, col2.IsPrimaryKey,
|
||
"Primary key mismatch in '%s.%s.%s'", schema1.Name, table1.Name, colName)
|
||
require.Equal(t, col1.NotNull, col2.NotNull,
|
||
"NotNull mismatch in '%s.%s.%s'", schema1.Name, table1.Name, colName)
|
||
|
||
// Log defaults that don't match (these can vary in representation)
|
||
if col1.Default != col2.Default {
|
||
t.Logf(" ℹ Default value differs for '%s.%s.%s': '%v' vs '%v'",
|
||
schema1.Name, table1.Name, colName, col1.Default, col2.Default)
|
||
}
|
||
}
|
||
|
||
// Log index and constraint differences (ORM readers may not capture all of these)
|
||
if len(table1.Indexes) != len(table2.Indexes) {
|
||
t.Logf(" ℹ Index count differs for table '%s.%s': %d vs %d",
|
||
schema1.Name, table1.Name, len(table1.Indexes), len(table2.Indexes))
|
||
}
|
||
if len(table1.Constraints) != len(table2.Constraints) {
|
||
t.Logf(" ℹ Constraint count differs for table '%s.%s': %d vs %d",
|
||
schema1.Name, table1.Name, len(table1.Constraints), len(table2.Constraints))
|
||
}
|
||
}
|
||
}
|
||
|
||
results.Tables = tables1
|
||
t.Logf(" ✓ Validated %d schemas, %d tables, %d columns", results.Schemas, results.Tables, results.Columns)
|
||
|
||
return results
|
||
}
|
||
|
||
// TestYAMLToBunRoundTrip tests YAML → Bun Go → YAML roundtrip
|
||
func TestYAMLToBunRoundTrip(t *testing.T) {
|
||
testDir := t.TempDir()
|
||
|
||
// Step 1: Read YAML file
|
||
t.Log("Step 1: Reading YAML file...")
|
||
yamlPath := filepath.Join("..", "assets", "yaml", "complex_database.yaml")
|
||
yamlReaderOpts := &readers.ReaderOptions{
|
||
FilePath: yamlPath,
|
||
}
|
||
yamlReader := yamlreader.NewReader(yamlReaderOpts)
|
||
|
||
dbFromYAML, err := yamlReader.ReadDatabase()
|
||
require.NoError(t, err, "Failed to read YAML file")
|
||
require.NotNil(t, dbFromYAML, "Database from YAML should not be nil")
|
||
t.Logf(" ✓ Read database '%s' with %d schemas", dbFromYAML.Name, len(dbFromYAML.Schemas))
|
||
|
||
// Log initial stats
|
||
totalTables, totalIndexes, totalConstraints := countDatabaseStats(dbFromYAML)
|
||
t.Logf(" ✓ Original: %d tables, %d indexes, %d constraints", totalTables, totalIndexes, totalConstraints)
|
||
|
||
// Step 2: Write to Bun Go code
|
||
t.Log("Step 2: Writing to Bun Go code...")
|
||
bunGoPath := filepath.Join(testDir, "models_bun.go")
|
||
bunWriterOpts := &writers.WriterOptions{
|
||
OutputPath: bunGoPath,
|
||
PackageName: "models",
|
||
Metadata: map[string]interface{}{
|
||
"generate_table_name": true,
|
||
"generate_get_id": false,
|
||
},
|
||
}
|
||
bunWriter := bunwriter.NewWriter(bunWriterOpts)
|
||
|
||
err = bunWriter.WriteDatabase(dbFromYAML)
|
||
require.NoError(t, err, "Failed to write Bun Go code")
|
||
|
||
bunStat, err := os.Stat(bunGoPath)
|
||
require.NoError(t, err, "Bun Go file should exist")
|
||
require.Greater(t, bunStat.Size(), int64(0), "Bun Go file should not be empty")
|
||
t.Logf(" ✓ Wrote Bun Go file (%d bytes)", bunStat.Size())
|
||
|
||
// Step 3: Read Bun Go code back
|
||
t.Log("Step 3: Reading Bun Go code back...")
|
||
bunReaderOpts := &readers.ReaderOptions{
|
||
FilePath: bunGoPath,
|
||
}
|
||
bunReader := bunreader.NewReader(bunReaderOpts)
|
||
|
||
dbFromBun, err := bunReader.ReadDatabase()
|
||
require.NoError(t, err, "Failed to read Bun Go code")
|
||
require.NotNil(t, dbFromBun, "Database from Bun should not be nil")
|
||
t.Logf(" ✓ Read database from Bun with %d schemas", len(dbFromBun.Schemas))
|
||
|
||
// Step 4: Write back to YAML
|
||
t.Log("Step 4: Writing back to YAML...")
|
||
yaml2Path := filepath.Join(testDir, "roundtrip.yaml")
|
||
yamlWriter2Opts := &writers.WriterOptions{
|
||
OutputPath: yaml2Path,
|
||
}
|
||
yamlWriter2 := yamlwriter.NewWriter(yamlWriter2Opts)
|
||
|
||
err = yamlWriter2.WriteDatabase(dbFromBun)
|
||
require.NoError(t, err, "Failed to write YAML")
|
||
|
||
yaml2Stat, err := os.Stat(yaml2Path)
|
||
require.NoError(t, err, "Second YAML file should exist")
|
||
require.Greater(t, yaml2Stat.Size(), int64(0), "Second YAML file should not be empty")
|
||
t.Logf(" ✓ Wrote second YAML file (%d bytes)", yaml2Stat.Size())
|
||
|
||
// Step 5: Compare YAML files
|
||
t.Log("Step 5: Comparing YAML outputs...")
|
||
|
||
// Read both YAML files
|
||
yaml1Data, err := os.ReadFile(yamlPath)
|
||
require.NoError(t, err, "Failed to read first YAML")
|
||
|
||
yaml2Data, err := os.ReadFile(yaml2Path)
|
||
require.NoError(t, err, "Failed to read second YAML")
|
||
|
||
// Parse into Database models for comparison
|
||
var db1, db2 models.Database
|
||
err = yaml.Unmarshal(yaml1Data, &db1)
|
||
require.NoError(t, err, "Failed to parse first YAML")
|
||
|
||
err = yaml.Unmarshal(yaml2Data, &db2)
|
||
require.NoError(t, err, "Failed to parse second YAML")
|
||
|
||
// Comprehensive comparison
|
||
compareResults := compareDatabases(t, &db1, &db2, "Bun")
|
||
|
||
// Summary
|
||
t.Log("Summary:")
|
||
t.Logf(" ✓ Round-trip completed: YAML → Bun → YAML")
|
||
t.Logf(" ✓ Schemas: %d", compareResults.Schemas)
|
||
t.Logf(" ✓ Tables: %d", compareResults.Tables)
|
||
t.Logf(" ✓ Columns: %d", compareResults.Columns)
|
||
t.Logf(" ✓ Indexes: %d (original), %d (roundtrip)", compareResults.OriginalIndexes, compareResults.RoundtripIndexes)
|
||
t.Logf(" ✓ Constraints: %d (original), %d (roundtrip)", compareResults.OriginalConstraints, compareResults.RoundtripConstraints)
|
||
|
||
if compareResults.OriginalIndexes != compareResults.RoundtripIndexes {
|
||
t.Logf(" ⚠ Note: Index counts differ - Bun reader may not parse all index information from Go code")
|
||
}
|
||
if compareResults.OriginalConstraints != compareResults.RoundtripConstraints {
|
||
t.Logf(" ⚠ Note: Constraint counts differ - Bun reader may not parse all constraint information from Go code")
|
||
}
|
||
}
|
||
|
||
// TestYAMLToGORMRoundTrip tests YAML → GORM Go → YAML roundtrip
|
||
func TestYAMLToGORMRoundTrip(t *testing.T) {
|
||
testDir := t.TempDir()
|
||
|
||
// Step 1: Read YAML file
|
||
t.Log("Step 1: Reading YAML file...")
|
||
yamlPath := filepath.Join("..", "assets", "yaml", "complex_database.yaml")
|
||
yamlReaderOpts := &readers.ReaderOptions{
|
||
FilePath: yamlPath,
|
||
}
|
||
yamlReader := yamlreader.NewReader(yamlReaderOpts)
|
||
|
||
dbFromYAML, err := yamlReader.ReadDatabase()
|
||
require.NoError(t, err, "Failed to read YAML file")
|
||
require.NotNil(t, dbFromYAML, "Database from YAML should not be nil")
|
||
t.Logf(" ✓ Read database '%s' with %d schemas", dbFromYAML.Name, len(dbFromYAML.Schemas))
|
||
|
||
// Log initial stats
|
||
totalTables, totalIndexes, totalConstraints := countDatabaseStats(dbFromYAML)
|
||
t.Logf(" ✓ Original: %d tables, %d indexes, %d constraints", totalTables, totalIndexes, totalConstraints)
|
||
|
||
// Step 2: Write to GORM Go code
|
||
t.Log("Step 2: Writing to GORM Go code...")
|
||
gormGoPath := filepath.Join(testDir, "models_gorm.go")
|
||
gormWriterOpts := &writers.WriterOptions{
|
||
OutputPath: gormGoPath,
|
||
PackageName: "models",
|
||
Metadata: map[string]interface{}{
|
||
"generate_table_name": true,
|
||
"generate_get_id": false,
|
||
},
|
||
}
|
||
gormWriter := gormwriter.NewWriter(gormWriterOpts)
|
||
|
||
err = gormWriter.WriteDatabase(dbFromYAML)
|
||
require.NoError(t, err, "Failed to write GORM Go code")
|
||
|
||
gormStat, err := os.Stat(gormGoPath)
|
||
require.NoError(t, err, "GORM Go file should exist")
|
||
require.Greater(t, gormStat.Size(), int64(0), "GORM Go file should not be empty")
|
||
t.Logf(" ✓ Wrote GORM Go file (%d bytes)", gormStat.Size())
|
||
|
||
// Step 3: Read GORM Go code back
|
||
t.Log("Step 3: Reading GORM Go code back...")
|
||
gormReaderOpts := &readers.ReaderOptions{
|
||
FilePath: gormGoPath,
|
||
}
|
||
gormReader := gormreader.NewReader(gormReaderOpts)
|
||
|
||
dbFromGORM, err := gormReader.ReadDatabase()
|
||
require.NoError(t, err, "Failed to read GORM Go code")
|
||
require.NotNil(t, dbFromGORM, "Database from GORM should not be nil")
|
||
t.Logf(" ✓ Read database from GORM with %d schemas", len(dbFromGORM.Schemas))
|
||
|
||
// Step 4: Write back to YAML
|
||
t.Log("Step 4: Writing back to YAML...")
|
||
yaml2Path := filepath.Join(testDir, "roundtrip.yaml")
|
||
yamlWriter2Opts := &writers.WriterOptions{
|
||
OutputPath: yaml2Path,
|
||
}
|
||
yamlWriter2 := yamlwriter.NewWriter(yamlWriter2Opts)
|
||
|
||
err = yamlWriter2.WriteDatabase(dbFromGORM)
|
||
require.NoError(t, err, "Failed to write YAML")
|
||
|
||
yaml2Stat, err := os.Stat(yaml2Path)
|
||
require.NoError(t, err, "Second YAML file should exist")
|
||
require.Greater(t, yaml2Stat.Size(), int64(0), "Second YAML file should not be empty")
|
||
t.Logf(" ✓ Wrote second YAML file (%d bytes)", yaml2Stat.Size())
|
||
|
||
// Step 5: Compare YAML files
|
||
t.Log("Step 5: Comparing YAML outputs...")
|
||
|
||
// Read both YAML files
|
||
yaml1Data, err := os.ReadFile(yamlPath)
|
||
require.NoError(t, err, "Failed to read first YAML")
|
||
|
||
yaml2Data, err := os.ReadFile(yaml2Path)
|
||
require.NoError(t, err, "Failed to read second YAML")
|
||
|
||
// Parse into Database models for comparison
|
||
var db1, db2 models.Database
|
||
err = yaml.Unmarshal(yaml1Data, &db1)
|
||
require.NoError(t, err, "Failed to parse first YAML")
|
||
|
||
err = yaml.Unmarshal(yaml2Data, &db2)
|
||
require.NoError(t, err, "Failed to parse second YAML")
|
||
|
||
// Comprehensive comparison
|
||
compareResults := compareDatabases(t, &db1, &db2, "GORM")
|
||
|
||
// Summary
|
||
t.Log("Summary:")
|
||
t.Logf(" ✓ Round-trip completed: YAML → GORM → YAML")
|
||
t.Logf(" ✓ Schemas: %d", compareResults.Schemas)
|
||
t.Logf(" ✓ Tables: %d", compareResults.Tables)
|
||
t.Logf(" ✓ Columns: %d", compareResults.Columns)
|
||
t.Logf(" ✓ Indexes: %d (original), %d (roundtrip)", compareResults.OriginalIndexes, compareResults.RoundtripIndexes)
|
||
t.Logf(" ✓ Constraints: %d (original), %d (roundtrip)", compareResults.OriginalConstraints, compareResults.RoundtripConstraints)
|
||
|
||
if compareResults.OriginalIndexes != compareResults.RoundtripIndexes {
|
||
t.Logf(" ⚠ Note: Index counts differ - GORM reader may not parse all index information from Go code")
|
||
}
|
||
if compareResults.OriginalConstraints != compareResults.RoundtripConstraints {
|
||
t.Logf(" ⚠ Note: Constraint counts differ - GORM reader may not parse all constraint information from Go code")
|
||
}
|
||
}
|