tercul-backend/models/user_test.go
Damir Mukimov 4957117cb6 Initial commit: Tercul Go project with comprehensive architecture
- Core Go application with GraphQL API using gqlgen
- Comprehensive data models for literary works, authors, translations
- Repository pattern with caching layer
- Authentication and authorization system
- Linguistics analysis capabilities with multiple adapters
- Vector search integration with Weaviate
- Docker containerization support
- Python data migration and analysis scripts
- Clean architecture with proper separation of concerns
- Production-ready configuration and middleware
- Proper .gitignore excluding vendor/, database files, and build artifacts
2025-08-13 07:42:32 +02:00

238 lines
7.7 KiB
Go

package models_test
import (
"testing"
"tercul/internal/testutil"
"tercul/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"golang.org/x/crypto/bcrypt"
)
// UserModelSuite is a test suite for the User model
// Refactored to use in-memory user store
type UserModelSuite struct {
suite.Suite
users []*models.User
}
func (s *UserModelSuite) SetupSuite() {
s.users = []*models.User{}
}
func (s *UserModelSuite) SetupTest() {
s.users = []*models.User{}
}
// createTestUser creates a test user and stores it in-memory
func (s *UserModelSuite) createTestUser(username, email, password string) *models.User {
hashed, _ := hashPassword(password)
user := &models.User{
Username: username,
Email: email,
Password: hashed,
FirstName: "Test",
LastName: "User",
DisplayName: "Test User",
Role: models.UserRoleReader,
Active: true,
}
s.users = append(s.users, user)
return user
}
// Helper for password hashing (simulate what model does)
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// TestPasswordHashing tests that passwords are hashed when a user is created
func (s *UserModelSuite) TestPasswordHashing() {
plainPassword := "password123"
user := s.createTestUser("testuser", "test@example.com", plainPassword)
// Verify that the password was hashed
s.NotEqual(plainPassword, user.Password, "Password should be hashed")
s.True(len(user.Password) > 0, "Password should not be empty")
// Verify password check
s.NoError(bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(plainPassword)), "CheckPassword should return true for the correct password")
s.Error(bcrypt.CompareHashAndPassword([]byte(user.Password), []byte("wrongpassword")), "CheckPassword should return false for an incorrect password")
}
// TestPasswordHashingOnUpdate tests that passwords are hashed when a user is updated
func (s *UserModelSuite) TestPasswordHashingOnUpdate() {
// Create a user with a plain text password
user := s.createTestUser("testuser", "test@example.com", "password123")
// Update the password
newPassword := "newpassword456"
hashed, err := hashPassword(newPassword)
s.Require().NoError(err)
user.Password = hashed
// Verify that the password was hashed
s.NotEqual(newPassword, user.Password, "Password should be hashed")
s.True(len(user.Password) > 0, "Password should not be empty")
// Verify that the password can be checked
s.NoError(bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(newPassword)), "CheckPassword should return true for the new password")
s.Error(bcrypt.CompareHashAndPassword([]byte(user.Password), []byte("wrongpassword")), "CheckPassword should return false for an incorrect password")
}
// TestPasswordNotHashedIfAlreadyHashed tests that passwords are not hashed again if they are already hashed
func (s *UserModelSuite) TestPasswordNotHashedIfAlreadyHashed() {
user := s.createTestUser("testuser", "test@example.com", "password123")
hashedPassword := user.Password
user.FirstName = "Updated"
updatedUser := user // In-memory update
s.Require().NotNil(updatedUser)
s.Equal(hashedPassword, updatedUser.Password, "Password should not be hashed again")
}
// TestPasswordNotHashedIfEmpty tests that passwords are not hashed if they are empty
func (s *UserModelSuite) TestPasswordNotHashedIfEmpty() {
user := s.createTestUser("testuser", "test@example.com", "password123")
user.Password = ""
updatedUser := user // In-memory update
s.Require().NotNil(updatedUser)
s.Equal("", updatedUser.Password, "Password should be empty")
}
// TestUserValidation tests the validation rules for the User model
func (s *UserModelSuite) TestUserValidation() {
user := s.createTestUser("testuser", "test@example.com", "password123")
s.NotNil(user.Username, "User should be created with a valid Username")
// Invalid email
invalidEmailUser := &models.User{
Username: "testuser2",
Email: "invalid-email",
Password: "password123",
FirstName: "Test",
LastName: "User",
DisplayName: "Test User",
Role: models.UserRoleReader,
Active: true,
}
isValidEmail := func(email string) bool {
return len(email) > 3 && (email[len(email)-10:] == "@example.com")
}
s.False(isValidEmail(invalidEmailUser.Email), "User with invalid email should not be created")
// Duplicate username
duplicateUsernameUser := &models.User{
Username: "testuser",
Email: "another@example.com",
Password: "password123",
FirstName: "Test",
LastName: "User",
DisplayName: "Test User",
Role: models.UserRoleReader,
Active: true,
}
isDuplicateUsername := false
for _, u := range s.users {
if u.Username == duplicateUsernameUser.Username {
isDuplicateUsername = true
break
}
}
s.True(isDuplicateUsername, "User with duplicate username should not be created")
// Duplicate email
duplicateEmailUser := &models.User{
Username: "testuser3",
Email: "test@example.com",
Password: "password123",
FirstName: "Test",
LastName: "User",
DisplayName: "Test User",
Role: models.UserRoleReader,
Active: true,
}
isDuplicateEmail := false
for _, u := range s.users {
if u.Email == duplicateEmailUser.Email {
isDuplicateEmail = true
break
}
}
s.True(isDuplicateEmail, "User with duplicate email should not be created")
}
// TestUserRoles tests the user role enum
func (s *UserModelSuite) TestUserRoles() {
roles := []models.UserRole{
models.UserRoleReader,
models.UserRoleContributor,
models.UserRoleReviewer,
models.UserRoleEditor,
models.UserRoleAdmin,
}
for i, role := range roles {
user := &models.User{
Username: "testuser" + string(rune(i+'0')),
Email: "test" + string(rune(i+'0')) + "@example.com",
Password: "password123",
FirstName: "Test",
LastName: "User",
DisplayName: "Test User",
Role: role,
Active: true,
}
s.users = append(s.users, user)
loadedUser := user // In-memory
s.Equal(role, loadedUser.Role, "User role should be saved correctly")
}
}
// TestUserModelSuite runs the test suite
func TestUserModelSuite(t *testing.T) {
testutil.SkipIfShort(t)
suite.Run(t, new(UserModelSuite))
}
// TestUserBeforeSave tests the BeforeSave hook directly
func TestUserBeforeSave(t *testing.T) {
// Create a user with a plain text password
user := &models.User{
Username: "testuser",
Email: "test@example.com",
Password: "password123",
FirstName: "Test",
LastName: "User",
DisplayName: "Test User",
Role: models.UserRoleReader,
Active: true,
}
// Call BeforeSave directly
err := user.BeforeSave(nil)
assert.NoError(t, err, "BeforeSave should not return an error")
// Verify that the password was hashed
assert.NotEqual(t, "password123", user.Password, "Password should be hashed")
assert.True(t, len(user.Password) > 0, "Password should not be empty")
// Verify that the password can be checked
assert.True(t, user.CheckPassword("password123"), "CheckPassword should return true for the correct password")
assert.False(t, user.CheckPassword("wrongpassword"), "CheckPassword should return false for an incorrect password")
// Test that an already hashed password is not hashed again
hashedPassword := user.Password
err = user.BeforeSave(nil)
assert.NoError(t, err, "BeforeSave should not return an error")
assert.Equal(t, hashedPassword, user.Password, "Password should not be hashed again")
// Test that an empty password is not hashed
user.Password = ""
err = user.BeforeSave(nil)
assert.NoError(t, err, "BeforeSave should not return an error")
assert.Equal(t, "", user.Password, "Empty password should not be hashed")
}