package gorm import ( "path/filepath" "testing" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/readers" ) func TestReader_ReadDatabase_Simple(t *testing.T) { opts := &readers.ReaderOptions{ FilePath: filepath.Join("..", "..", "..", "tests", "assets", "gorm", "simple.go"), } 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 len(db.Schemas) == 0 { t.Fatal("Expected at least one schema") } schema := db.Schemas[0] if schema.Name != "public" { t.Errorf("Expected schema name 'public', got '%s'", schema.Name) } if len(schema.Tables) != 1 { t.Fatalf("Expected 1 table, got %d", len(schema.Tables)) } table := schema.Tables[0] if table.Name != "users" { t.Errorf("Expected table name 'users', got '%s'", table.Name) } if len(table.Columns) != 6 { t.Errorf("Expected 6 columns, got %d", len(table.Columns)) } // Verify id column - primary key should be NOT NULL idCol, exists := table.Columns["id"] if !exists { t.Fatal("Column 'id' not found") } if !idCol.IsPrimaryKey { t.Error("Column 'id' should be primary key") } if !idCol.AutoIncrement { t.Error("Column 'id' should be auto-increment") } if !idCol.NotNull { t.Error("Column 'id' should be NOT NULL (primary keys are always NOT NULL)") } if idCol.Type != "bigint" { t.Errorf("Expected id type 'bigint', got '%s'", idCol.Type) } // Verify email column - explicit "not null" tag should be NOT NULL emailCol, exists := table.Columns["email"] if !exists { t.Fatal("Column 'email' not found") } if !emailCol.NotNull { t.Error("Column 'email' should be NOT NULL (explicit 'not null' tag)") } if emailCol.Type != "varchar" || emailCol.Length != 255 { t.Errorf("Expected email type 'varchar(255)', got '%s' with length %d", emailCol.Type, emailCol.Length) } // Verify name column - primitive string type should be NOT NULL by default nameCol, exists := table.Columns["name"] if !exists { t.Fatal("Column 'name' not found") } if !nameCol.NotNull { t.Error("Column 'name' should be NOT NULL (primitive string type defaults to NOT NULL)") } if nameCol.Type != "text" { t.Errorf("Expected name type 'text', got '%s'", nameCol.Type) } // Verify age column - pointer type should be nullable (NOT NULL = false) ageCol, exists := table.Columns["age"] if !exists { t.Fatal("Column 'age' not found") } if ageCol.NotNull { t.Error("Column 'age' should be nullable (pointer type *int)") } if ageCol.Type != "integer" { t.Errorf("Expected age type 'integer', got '%s'", ageCol.Type) } // Verify is_active column - primitive bool type should be NOT NULL by default isActiveCol, exists := table.Columns["is_active"] if !exists { t.Fatal("Column 'is_active' not found") } if !isActiveCol.NotNull { t.Error("Column 'is_active' should be NOT NULL (primitive bool type defaults to NOT NULL)") } if isActiveCol.Type != "boolean" { t.Errorf("Expected is_active type 'boolean', got '%s'", isActiveCol.Type) } // Verify created_at column - time.Time should be NOT NULL by default createdAtCol, exists := table.Columns["created_at"] if !exists { t.Fatal("Column 'created_at' not found") } if !createdAtCol.NotNull { t.Error("Column 'created_at' should be NOT NULL (time.Time is NOT NULL by default)") } if createdAtCol.Type != "timestamp" { t.Errorf("Expected created_at type 'timestamp', got '%s'", createdAtCol.Type) } if createdAtCol.Default != "now()" { t.Errorf("Expected created_at default 'now()', got '%v'", createdAtCol.Default) } } func TestReader_ReadDatabase_Complex(t *testing.T) { opts := &readers.ReaderOptions{ FilePath: filepath.Join("..", "..", "..", "tests", "assets", "gorm", "complex.go"), } 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") } // Verify schema if len(db.Schemas) != 1 { t.Fatalf("Expected 1 schema, got %d", len(db.Schemas)) } schema := db.Schemas[0] if schema.Name != "public" { t.Errorf("Expected schema name 'public', got '%s'", schema.Name) } // Verify tables if len(schema.Tables) != 3 { t.Fatalf("Expected 3 tables, got %d", len(schema.Tables)) } // Find tables var usersTable, postsTable, commentsTable *models.Table for _, table := range schema.Tables { switch table.Name { case "users": usersTable = table case "posts": postsTable = table case "comments": commentsTable = table } } if usersTable == nil { t.Fatal("Users table not found") } if postsTable == nil { t.Fatal("Posts table not found") } if commentsTable == nil { t.Fatal("Comments table not found") } // Verify users table - test NOT NULL logic for various field types if len(usersTable.Columns) != 10 { t.Errorf("Expected 10 columns in users table, got %d", len(usersTable.Columns)) } // username - NOT NULL (explicit tag) usernameCol, exists := usersTable.Columns["username"] if !exists { t.Fatal("Column 'username' not found") } if !usernameCol.NotNull { t.Error("Column 'username' should be NOT NULL (explicit 'not null' tag)") } // first_name - nullable (pointer type) firstNameCol, exists := usersTable.Columns["first_name"] if !exists { t.Fatal("Column 'first_name' not found") } if firstNameCol.NotNull { t.Error("Column 'first_name' should be nullable (pointer type *string)") } // last_name - nullable (pointer type) lastNameCol, exists := usersTable.Columns["last_name"] if !exists { t.Fatal("Column 'last_name' not found") } if lastNameCol.NotNull { t.Error("Column 'last_name' should be nullable (pointer type *string)") } // bio - nullable (pointer type) bioCol, exists := usersTable.Columns["bio"] if !exists { t.Fatal("Column 'bio' not found") } if bioCol.NotNull { t.Error("Column 'bio' should be nullable (pointer type *string)") } // is_active - NOT NULL (primitive bool) isActiveCol, exists := usersTable.Columns["is_active"] if !exists { t.Fatal("Column 'is_active' not found") } if !isActiveCol.NotNull { t.Error("Column 'is_active' should be NOT NULL (primitive bool type)") } // Verify users table indexes if len(usersTable.Indexes) < 1 { t.Error("Expected at least 1 index on users table") } // Verify posts table if len(postsTable.Columns) != 11 { t.Errorf("Expected 11 columns in posts table, got %d", len(postsTable.Columns)) } // excerpt - nullable (pointer type) excerptCol, exists := postsTable.Columns["excerpt"] if !exists { t.Fatal("Column 'excerpt' not found") } if excerptCol.NotNull { t.Error("Column 'excerpt' should be nullable (pointer type *string)") } // published - NOT NULL (primitive bool with default) publishedCol, exists := postsTable.Columns["published"] if !exists { t.Fatal("Column 'published' not found") } if !publishedCol.NotNull { t.Error("Column 'published' should be NOT NULL (primitive bool type)") } if publishedCol.Default != "false" { t.Errorf("Expected published default 'false', got '%v'", publishedCol.Default) } // published_at - nullable (pointer to time.Time) publishedAtCol, exists := postsTable.Columns["published_at"] if !exists { t.Fatal("Column 'published_at' not found") } if publishedAtCol.NotNull { t.Error("Column 'published_at' should be nullable (pointer type *time.Time)") } // view_count - NOT NULL (primitive int64 with default) viewCountCol, exists := postsTable.Columns["view_count"] if !exists { t.Fatal("Column 'view_count' not found") } if !viewCountCol.NotNull { t.Error("Column 'view_count' should be NOT NULL (primitive int64 type)") } if viewCountCol.Default != "0" { t.Errorf("Expected view_count default '0', got '%v'", viewCountCol.Default) } // Verify posts table indexes if len(postsTable.Indexes) < 1 { t.Error("Expected at least 1 index on posts table") } // Verify comments table if len(commentsTable.Columns) != 6 { t.Errorf("Expected 6 columns in comments table, got %d", len(commentsTable.Columns)) } // user_id - nullable (pointer type) userIDCol, exists := commentsTable.Columns["user_id"] if !exists { t.Fatal("Column 'user_id' not found in comments table") } if userIDCol.NotNull { t.Error("Column 'user_id' should be nullable (pointer type *int64)") } // post_id - NOT NULL (explicit tag) postIDCol, exists := commentsTable.Columns["post_id"] if !exists { t.Fatal("Column 'post_id' not found in comments table") } if !postIDCol.NotNull { t.Error("Column 'post_id' should be NOT NULL (explicit 'not null' tag)") } // Verify foreign key constraints if len(postsTable.Constraints) == 0 { t.Error("Expected at least one constraint on posts table") } // Find FK constraint to users var fkPostsUser *models.Constraint for _, c := range postsTable.Constraints { if c.Type == models.ForeignKeyConstraint && c.ReferencedTable == "users" { fkPostsUser = c break } } if fkPostsUser != nil { if fkPostsUser.OnDelete != "CASCADE" { t.Errorf("Expected ON DELETE CASCADE for posts->users FK, got '%s'", fkPostsUser.OnDelete) } if fkPostsUser.OnUpdate != "CASCADE" { t.Errorf("Expected ON UPDATE CASCADE for posts->users FK, got '%s'", fkPostsUser.OnUpdate) } } // Verify comments table constraints if len(commentsTable.Constraints) == 0 { t.Error("Expected at least one constraint on comments table") } // Find FK constraints var fkCommentsPost, fkCommentsUser *models.Constraint for _, c := range commentsTable.Constraints { if c.Type == models.ForeignKeyConstraint { if c.ReferencedTable == "posts" { fkCommentsPost = c } else if c.ReferencedTable == "users" { fkCommentsUser = c } } } if fkCommentsPost != nil { if fkCommentsPost.OnDelete != "CASCADE" { t.Errorf("Expected ON DELETE CASCADE for comments->posts FK, got '%s'", fkCommentsPost.OnDelete) } } if fkCommentsUser != nil { if fkCommentsUser.OnDelete != "SET NULL" { t.Errorf("Expected ON DELETE SET NULL for comments->users FK, got '%s'", fkCommentsUser.OnDelete) } } } func TestReader_ReadSchema(t *testing.T) { opts := &readers.ReaderOptions{ FilePath: filepath.Join("..", "..", "..", "tests", "assets", "gorm", "simple.go"), } 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 != "public" { t.Errorf("Expected schema name 'public', got '%s'", schema.Name) } if len(schema.Tables) != 1 { t.Errorf("Expected 1 table, got %d", len(schema.Tables)) } } func TestReader_ReadTable(t *testing.T) { opts := &readers.ReaderOptions{ FilePath: filepath.Join("..", "..", "..", "tests", "assets", "gorm", "simple.go"), } 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 != "users" { t.Errorf("Expected table name 'users', got '%s'", table.Name) } if len(table.Columns) != 6 { t.Errorf("Expected 6 columns, got %d", len(table.Columns)) } } func TestReader_ReadDatabase_Directory(t *testing.T) { opts := &readers.ReaderOptions{ FilePath: filepath.Join("..", "..", "..", "tests", "assets", "gorm"), } 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") } // Should read both simple.go and complex.go if len(db.Schemas) == 0 { t.Fatal("Expected at least one schema") } schema := db.Schemas[0] // Should have at least 3 tables from complex.go (users, posts, comments) // plus 1 from simple.go (users) - but same table name, so may be overwritten if len(schema.Tables) < 3 { t.Errorf("Expected at least 3 tables, got %d", len(schema.Tables)) } } func TestReader_ReadDatabase_InvalidPath(t *testing.T) { opts := &readers.ReaderOptions{ FilePath: "/nonexistent/file.go", } 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") } }