453 lines
10 KiB
Go
453 lines
10 KiB
Go
package testutils
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
"goyco/internal/database"
|
|
)
|
|
|
|
type TestConfig struct {
|
|
Database DatabaseConfig `json:"database"`
|
|
Server ServerConfig `json:"server"`
|
|
JWT JWTConfig `json:"jwt"`
|
|
Email EmailConfig `json:"email"`
|
|
RateLimit RateLimitConfig `json:"rate_limit"`
|
|
Cache CacheConfig `json:"cache"`
|
|
Security SecurityConfig `json:"security"`
|
|
Logging LoggingConfig `json:"logging"`
|
|
}
|
|
|
|
type DatabaseConfig struct {
|
|
Driver string `json:"driver"`
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
User string `json:"user"`
|
|
Password string `json:"password"`
|
|
DBName string `json:"dbname"`
|
|
SSLMode string `json:"sslmode"`
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Host string `json:"host"`
|
|
Port int `json:"port"`
|
|
}
|
|
|
|
type JWTConfig struct {
|
|
Secret string `json:"secret"`
|
|
Expiration int `json:"expiration"`
|
|
}
|
|
|
|
type EmailConfig struct {
|
|
SMTPHost string `json:"smtp_host"`
|
|
SMTPPort int `json:"smtp_port"`
|
|
SMTPUsername string `json:"smtp_username"`
|
|
SMTPPassword string `json:"smtp_password"`
|
|
FromEmail string `json:"from_email"`
|
|
FromName string `json:"from_name"`
|
|
}
|
|
|
|
type RateLimitConfig struct {
|
|
RequestsPerMinute int `json:"requests_per_minute"`
|
|
BurstSize int `json:"burst_size"`
|
|
}
|
|
|
|
type CacheConfig struct {
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
type SecurityConfig struct {
|
|
EnableCSRF bool `json:"enable_csrf"`
|
|
CSRFSecret string `json:"csrf_secret"`
|
|
EnableCORS bool `json:"enable_cors"`
|
|
AllowedOrigins []string `json:"allowed_origins"`
|
|
EnableRateLimit bool `json:"enable_rate_limit"`
|
|
EnableCompression bool `json:"enable_compression"`
|
|
}
|
|
|
|
type LoggingConfig struct {
|
|
Level string `json:"level"`
|
|
Format string `json:"format"`
|
|
Output string `json:"output"`
|
|
}
|
|
|
|
type TestFixtures struct {
|
|
Users []*database.User
|
|
Posts []*database.Post
|
|
Votes []*database.Vote
|
|
Config *TestConfig
|
|
}
|
|
|
|
func NewTestFixtures(t *testing.T) *TestFixtures {
|
|
t.Helper()
|
|
|
|
return &TestFixtures{
|
|
Users: []*database.User{
|
|
{
|
|
Username: "testuser1",
|
|
Email: "user1@test.local",
|
|
Password: "SecurePass123!",
|
|
EmailVerified: true,
|
|
},
|
|
{
|
|
Username: "testuser2",
|
|
Email: "user2@test.local",
|
|
Password: "SecurePass456!",
|
|
EmailVerified: true,
|
|
},
|
|
{
|
|
Username: "unverified_user",
|
|
Email: "unverified@test.local",
|
|
Password: "SecurePass789!",
|
|
EmailVerified: false,
|
|
},
|
|
},
|
|
Posts: []*database.Post{
|
|
{
|
|
Title: "Test Post 1",
|
|
URL: "https://example.com/post1",
|
|
Content: "This is test content for post 1",
|
|
UpVotes: 5,
|
|
DownVotes: 1,
|
|
Score: 4,
|
|
},
|
|
{
|
|
Title: "Test Post 2",
|
|
URL: "https://example.com/post2",
|
|
Content: "This is test content for post 2",
|
|
UpVotes: 3,
|
|
DownVotes: 0,
|
|
Score: 3,
|
|
},
|
|
},
|
|
Votes: []*database.Vote{
|
|
{
|
|
Type: database.VoteUp,
|
|
},
|
|
{
|
|
Type: database.VoteDown,
|
|
},
|
|
{
|
|
Type: database.VoteNone,
|
|
},
|
|
},
|
|
Config: &TestConfig{
|
|
Database: DatabaseConfig{
|
|
Driver: "sqlite",
|
|
Host: ":memory:",
|
|
Port: 0,
|
|
User: "",
|
|
Password: "",
|
|
DBName: "test",
|
|
SSLMode: "disable",
|
|
},
|
|
Server: ServerConfig{
|
|
Host: "localhost",
|
|
Port: 8080,
|
|
},
|
|
JWT: JWTConfig{
|
|
Secret: "test-secret-key",
|
|
Expiration: 24,
|
|
},
|
|
Email: EmailConfig{
|
|
SMTPHost: "localhost",
|
|
SMTPPort: 587,
|
|
SMTPUsername: "test@example.com",
|
|
SMTPPassword: "test-password",
|
|
FromEmail: "test@example.com",
|
|
FromName: "Test App",
|
|
},
|
|
RateLimit: RateLimitConfig{
|
|
RequestsPerMinute: 60,
|
|
BurstSize: 10,
|
|
},
|
|
Cache: CacheConfig{
|
|
Type: "memory",
|
|
},
|
|
Security: SecurityConfig{
|
|
EnableCSRF: true,
|
|
CSRFSecret: "test-csrf-secret",
|
|
EnableCORS: true,
|
|
AllowedOrigins: []string{"http://localhost:3000"},
|
|
EnableRateLimit: true,
|
|
EnableCompression: true,
|
|
},
|
|
Logging: LoggingConfig{
|
|
Level: "debug",
|
|
Format: "json",
|
|
Output: "stdout",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func CreateSecureTestUser(t *testing.T, db *gorm.DB, username, email string) *database.User {
|
|
t.Helper()
|
|
|
|
if username == "" {
|
|
username = generateSecureRandomString(8)
|
|
}
|
|
if email == "" {
|
|
email = fmt.Sprintf("%s@test.local", username)
|
|
}
|
|
|
|
password := generateSecurePassword()
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
t.Fatalf("Failed to hash password: %v", err)
|
|
}
|
|
|
|
user := &database.User{
|
|
Username: username,
|
|
Email: email,
|
|
Password: string(hashedPassword),
|
|
EmailVerified: true,
|
|
}
|
|
|
|
if err := db.Create(user).Error; err != nil {
|
|
t.Fatalf("Failed to create test user: %v", err)
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
func CreateSecureTestPost(t *testing.T, db *gorm.DB, authorID uint) *database.Post {
|
|
t.Helper()
|
|
|
|
title := generateSecureRandomString(12)
|
|
url := fmt.Sprintf("https://example.com/%s", generateSecureRandomString(8))
|
|
content := fmt.Sprintf("Test content for %s", title)
|
|
|
|
post := &database.Post{
|
|
Title: title,
|
|
URL: url,
|
|
Content: content,
|
|
AuthorID: &authorID,
|
|
UpVotes: 0,
|
|
DownVotes: 0,
|
|
Score: 0,
|
|
}
|
|
|
|
if err := db.Create(post).Error; err != nil {
|
|
t.Fatalf("Failed to create test post: %v", err)
|
|
}
|
|
|
|
return post
|
|
}
|
|
|
|
func CreateSecureTestVote(t *testing.T, db *gorm.DB, userID, postID uint, voteType database.VoteType) *database.Vote {
|
|
t.Helper()
|
|
|
|
vote := &database.Vote{
|
|
UserID: &userID,
|
|
PostID: postID,
|
|
Type: voteType,
|
|
}
|
|
|
|
if err := db.Create(vote).Error; err != nil {
|
|
t.Fatalf("Failed to create test vote: %v", err)
|
|
}
|
|
|
|
return vote
|
|
}
|
|
|
|
func (f *TestFixtures) CreateTestUsers(t *testing.T, db *gorm.DB) []*database.User {
|
|
t.Helper()
|
|
|
|
var users []*database.User
|
|
for _, userData := range f.Users {
|
|
user := *userData
|
|
|
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
t.Fatalf("Failed to hash password: %v", err)
|
|
}
|
|
user.Password = string(hashedPassword)
|
|
|
|
if err := db.Create(&user).Error; err != nil {
|
|
t.Fatalf("Failed to create test user: %v", err)
|
|
}
|
|
users = append(users, &user)
|
|
}
|
|
|
|
return users
|
|
}
|
|
|
|
func (f *TestFixtures) CreateTestPosts(t *testing.T, db *gorm.DB, authorID uint) []*database.Post {
|
|
t.Helper()
|
|
|
|
var posts []*database.Post
|
|
for _, postData := range f.Posts {
|
|
post := *postData
|
|
post.AuthorID = &authorID
|
|
|
|
if err := db.Create(&post).Error; err != nil {
|
|
t.Fatalf("Failed to create test post: %v", err)
|
|
}
|
|
posts = append(posts, &post)
|
|
}
|
|
|
|
return posts
|
|
}
|
|
|
|
func (f *TestFixtures) CreateTestVotes(t *testing.T, db *gorm.DB, userID, postID uint) []*database.Vote {
|
|
t.Helper()
|
|
|
|
var votes []*database.Vote
|
|
for _, voteData := range f.Votes {
|
|
vote := *voteData
|
|
vote.UserID = &userID
|
|
vote.PostID = postID
|
|
|
|
if err := db.Create(&vote).Error; err != nil {
|
|
t.Fatalf("Failed to create test vote: %v", err)
|
|
}
|
|
votes = append(votes, &vote)
|
|
}
|
|
|
|
return votes
|
|
}
|
|
|
|
func generateSecureRandomString(length int) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
result := make([]byte, length)
|
|
|
|
for i := range result {
|
|
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
result[i] = charset[num.Int64()]
|
|
}
|
|
|
|
return string(result)
|
|
}
|
|
|
|
func generateSecurePassword() string {
|
|
letters := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
numbers := "0123456789"
|
|
special := "!@#$%^&*"
|
|
|
|
password := ""
|
|
|
|
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
|
password += string(letters[num.Int64()])
|
|
|
|
num, _ = rand.Int(rand.Reader, big.NewInt(int64(len(numbers))))
|
|
password += string(numbers[num.Int64()])
|
|
|
|
num, _ = rand.Int(rand.Reader, big.NewInt(int64(len(special))))
|
|
password += string(special[num.Int64()])
|
|
|
|
for len(password) < 12 {
|
|
charset := letters + numbers + special
|
|
num, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
|
|
password += string(charset[num.Int64()])
|
|
}
|
|
|
|
return password
|
|
}
|
|
|
|
func CleanupTestData(t *testing.T, db *gorm.DB) {
|
|
t.Helper()
|
|
|
|
if err := db.Exec("DELETE FROM votes").Error; err != nil {
|
|
t.Logf("Warning: Failed to clean up votes: %v", err)
|
|
}
|
|
if err := db.Exec("DELETE FROM posts").Error; err != nil {
|
|
t.Logf("Warning: Failed to clean up posts: %v", err)
|
|
}
|
|
if err := db.Exec("DELETE FROM account_deletion_requests").Error; err != nil {
|
|
t.Logf("Warning: Failed to clean up account deletion requests: %v", err)
|
|
}
|
|
if err := db.Exec("DELETE FROM users").Error; err != nil {
|
|
t.Logf("Warning: Failed to clean up users: %v", err)
|
|
}
|
|
}
|
|
|
|
func AssertUserExists(t *testing.T, db *gorm.DB, userID uint) {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.User{}).Where("id = ?", userID).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to check user existence: %v", err)
|
|
}
|
|
|
|
if count == 0 {
|
|
t.Errorf("Expected user with ID %d to exist", userID)
|
|
}
|
|
}
|
|
|
|
func AssertUserNotExists(t *testing.T, db *gorm.DB, userID uint) {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.User{}).Where("id = ?", userID).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to check user existence: %v", err)
|
|
}
|
|
|
|
if count > 0 {
|
|
t.Errorf("Expected user with ID %d to not exist", userID)
|
|
}
|
|
}
|
|
|
|
func AssertPostExists(t *testing.T, db *gorm.DB, postID uint) {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.Post{}).Where("id = ?", postID).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to check post existence: %v", err)
|
|
}
|
|
|
|
if count == 0 {
|
|
t.Errorf("Expected post with ID %d to exist", postID)
|
|
}
|
|
}
|
|
|
|
func AssertVoteExists(t *testing.T, db *gorm.DB, userID, postID uint) {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.Vote{}).Where("user_id = ? AND post_id = ?", userID, postID).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to check vote existence: %v", err)
|
|
}
|
|
|
|
if count == 0 {
|
|
t.Errorf("Expected vote for user %d and post %d to exist", userID, postID)
|
|
}
|
|
}
|
|
|
|
func GetUserCount(t *testing.T, db *gorm.DB) int64 {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.User{}).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to get user count: %v", err)
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func GetPostCount(t *testing.T, db *gorm.DB) int64 {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.Post{}).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to get post count: %v", err)
|
|
}
|
|
|
|
return count
|
|
}
|
|
|
|
func GetVoteCount(t *testing.T, db *gorm.DB) int64 {
|
|
t.Helper()
|
|
|
|
var count int64
|
|
if err := db.Model(&database.Vote{}).Count(&count).Error; err != nil {
|
|
t.Fatalf("Failed to get vote count: %v", err)
|
|
}
|
|
|
|
return count
|
|
}
|