604 lines
13 KiB
Go
604 lines
13 KiB
Go
package testutils
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"goyco/internal/database"
|
|
"goyco/internal/repositories"
|
|
)
|
|
|
|
type TestDataFactory struct{}
|
|
|
|
type AuthResult struct {
|
|
User *database.User `json:"user"`
|
|
AccessToken string `json:"access_token"`
|
|
}
|
|
|
|
type RegistrationResult struct {
|
|
User *database.User `json:"user"`
|
|
}
|
|
|
|
type VoteRequest struct {
|
|
Type database.VoteType `json:"type"`
|
|
UserID uint `json:"user_id"`
|
|
PostID uint `json:"post_id"`
|
|
IPAddress string `json:"ip_address"`
|
|
UserAgent string `json:"user_agent"`
|
|
}
|
|
|
|
func NewTestDataFactory() *TestDataFactory {
|
|
return &TestDataFactory{}
|
|
}
|
|
|
|
type UserBuilder struct {
|
|
user *database.User
|
|
}
|
|
|
|
func (f *TestDataFactory) NewUserBuilder() *UserBuilder {
|
|
return &UserBuilder{
|
|
user: &database.User{
|
|
ID: 1,
|
|
Username: "testuser",
|
|
Email: "test@example.com",
|
|
EmailVerified: true,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *UserBuilder) WithID(id uint) *UserBuilder {
|
|
b.user.ID = id
|
|
return b
|
|
}
|
|
|
|
func (b *UserBuilder) WithUsername(username string) *UserBuilder {
|
|
b.user.Username = username
|
|
return b
|
|
}
|
|
|
|
func (b *UserBuilder) WithEmail(email string) *UserBuilder {
|
|
b.user.Email = email
|
|
return b
|
|
}
|
|
|
|
func (b *UserBuilder) WithPassword(password string) *UserBuilder {
|
|
hashed, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
b.user.Password = string(hashed)
|
|
return b
|
|
}
|
|
|
|
func (b *UserBuilder) WithEmailVerified(verified bool) *UserBuilder {
|
|
b.user.EmailVerified = verified
|
|
return b
|
|
}
|
|
|
|
func (b *UserBuilder) WithCreatedAt(t time.Time) *UserBuilder {
|
|
b.user.CreatedAt = t
|
|
return b
|
|
}
|
|
|
|
func (b *UserBuilder) Build() *database.User {
|
|
return b.user
|
|
}
|
|
|
|
type PostBuilder struct {
|
|
post *database.Post
|
|
}
|
|
|
|
func (f *TestDataFactory) NewPostBuilder() *PostBuilder {
|
|
return &PostBuilder{
|
|
post: &database.Post{
|
|
ID: 1,
|
|
Title: "Test Post",
|
|
URL: "https://example.com",
|
|
Content: "Test content",
|
|
AuthorID: uintPtr(1),
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *PostBuilder) WithID(id uint) *PostBuilder {
|
|
b.post.ID = id
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) WithTitle(title string) *PostBuilder {
|
|
b.post.Title = title
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) WithURL(url string) *PostBuilder {
|
|
b.post.URL = url
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) WithContent(content string) *PostBuilder {
|
|
b.post.Content = content
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) WithAuthorID(authorID uint) *PostBuilder {
|
|
b.post.AuthorID = &authorID
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) WithVotes(upVotes, downVotes int) *PostBuilder {
|
|
b.post.UpVotes = upVotes
|
|
b.post.DownVotes = downVotes
|
|
b.post.Score = upVotes - downVotes
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) WithCreatedAt(t time.Time) *PostBuilder {
|
|
b.post.CreatedAt = t
|
|
return b
|
|
}
|
|
|
|
func (b *PostBuilder) Build() *database.Post {
|
|
return b.post
|
|
}
|
|
|
|
type VoteBuilder struct {
|
|
vote *database.Vote
|
|
}
|
|
|
|
func (f *TestDataFactory) NewVoteBuilder() *VoteBuilder {
|
|
return &VoteBuilder{
|
|
vote: &database.Vote{
|
|
ID: 1,
|
|
Type: database.VoteUp,
|
|
UserID: uintPtr(1),
|
|
PostID: 1,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *VoteBuilder) WithID(id uint) *VoteBuilder {
|
|
b.vote.ID = id
|
|
return b
|
|
}
|
|
|
|
func (b *VoteBuilder) WithType(voteType database.VoteType) *VoteBuilder {
|
|
b.vote.Type = voteType
|
|
return b
|
|
}
|
|
|
|
func (b *VoteBuilder) WithUserID(userID uint) *VoteBuilder {
|
|
b.vote.UserID = &userID
|
|
return b
|
|
}
|
|
|
|
func (b *VoteBuilder) WithPostID(postID uint) *VoteBuilder {
|
|
b.vote.PostID = postID
|
|
return b
|
|
}
|
|
|
|
func (b *VoteBuilder) Build() *database.Vote {
|
|
return b.vote
|
|
}
|
|
|
|
type AuthResultBuilder struct {
|
|
result *AuthResult
|
|
}
|
|
|
|
func (f *TestDataFactory) NewAuthResultBuilder() *AuthResultBuilder {
|
|
return &AuthResultBuilder{
|
|
result: &AuthResult{
|
|
User: &database.User{ID: 1, Username: "testuser"},
|
|
AccessToken: "access_token",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *AuthResultBuilder) WithUser(user *database.User) *AuthResultBuilder {
|
|
b.result.User = user
|
|
return b
|
|
}
|
|
|
|
func (b *AuthResultBuilder) WithAccessToken(token string) *AuthResultBuilder {
|
|
b.result.AccessToken = token
|
|
return b
|
|
}
|
|
|
|
func (b *AuthResultBuilder) Build() *AuthResult {
|
|
return b.result
|
|
}
|
|
|
|
type RegistrationResultBuilder struct {
|
|
result *RegistrationResult
|
|
}
|
|
|
|
func (f *TestDataFactory) NewRegistrationResultBuilder() *RegistrationResultBuilder {
|
|
return &RegistrationResultBuilder{
|
|
result: &RegistrationResult{
|
|
User: &database.User{ID: 1, Username: "testuser"},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *RegistrationResultBuilder) WithUser(user *database.User) *RegistrationResultBuilder {
|
|
b.result.User = user
|
|
return b
|
|
}
|
|
|
|
func (b *RegistrationResultBuilder) WithMessage(message string) *RegistrationResultBuilder {
|
|
return b
|
|
}
|
|
|
|
func (b *RegistrationResultBuilder) Build() *RegistrationResult {
|
|
return b.result
|
|
}
|
|
|
|
type VoteRequestBuilder struct {
|
|
request VoteRequest
|
|
}
|
|
|
|
func (f *TestDataFactory) NewVoteRequestBuilder() *VoteRequestBuilder {
|
|
return &VoteRequestBuilder{
|
|
request: VoteRequest{
|
|
Type: database.VoteUp,
|
|
UserID: 1,
|
|
PostID: 1,
|
|
IPAddress: "127.0.0.1",
|
|
UserAgent: "test-agent",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (b *VoteRequestBuilder) WithType(voteType database.VoteType) *VoteRequestBuilder {
|
|
b.request.Type = voteType
|
|
return b
|
|
}
|
|
|
|
func (b *VoteRequestBuilder) WithUserID(userID uint) *VoteRequestBuilder {
|
|
b.request.UserID = userID
|
|
return b
|
|
}
|
|
|
|
func (b *VoteRequestBuilder) WithPostID(postID uint) *VoteRequestBuilder {
|
|
b.request.PostID = postID
|
|
return b
|
|
}
|
|
|
|
func (b *VoteRequestBuilder) WithIPAddress(ip string) *VoteRequestBuilder {
|
|
b.request.IPAddress = ip
|
|
return b
|
|
}
|
|
|
|
func (b *VoteRequestBuilder) WithUserAgent(agent string) *VoteRequestBuilder {
|
|
b.request.UserAgent = agent
|
|
return b
|
|
}
|
|
|
|
func (b *VoteRequestBuilder) Build() VoteRequest {
|
|
return b.request
|
|
}
|
|
|
|
func (f *TestDataFactory) CreateTestUsers(count int) []*database.User {
|
|
users := make([]*database.User, count)
|
|
for i := 0; i < count; i++ {
|
|
users[i] = f.NewUserBuilder().
|
|
WithID(uint(i + 1)).
|
|
WithUsername(fmt.Sprintf("user%d", i+1)).
|
|
WithEmail(fmt.Sprintf("user%d@example.com", i+1)).
|
|
Build()
|
|
}
|
|
return users
|
|
}
|
|
|
|
func (f *TestDataFactory) CreateTestPosts(count int) []*database.Post {
|
|
posts := make([]*database.Post, count)
|
|
for i := 0; i < count; i++ {
|
|
posts[i] = f.NewPostBuilder().
|
|
WithID(uint(i+1)).
|
|
WithTitle(fmt.Sprintf("Post %d", i+1)).
|
|
WithURL(fmt.Sprintf("https://example.com/post%d", i+1)).
|
|
WithContent(fmt.Sprintf("Content for post %d", i+1)).
|
|
WithAuthorID(uint((i%10)+1)).
|
|
WithVotes(i%10, i%5).
|
|
Build()
|
|
}
|
|
return posts
|
|
}
|
|
|
|
func (f *TestDataFactory) CreateTestVotes(count int) []*database.Vote {
|
|
votes := make([]*database.Vote, count)
|
|
for i := range count {
|
|
voteType := database.VoteUp
|
|
if i%3 == 0 {
|
|
voteType = database.VoteDown
|
|
} else if i%5 == 0 {
|
|
voteType = database.VoteNone
|
|
}
|
|
|
|
votes[i] = f.NewVoteBuilder().
|
|
WithID(uint(i + 1)).
|
|
WithType(voteType).
|
|
WithUserID(uint((i % 20) + 1)).
|
|
WithPostID(uint((i % 100) + 1)).
|
|
Build()
|
|
}
|
|
return votes
|
|
}
|
|
|
|
func (f *TestDataFactory) CreateTestAuthResults(count int) []*AuthResult {
|
|
results := make([]*AuthResult, count)
|
|
for i := 0; i < count; i++ {
|
|
results[i] = f.NewAuthResultBuilder().
|
|
WithUser(f.NewUserBuilder().
|
|
WithID(uint(i + 1)).
|
|
WithUsername(fmt.Sprintf("user%d", i+1)).
|
|
Build()).
|
|
WithAccessToken(fmt.Sprintf("token_%d", i+1)).
|
|
Build()
|
|
}
|
|
return results
|
|
}
|
|
|
|
func (f *TestDataFactory) CreateTestVoteRequests(count int) []VoteRequest {
|
|
requests := make([]VoteRequest, count)
|
|
for i := 0; i < count; i++ {
|
|
voteType := database.VoteUp
|
|
if i%3 == 0 {
|
|
voteType = database.VoteDown
|
|
} else if i%5 == 0 {
|
|
voteType = database.VoteNone
|
|
}
|
|
|
|
requests[i] = f.NewVoteRequestBuilder().
|
|
WithType(voteType).
|
|
WithUserID(uint((i % 20) + 1)).
|
|
WithPostID(uint((i % 100) + 1)).
|
|
WithIPAddress(fmt.Sprintf("192.168.1.%d", (i%254)+1)).
|
|
WithUserAgent(fmt.Sprintf("test-agent-%d", i+1)).
|
|
Build()
|
|
}
|
|
return requests
|
|
}
|
|
|
|
func uintPtr(u uint) *uint {
|
|
return &u
|
|
}
|
|
|
|
type E2ETestDataFactory struct {
|
|
UserRepo repositories.UserRepository
|
|
PostRepo repositories.PostRepository
|
|
}
|
|
|
|
func NewE2ETestDataFactory(userRepo repositories.UserRepository, postRepo repositories.PostRepository) *E2ETestDataFactory {
|
|
return &E2ETestDataFactory{
|
|
UserRepo: userRepo,
|
|
PostRepo: postRepo,
|
|
}
|
|
}
|
|
|
|
type PostData struct {
|
|
Title string
|
|
URL string
|
|
Content string
|
|
}
|
|
|
|
func (f *E2ETestDataFactory) CreateUserWithPosts(t *testing.T, username, email, password string, posts []PostData) (*TestUser, []*TestPost) {
|
|
t.Helper()
|
|
|
|
user := CreateE2ETestUser(t, f.UserRepo, username, email, password)
|
|
|
|
var createdPosts []*TestPost
|
|
for i, postData := range posts {
|
|
url := postData.URL
|
|
if url == "" {
|
|
url = fmt.Sprintf("https://example.com/post-%d-%d", user.ID, i+1)
|
|
}
|
|
|
|
title := postData.Title
|
|
if title == "" {
|
|
title = fmt.Sprintf("Test Post %d", i+1)
|
|
}
|
|
|
|
content := postData.Content
|
|
if content == "" {
|
|
content = fmt.Sprintf("Test content for post %d", i+1)
|
|
}
|
|
|
|
post := &database.Post{
|
|
Title: title,
|
|
URL: url,
|
|
Content: content,
|
|
AuthorID: &user.ID,
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
|
|
if err := f.PostRepo.Create(post); err != nil {
|
|
t.Fatalf("Failed to create test post: %v", err)
|
|
}
|
|
|
|
createdPost, err := f.PostRepo.GetByID(post.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to fetch created post: %v", err)
|
|
}
|
|
|
|
createdPosts = append(createdPosts, &TestPost{
|
|
ID: createdPost.ID,
|
|
Title: createdPost.Title,
|
|
URL: createdPost.URL,
|
|
Content: createdPost.Content,
|
|
AuthorID: *createdPost.AuthorID,
|
|
})
|
|
}
|
|
|
|
return user, createdPosts
|
|
}
|
|
|
|
func (f *E2ETestDataFactory) CreateMultipleUsers(t *testing.T, count int, usernamePrefix, emailPrefix, password string) []*TestUser {
|
|
t.Helper()
|
|
|
|
if count <= 0 {
|
|
t.Fatalf("count must be greater than 0, got %d", count)
|
|
}
|
|
|
|
var users []*TestUser
|
|
timestamp := time.Now().UnixNano()
|
|
|
|
for i := 0; i < count; i++ {
|
|
uniqueID := timestamp + int64(i)
|
|
username := fmt.Sprintf("%s%d", usernamePrefix, uniqueID)
|
|
email := fmt.Sprintf("%s%d@example.com", emailPrefix, uniqueID)
|
|
|
|
userPassword := password
|
|
if userPassword == "" {
|
|
userPassword = "StrongPass123!"
|
|
}
|
|
|
|
user := CreateE2ETestUser(t, f.UserRepo, username, email, userPassword)
|
|
users = append(users, user)
|
|
}
|
|
|
|
return users
|
|
}
|
|
|
|
func (f *E2ETestDataFactory) CreateUserWithDefaultPosts(t *testing.T, username, email, password string, count int) (*TestUser, []*TestPost) {
|
|
t.Helper()
|
|
|
|
if count <= 0 {
|
|
count = 3
|
|
}
|
|
|
|
posts := make([]PostData, count)
|
|
for i := 0; i < count; i++ {
|
|
posts[i] = PostData{
|
|
Title: fmt.Sprintf("Default Post %d", i+1),
|
|
URL: fmt.Sprintf("https://example.com/default-post-%d", i+1),
|
|
Content: fmt.Sprintf("This is default post content number %d", i+1),
|
|
}
|
|
}
|
|
|
|
return f.CreateUserWithPosts(t, username, email, password, posts)
|
|
}
|
|
|
|
func (f *E2ETestDataFactory) CreateUsersWithPosts(t *testing.T, count int, usernamePrefix, emailPrefix, password string, postsPerUser []PostData) map[uint]*UserWithPosts {
|
|
t.Helper()
|
|
|
|
if count <= 0 {
|
|
t.Fatalf("count must be greater than 0, got %d", count)
|
|
}
|
|
|
|
users := f.CreateMultipleUsers(t, count, usernamePrefix, emailPrefix, password)
|
|
|
|
result := make(map[uint]*UserWithPosts)
|
|
for _, user := range users {
|
|
var createdPosts []*TestPost
|
|
for i, postData := range postsPerUser {
|
|
url := postData.URL
|
|
if url == "" {
|
|
url = fmt.Sprintf("https://example.com/post-%d-%d", user.ID, i+1)
|
|
}
|
|
|
|
title := postData.Title
|
|
if title == "" {
|
|
title = fmt.Sprintf("Test Post %d for User %d", i+1, user.ID)
|
|
}
|
|
|
|
content := postData.Content
|
|
if content == "" {
|
|
content = fmt.Sprintf("Test content for post %d", i+1)
|
|
}
|
|
|
|
post := &database.Post{
|
|
Title: title,
|
|
URL: url,
|
|
Content: content,
|
|
AuthorID: &user.ID,
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
|
|
if err := f.PostRepo.Create(post); err != nil {
|
|
t.Fatalf("Failed to create test post: %v", err)
|
|
}
|
|
|
|
createdPost, err := f.PostRepo.GetByID(post.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to fetch created post: %v", err)
|
|
}
|
|
|
|
createdPosts = append(createdPosts, &TestPost{
|
|
ID: createdPost.ID,
|
|
Title: createdPost.Title,
|
|
URL: createdPost.URL,
|
|
Content: createdPost.Content,
|
|
AuthorID: *createdPost.AuthorID,
|
|
})
|
|
}
|
|
|
|
result[user.ID] = &UserWithPosts{
|
|
User: user,
|
|
Posts: createdPosts,
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
type UserWithPosts struct {
|
|
User *TestUser
|
|
Posts []*TestPost
|
|
}
|
|
|
|
func (f *E2ETestDataFactory) CreatePostForUser(t *testing.T, userID uint, postData PostData) *TestPost {
|
|
t.Helper()
|
|
|
|
url := postData.URL
|
|
if url == "" {
|
|
url = fmt.Sprintf("https://example.com/post-%d-%d", userID, time.Now().UnixNano())
|
|
}
|
|
|
|
title := postData.Title
|
|
if title == "" {
|
|
title = "Test Post"
|
|
}
|
|
|
|
content := postData.Content
|
|
if content == "" {
|
|
content = "Test post content"
|
|
}
|
|
|
|
post := &database.Post{
|
|
Title: title,
|
|
URL: url,
|
|
Content: content,
|
|
AuthorID: &userID,
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
|
|
if err := f.PostRepo.Create(post); err != nil {
|
|
t.Fatalf("Failed to create test post: %v", err)
|
|
}
|
|
|
|
createdPost, err := f.PostRepo.GetByID(post.ID)
|
|
if err != nil {
|
|
t.Fatalf("Failed to fetch created post: %v", err)
|
|
}
|
|
|
|
return &TestPost{
|
|
ID: createdPost.ID,
|
|
Title: createdPost.Title,
|
|
URL: createdPost.URL,
|
|
Content: createdPost.Content,
|
|
AuthorID: *createdPost.AuthorID,
|
|
}
|
|
}
|