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") } }