Files
goyco/internal/e2e/consistency_test.go

259 lines
9.3 KiB
Go

package e2e
import (
"testing"
"goyco/internal/database"
)
func TestE2E_VoteCountConsistency(t *testing.T) {
ctx := setupTestContext(t)
t.Run("vote_count_consistency", func(t *testing.T) {
user1 := ctx.createUserWithCleanup(t, "voteuser1", "Password123!")
user2 := ctx.createUserWithCleanup(t, "voteuser2", "Password123!")
user3 := ctx.createUserWithCleanup(t, "voteuser3", "Password123!")
client1 := ctx.loginUser(t, user1.Username, user1.Password)
post := client1.CreatePost(t, "Vote Count Test", "https://example.com/votecount", "Content")
client1.VoteOnPost(t, post.ID, "up")
client2 := ctx.loginUser(t, user2.Username, user2.Password)
client2.VoteOnPost(t, post.ID, "up")
client3 := ctx.loginUser(t, user3.Username, user3.Password)
client3.VoteOnPost(t, post.ID, "down")
var dbPost database.Post
if err := ctx.server.DB.First(&dbPost, post.ID).Error; err != nil {
t.Fatalf("Failed to find post in database: %v", err)
}
var voteCount int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id = ? AND type = ?", post.ID, database.VoteUp).Count(&voteCount)
if voteCount != int64(dbPost.UpVotes) {
t.Errorf("Expected upvote count %d to match database count %d", dbPost.UpVotes, voteCount)
}
ctx.server.DB.Model(&database.Vote{}).Where("post_id = ? AND type = ?", post.ID, database.VoteDown).Count(&voteCount)
if voteCount != int64(dbPost.DownVotes) {
t.Errorf("Expected downvote count %d to match database count %d", dbPost.DownVotes, voteCount)
}
postsResp := client1.GetPosts(t)
apiPost := findPostInList(postsResp, post.ID)
if apiPost == nil {
t.Fatalf("Expected to find post in API response")
}
if apiPost.UpVotes != dbPost.UpVotes {
t.Errorf("Expected API upvote count %d to match database %d", apiPost.UpVotes, dbPost.UpVotes)
}
if apiPost.DownVotes != dbPost.DownVotes {
t.Errorf("Expected API downvote count %d to match database %d", apiPost.DownVotes, dbPost.DownVotes)
}
})
}
func TestE2E_PostScoreCalculation(t *testing.T) {
ctx := setupTestContext(t)
t.Run("post_score_calculation", func(t *testing.T) {
user1 := ctx.createUserWithCleanup(t, "scoreuser1", "Password123!")
user2 := ctx.createUserWithCleanup(t, "scoreuser2", "Password123!")
user3 := ctx.createUserWithCleanup(t, "scoreuser3", "Password123!")
client1 := ctx.loginUser(t, user1.Username, user1.Password)
post := client1.CreatePost(t, "Score Test", "https://example.com/score", "Content")
client1.VoteOnPost(t, post.ID, "up")
client2 := ctx.loginUser(t, user2.Username, user2.Password)
client2.VoteOnPost(t, post.ID, "up")
client3 := ctx.loginUser(t, user3.Username, user3.Password)
client3.VoteOnPost(t, post.ID, "down")
var dbPost database.Post
if err := ctx.server.DB.First(&dbPost, post.ID).Error; err != nil {
t.Fatalf("Failed to find post in database: %v", err)
}
expectedScore := dbPost.UpVotes - dbPost.DownVotes
if dbPost.Score != expectedScore {
t.Errorf("Expected score %d (upvotes %d - downvotes %d), got %d", expectedScore, dbPost.UpVotes, dbPost.DownVotes, dbPost.Score)
}
postsResp := client1.GetPosts(t)
apiPost := findPostInList(postsResp, post.ID)
if apiPost == nil {
t.Fatalf("Expected to find post in API response")
}
if apiPost.Score != expectedScore {
t.Errorf("Expected API score %d to match calculated score %d", apiPost.Score, expectedScore)
}
})
}
func TestE2E_PostDeletionCascades(t *testing.T) {
ctx := setupTestContext(t)
t.Run("post_deletion_cascades", func(t *testing.T) {
user1 := ctx.createUserWithCleanup(t, "cascadeuser1", "Password123!")
user2 := ctx.createUserWithCleanup(t, "cascadeuser2", "Password123!")
client1 := ctx.loginUser(t, user1.Username, user1.Password)
post := client1.CreatePost(t, "Cascade Test", "https://example.com/cascade", "Content")
client1.VoteOnPost(t, post.ID, "up")
client2 := ctx.loginUser(t, user2.Username, user2.Password)
client2.VoteOnPost(t, post.ID, "down")
var voteCountBefore int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id = ?", post.ID).Count(&voteCountBefore)
if voteCountBefore == 0 {
t.Fatalf("Expected votes to exist before deletion")
}
client1.DeletePost(t, post.ID)
var voteCountAfter int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id = ?", post.ID).Count(&voteCountAfter)
if voteCountAfter != 0 {
t.Errorf("Expected votes to be deleted after post deletion, found %d votes", voteCountAfter)
}
var dbPost database.Post
if err := ctx.server.DB.First(&dbPost, post.ID).Error; err == nil {
t.Errorf("Expected post to be deleted from database")
}
})
}
func TestE2E_UserDeletionCascades(t *testing.T) {
ctx := setupTestContext(t)
t.Run("user_deletion_cascades", func(t *testing.T) {
user1 := ctx.createUserWithCleanup(t, "deleteuser1", "Password123!")
user2 := ctx.createUserWithCleanup(t, "deleteuser2", "Password123!")
client1 := ctx.loginUser(t, user1.Username, user1.Password)
post1 := client1.CreatePost(t, "Post 1", "https://example.com/post1", "Content 1")
post2 := client1.CreatePost(t, "Post 2", "https://example.com/post2", "Content 2")
client2 := ctx.loginUser(t, user2.Username, user2.Password)
client2.VoteOnPost(t, post1.ID, "up")
var postCountBefore int64
ctx.server.DB.Model(&database.Post{}).Where("author_id = ?", user1.ID).Count(&postCountBefore)
if postCountBefore == 0 {
t.Fatalf("Expected posts to exist before deletion")
}
var voteCountBefore int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id IN (?)", []uint{post1.ID, post2.ID}).Count(&voteCountBefore)
if voteCountBefore == 0 {
t.Fatalf("Expected votes to exist before deletion")
}
ctx.server.EmailSender.Reset()
client1.RequestAccountDeletion(t)
deletionToken := ctx.server.EmailSender.DeletionToken()
if deletionToken == "" {
t.Fatalf("Expected deletion token")
}
client1.ConfirmAccountDeletion(t, deletionToken, false)
var postCountAfter int64
ctx.server.DB.Model(&database.Post{}).Where("author_id = ?", user1.ID).Count(&postCountAfter)
if postCountAfter != 0 {
t.Errorf("Expected posts to be deleted after user deletion, found %d posts", postCountAfter)
}
var voteCountAfter int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id IN (?)", []uint{post1.ID, post2.ID}).Count(&voteCountAfter)
if voteCountAfter != 0 {
t.Errorf("Expected votes to be deleted after post deletion, found %d votes", voteCountAfter)
}
var dbUser database.User
if err := ctx.server.DB.First(&dbUser, user1.ID).Error; err == nil {
t.Errorf("Expected user to be deleted from database")
}
})
}
func TestE2E_ReferentialIntegrity(t *testing.T) {
ctx := setupTestContext(t)
t.Run("referential_integrity", func(t *testing.T) {
user1 := ctx.createUserWithCleanup(t, "refuser1", "Password123!")
user2 := ctx.createUserWithCleanup(t, "refuser2", "Password123!")
client1 := ctx.loginUser(t, user1.Username, user1.Password)
post := client1.CreatePost(t, "Ref Integrity Test", "https://example.com/ref", "Content")
client2 := ctx.loginUser(t, user2.Username, user2.Password)
client2.VoteOnPost(t, post.ID, "up")
var voteCount int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id = ? AND user_id = ?", post.ID, user2.ID).Count(&voteCount)
if voteCount != 1 {
t.Errorf("Expected vote to exist with correct foreign keys")
}
var postCount int64
ctx.server.DB.Model(&database.Post{}).Where("author_id = ?", user1.ID).Count(&postCount)
if postCount == 0 {
t.Errorf("Expected post to exist with correct author foreign key")
}
})
}
func TestE2E_OrphanedRecordsPrevention(t *testing.T) {
ctx := setupTestContext(t)
t.Run("orphaned_records_prevention", func(t *testing.T) {
user1 := ctx.createUserWithCleanup(t, "orphanuser1", "Password123!")
user2 := ctx.createUserWithCleanup(t, "orphanuser2", "Password123!")
client1 := ctx.loginUser(t, user1.Username, user1.Password)
post := client1.CreatePost(t, "Orphan Test", "https://example.com/orphan", "Content")
client2 := ctx.loginUser(t, user2.Username, user2.Password)
client2.VoteOnPost(t, post.ID, "up")
var voteCountBefore int64
ctx.server.DB.Model(&database.Vote{}).Where("post_id = ?", post.ID).Count(&voteCountBefore)
client1.DeletePost(t, post.ID)
var orphanedVotes int64
ctx.server.DB.Unscoped().Model(&database.Vote{}).Where("post_id = ?", post.ID).Count(&orphanedVotes)
if orphanedVotes != 0 {
t.Errorf("Expected no orphaned votes after post deletion, found %d", orphanedVotes)
}
post2 := client1.CreatePost(t, "Orphan Test 2", "https://example.com/orphan2", "Content")
client2.VoteOnPost(t, post2.ID, "up")
ctx.server.EmailSender.Reset()
client1.RequestAccountDeletion(t)
deletionToken := ctx.server.EmailSender.DeletionToken()
if deletionToken == "" {
t.Fatalf("Expected deletion token")
}
client1.ConfirmAccountDeletion(t, deletionToken, false)
var orphanedPosts int64
ctx.server.DB.Unscoped().Model(&database.Post{}).Where("author_id = ?", user1.ID).Count(&orphanedPosts)
if orphanedPosts != 0 {
t.Errorf("Expected no posts with author_id = %d after user deletion, found %d", user1.ID, orphanedPosts)
}
var orphanedVotesAfter int64
ctx.server.DB.Unscoped().Model(&database.Vote{}).Where("post_id = ?", post2.ID).Count(&orphanedVotesAfter)
if orphanedVotesAfter != 0 {
t.Errorf("Expected no orphaned votes after post deletion via user deletion, found %d", orphanedVotesAfter)
}
})
}