Files
goyco/internal/integration/repositories_integration_test.go

622 lines
15 KiB
Go

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<script>alert('xss')</script>",
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)
}