Files
relspecgo/tests/integration/orm_roundtrip_test.go
Hein d93a4b6f08
Some checks are pending
CI / Build (push) Waiting to run
CI / Test (1.23) (push) Waiting to run
CI / Test (1.24) (push) Waiting to run
CI / Test (1.25) (push) Waiting to run
CI / Lint (push) Waiting to run
Fixed bug/gorm indexes
2025-12-18 19:15:22 +02:00

354 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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")
}
}