Files
relspecgo/pkg/readers/dctx/reader_test.go
Hein c0880cb076
Some checks failed
Release / test (push) Failing after -31m27s
Release / release (push) Has been skipped
Release / pkg-aur (push) Has been skipped
Release / pkg-deb (push) Has been skipped
Release / pkg-rpm (push) Has been skipped
feat(pkg): preserve PostgreSQL types in mapDataType function
* Add support for known PostgreSQL types and modifiers
* Implement canonicalization for PostgreSQL types
* Introduce unit tests for PostgreSQL type handling
2026-04-26 12:43:44 +02:00

548 lines
13 KiB
Go

package dctx
import (
"path/filepath"
"testing"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/readers"
)
func TestReader_ReadDatabase(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
db, err := reader.ReadDatabase()
if err != nil {
t.Fatalf("ReadDatabase() error = %v", err)
}
if db == nil {
t.Fatal("ReadDatabase() returned nil database")
}
if db.Name == "" {
t.Error("Expected non-empty database name")
}
if len(db.Schemas) == 0 {
t.Fatal("Expected at least one schema")
}
schema := db.Schemas[0]
if schema.Name == "" {
t.Error("Expected non-empty schema name")
}
if len(schema.Tables) == 0 {
t.Fatal("Expected at least one table")
}
// Verify at least one table has columns
hasColumns := false
for _, table := range schema.Tables {
if len(table.Columns) > 0 {
hasColumns = true
break
}
}
if !hasColumns {
t.Error("Expected at least one table with columns")
}
// Verify at least one table has a primary key
hasPK := false
for _, table := range schema.Tables {
pk := table.GetPrimaryKey()
if pk != nil {
hasPK = true
break
}
}
if !hasPK {
t.Error("Expected at least one table with a primary key")
}
// Verify at least one foreign key relationship exists
hasFKs := false
for _, table := range schema.Tables {
fks := table.GetForeignKeys()
if len(fks) > 0 {
hasFKs = true
// Verify foreign key properties
for _, fk := range fks {
if fk.Type != models.ForeignKeyConstraint {
t.Error("Expected foreign key constraint type")
}
if len(fk.Columns) == 0 {
t.Error("Foreign key should have at least one column")
}
if fk.ReferencedTable == "" {
t.Error("Foreign key should have referenced table")
}
if len(fk.ReferencedColumns) == 0 {
t.Error("Foreign key should have at least one referenced column")
}
}
break
}
}
if !hasFKs {
t.Error("Expected at least one foreign key relationship")
}
// Verify indexes exist on some tables
hasIndexes := false
for _, table := range schema.Tables {
if len(table.Indexes) > 0 {
hasIndexes = true
// Verify index properties
for _, idx := range table.Indexes {
if idx.Name == "" {
t.Error("Index should have a name")
}
if len(idx.Columns) == 0 {
t.Error("Index should have at least one column")
}
}
break
}
}
if !hasIndexes {
t.Error("Expected at least one table with indexes")
}
}
func TestReader_ReadSchema(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
schema, err := reader.ReadSchema()
if err != nil {
t.Fatalf("ReadSchema() error = %v", err)
}
if schema == nil {
t.Fatal("ReadSchema() returned nil schema")
}
if schema.Name == "" {
t.Error("Expected non-empty schema name")
}
if len(schema.Tables) == 0 {
t.Fatal("Expected at least one table")
}
}
func TestReader_ReadTable(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
table, err := reader.ReadTable()
if err != nil {
t.Fatalf("ReadTable() error = %v", err)
}
if table == nil {
t.Fatal("ReadTable() returned nil table")
}
if table.Name == "" {
t.Error("Expected non-empty table name")
}
if table.Schema == "" {
t.Error("Expected non-empty schema name")
}
if len(table.Columns) == 0 {
t.Error("Expected at least one column")
}
// Verify column properties
for _, col := range table.Columns {
if col.Name == "" {
t.Error("Column should have a name")
}
if col.Type == "" {
t.Error("Column should have a type")
}
if col.Table != table.Name {
t.Errorf("Column table '%s' should match table name '%s'", col.Table, table.Name)
}
if col.Schema != table.Schema {
t.Errorf("Column schema '%s' should match table schema '%s'", col.Schema, table.Schema)
}
}
}
func TestReader_ReadDatabase_InvalidPath(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: "/nonexistent/file.dctx",
}
reader := NewReader(opts)
_, err := reader.ReadDatabase()
if err == nil {
t.Error("Expected error for invalid file path")
}
}
func TestReader_ReadDatabase_EmptyPath(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: "",
}
reader := NewReader(opts)
_, err := reader.ReadDatabase()
if err == nil {
t.Error("Expected error for empty file path")
}
}
func TestReader_ReadSchema_EmptyPath(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: "",
}
reader := NewReader(opts)
_, err := reader.ReadSchema()
if err == nil {
t.Error("Expected error for empty file path")
}
}
func TestReader_ReadTable_EmptyPath(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: "",
}
reader := NewReader(opts)
_, err := reader.ReadTable()
if err == nil {
t.Error("Expected error for empty file path")
}
}
func TestGetPrimaryKey(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
db, err := reader.ReadDatabase()
if err != nil {
t.Fatalf("ReadDatabase() error = %v", err)
}
// Find a table with a primary key
var tableName string
var pk *models.Column
for _, schema := range db.Schemas {
for _, table := range schema.Tables {
pk = table.GetPrimaryKey()
if pk != nil {
tableName = table.Name
break
}
}
if pk != nil {
break
}
}
if pk == nil {
t.Fatal("Expected to find at least one table with a primary key")
}
if pk.Name == "" {
t.Error("Primary key should have a name")
}
if !pk.IsPrimaryKey {
t.Error("Primary key column should have IsPrimaryKey set to true")
}
t.Logf("Found primary key '%s' in table '%s'", pk.Name, tableName)
}
func TestGetForeignKeys(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
db, err := reader.ReadDatabase()
if err != nil {
t.Fatalf("ReadDatabase() error = %v", err)
}
// Find a table with foreign keys
var tableName string
var fks []*models.Constraint
for _, schema := range db.Schemas {
for _, table := range schema.Tables {
fks = table.GetForeignKeys()
if len(fks) > 0 {
tableName = table.Name
break
}
}
if len(fks) > 0 {
break
}
}
if len(fks) == 0 {
t.Fatal("Expected to find at least one table with foreign keys")
}
t.Logf("Found %d foreign keys in table '%s'", len(fks), tableName)
// Verify foreign key structure
for i, fk := range fks {
if fk.Type != models.ForeignKeyConstraint {
t.Errorf("FK %d: Expected foreign key constraint type", i)
}
if fk.Name == "" {
t.Errorf("FK %d: Expected foreign key to have a name", i)
}
if len(fk.Columns) == 0 {
t.Errorf("FK %d: Expected foreign key to have at least one column", i)
}
if fk.ReferencedTable == "" {
t.Errorf("FK %d: Expected foreign key to have a referenced table", i)
}
if len(fk.ReferencedColumns) == 0 {
t.Errorf("FK %d: Expected foreign key to have at least one referenced column", i)
}
t.Logf("FK %d: %s.%s -> %s.%s",
i,
tableName,
fk.Columns,
fk.ReferencedTable,
fk.ReferencedColumns,
)
}
}
func TestDatabaseStructure(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
db, err := reader.ReadDatabase()
if err != nil {
t.Fatalf("ReadDatabase() error = %v", err)
}
// Comprehensive structure validation
for _, schema := range db.Schemas {
if schema.Name == "" {
t.Error("Schema should have a name")
}
for _, table := range schema.Tables {
if table.Name == "" {
t.Error("Table should have a name")
}
if table.Schema == "" {
t.Error("Table should have a schema")
}
// Verify columns
for _, col := range table.Columns {
if col.Name == "" {
t.Errorf("Column in table '%s' should have a name", table.Name)
}
if col.Type == "" {
t.Errorf("Column '%s' in table '%s' should have a type", col.Name, table.Name)
}
if col.Table != table.Name {
t.Errorf("Column '%s' table reference should match table name", col.Name)
}
if col.Schema != table.Schema {
t.Errorf("Column '%s' schema reference should match table schema", col.Name)
}
}
// Verify constraints
for _, constraint := range table.Constraints {
if constraint.Name == "" {
t.Errorf("Constraint in table '%s' should have a name", table.Name)
}
if constraint.Type == "" {
t.Errorf("Constraint '%s' should have a type", constraint.Name)
}
if constraint.Table != table.Name {
t.Errorf("Constraint '%s' table reference should match table name", constraint.Name)
}
}
// Verify indexes
for _, index := range table.Indexes {
if index.Name == "" {
t.Errorf("Index in table '%s' should have a name", table.Name)
}
if len(index.Columns) == 0 {
t.Errorf("Index '%s' should have at least one column", index.Name)
}
if index.Table != table.Name {
t.Errorf("Index '%s' table reference should match table name", index.Name)
}
}
}
}
}
func TestColumnProperties(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
db, err := reader.ReadDatabase()
if err != nil {
t.Fatalf("ReadDatabase() error = %v", err)
}
// Find various column types
hasNotNullColumn := false
hasNullableColumn := false
hasDefaultValue := false
for _, schema := range db.Schemas {
for _, table := range schema.Tables {
for _, col := range table.Columns {
if col.NotNull {
hasNotNullColumn = true
} else {
hasNullableColumn = true
}
if col.Default != nil {
hasDefaultValue = true
}
}
}
}
if !hasNotNullColumn {
t.Log("Note: No NOT NULL columns found (this may be valid for the test data)")
}
if !hasNullableColumn {
t.Log("Note: No nullable columns found (this may be valid for the test data)")
}
if !hasDefaultValue {
t.Log("Note: No columns with default values found (this may be valid for the test data)")
}
}
func TestRelationships(t *testing.T) {
opts := &readers.ReaderOptions{
FilePath: filepath.Join("..", "..", "..", "examples", "dctx", "example.dctx"),
}
reader := NewReader(opts)
db, err := reader.ReadDatabase()
if err != nil {
t.Fatalf("ReadDatabase() error = %v", err)
}
// Count total relationships across all tables
relationshipCount := 0
for _, schema := range db.Schemas {
for _, table := range schema.Tables {
relationshipCount += len(table.Relationships)
}
}
// The example.dctx file should have a significant number of relationships
// With the fix for nested field GUID mapping, we expect around 100+ relationships
if relationshipCount < 50 {
t.Errorf("Expected at least 50 relationships, got %d. This may indicate relationships are not being parsed correctly", relationshipCount)
}
t.Logf("Successfully parsed %d relationships", relationshipCount)
// Verify relationship properties
for _, schema := range db.Schemas {
for _, table := range schema.Tables {
for _, rel := range table.Relationships {
if rel.Name == "" {
t.Errorf("Relationship in table '%s' should have a name", table.Name)
}
if rel.FromTable == "" {
t.Errorf("Relationship '%s' should have a from table", rel.Name)
}
if rel.ToTable == "" {
t.Errorf("Relationship '%s' should have a to table", rel.Name)
}
if rel.ForeignKey == "" {
t.Errorf("Relationship '%s' should reference a foreign key", rel.Name)
}
}
}
}
}
func TestMapDataType_PostgresTypes(t *testing.T) {
reader := &Reader{}
tests := []struct {
name string
inputType string
size int
wantType string
wantLength int
}{
{
name: "integer array preserved",
inputType: "integer[]",
wantType: "integer[]",
},
{
name: "citext array preserved",
inputType: "citext[]",
wantType: "citext[]",
},
{
name: "vector modifier preserved",
inputType: "vector(1536)",
wantType: "vector(1536)",
},
{
name: "alias canonicalized in array",
inputType: "int4[]",
wantType: "integer[]",
},
{
name: "varchar length from size",
inputType: "varchar",
size: 120,
wantType: "varchar",
wantLength: 120,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotType, gotLength := reader.mapDataType(tt.inputType, tt.size)
if gotType != tt.wantType {
t.Fatalf("mapDataType(%q, %d) type = %q, want %q", tt.inputType, tt.size, gotType, tt.wantType)
}
if gotLength != tt.wantLength {
t.Fatalf("mapDataType(%q, %d) length = %d, want %d", tt.inputType, tt.size, gotLength, tt.wantLength)
}
})
}
}