Compare commits
2 Commits
4749213bf0
...
df5e67c7f3
| Author | SHA1 | Date | |
|---|---|---|---|
| df5e67c7f3 | |||
| b2580d2380 |
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"goyco/internal/config"
|
"goyco/internal/config"
|
||||||
"goyco/internal/database"
|
"goyco/internal/database"
|
||||||
@@ -169,6 +170,10 @@ func seedDatabase(userRepo repositories.UserRepository, postRepo repositories.Po
|
|||||||
progress.Complete()
|
progress.Complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := validateSeedConsistency(postRepo, voteRepo, allUsers, posts); err != nil {
|
||||||
|
return fmt.Errorf("seed consistency validation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if IsJSONOutput() {
|
if IsJSONOutput() {
|
||||||
outputJSON(map[string]any{
|
outputJSON(map[string]any{
|
||||||
"action": "seed_completed",
|
"action": "seed_completed",
|
||||||
@@ -188,7 +193,32 @@ func seedDatabase(userRepo repositories.UserRepository, postRepo repositories.Po
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findExistingSeedUser(userRepo repositories.UserRepository) (*database.User, error) {
|
||||||
|
users, err := userRepo.GetAll(100, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range users {
|
||||||
|
if len(user.Username) >= 11 && user.Username[:11] == "seed_admin_" {
|
||||||
|
if len(user.Email) >= 13 && strings.HasSuffix(user.Email, "@goyco.local") {
|
||||||
|
emailPrefix := user.Email[:len(user.Email)-13]
|
||||||
|
if len(emailPrefix) >= 11 && emailPrefix[:11] == "seed_admin_" {
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no existing seed user found")
|
||||||
|
}
|
||||||
|
|
||||||
func ensureSeedUser(userRepo repositories.UserRepository) (*database.User, error) {
|
func ensureSeedUser(userRepo repositories.UserRepository) (*database.User, error) {
|
||||||
|
existingUser, err := findExistingSeedUser(userRepo)
|
||||||
|
if err == nil && existingUser != nil {
|
||||||
|
return existingUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
seedPassword := "seed-password"
|
seedPassword := "seed-password"
|
||||||
randomID := generateRandomIdentifier()
|
randomID := generateRandomIdentifier()
|
||||||
seedUsername := fmt.Sprintf("seed_admin_%s", randomID)
|
seedUsername := fmt.Sprintf("seed_admin_%s", randomID)
|
||||||
@@ -427,3 +457,35 @@ func getVoteCounts(voteRepo repositories.VoteRepository, postID uint) (int, int,
|
|||||||
|
|
||||||
return upVotes, downVotes, nil
|
return upVotes, downVotes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateSeedConsistency(postRepo repositories.PostRepository, voteRepo repositories.VoteRepository, users []database.User, posts []database.Post) error {
|
||||||
|
userIDs := make(map[uint]bool)
|
||||||
|
for _, user := range users {
|
||||||
|
userIDs[user.ID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, post := range posts {
|
||||||
|
if post.AuthorID == nil {
|
||||||
|
return fmt.Errorf("post %d has no author", post.ID)
|
||||||
|
}
|
||||||
|
if !userIDs[*post.AuthorID] {
|
||||||
|
return fmt.Errorf("post %d has invalid author ID %d", post.ID, *post.AuthorID)
|
||||||
|
}
|
||||||
|
|
||||||
|
votes, err := voteRepo.GetByPostID(post.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get votes for post %d: %w", post.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, vote := range votes {
|
||||||
|
if vote.UserID != nil && !userIDs[*vote.UserID] {
|
||||||
|
return fmt.Errorf("vote %d has invalid user ID %d", vote.ID, *vote.UserID)
|
||||||
|
}
|
||||||
|
if vote.PostID != post.ID {
|
||||||
|
return fmt.Errorf("vote %d has invalid post ID %d (expected %d)", vote.ID, vote.PostID, post.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -228,3 +229,124 @@ func TestSeedDatabaseFlagParsing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user