Files
goyco/cmd/goyco/commands/seed_test.go
2025-11-21 15:34:08 +01:00

353 lines
9.3 KiB
Go

package commands
import (
"fmt"
"strings"
"testing"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"goyco/internal/database"
"goyco/internal/repositories"
"goyco/internal/testutils"
)
func TestSeedCommand(t *testing.T) {
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
t.Fatalf("Failed to connect to database: %v", err)
}
err = db.AutoMigrate(&database.User{}, &database.Post{}, &database.Vote{})
if err != nil {
t.Fatalf("Failed to migrate database: %v", err)
}
userRepo := repositories.NewUserRepository(db)
postRepo := repositories.NewPostRepository(db)
voteRepo := repositories.NewVoteRepository(db)
seedUser, err := ensureSeedUser(userRepo)
if err != nil {
t.Fatalf("Failed to ensure seed user: %v", err)
}
if !strings.HasPrefix(seedUser.Username, "seed_admin_") {
t.Errorf("Expected username to start with 'seed_admin_', got '%s'", seedUser.Username)
}
if !strings.HasPrefix(seedUser.Email, "seed_admin_") || !strings.HasSuffix(seedUser.Email, "@goyco.local") {
t.Errorf("Expected email to start with 'seed_admin_' and end with '@goyco.local', got '%s'", seedUser.Email)
}
if !seedUser.EmailVerified {
t.Error("Expected seed user to be email verified")
}
users, err := createRandomUsers(userRepo, 2)
if err != nil {
t.Fatalf("Failed to create random users: %v", err)
}
if len(users) != 2 {
t.Errorf("Expected 2 users, got %d", len(users))
}
posts, err := createRandomPosts(postRepo, seedUser.ID, 5)
if err != nil {
t.Fatalf("Failed to create random posts: %v", err)
}
if len(posts) != 5 {
t.Errorf("Expected 5 posts, got %d", len(posts))
}
for i, post := range posts {
if post.Title == "" {
t.Errorf("Post %d has empty title", i)
}
if post.URL == "" {
t.Errorf("Post %d has empty URL", i)
}
if post.AuthorID == nil || *post.AuthorID != seedUser.ID {
t.Errorf("Post %d has wrong author ID: expected %d, got %v", i, seedUser.ID, post.AuthorID)
}
}
allUsers := append([]database.User{*seedUser}, users...)
votes, err := createRandomVotes(voteRepo, allUsers, posts, 3)
if err != nil {
t.Fatalf("Failed to create random votes: %v", err)
}
if votes == 0 {
t.Error("Expected some votes to be created")
}
err = updatePostScores(postRepo, voteRepo, posts)
if err != nil {
t.Fatalf("Failed to update post scores: %v", err)
}
for i, post := range posts {
updatedPost, err := postRepo.GetByID(post.ID)
if err != nil {
t.Errorf("Failed to get updated post %d: %v", i, err)
continue
}
expectedScore := updatedPost.UpVotes - updatedPost.DownVotes
if updatedPost.Score != expectedScore {
t.Errorf("Post %d has incorrect score: expected %d, got %d", i, expectedScore, updatedPost.Score)
}
}
}
func TestGenerateRandomPath(t *testing.T) {
path := generateRandomPath()
if path == "" {
t.Error("Generated path should not be empty")
}
if len(path) < 8 {
t.Errorf("Generated path too short: %s", path)
}
secondPath := generateRandomPath()
if path == secondPath {
t.Error("Generated paths should be different")
}
}
func TestSeedDatabaseFlagParsing(t *testing.T) {
userRepo := testutils.NewMockUserRepository()
postRepo := testutils.NewMockPostRepository()
voteRepo := testutils.NewMockVoteRepository()
t.Run("invalid posts type", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--posts", "abc"})
if err == nil {
t.Error("expected error for invalid posts type")
}
})
t.Run("invalid users type", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "xyz"})
if err == nil {
t.Error("expected error for invalid users type")
}
})
t.Run("invalid votes-per-post type", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--votes-per-post", "invalid"})
if err == nil {
t.Error("expected error for invalid votes-per-post type")
}
})
t.Run("unknown flag", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--unknown-flag"})
if err == nil {
t.Error("expected error for unknown flag")
}
})
t.Run("missing posts value", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--posts"})
if err == nil {
t.Error("expected error for missing posts value")
}
})
t.Run("missing users value", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users"})
if err == nil {
t.Error("expected error for missing users value")
}
})
t.Run("missing votes-per-post value", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--votes-per-post"})
if err == nil {
t.Error("expected error for missing votes-per-post value")
}
})
t.Run("negative users value is clamped", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "-1", "--posts", "1"})
if err != nil {
t.Errorf("negative users should be clamped, not rejected. Got error: %v", err)
}
})
t.Run("negative posts value is clamped", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--posts", "-5"})
if err != nil {
t.Errorf("negative posts should be clamped, not rejected. Got error: %v", err)
}
})
t.Run("zero posts value is clamped", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--posts", "0"})
if err != nil {
t.Errorf("zero posts should be clamped, not rejected. Got error: %v", err)
}
})
t.Run("negative votes-per-post value is clamped", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--votes-per-post", "-10", "--posts", "1"})
if err != nil {
t.Errorf("negative votes-per-post should be clamped, not rejected. Got error: %v", err)
}
})
t.Run("zero users value is valid", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "0", "--posts", "1"})
if err != nil {
t.Errorf("zero users should be valid, got error: %v", err)
}
})
t.Run("zero votes-per-post value is valid", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--votes-per-post", "0", "--posts", "1"})
if err != nil {
t.Errorf("zero votes-per-post should be valid, got error: %v", err)
}
})
}
func TestSeedCommandIdempotency(t *testing.T) {
dbName := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
if err != nil {
t.Fatalf("Failed to connect to database: %v", err)
}
err = db.AutoMigrate(&database.User{}, &database.Post{}, &database.Vote{})
if err != nil {
t.Fatalf("Failed to migrate database: %v", err)
}
userRepo := repositories.NewUserRepository(db)
postRepo := repositories.NewPostRepository(db)
voteRepo := repositories.NewVoteRepository(db)
t.Run("first run creates seed user", func(t *testing.T) {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "1", "--posts", "2"})
if err != nil {
t.Fatalf("First seed run failed: %v", err)
}
users, err := userRepo.GetAll(100, 0)
if err != nil {
t.Fatalf("Failed to get users: %v", err)
}
seedUserCount := 0
for _, user := range users {
if strings.HasPrefix(user.Username, "seed_admin_") {
seedUserCount++
}
}
if seedUserCount < 1 {
t.Errorf("Expected at least 1 seed user, got %d", seedUserCount)
}
})
t.Run("second run reuses seed user", func(t *testing.T) {
usersBefore, _ := userRepo.GetAll(100, 0)
seedUserBefore := findSeedUser(usersBefore)
if seedUserBefore == nil {
t.Fatal("No seed user found before second run")
}
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "1", "--posts", "2"})
if err != nil {
t.Fatalf("Second seed run failed: %v", err)
}
usersAfter, _ := userRepo.GetAll(100, 0)
seedUserAfter := findSeedUser(usersAfter)
if seedUserAfter == nil {
t.Fatal("Seed user not found after second run")
}
if seedUserBefore.ID != seedUserAfter.ID {
t.Errorf("Expected seed user to be reused (ID %d), but got different user (ID %d)", seedUserBefore.ID, seedUserAfter.ID)
}
})
t.Run("database remains consistent after multiple runs", func(t *testing.T) {
for i := 0; i < 2; i++ {
err := seedDatabase(userRepo, postRepo, voteRepo, []string{"--users", "0", "--posts", "1"})
if err != nil {
t.Fatalf("Seed run %d failed: %v", i+1, err)
}
}
users, _ := userRepo.GetAll(100, 0)
posts, _ := postRepo.GetAll(100, 0)
for _, post := range posts {
if post.AuthorID == nil {
t.Errorf("Post %d has no author", post.ID)
continue
}
authorExists := false
for _, user := range users {
if user.ID == *post.AuthorID {
authorExists = true
break
}
}
if !authorExists {
t.Errorf("Post %d has invalid author ID %d", post.ID, *post.AuthorID)
}
votes, _ := voteRepo.GetByPostID(post.ID)
for _, vote := range votes {
if vote.UserID != nil {
userExists := false
for _, user := range users {
if user.ID == *vote.UserID {
userExists = true
break
}
}
if !userExists {
t.Errorf("Vote %d has invalid user ID %d", vote.ID, *vote.UserID)
}
}
}
}
})
}
func findSeedUser(users []database.User) *database.User {
for i := range users {
if strings.HasPrefix(users[i].Username, "seed_admin_") {
return &users[i]
}
}
return nil
}