package integration import ( "fmt" "testing" "golang.org/x/crypto/bcrypt" "goyco/internal/database" "goyco/internal/repositories" ) func TestIntegration_Repositories(t *testing.T) { suite := repositories.NewTestSuite(t) t.Run("User_Lifecycle", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "lifecycle_user", Email: "lifecycle@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: false, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } retrieved, err := suite.UserRepo.GetByID(user.ID) if err != nil { t.Fatalf("Failed to retrieve user: %v", err) } if retrieved.Username != "lifecycle_user" { t.Errorf("Expected username 'lifecycle_user', got '%s'", retrieved.Username) } retrieved.EmailVerified = true err = suite.UserRepo.Update(retrieved) if err != nil { t.Fatalf("Failed to update user: %v", err) } updated, err := suite.UserRepo.GetByID(user.ID) if err != nil { t.Fatalf("Failed to retrieve updated user: %v", err) } if !updated.EmailVerified { t.Error("Expected email to be verified") } err = suite.UserRepo.Delete(user.ID) if err != nil { t.Fatalf("Failed to delete user: %v", err) } _, err = suite.UserRepo.GetByID(user.ID) if err == nil { t.Error("Expected user to be deleted") } }) t.Run("Post_Lifecycle", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "post_author", Email: "author@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } post := &database.Post{ Title: "Integration Test Post", URL: "https://example.com/integration-test", Content: "This is a comprehensive integration test post", AuthorID: &user.ID, } err = suite.PostRepo.Create(post) if err != nil { t.Fatalf("Failed to create post: %v", err) } retrieved, err := suite.PostRepo.GetByID(post.ID) if err != nil { t.Fatalf("Failed to retrieve post: %v", err) } if retrieved.Title != "Integration Test Post" { t.Errorf("Expected title 'Integration Test Post', got '%s'", retrieved.Title) } retrieved.Title = "Updated Integration Test Post" err = suite.PostRepo.Update(retrieved) if err != nil { t.Fatalf("Failed to update post: %v", err) } updated, err := suite.PostRepo.GetByID(post.ID) if err != nil { t.Fatalf("Failed to retrieve updated post: %v", err) } if updated.Title != "Updated Integration Test Post" { t.Errorf("Expected updated title, got '%s'", updated.Title) } err = suite.PostRepo.Delete(post.ID) if err != nil { t.Fatalf("Failed to delete post: %v", err) } }) t.Run("Vote_Lifecycle", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "voter", Email: "voter@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } post := &database.Post{ Title: "Vote Test Post", URL: "https://example.com/vote-test", Content: "Vote test content", AuthorID: &user.ID, } err = suite.PostRepo.Create(post) if err != nil { t.Fatalf("Failed to create post: %v", err) } vote := &database.Vote{ UserID: &user.ID, PostID: post.ID, Type: database.VoteUp, } err = suite.VoteRepo.Create(vote) if err != nil { t.Fatalf("Failed to create vote: %v", err) } retrieved, err := suite.VoteRepo.GetByID(vote.ID) if err != nil { t.Fatalf("Failed to retrieve vote: %v", err) } if retrieved.Type != database.VoteUp { t.Errorf("Expected vote type %v, got %v", database.VoteUp, retrieved.Type) } retrieved.Type = database.VoteDown err = suite.VoteRepo.Update(retrieved) if err != nil { t.Fatalf("Failed to update vote: %v", err) } updated, err := suite.VoteRepo.GetByID(vote.ID) if err != nil { t.Fatalf("Failed to retrieve updated vote: %v", err) } if updated.Type != database.VoteDown { t.Errorf("Expected updated vote type, got %v", updated.Type) } err = suite.VoteRepo.Delete(vote.ID) if err != nil { t.Fatalf("Failed to delete vote: %v", err) } }) t.Run("Security_SQL_Injection_Protection", func(t *testing.T) { suite.Reset() initialCount, err := suite.UserRepo.Count() if err != nil { t.Fatalf("Failed to get initial user count: %v", err) } maliciousUser := &database.User{ Username: "'; DROP TABLE users; --", Email: "malicious@example.com", Password: hashPassword("password"), EmailVerified: true, } err = suite.UserRepo.Create(maliciousUser) if err != nil { t.Fatalf("Failed to create user with malicious username: %v", err) } finalCount, err := suite.UserRepo.Count() if err != nil { t.Fatalf("Failed to get final user count: %v", err) } if finalCount != initialCount+1 { t.Errorf("Expected user count to increase by 1, got %d -> %d", initialCount, finalCount) } retrieved, err := suite.UserRepo.GetByUsername("'; DROP TABLE users; --") if err != nil { t.Fatalf("Failed to retrieve user with malicious username: %v", err) } if retrieved.Username != "'; DROP TABLE users; --" { t.Errorf("Expected malicious username to be stored as-is") } users, err := suite.UserRepo.GetAll(10, 0) if err != nil { t.Fatalf("Failed to get users after SQL injection test: %v", err) } if len(users) == 0 { t.Error("Users table appears to have been dropped") } var tableName string err = suite.DB.Raw("SELECT name FROM sqlite_master WHERE type='table' AND name='users'").Scan(&tableName).Error if err != nil || tableName != "users" { t.Error("Users table should still exist after SQL injection attempt") } }) t.Run("Security_Input_Validation", func(t *testing.T) { suite.Reset() longString := string(make([]byte, 10000)) for i := range longString { longString = longString[:i] + "a" + longString[i+1:] } user := &database.User{ Username: longString, Email: "long@example.com", Password: hashPassword("password"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user with long username: %v", err) } specialUser := &database.User{ Username: "user", Email: "special@example.com", Password: hashPassword("password"), EmailVerified: true, } err = suite.UserRepo.Create(specialUser) if err != nil { t.Fatalf("Failed to create user with special characters: %v", err) } }) t.Run("Data_Consistency_Cross_Repository", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "consistency_user", Email: "consistency@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } post := &database.Post{ Title: "Consistency Test Post", URL: "https://example.com/consistency", Content: "Consistency test content", AuthorID: &user.ID, } err = suite.PostRepo.Create(post) if err != nil { t.Fatalf("Failed to create post: %v", err) } voters := make([]*database.User, 5) for i := 0; i < 5; i++ { voter := &database.User{ Username: fmt.Sprintf("voter_%d", i), Email: fmt.Sprintf("voter%d@example.com", i), Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(voter) if err != nil { t.Fatalf("Failed to create voter %d: %v", i, err) } voters[i] = voter } for i, voter := range voters { voteType := database.VoteUp if i%2 == 0 { voteType = database.VoteDown } vote := &database.Vote{ UserID: &voter.ID, PostID: post.ID, Type: voteType, } err := suite.VoteRepo.Create(vote) if err != nil { t.Fatalf("Failed to create vote %d: %v", i, err) } } votes, err := suite.VoteRepo.GetByPostID(post.ID) if err != nil { t.Fatalf("Failed to get votes: %v", err) } var upVotes, downVotes int64 for _, vote := range votes { if vote.Type == database.VoteUp { upVotes++ } else if vote.Type == database.VoteDown { downVotes++ } } expectedScore := int(upVotes - downVotes) post.Score = expectedScore err = suite.PostRepo.Update(post) if err != nil { t.Fatalf("Failed to update post score: %v", err) } updatedPost, err := suite.PostRepo.GetByID(post.ID) if err != nil { t.Fatalf("Failed to retrieve updated post: %v", err) } if updatedPost.Score != expectedScore { t.Errorf("Expected post score %d, got %d", expectedScore, updatedPost.Score) } }) t.Run("Edge_Cases_Invalid_Data", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "", Email: "empty@example.com", Password: hashPassword("password"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err == nil { t.Error("Expected error for empty username") } user = &database.User{ Username: "invalid_email", Email: "not-an-email", Password: hashPassword("password"), EmailVerified: true, } err = suite.UserRepo.Create(user) if err == nil { t.Error("Expected error for invalid email format") } user1 := &database.User{ Username: "duplicate", Email: "duplicate1@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err = suite.UserRepo.Create(user1) if err != nil { t.Fatalf("Failed to create first user: %v", err) } user2 := &database.User{ Username: "duplicate", Email: "duplicate2@example.com", Password: hashPassword("password"), EmailVerified: true, } err = suite.UserRepo.Create(user2) if err == nil { t.Error("Expected error for duplicate username") } user3 := &database.User{ Username: "duplicate_email", Email: "duplicate1@example.com", Password: hashPassword("password"), EmailVerified: true, } err = suite.UserRepo.Create(user3) if err == nil { t.Error("Expected error for duplicate email") } }) t.Run("Edge_Cases_Concurrent_Conflicts", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "conflict_user", Email: "conflict@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } post := &database.Post{ Title: "Conflict Test Post", URL: "https://example.com/conflict", Content: "Conflict test content", AuthorID: &user.ID, } err = suite.PostRepo.Create(post) if err != nil { t.Fatalf("Failed to create post: %v", err) } vote1 := &database.Vote{ UserID: &user.ID, PostID: post.ID, Type: database.VoteUp, } err = suite.VoteRepo.Create(vote1) if err != nil { t.Fatalf("Failed to create first vote: %v", err) } vote2 := &database.Vote{ UserID: &user.ID, PostID: post.ID, Type: database.VoteDown, } err = suite.VoteRepo.Create(vote2) if err == nil { t.Error("Expected error for duplicate vote") } }) t.Run("Transaction_Rollback_On_Error", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "transaction_user", Email: "transaction@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } tx := suite.DB.Begin() defer tx.Rollback() post := &database.Post{ Title: "Transaction Test Post", URL: "https://example.com/transaction", Content: "This is a transaction test post", AuthorID: &user.ID, } err = tx.Create(post).Error if err != nil { t.Fatalf("Failed to create post in transaction: %v", err) } var postInTx database.Post err = tx.First(&postInTx, post.ID).Error if err != nil { t.Fatalf("Failed to retrieve post in transaction: %v", err) } tx.Rollback() var postAfterRollback database.Post err = suite.DB.First(&postAfterRollback, post.ID).Error if err == nil { t.Error("Expected post to not exist after transaction rollback") } }) t.Run("Cascading_Delete_User_With_Posts", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "cascade_user", Email: "cascade@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } post1 := &database.Post{ Title: "Post 1", URL: "https://example.com/1", Content: "Content 1", AuthorID: &user.ID, } post2 := &database.Post{ Title: "Post 2", URL: "https://example.com/2", Content: "Content 2", AuthorID: &user.ID, } err = suite.PostRepo.Create(post1) if err != nil { t.Fatalf("Failed to create post1: %v", err) } err = suite.PostRepo.Create(post2) if err != nil { t.Fatalf("Failed to create post2: %v", err) } vote := &database.Vote{ UserID: &user.ID, PostID: post1.ID, Type: database.VoteUp, } err = suite.VoteRepo.Create(vote) if err != nil { t.Fatalf("Failed to create vote: %v", err) } err = suite.UserRepo.Delete(user.ID) if err != nil { t.Fatalf("Failed to delete user: %v", err) } _, err = suite.UserRepo.GetByID(user.ID) if err == nil { t.Error("Expected user to be deleted") } posts, err := suite.PostRepo.GetByUserID(user.ID, 10, 0) if err != nil { t.Fatalf("Failed to get posts: %v", err) } if len(posts) > 0 { t.Errorf("Expected posts to be deleted or orphaned, found %d posts", len(posts)) } }) t.Run("Search_Functionality", func(t *testing.T) { suite.Reset() user := &database.User{ Username: "search_user", Email: "search@example.com", Password: hashPassword("SecurePass123!"), EmailVerified: true, } err := suite.UserRepo.Create(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } posts := []struct { title string content string }{ {"Go Programming", "This post is about Go programming language"}, {"Python Tutorial", "Learn Python programming with this tutorial"}, {"Database Design", "Best practices for database design"}, {"Web Development", "Modern web development techniques"}, } for i, p := range posts { post := &database.Post{ Title: p.title, URL: fmt.Sprintf("https://example.com/post-%d", i), Content: p.content, AuthorID: &user.ID, } err := suite.PostRepo.Create(post) if err != nil { t.Fatalf("Failed to create post %d: %v", i, err) } } results, err := suite.PostRepo.Search("Go", 10, 0) if err != nil { t.Fatalf("Failed to search posts: %v", err) } if len(results) == 0 { t.Error("Expected to find posts containing 'Go'") } found := false for _, result := range results { if result.Title == "Go Programming" { found = true break } } if !found { t.Error("Expected to find 'Go Programming' post in search results") } }) } func hashPassword(password string) string { hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { panic(fmt.Sprintf("Failed to hash password: %v", err)) } return string(hashed) }