package handlers import ( "encoding/json" "net/http" "testing" "time" "github.com/labstack/echo/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/gorm" "github.com/treytartt/honeydue-api/internal/config" "github.com/treytartt/honeydue-api/internal/models" "github.com/treytartt/honeydue-api/internal/repositories" "github.com/treytartt/honeydue-api/internal/services" "github.com/treytartt/honeydue-api/internal/testutil" ) func setupDeleteAccountHandler(t *testing.T) (*AuthHandler, *echo.Echo, *gorm.DB) { 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) e := testutil.SetupTestRouter() return handler, e, db } // TestAuthHandler_DeleteAccount_WithConfirmation verifies that DELETE /account/ // succeeds when the user sends confirmation: "DELETE". // Post-Kratos: all users (regardless of provider) must confirm with "DELETE". func TestAuthHandler_DeleteAccount_WithConfirmation(t *testing.T) { handler, e, db := setupDeleteAccountHandler(t) user := testutil.CreateTestUser(t, db, "deletetest", "delete@test.com", "ignored") // Create profile for the user profile := &models.UserProfile{UserID: user.ID, Verified: true} require.NoError(t, db.Create(profile).Error) authGroup := e.Group("/api/auth") authGroup.Use(testutil.MockAuthMiddleware(user)) authGroup.DELETE("/account/", handler.DeleteAccount) t.Run("successful deletion with DELETE confirmation", func(t *testing.T) { req := map[string]interface{}{ "confirmation": "DELETE", } w := testutil.MakeRequest(e, "DELETE", "/api/auth/account/", 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.Contains(t, response["message"], "Account deleted successfully") // Verify user is actually deleted var count int64 db.Model(&models.User{}).Where("id = ?", user.ID).Count(&count) assert.Equal(t, int64(0), count) // Verify profile is deleted db.Model(&models.UserProfile{}).Where("user_id = ?", user.ID).Count(&count) assert.Equal(t, int64(0), count) }) } // TestAuthHandler_DeleteAccount_MissingConfirmation verifies that a missing // confirmation string is rejected with 400. func TestAuthHandler_DeleteAccount_MissingConfirmation(t *testing.T) { handler, e, db := setupDeleteAccountHandler(t) user := testutil.CreateTestUser(t, db, "nopw", "nopw@test.com", "ignored") authGroup := e.Group("/api/auth") authGroup.Use(testutil.MockAuthMiddleware(user)) authGroup.DELETE("/account/", handler.DeleteAccount) t.Run("missing confirmation returns 400", func(t *testing.T) { req := map[string]interface{}{} w := testutil.MakeRequest(e, "DELETE", "/api/auth/account/", req, "test-token") testutil.AssertStatusCode(t, w, http.StatusBadRequest) }) t.Run("wrong confirmation returns 400", func(t *testing.T) { req := map[string]interface{}{ "confirmation": "delete", // lowercase — must be exact "DELETE" } w := testutil.MakeRequest(e, "DELETE", "/api/auth/account/", req, "test-token") testutil.AssertStatusCode(t, w, http.StatusBadRequest) }) } // TestAuthHandler_DeleteAccount_Unauthenticated verifies that 401 is returned // when no auth middleware is set. func TestAuthHandler_DeleteAccount_Unauthenticated(t *testing.T) { handler, e, _ := setupDeleteAccountHandler(t) // No auth middleware - unauthenticated request e.DELETE("/api/auth/account/", handler.DeleteAccount) t.Run("unauthenticated request returns 401", func(t *testing.T) { req := map[string]interface{}{ "confirmation": "DELETE", } w := testutil.MakeRequest(e, "DELETE", "/api/auth/account/", req, "") testutil.AssertStatusCode(t, w, http.StatusUnauthorized) }) }