package handlers import ( "encoding/json" "net/http" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/treytartt/casera-api/internal/config" "github.com/treytartt/casera-api/internal/dto/requests" "github.com/treytartt/casera-api/internal/repositories" "github.com/treytartt/casera-api/internal/services" "github.com/treytartt/casera-api/internal/testutil" ) func setupAuthHandler(t *testing.T) (*AuthHandler, *gin.Engine, *repositories.UserRepository) { db := testutil.SetupTestDB(t) userRepo := repositories.NewUserRepository(db) cfg := &config.Config{ Security: config.SecurityConfig{ SecretKey: "test-secret-key", PasswordResetExpiry: 15 * time.Minute, ConfirmationExpiry: 24 * time.Hour, MaxPasswordResetRate: 3, }, } authService := services.NewAuthService(userRepo, cfg) handler := NewAuthHandler(authService, nil, nil) // No email or cache for tests router := testutil.SetupTestRouter() return handler, router, userRepo } func TestAuthHandler_Register(t *testing.T) { handler, router, _ := setupAuthHandler(t) router.POST("/api/auth/register/", handler.Register) t.Run("successful registration", func(t *testing.T) { req := requests.RegisterRequest{ Username: "newuser", Email: "new@test.com", Password: "password123", FirstName: "New", LastName: "User", } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusCreated) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) testutil.AssertJSONFieldExists(t, response, "token") testutil.AssertJSONFieldExists(t, response, "user") testutil.AssertJSONFieldExists(t, response, "message") user := response["user"].(map[string]interface{}) assert.Equal(t, "newuser", user["username"]) assert.Equal(t, "new@test.com", user["email"]) assert.Equal(t, "New", user["first_name"]) assert.Equal(t, "User", user["last_name"]) }) t.Run("registration with missing fields", func(t *testing.T) { req := map[string]string{ "username": "test", // Missing email and password } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusBadRequest) response := testutil.ParseJSON(t, w.Body.Bytes()) testutil.AssertJSONFieldExists(t, response, "error") }) t.Run("registration with short password", func(t *testing.T) { req := requests.RegisterRequest{ Username: "testuser", Email: "test@test.com", Password: "short", // Less than 8 chars } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusBadRequest) }) t.Run("registration with duplicate username", func(t *testing.T) { // First registration req := requests.RegisterRequest{ Username: "duplicate", Email: "unique1@test.com", Password: "password123", } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusCreated) // Try to register again with same username req.Email = "unique2@test.com" w = testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusBadRequest) response := testutil.ParseJSON(t, w.Body.Bytes()) assert.Contains(t, response["error"], "Username already taken") }) t.Run("registration with duplicate email", func(t *testing.T) { // First registration req := requests.RegisterRequest{ Username: "user1", Email: "duplicate@test.com", Password: "password123", } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusCreated) // Try to register again with same email req.Username = "user2" w = testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusBadRequest) response := testutil.ParseJSON(t, w.Body.Bytes()) assert.Contains(t, response["error"], "Email already registered") }) } func TestAuthHandler_Login(t *testing.T) { handler, router, _ := setupAuthHandler(t) router.POST("/api/auth/register/", handler.Register) router.POST("/api/auth/login/", handler.Login) // Create a test user registerReq := requests.RegisterRequest{ Username: "logintest", Email: "login@test.com", Password: "password123", FirstName: "Test", LastName: "User", } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", registerReq, "") testutil.AssertStatusCode(t, w, http.StatusCreated) t.Run("successful login with username", func(t *testing.T) { req := requests.LoginRequest{ Username: "logintest", Password: "password123", } w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "") testutil.AssertStatusCode(t, w, http.StatusOK) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) testutil.AssertJSONFieldExists(t, response, "token") testutil.AssertJSONFieldExists(t, response, "user") user := response["user"].(map[string]interface{}) assert.Equal(t, "logintest", user["username"]) assert.Equal(t, "login@test.com", user["email"]) }) t.Run("successful login with email", func(t *testing.T) { req := requests.LoginRequest{ Username: "login@test.com", // Using email as username Password: "password123", } w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "") testutil.AssertStatusCode(t, w, http.StatusOK) }) t.Run("login with wrong password", func(t *testing.T) { req := requests.LoginRequest{ Username: "logintest", Password: "wrongpassword", } w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "") testutil.AssertStatusCode(t, w, http.StatusUnauthorized) response := testutil.ParseJSON(t, w.Body.Bytes()) assert.Contains(t, response["error"], "Invalid credentials") }) t.Run("login with non-existent user", func(t *testing.T) { req := requests.LoginRequest{ Username: "nonexistent", Password: "password123", } w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "") testutil.AssertStatusCode(t, w, http.StatusUnauthorized) }) t.Run("login with missing fields", func(t *testing.T) { req := map[string]string{ "username": "logintest", // Missing password } w := testutil.MakeRequest(router, "POST", "/api/auth/login/", req, "") testutil.AssertStatusCode(t, w, http.StatusBadRequest) }) } func TestAuthHandler_CurrentUser(t *testing.T) { handler, router, userRepo := setupAuthHandler(t) db := testutil.SetupTestDB(t) user := testutil.CreateTestUser(t, db, "metest", "me@test.com", "password123") user.FirstName = "Test" user.LastName = "User" userRepo.Update(user) // Set up route with mock auth middleware authGroup := router.Group("/api/auth") authGroup.Use(testutil.MockAuthMiddleware(user)) authGroup.GET("/me/", handler.CurrentUser) t.Run("get current user", func(t *testing.T) { w := testutil.MakeRequest(router, "GET", "/api/auth/me/", nil, "test-token") testutil.AssertStatusCode(t, w, http.StatusOK) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, "metest", response["username"]) assert.Equal(t, "me@test.com", response["email"]) }) } func TestAuthHandler_UpdateProfile(t *testing.T) { handler, router, userRepo := setupAuthHandler(t) db := testutil.SetupTestDB(t) user := testutil.CreateTestUser(t, db, "updatetest", "update@test.com", "password123") userRepo.Update(user) authGroup := router.Group("/api/auth") authGroup.Use(testutil.MockAuthMiddleware(user)) authGroup.PUT("/profile/", handler.UpdateProfile) t.Run("update profile", func(t *testing.T) { firstName := "Updated" lastName := "Name" req := requests.UpdateProfileRequest{ FirstName: &firstName, LastName: &lastName, } w := testutil.MakeRequest(router, "PUT", "/api/auth/profile/", req, "test-token") testutil.AssertStatusCode(t, w, http.StatusOK) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Equal(t, "Updated", response["first_name"]) assert.Equal(t, "Name", response["last_name"]) }) } func TestAuthHandler_ForgotPassword(t *testing.T) { handler, router, _ := setupAuthHandler(t) router.POST("/api/auth/register/", handler.Register) router.POST("/api/auth/forgot-password/", handler.ForgotPassword) // Create a test user registerReq := requests.RegisterRequest{ Username: "forgottest", Email: "forgot@test.com", Password: "password123", } testutil.MakeRequest(router, "POST", "/api/auth/register/", registerReq, "") t.Run("forgot password with valid email", func(t *testing.T) { req := requests.ForgotPasswordRequest{ Email: "forgot@test.com", } w := testutil.MakeRequest(router, "POST", "/api/auth/forgot-password/", req, "") // Always returns 200 to prevent email enumeration testutil.AssertStatusCode(t, w, http.StatusOK) response := testutil.ParseJSON(t, w.Body.Bytes()) testutil.AssertJSONFieldExists(t, response, "message") }) t.Run("forgot password with invalid email", func(t *testing.T) { req := requests.ForgotPasswordRequest{ Email: "nonexistent@test.com", } w := testutil.MakeRequest(router, "POST", "/api/auth/forgot-password/", req, "") // Still returns 200 to prevent email enumeration testutil.AssertStatusCode(t, w, http.StatusOK) }) } func TestAuthHandler_Logout(t *testing.T) { handler, router, userRepo := setupAuthHandler(t) db := testutil.SetupTestDB(t) user := testutil.CreateTestUser(t, db, "logouttest", "logout@test.com", "password123") userRepo.Update(user) authGroup := router.Group("/api/auth") authGroup.Use(testutil.MockAuthMiddleware(user)) authGroup.POST("/logout/", handler.Logout) t.Run("successful logout", func(t *testing.T) { w := testutil.MakeRequest(router, "POST", "/api/auth/logout/", nil, "test-token") testutil.AssertStatusCode(t, w, http.StatusOK) response := testutil.ParseJSON(t, w.Body.Bytes()) assert.Contains(t, response["message"], "Logged out successfully") }) } func TestAuthHandler_JSONResponses(t *testing.T) { handler, router, _ := setupAuthHandler(t) router.POST("/api/auth/register/", handler.Register) router.POST("/api/auth/login/", handler.Login) t.Run("register response has correct JSON structure", func(t *testing.T) { req := requests.RegisterRequest{ Username: "jsontest", Email: "json@test.com", Password: "password123", FirstName: "JSON", LastName: "Test", } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusCreated) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) // Verify top-level structure assert.Contains(t, response, "token") assert.Contains(t, response, "user") assert.Contains(t, response, "message") // Verify token is not empty assert.NotEmpty(t, response["token"]) // Verify user structure user := response["user"].(map[string]interface{}) assert.Contains(t, user, "id") assert.Contains(t, user, "username") assert.Contains(t, user, "email") assert.Contains(t, user, "first_name") assert.Contains(t, user, "last_name") assert.Contains(t, user, "is_active") assert.Contains(t, user, "date_joined") // Verify types assert.IsType(t, float64(0), user["id"]) // JSON numbers are float64 assert.IsType(t, "", user["username"]) assert.IsType(t, "", user["email"]) assert.IsType(t, true, user["is_active"]) }) t.Run("error response has correct JSON structure", func(t *testing.T) { req := map[string]string{ "username": "test", } w := testutil.MakeRequest(router, "POST", "/api/auth/register/", req, "") testutil.AssertStatusCode(t, w, http.StatusBadRequest) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.Contains(t, response, "error") assert.IsType(t, "", response["error"]) }) }