ForFarm/backend/internal/api/auth_test.go
2025-04-04 22:15:13 +07:00

240 lines
6.0 KiB
Go

package api
import (
"context"
"github.com/danielgtaylor/huma/v2"
"github.com/forfarm/backend/internal/utilities"
"golang.org/x/crypto/bcrypt"
"log/slog"
"os"
"testing"
"github.com/forfarm/backend/internal/domain"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockUserRepository struct {
mock.Mock
}
type EmailPasswordInput struct {
Email string `json:"email" example:"Email address of the user"`
Password string `json:"password" example:"Password of the user"`
}
func (m *MockUserRepository) GetByID(ctx context.Context, id int64) (domain.User, error) {
args := m.Called(ctx, id)
return args.Get(0).(domain.User), args.Error(1)
}
func (m *MockUserRepository) GetByUUID(ctx context.Context, uuid string) (domain.User, error) {
args := m.Called(ctx, uuid)
return args.Get(0).(domain.User), args.Error(1)
}
func (m *MockUserRepository) GetByUsername(ctx context.Context, username string) (domain.User, error) {
args := m.Called(ctx, username)
return args.Get(0).(domain.User), args.Error(1)
}
func (m *MockUserRepository) GetByEmail(ctx context.Context, email string) (domain.User, error) {
args := m.Called(ctx, email)
return args.Get(0).(domain.User), args.Error(1)
}
func (m *MockUserRepository) CreateOrUpdate(ctx context.Context, u *domain.User) error {
args := m.Called(ctx, u)
return args.Error(0)
}
func (m *MockUserRepository) Delete(ctx context.Context, id int64) error {
args := m.Called(ctx, id)
return args.Error(0)
}
func TestRegisterHandler(t *testing.T) {
var tests = []struct {
name string
input RegisterInput
mockSetup func(*MockUserRepository)
expectedError error
}{
{
name: "successful registration",
input: RegisterInput{
Body: EmailPasswordInput{
Email: "test@example.com",
Password: "ValidPass123!",
},
},
mockSetup: func(m *MockUserRepository) {
m.On("GetByEmail", mock.Anything, "test@example.com").Return(domain.User{}, domain.ErrNotFound)
m.On("CreateOrUpdate", mock.Anything, mock.AnythingOfType("*domain.User")).Return(nil)
},
expectedError: nil,
},
{
name: "existing email",
input: RegisterInput{
Body: struct {
Email string `json:"email" example:"Email address of the user"`
Password string `json:"password" example:"Password of the user"`
}(struct {
Email string `json:"email"`
Password string `json:"password"`
}{
Email: "existing@example.com",
Password: "ValidPass123!",
}),
},
mockSetup: func(m *MockUserRepository) {
m.On("GetByEmail", mock.Anything, "existing@example.com").Return(domain.User{
Email: "existing@example.com",
}, nil)
},
expectedError: huma.Error409Conflict("User with this email already exists"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRepo := &MockUserRepository{}
if tt.mockSetup != nil {
tt.mockSetup(mockRepo)
}
api := &api{
userRepo: mockRepo,
logger: nil,
}
_, err := api.registerHandler(context.Background(), &tt.input)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, tt.expectedError.Error())
}
mockRepo.AssertExpectations(t)
})
}
}
func TestLoginHandler(t *testing.T) {
correctPassword := "ValidPass123!"
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(correctPassword), bcrypt.DefaultCost)
if err != nil {
t.Fatalf("Failed to generate bcrypt hash: %v", err)
}
userUUID := uuid.New().String()
testUser := domain.User{
UUID: userUUID,
Email: "test@example.com",
Password: string(hashedPassword),
IsActive: true,
}
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelError,
}))
tests := []struct {
name string
input LoginInput
mockSetup func(*MockUserRepository)
expectedError error
}{
{
name: "successful login",
input: LoginInput{
Body: EmailPasswordInput{
Email: "test@example.com",
Password: correctPassword,
},
},
mockSetup: func(m *MockUserRepository) {
m.On("GetByEmail", mock.Anything, "test@example.com").Return(testUser, nil)
},
expectedError: nil,
},
{
name: "invalid credentials",
input: LoginInput{
Body: EmailPasswordInput{
Email: "test@example.com",
Password: "wrongpassword",
},
},
mockSetup: func(m *MockUserRepository) {
m.On("GetByEmail", mock.Anything, "test@example.com").Return(testUser, nil)
},
expectedError: huma.Error401Unauthorized("Invalid email or password"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRepo := &MockUserRepository{}
if tt.mockSetup != nil {
tt.mockSetup(mockRepo)
}
api := &api{
userRepo: mockRepo,
logger: logger,
}
_, err := api.loginHandler(context.Background(), &tt.input)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, tt.expectedError.Error())
}
mockRepo.AssertExpectations(t)
})
}
}
func TestLoginHandler_TokenGeneration(t *testing.T) {
userUUID := uuid.New().String()
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("ValidPass123!"), bcrypt.DefaultCost)
testUser := domain.User{
UUID: userUUID,
Email: "test@example.com",
Password: string(hashedPassword),
IsActive: true,
}
mockRepo := &MockUserRepository{}
mockRepo.On("GetByEmail", mock.Anything, "test@example.com").Return(testUser, nil)
api := &api{
userRepo: mockRepo,
logger: nil,
}
input := &LoginInput{
Body: EmailPasswordInput{
Email: "test@example.com",
Password: "ValidPass123!",
},
}
output, err := api.loginHandler(context.Background(), input)
assert.NoError(t, err)
assert.NotEmpty(t, output.Body.Token)
err = utilities.VerifyJwtToken(output.Body.Token)
assert.NoError(t, err)
extractedUUID, err := utilities.ExtractUUIDFromToken(output.Body.Token)
assert.NoError(t, err)
assert.Equal(t, userUUID, extractedUUID)
}