a547d850a2
after connection to local MySQL database is working for go tests
917 lines
27 KiB
Go
917 lines
27 KiB
Go
package user
|
|
|
|
import (
|
|
"bamort/database"
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/rand"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// setupTestUserWithReset creates a test user for password reset functionality tests
|
|
func setupTestUserWithReset(t *testing.T) User {
|
|
database.SetupTestDB()
|
|
|
|
// Migrate User structure to ensure reset password fields exist
|
|
err := MigrateStructure()
|
|
require.NoError(t, err, "Failed to migrate user structure")
|
|
|
|
// Generate unique email for each test to avoid conflicts
|
|
randomSuffix := rand.Intn(100000)
|
|
user := User{
|
|
Username: fmt.Sprintf("testuser_reset_%d", randomSuffix),
|
|
PasswordHash: "testpassword123",
|
|
Email: fmt.Sprintf("test.reset.%d@example.com", randomSuffix),
|
|
}
|
|
|
|
// Hash password like in RegisterUser
|
|
hashedPassword := md5.Sum([]byte(user.PasswordHash))
|
|
user.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
|
|
|
err = user.Create()
|
|
require.NoError(t, err, "Failed to create test user")
|
|
|
|
return user
|
|
}
|
|
|
|
// setupUserModelTestEnvironment sets up the test environment for user model tests
|
|
func setupUserModelTestEnvironment(t *testing.T) *gorm.DB {
|
|
// Save original state
|
|
originalDB := database.DB
|
|
|
|
// Create a test database
|
|
tempDir := t.TempDir()
|
|
dbPath := filepath.Join(tempDir, "user_model_test.db")
|
|
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
|
require.NoError(t, err, "Should be able to create test database")
|
|
|
|
// Migrate the User table
|
|
err = testDB.AutoMigrate(&User{})
|
|
require.NoError(t, err, "Should be able to migrate User table")
|
|
|
|
// Set global DB to test DB
|
|
database.DB = testDB
|
|
|
|
// Cleanup function
|
|
t.Cleanup(func() {
|
|
// Restore original state
|
|
database.DB = originalDB
|
|
})
|
|
|
|
return testDB
|
|
}
|
|
|
|
// =============================================================================
|
|
// User Model Method Tests - Core CRUD Operations
|
|
// =============================================================================
|
|
|
|
func TestUser_Create(t *testing.T) {
|
|
testDB := setupUserModelTestEnvironment(t)
|
|
|
|
user := &User{
|
|
Username: "testuser",
|
|
PasswordHash: "hashedpassword123",
|
|
Email: "test@example.com",
|
|
}
|
|
|
|
// Test successful creation
|
|
err := user.Create()
|
|
assert.NoError(t, err, "Create should succeed with valid user data")
|
|
assert.NotZero(t, user.UserID, "UserID should be set after creation")
|
|
assert.NotZero(t, user.CreatedAt, "CreatedAt should be set after creation")
|
|
assert.NotZero(t, user.UpdatedAt, "UpdatedAt should be set after creation")
|
|
|
|
// Verify user was saved to database
|
|
var retrievedUser User
|
|
err = testDB.First(&retrievedUser, user.UserID).Error
|
|
assert.NoError(t, err, "Should be able to retrieve created user")
|
|
assert.Equal(t, user.Username, retrievedUser.Username, "Username should match")
|
|
assert.Equal(t, user.Email, retrievedUser.Email, "Email should match")
|
|
assert.Equal(t, user.PasswordHash, retrievedUser.PasswordHash, "PasswordHash should match")
|
|
}
|
|
|
|
func TestUser_Create_DuplicateConstraints(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create first user
|
|
user1 := &User{
|
|
Username: "uniqueuser",
|
|
PasswordHash: "hash1",
|
|
Email: "unique@example.com",
|
|
}
|
|
err := user1.Create()
|
|
require.NoError(t, err, "First user creation should succeed")
|
|
|
|
// Try to create second user with same username
|
|
user2 := &User{
|
|
Username: "uniqueuser", // Same username
|
|
PasswordHash: "hash2",
|
|
Email: "different@example.com",
|
|
}
|
|
err = user2.Create()
|
|
assert.Error(t, err, "Should fail to create user with duplicate username")
|
|
|
|
// Try to create third user with same email
|
|
user3 := &User{
|
|
Username: "differentuser",
|
|
PasswordHash: "hash3",
|
|
Email: "unique@example.com", // Same email
|
|
}
|
|
err = user3.Create()
|
|
assert.Error(t, err, "Should fail to create user with duplicate email")
|
|
}
|
|
|
|
func TestUser_Create_WithNilDatabase(t *testing.T) {
|
|
// Save original state
|
|
originalDB := database.DB
|
|
defer func() {
|
|
database.DB = originalDB
|
|
}()
|
|
|
|
// Set database to nil
|
|
database.DB = nil
|
|
|
|
user := &User{
|
|
Username: "nildbuser",
|
|
PasswordHash: "hash",
|
|
Email: "nildb@example.com",
|
|
}
|
|
|
|
// Should error when database is nil
|
|
err := user.Create()
|
|
assert.Error(t, err, "Should error when database is nil")
|
|
}
|
|
|
|
func TestUser_First(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create a test user
|
|
originalUser := &User{
|
|
Username: "findmeuser",
|
|
PasswordHash: "findhash",
|
|
Email: "findme@example.com",
|
|
}
|
|
err := originalUser.Create()
|
|
require.NoError(t, err, "Should be able to create test user")
|
|
|
|
// Test finding existing user
|
|
var foundUser User
|
|
err = foundUser.First("findmeuser")
|
|
assert.NoError(t, err, "Should find existing user")
|
|
assert.Equal(t, originalUser.UserID, foundUser.UserID, "Found user should have same ID")
|
|
assert.Equal(t, originalUser.Username, foundUser.Username, "Found user should have same username")
|
|
assert.Equal(t, originalUser.Email, foundUser.Email, "Found user should have same email")
|
|
|
|
// Test finding non-existent user
|
|
var notFoundUser User
|
|
err = notFoundUser.First("nonexistentuser")
|
|
assert.Error(t, err, "Should return error for non-existent user")
|
|
assert.Equal(t, gorm.ErrRecordNotFound, err, "Should return RecordNotFound error")
|
|
|
|
// Test with empty username
|
|
var emptyUser User
|
|
err = emptyUser.First("")
|
|
assert.Error(t, err, "Should return error for empty username")
|
|
}
|
|
|
|
func TestUser_First_WithNilDatabase(t *testing.T) {
|
|
// Save original state
|
|
originalDB := database.DB
|
|
defer func() {
|
|
database.DB = originalDB
|
|
}()
|
|
|
|
// Set database to nil
|
|
database.DB = nil
|
|
|
|
var user User
|
|
err := user.First("anyuser")
|
|
assert.Error(t, err, "Should error when database is nil")
|
|
}
|
|
|
|
func TestUser_FirstId(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create a test user
|
|
originalUser := &User{
|
|
Username: "findbyiduser",
|
|
PasswordHash: "idhash",
|
|
Email: "findbyid@example.com",
|
|
}
|
|
err := originalUser.Create()
|
|
require.NoError(t, err, "Should be able to create test user")
|
|
|
|
// Test finding existing user by ID
|
|
var foundUser User
|
|
err = foundUser.FirstId(originalUser.UserID)
|
|
assert.NoError(t, err, "Should find existing user by ID")
|
|
assert.Equal(t, originalUser.UserID, foundUser.UserID, "Found user should have same ID")
|
|
assert.Equal(t, originalUser.Username, foundUser.Username, "Found user should have same username")
|
|
assert.Equal(t, originalUser.Email, foundUser.Email, "Found user should have same email")
|
|
assert.Equal(t, originalUser.PasswordHash, foundUser.PasswordHash, "Found user should have same password hash")
|
|
|
|
// Test finding non-existent user by ID
|
|
var notFoundUser User
|
|
err = notFoundUser.FirstId(99999) // Non-existent ID
|
|
assert.Error(t, err, "Should return error for non-existent user ID")
|
|
assert.Equal(t, gorm.ErrRecordNotFound, err, "Should return RecordNotFound error")
|
|
|
|
// Test with ID 0 (should be invalid)
|
|
var zeroIdUser User
|
|
err = zeroIdUser.FirstId(0)
|
|
assert.Error(t, err, "Should return error for ID 0")
|
|
|
|
// Test with very large ID
|
|
var largeIdUser User
|
|
err = largeIdUser.FirstId(4294967295) // Max uint32
|
|
assert.Error(t, err, "Should return error for very large non-existent ID")
|
|
}
|
|
|
|
func TestUser_FirstId_WithNilDatabase(t *testing.T) {
|
|
// Save original state
|
|
originalDB := database.DB
|
|
defer func() {
|
|
database.DB = originalDB
|
|
}()
|
|
|
|
// Set database to nil
|
|
database.DB = nil
|
|
|
|
var user User
|
|
err := user.FirstId(1)
|
|
assert.Error(t, err, "Should error when database is nil")
|
|
}
|
|
|
|
func TestUser_Save(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create a test user
|
|
user := &User{
|
|
Username: "saveuser",
|
|
PasswordHash: "savehash",
|
|
Email: "save@example.com",
|
|
}
|
|
err := user.Create()
|
|
require.NoError(t, err, "Should be able to create test user")
|
|
|
|
originalID := user.UserID
|
|
originalCreatedAt := user.CreatedAt
|
|
|
|
// Modify the user
|
|
user.Username = "modifieduser"
|
|
user.Email = "modified@example.com"
|
|
user.PasswordHash = "modifiedhash"
|
|
|
|
// Test saving changes
|
|
err = user.Save()
|
|
assert.NoError(t, err, "Save should succeed")
|
|
|
|
// Verify changes were saved to database
|
|
var retrievedUser User
|
|
err = retrievedUser.FirstId(user.UserID)
|
|
assert.NoError(t, err, "Should be able to retrieve saved user")
|
|
assert.Equal(t, "modifieduser", retrievedUser.Username, "Username should be updated")
|
|
assert.Equal(t, "modified@example.com", retrievedUser.Email, "Email should be updated")
|
|
assert.Equal(t, "modifiedhash", retrievedUser.PasswordHash, "PasswordHash should be updated")
|
|
|
|
// Verify ID and CreatedAt remain unchanged
|
|
assert.Equal(t, originalID, retrievedUser.UserID, "UserID should remain unchanged")
|
|
assert.Equal(t, originalCreatedAt.Unix(), retrievedUser.CreatedAt.Unix(), "CreatedAt should remain unchanged")
|
|
|
|
// Verify UpdatedAt was changed
|
|
assert.True(t, retrievedUser.UpdatedAt.After(retrievedUser.CreatedAt), "UpdatedAt should be after CreatedAt")
|
|
}
|
|
|
|
func TestUser_Save_UniqueConstraintViolation(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create two test users
|
|
user1 := &User{
|
|
Username: "user1",
|
|
PasswordHash: "hash1",
|
|
Email: "user1@example.com",
|
|
}
|
|
err := user1.Create()
|
|
require.NoError(t, err, "Should be able to create first user")
|
|
|
|
user2 := &User{
|
|
Username: "user2",
|
|
PasswordHash: "hash2",
|
|
Email: "user2@example.com",
|
|
}
|
|
err = user2.Create()
|
|
require.NoError(t, err, "Should be able to create second user")
|
|
|
|
// Try to update user2 to have same username as user1
|
|
user2.Username = "user1"
|
|
err = user2.Save()
|
|
assert.Error(t, err, "Save should fail due to unique constraint violation on username")
|
|
|
|
// Try to update user2 to have same email as user1
|
|
user2.Username = "user2" // Reset username
|
|
user2.Email = "user1@example.com"
|
|
err = user2.Save()
|
|
assert.Error(t, err, "Save should fail due to unique constraint violation on email")
|
|
}
|
|
|
|
func TestUser_Save_WithNilDatabase(t *testing.T) {
|
|
// Save original state
|
|
originalDB := database.DB
|
|
defer func() {
|
|
database.DB = originalDB
|
|
}()
|
|
|
|
// Set database to nil
|
|
database.DB = nil
|
|
|
|
user := &User{
|
|
UserID: 1,
|
|
Username: "nildbuser",
|
|
PasswordHash: "hash",
|
|
Email: "nildb@example.com",
|
|
}
|
|
|
|
// Should error when database is nil
|
|
err := user.Save()
|
|
assert.Error(t, err, "Should error when database is nil")
|
|
}
|
|
|
|
func TestUser_Save_NewRecord(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create user without using Create() (no ID set)
|
|
user := &User{
|
|
Username: "newrecorduser",
|
|
PasswordHash: "newrecordhash",
|
|
Email: "newrecord@example.com",
|
|
}
|
|
|
|
// Save should work like Create for new records
|
|
err := user.Save()
|
|
assert.NoError(t, err, "Save should succeed for new record")
|
|
assert.NotZero(t, user.UserID, "UserID should be set after save")
|
|
assert.NotZero(t, user.CreatedAt, "CreatedAt should be set after save")
|
|
assert.NotZero(t, user.UpdatedAt, "UpdatedAt should be set after save")
|
|
|
|
// Verify record was saved to database
|
|
var retrievedUser User
|
|
err = retrievedUser.FirstId(user.UserID)
|
|
assert.NoError(t, err, "Should be able to retrieve saved user")
|
|
assert.Equal(t, user.Username, retrievedUser.Username, "Username should match")
|
|
assert.Equal(t, user.Email, retrievedUser.Email, "Email should match")
|
|
}
|
|
|
|
func TestUser_Save_PartialUpdate(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create a test user
|
|
user := &User{
|
|
Username: "partialuser",
|
|
PasswordHash: "partialhash",
|
|
Email: "partial@example.com",
|
|
}
|
|
err := user.Create()
|
|
require.NoError(t, err, "Should be able to create test user")
|
|
|
|
// Update only username
|
|
originalEmail := user.Email
|
|
originalHash := user.PasswordHash
|
|
user.Username = "updatedpartialuser"
|
|
|
|
err = user.Save()
|
|
assert.NoError(t, err, "Partial save should succeed")
|
|
|
|
// Verify only username was updated
|
|
var retrievedUser User
|
|
err = retrievedUser.FirstId(user.UserID)
|
|
assert.NoError(t, err, "Should be able to retrieve saved user")
|
|
assert.Equal(t, "updatedpartialuser", retrievedUser.Username, "Username should be updated")
|
|
assert.Equal(t, originalEmail, retrievedUser.Email, "Email should remain unchanged")
|
|
assert.Equal(t, originalHash, retrievedUser.PasswordHash, "PasswordHash should remain unchanged")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Password Reset Method Tests
|
|
// =============================================================================
|
|
|
|
func TestUser_FindByEmail(t *testing.T) {
|
|
user := setupTestUserWithReset(t)
|
|
|
|
// Test finding existing email
|
|
var foundUser User
|
|
err := foundUser.FindByEmail(user.Email)
|
|
assert.NoError(t, err, "Should find user by existing email")
|
|
assert.Equal(t, user.UserID, foundUser.UserID, "Found user should have correct ID")
|
|
assert.Equal(t, user.Username, foundUser.Username, "Found user should have correct username")
|
|
|
|
// Test finding non-existent email
|
|
var notFoundUser User
|
|
err = notFoundUser.FindByEmail("nonexistent@example.com")
|
|
assert.Error(t, err, "Should return error for non-existent email")
|
|
assert.Equal(t, gorm.ErrRecordNotFound, err, "Should return RecordNotFound error")
|
|
|
|
// Test with empty email
|
|
var emptyEmailUser User
|
|
err = emptyEmailUser.FindByEmail("")
|
|
assert.Error(t, err, "Should return error for empty email")
|
|
}
|
|
|
|
func TestUser_FindByResetHash(t *testing.T) {
|
|
user := setupTestUserWithReset(t)
|
|
|
|
resetHash := "find_by_hash_test_123"
|
|
err := user.SetPasswordResetHash(resetHash)
|
|
require.NoError(t, err, "Should be able to set reset hash")
|
|
|
|
// Test finding valid hash
|
|
var foundUser User
|
|
err = foundUser.FindByResetHash(resetHash)
|
|
assert.NoError(t, err, "Should find user by valid reset hash")
|
|
assert.Equal(t, user.UserID, foundUser.UserID, "Found user should have correct ID")
|
|
assert.Equal(t, user.Email, foundUser.Email, "Found user should have correct email")
|
|
|
|
// Test finding invalid hash
|
|
var notFoundUser User
|
|
err = notFoundUser.FindByResetHash("invalid_hash")
|
|
assert.Error(t, err, "Should return error for invalid reset hash")
|
|
|
|
// Test finding expired hash
|
|
expiredTime := time.Now().Add(-1 * time.Hour)
|
|
user.ResetPwHashExpires = &expiredTime
|
|
err = user.Save()
|
|
require.NoError(t, err, "Should be able to save expired hash")
|
|
|
|
var expiredUser User
|
|
err = expiredUser.FindByResetHash(resetHash)
|
|
assert.Error(t, err, "Should not find expired reset hash")
|
|
|
|
// Test with empty hash
|
|
var emptyHashUser User
|
|
err = emptyHashUser.FindByResetHash("")
|
|
assert.Error(t, err, "Should return error for empty hash")
|
|
}
|
|
|
|
func TestUser_SetPasswordResetHash(t *testing.T) {
|
|
user := setupTestUserWithReset(t)
|
|
|
|
resetHash := "test_hash_123456789"
|
|
beforeTime := time.Now()
|
|
|
|
err := user.SetPasswordResetHash(resetHash)
|
|
afterTime := time.Now()
|
|
|
|
assert.NoError(t, err, "Should successfully set reset hash")
|
|
assert.NotNil(t, user.ResetPwHash, "ResetPwHash should be set")
|
|
assert.Equal(t, resetHash, *user.ResetPwHash, "ResetPwHash should match provided value")
|
|
assert.NotNil(t, user.ResetPwHashExpires, "ResetPwHashExpires should be set")
|
|
|
|
// Verify expiry time is approximately 14 days from now
|
|
expectedMinExpiry := beforeTime.Add(14 * 24 * time.Hour)
|
|
expectedMaxExpiry := afterTime.Add(14 * 24 * time.Hour)
|
|
assert.True(t, user.ResetPwHashExpires.After(expectedMinExpiry), "Expiry should be after minimum expected time")
|
|
assert.True(t, user.ResetPwHashExpires.Before(expectedMaxExpiry), "Expiry should be before maximum expected time")
|
|
|
|
// Test setting multiple times (should overwrite)
|
|
newResetHash := "new_reset_hash_456"
|
|
err = user.SetPasswordResetHash(newResetHash)
|
|
assert.NoError(t, err, "Should successfully overwrite reset hash")
|
|
assert.Equal(t, newResetHash, *user.ResetPwHash, "ResetPwHash should be updated to new value")
|
|
}
|
|
|
|
func TestUser_ClearPasswordResetHash(t *testing.T) {
|
|
user := setupTestUserWithReset(t)
|
|
|
|
// First set a reset hash
|
|
resetHash := "test_hash_to_clear"
|
|
err := user.SetPasswordResetHash(resetHash)
|
|
require.NoError(t, err, "Should be able to set reset hash")
|
|
require.NotNil(t, user.ResetPwHash, "Reset hash should be set before clearing")
|
|
|
|
// Then clear it
|
|
err = user.ClearPasswordResetHash()
|
|
assert.NoError(t, err, "Should successfully clear reset hash")
|
|
assert.Nil(t, user.ResetPwHash, "ResetPwHash should be nil after clearing")
|
|
assert.Nil(t, user.ResetPwHashExpires, "ResetPwHashExpires should be nil after clearing")
|
|
|
|
// Verify changes were saved to database
|
|
var retrievedUser User
|
|
err = retrievedUser.FirstId(user.UserID)
|
|
assert.NoError(t, err, "Should be able to retrieve user after clearing")
|
|
assert.Nil(t, retrievedUser.ResetPwHash, "Retrieved user should have nil reset hash")
|
|
assert.Nil(t, retrievedUser.ResetPwHashExpires, "Retrieved user should have nil expiry")
|
|
|
|
// Test clearing when already cleared (should not error)
|
|
err = user.ClearPasswordResetHash()
|
|
assert.NoError(t, err, "Should not error when clearing already cleared hash")
|
|
}
|
|
|
|
func TestUser_IsResetHashValid(t *testing.T) {
|
|
user := setupTestUserWithReset(t)
|
|
|
|
resetHash := "valid_test_hash_123"
|
|
err := user.SetPasswordResetHash(resetHash)
|
|
require.NoError(t, err, "Should be able to set reset hash")
|
|
|
|
// Test valid hash
|
|
assert.True(t, user.IsResetHashValid(resetHash), "Should be valid with correct hash and future expiry")
|
|
|
|
// Test invalid hash
|
|
assert.False(t, user.IsResetHashValid("wrong_hash"), "Should be invalid with wrong hash")
|
|
|
|
// Test expired hash
|
|
expiredTime := time.Now().Add(-1 * time.Hour)
|
|
user.ResetPwHashExpires = &expiredTime
|
|
assert.False(t, user.IsResetHashValid(resetHash), "Should be invalid with expired hash")
|
|
|
|
// Test nil hash
|
|
user.ResetPwHash = nil
|
|
user.ResetPwHashExpires = nil
|
|
assert.False(t, user.IsResetHashValid(resetHash), "Should be invalid when hash is nil")
|
|
|
|
// Test only hash set (no expiry)
|
|
user.ResetPwHash = &resetHash
|
|
user.ResetPwHashExpires = nil
|
|
assert.False(t, user.IsResetHashValid(resetHash), "Should be invalid when expiry is nil")
|
|
|
|
// Test only expiry set (no hash)
|
|
user.ResetPwHash = nil
|
|
futureTime := time.Now().Add(1 * time.Hour)
|
|
user.ResetPwHashExpires = &futureTime
|
|
assert.False(t, user.IsResetHashValid(resetHash), "Should be invalid when hash is nil")
|
|
|
|
// Test with empty string hash
|
|
user.ResetPwHash = &resetHash
|
|
user.ResetPwHashExpires = &futureTime
|
|
assert.False(t, user.IsResetHashValid(""), "Should be invalid with empty hash string")
|
|
}
|
|
|
|
// =============================================================================
|
|
// Legacy Tests from original user_test.go (Fixed for isolated testing)
|
|
// =============================================================================
|
|
|
|
func TestRegisterUser(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
usr := User{
|
|
Username: "bebe",
|
|
PasswordHash: "osiris",
|
|
Email: "frank@wuenscheonline.de",
|
|
Role: "admin",
|
|
}
|
|
|
|
hashedPassword := md5.Sum([]byte(usr.PasswordHash))
|
|
usr.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
|
err := usr.Create()
|
|
assert.NoError(t, err, "no error expected when creating record")
|
|
|
|
usr2 := User{
|
|
Username: "bubnu",
|
|
PasswordHash: "osiris",
|
|
Email: "spacer@wuenscheonline.de",
|
|
Role: "standard",
|
|
}
|
|
hashedPassword = md5.Sum([]byte(usr2.PasswordHash))
|
|
usr2.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
|
err = usr2.Create()
|
|
assert.NoError(t, err, "no error expected when creating record")
|
|
}
|
|
|
|
func TestLoginUser(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create test user first
|
|
usr := User{
|
|
Username: "logintest",
|
|
PasswordHash: "osiris",
|
|
Email: "login@test.com",
|
|
Role: "standard",
|
|
}
|
|
hashedPassword := md5.Sum([]byte(usr.PasswordHash))
|
|
usr.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
|
err := usr.Create()
|
|
require.NoError(t, err, "Should create test user")
|
|
|
|
// Test login functionality
|
|
var foundUser User
|
|
input := struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
HashedPassword string
|
|
}{
|
|
Username: "logintest",
|
|
Password: "osiris",
|
|
}
|
|
err = foundUser.First(input.Username)
|
|
assert.NoError(t, err, "no error expected when finding record")
|
|
|
|
hashedPassword = md5.Sum([]byte(input.Password))
|
|
input.HashedPassword = hex.EncodeToString(hashedPassword[:])
|
|
assert.Equal(t, input.HashedPassword, foundUser.PasswordHash, "Password hashes should match")
|
|
}
|
|
|
|
func TestHashing(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create users similar to TestRegisterUser
|
|
usr := User{
|
|
Username: "hashtest1",
|
|
PasswordHash: "osiris",
|
|
Email: "hash1@test.com",
|
|
}
|
|
hashedPassword := md5.Sum([]byte(usr.PasswordHash))
|
|
usr.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
|
err := usr.Create()
|
|
require.NoError(t, err, "Should create first user")
|
|
|
|
usr2 := User{
|
|
Username: "hashtest2",
|
|
PasswordHash: "osiris",
|
|
Email: "hash2@test.com",
|
|
}
|
|
hashedPassword = md5.Sum([]byte(usr2.PasswordHash))
|
|
usr2.PasswordHash = hex.EncodeToString(hashedPassword[:])
|
|
err = usr2.Create()
|
|
require.NoError(t, err, "Should create second user")
|
|
|
|
// Test first user hash
|
|
var foundUser1 User
|
|
input1 := struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
HashedPassword string
|
|
}{
|
|
Username: "hashtest1",
|
|
Password: "osiris",
|
|
}
|
|
err = foundUser1.First(input1.Username)
|
|
assert.NoError(t, err, "no error expected when finding record")
|
|
|
|
hashedPassword = md5.Sum([]byte(input1.Password))
|
|
input1.HashedPassword = hex.EncodeToString(hashedPassword[:])
|
|
assert.Equal(t, input1.HashedPassword, foundUser1.PasswordHash)
|
|
|
|
// Test second user hash
|
|
var foundUser2 User
|
|
input2 := struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
HashedPassword string
|
|
}{
|
|
Username: "hashtest2",
|
|
Password: "osiris",
|
|
}
|
|
err = foundUser2.First(input2.Username)
|
|
assert.NoError(t, err, "no error expected when finding record")
|
|
|
|
hashedPassword = md5.Sum([]byte(input2.Password))
|
|
input2.HashedPassword = hex.EncodeToString(hashedPassword[:])
|
|
assert.Equal(t, input2.HashedPassword, foundUser2.PasswordHash)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Edge Cases and Error Handling Tests
|
|
// =============================================================================
|
|
|
|
func TestUser_EdgeCases(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Test with empty strings
|
|
user := &User{
|
|
Username: "",
|
|
PasswordHash: "",
|
|
Email: "",
|
|
Role: "standard",
|
|
}
|
|
err := user.Save()
|
|
assert.NoError(t, err, "Should save user with empty strings")
|
|
assert.NotZero(t, user.UserID, "UserID should be set")
|
|
|
|
// Test FirstId with the created user
|
|
var foundUser User
|
|
err = foundUser.FirstId(user.UserID)
|
|
assert.NoError(t, err, "Should find user with empty strings")
|
|
assert.Equal(t, "", foundUser.Username, "Username should be empty")
|
|
assert.Equal(t, "", foundUser.Email, "Email should be empty")
|
|
|
|
// Test with very long strings
|
|
longString := strings.Repeat("a", 500)
|
|
user2 := &User{
|
|
Username: "longuser",
|
|
PasswordHash: longString,
|
|
Email: "long@example.com",
|
|
Role: "standard",
|
|
}
|
|
err = user2.Create()
|
|
assert.NoError(t, err, "Should create user with long password hash")
|
|
}
|
|
|
|
func TestUser_ConcurrentAccess(t *testing.T) {
|
|
setupUserModelTestEnvironment(t)
|
|
|
|
// Create a test user
|
|
user := &User{
|
|
Username: "concurrentuser",
|
|
PasswordHash: "hash",
|
|
Email: "concurrent@example.com",
|
|
Role: "standard",
|
|
}
|
|
err := user.Create()
|
|
require.NoError(t, err, "Should be able to create test user")
|
|
|
|
// Test concurrent modifications
|
|
done := make(chan bool, 2)
|
|
|
|
// Goroutine 1: Update username
|
|
go func() {
|
|
defer func() { done <- true }()
|
|
var u1 User
|
|
u1.FirstId(user.UserID)
|
|
u1.Username = "concurrent1"
|
|
u1.Save()
|
|
}()
|
|
|
|
// Goroutine 2: Update email
|
|
go func() {
|
|
defer func() { done <- true }()
|
|
var u2 User
|
|
u2.FirstId(user.UserID)
|
|
u2.Email = "concurrent2@example.com"
|
|
u2.Save()
|
|
}()
|
|
|
|
// Wait for both to complete
|
|
<-done
|
|
<-done
|
|
|
|
// Verify final state (one of the updates should have won)
|
|
var finalUser User
|
|
err = finalUser.FirstId(user.UserID)
|
|
assert.NoError(t, err, "Should be able to retrieve user after concurrent updates")
|
|
// We can't predict exact final state due to race conditions, but should not crash
|
|
}
|
|
|
|
// =============================================================================
|
|
// Benchmark Tests
|
|
// =============================================================================
|
|
|
|
func BenchmarkUser_Create(b *testing.B) {
|
|
// Setup test database
|
|
tempDir := b.TempDir()
|
|
dbPath := filepath.Join(tempDir, "benchmark_create.db")
|
|
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
|
|
err = testDB.AutoMigrate(&User{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to migrate: %v", err)
|
|
}
|
|
|
|
originalDB := database.DB
|
|
database.DB = testDB
|
|
defer func() { database.DB = originalDB }()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
user := &User{
|
|
Username: fmt.Sprintf("benchuser%d", i),
|
|
PasswordHash: "benchhash",
|
|
Email: fmt.Sprintf("bench%d@example.com", i),
|
|
}
|
|
err := user.Create()
|
|
if err != nil {
|
|
b.Fatalf("Create failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkUser_First(b *testing.B) {
|
|
// Setup test database with users
|
|
tempDir := b.TempDir()
|
|
dbPath := filepath.Join(tempDir, "benchmark_first.db")
|
|
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
|
|
err = testDB.AutoMigrate(&User{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to migrate: %v", err)
|
|
}
|
|
|
|
originalDB := database.DB
|
|
database.DB = testDB
|
|
defer func() { database.DB = originalDB }()
|
|
|
|
// Create test users
|
|
for i := 0; i < 100; i++ {
|
|
user := &User{
|
|
Username: fmt.Sprintf("finduser%d", i),
|
|
PasswordHash: "findhash",
|
|
Email: fmt.Sprintf("find%d@example.com", i),
|
|
}
|
|
user.Create()
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
username := fmt.Sprintf("finduser%d", i%100)
|
|
err := user.First(username)
|
|
if err != nil {
|
|
b.Fatalf("First failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkUser_Save(b *testing.B) {
|
|
// Setup test database with a user
|
|
tempDir := b.TempDir()
|
|
dbPath := filepath.Join(tempDir, "benchmark_save.db")
|
|
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
|
|
err = testDB.AutoMigrate(&User{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to migrate: %v", err)
|
|
}
|
|
|
|
originalDB := database.DB
|
|
database.DB = testDB
|
|
defer func() { database.DB = originalDB }()
|
|
|
|
// Create a test user
|
|
user := &User{
|
|
Username: "saveuser",
|
|
PasswordHash: "savehash",
|
|
Email: "save@example.com",
|
|
}
|
|
user.Create()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
user.Username = fmt.Sprintf("saveuser%d", i)
|
|
err := user.Save()
|
|
if err != nil {
|
|
b.Fatalf("Save failed: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkUser_FirstId(b *testing.B) {
|
|
// Setup test database with users
|
|
tempDir := b.TempDir()
|
|
dbPath := filepath.Join(tempDir, "benchmark_firstid.db")
|
|
testDB, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to create test database: %v", err)
|
|
}
|
|
|
|
err = testDB.AutoMigrate(&User{})
|
|
if err != nil {
|
|
b.Fatalf("Failed to migrate: %v", err)
|
|
}
|
|
|
|
originalDB := database.DB
|
|
database.DB = testDB
|
|
defer func() { database.DB = originalDB }()
|
|
|
|
// Create test users and collect their IDs
|
|
var userIDs []uint
|
|
for i := 0; i < 100; i++ {
|
|
user := &User{
|
|
Username: fmt.Sprintf("finduser%d", i),
|
|
PasswordHash: "findhash",
|
|
Email: fmt.Sprintf("find%d@example.com", i),
|
|
}
|
|
user.Create()
|
|
userIDs = append(userIDs, user.UserID)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
var user User
|
|
userID := userIDs[i%len(userIDs)]
|
|
err := user.FirstId(userID)
|
|
if err != nil {
|
|
b.Fatalf("FirstId failed: %v", err)
|
|
}
|
|
}
|
|
}
|