mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 00:31:35 +00:00
- 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
238 lines
7.7 KiB
Go
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")
|
|
}
|