veza/veza-backend-api/internal/api/user/handler_test.go
senke b28d0e7eac [T0-006] test(backend): Ajout tests pour frontend_log_handler
- Tests complets pour frontend_log_handler.go (12 tests)
- Tests couvrent NewFrontendLogHandler et ReceiveLog
- Tests pour tous les niveaux de log (DEBUG, INFO, WARN, ERROR)
- Tests pour gestion des erreurs et validation JSON
- Couverture actuelle: 30.6% (objectif: 80%)

Files: veza-backend-api/internal/handlers/frontend_log_handler_test.go
       VEZA_ROADMAP.json
Hours: 16 estimated, 23 actual
2026-01-04 01:44:22 +01:00

683 lines
20 KiB
Go

package user
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"veza-backend-api/internal/common"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// MockUserService is a mock implementation of Service for testing
type MockUserService struct {
mock.Mock
}
func (m *MockUserService) GetUserByID(userID uuid.UUID) (*UserResponse, error) {
args := m.Called(userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*UserResponse), args.Error(1)
}
func (m *MockUserService) GetUsers(page, limit int, search string) ([]UserResponse, int, error) {
args := m.Called(page, limit, search)
if args.Get(0) == nil {
return nil, args.Int(1), args.Error(2)
}
return args.Get(0).([]UserResponse), args.Int(1), args.Error(2)
}
func (m *MockUserService) UpdateUser(userID uuid.UUID, req UpdateUserRequest) (*UserResponse, error) {
args := m.Called(userID, req)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*UserResponse), args.Error(1)
}
func (m *MockUserService) ChangePassword(userID uuid.UUID, currentPassword, newPassword string) error {
args := m.Called(userID, currentPassword, newPassword)
return args.Error(0)
}
func (m *MockUserService) GetUserPreferences(userID uuid.UUID) (*UserPreferencesResponse, error) {
args := m.Called(userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*UserPreferencesResponse), args.Error(1)
}
func (m *MockUserService) UpdateUserPreferences(userID uuid.UUID, req UserPreferencesRequest) (*UserPreferencesResponse, error) {
args := m.Called(userID, req)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*UserPreferencesResponse), args.Error(1)
}
func (m *MockUserService) DeleteAccount(userID uuid.UUID, password, reason string) error {
args := m.Called(userID, password, reason)
return args.Error(0)
}
func (m *MockUserService) RecoverAccount(email, password string) error {
args := m.Called(email, password)
return args.Error(0)
}
func (m *MockUserService) RequestDataDeletion(userID uuid.UUID, password, reason string) error {
args := m.Called(userID, password, reason)
return args.Error(0)
}
func (m *MockUserService) GetAccountStatus(userID uuid.UUID) (*AccountStatus, error) {
args := m.Called(userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*AccountStatus), args.Error(1)
}
// MockDataExportService is a mock implementation of DataExportService for testing
type MockDataExportService struct {
mock.Mock
}
func (m *MockDataExportService) ExportUserDataAsJSON(ctx context.Context, userID uuid.UUID) ([]byte, error) {
args := m.Called(ctx, userID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).([]byte), args.Error(1)
}
// setupTestUserRouter creates a test router with user handlers
func setupTestUserRouter(mockService *MockUserService, mockDataExportService *MockDataExportService) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
handler := NewHandlerWithInterfaces(mockService, mockDataExportService)
api := router.Group("/api/v1")
api.Use(func(c *gin.Context) {
// Mock auth middleware - set user_id from header if present
userIDStr := c.GetHeader("X-User-ID")
if userIDStr != "" {
uid, err := uuid.Parse(userIDStr)
if err == nil {
common.SetUserIDInContext(c, uid)
}
}
c.Next()
})
users := api.Group("/users")
{
users.GET("/me", handler.GetMe)
users.PATCH("/me", handler.UpdateMe)
users.POST("/me/password", handler.ChangePassword)
users.GET("", handler.GetUsers)
users.GET("/except-me", handler.GetUsersExceptMe)
users.GET("/search", handler.SearchUsers)
users.GET("/:id/avatar", handler.GetUserAvatar)
users.GET("/me/preferences", handler.GetPreferences)
users.PATCH("/me/preferences", handler.UpdatePreferences)
users.DELETE("/me", handler.DeleteAccount)
users.POST("/recover", handler.RecoverAccount)
users.GET("/me/export", handler.ExportData)
users.POST("/me/delete-request", handler.RequestDataDeletion)
users.GET("/me/status", handler.GetAccountStatus)
}
return router
}
func TestUserHandler_GetMe_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
expectedUser := &UserResponse{
ID: userID,
Email: "test@example.com",
Username: "testuser",
IsActive: true,
IsVerified: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
mockService.On("GetUserByID", userID).Return(expectedUser, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/me", nil)
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestUserHandler_GetMe_Unauthorized(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/me", nil)
// No X-User-ID header
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
func TestUserHandler_GetMe_NotFound(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
mockService.On("GetUserByID", userID).Return(nil, assert.AnError)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/me", nil)
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_UpdateMe_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
username := "newusername"
reqBody := UpdateUserRequest{
Username: &username,
}
expectedUser := &UserResponse{
ID: userID,
Username: username,
Email: "test@example.com",
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
mockService.On("UpdateUser", userID, reqBody).Return(expectedUser, nil)
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/users/me", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestUserHandler_ChangePassword_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
reqBody := map[string]interface{}{
"current_password": "oldpass123",
"new_password": "newpass123",
}
mockService.On("ChangePassword", userID, "oldpass123", "newpass123").Return(nil)
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users/me/password", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_ChangePassword_InvalidRequest(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
reqBody := map[string]interface{}{
"current_password": "oldpass123",
// Missing new_password
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users/me/password", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestUserHandler_GetUsers_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
expectedUsers := []UserResponse{
{
ID: uuid.New(),
Username: "user1",
Email: "user1@example.com",
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
{
ID: uuid.New(),
Username: "user2",
Email: "user2@example.com",
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}
mockService.On("GetUsers", 1, 20, "").Return(expectedUsers, 2, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users?page=1&limit=20", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestUserHandler_SearchUsers_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
query := "test"
expectedUsers := []UserResponse{
{
ID: uuid.New(),
Username: "testuser",
Email: "test@example.com",
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}
mockService.On("GetUsers", 1, 20, query).Return(expectedUsers, 1, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/search?q=test", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_SearchUsers_MissingQuery(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/search", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestUserHandler_GetUserAvatar_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
avatarURL := "https://example.com/avatar.jpg"
expectedUser := &UserResponse{
ID: userID,
Username: "testuser",
Email: "test@example.com",
Avatar: sql.NullString{
String: avatarURL,
Valid: true,
},
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
mockService.On("GetUserByID", userID).Return(expectedUser, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/avatar", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusFound, w.Code)
assert.Equal(t, avatarURL, w.Header().Get("Location"))
mockService.AssertExpectations(t)
}
func TestUserHandler_GetUserAvatar_NoAvatar(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
expectedUser := &UserResponse{
ID: userID,
Username: "testuser",
Email: "test@example.com",
Avatar: sql.NullString{
Valid: false,
},
IsActive: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
mockService.On("GetUserByID", userID).Return(expectedUser, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/"+userID.String()+"/avatar", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_GetPreferences_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
expectedPreferences := &UserPreferencesResponse{
UserID: userID,
Theme: "dark",
Language: "en",
Timezone: "UTC",
Notifications: NotificationSettings{
Email: true,
Push: false,
},
Privacy: PrivacySettings{
ShowEmail: false,
},
Audio: AudioSettings{
AutoPlay: true,
Quality: "high",
Volume: 0.8,
},
UpdatedAt: time.Now(),
}
mockService.On("GetUserPreferences", userID).Return(expectedPreferences, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/me/preferences", nil)
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestUserHandler_DeleteAccount_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
reqBody := map[string]interface{}{
"password": "password123",
"reason": "No longer needed",
"confirm_text": "DELETE",
}
mockService.On("DeleteAccount", userID, "password123", "No longer needed").Return(nil)
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/users/me", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_DeleteAccount_InvalidConfirmText(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
reqBody := map[string]interface{}{
"password": "password123",
"reason": "No longer needed",
"confirm_text": "WRONG",
}
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/users/me", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestUserHandler_ExportData_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
exportData := []byte(`{"user_id":"` + userID.String() + `","profile":{}}`)
mockDataExportService.On("ExportUserDataAsJSON", mock.Anything, userID).Return(exportData, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/me/export", nil)
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
assert.Contains(t, w.Header().Get("Content-Disposition"), "attachment")
mockDataExportService.AssertExpectations(t)
}
func TestUserHandler_GetAccountStatus_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
expectedStatus := &AccountStatus{
UserID: userID,
Status: "active",
IsActive: true,
IsVerified: true,
CreatedAt: time.Now(),
}
mockService.On("GetAccountStatus", userID).Return(expectedStatus, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/me/status", nil)
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.True(t, response["success"].(bool))
}
func TestUserHandler_RecoverAccount_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
reqBody := map[string]interface{}{
"email": "test@example.com",
"password": "password123",
}
mockService.On("RecoverAccount", "test@example.com", "password123").Return(nil)
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users/recover", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_RequestDataDeletion_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
reqBody := map[string]interface{}{
"password": "password123",
"reason": "GDPR",
}
mockService.On("RequestDataDeletion", userID, "password123", "GDPR").Return(nil)
body, _ := json.Marshal(reqBody)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/users/me/delete-request", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}
func TestUserHandler_GetUsersExceptMe_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
otherUserID := uuid.New()
users := []UserResponse{
{ID: userID, Username: "me"},
{ID: otherUserID, Username: "other"},
}
// Mock returns both users
mockService.On("GetUsers", 1, 20, "").Return(users, 2, nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/users/except-me", nil)
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Verify data only contains "other" user
// response["data"] is the payload map (gin.H)
payload := response["data"].(map[string]interface{})
// payload["data"] is the users list
data := payload["data"].([]interface{})
assert.Len(t, data, 1)
assert.Equal(t, "other", data[0].(map[string]interface{})["username"])
}
func TestUserHandler_UpdatePreferences_Success(t *testing.T) {
mockService := new(MockUserService)
mockDataExportService := new(MockDataExportService)
router := setupTestUserRouter(mockService, mockDataExportService)
userID := uuid.New()
theme := "light"
language := "fr"
reqPreference := UserPreferencesRequest{
Theme: &theme,
Language: &language,
}
expectedResponse := &UserPreferencesResponse{
UserID: userID,
Theme: "light",
Language: "fr",
}
mockService.On("UpdateUserPreferences", userID, reqPreference).Return(expectedResponse, nil)
body, _ := json.Marshal(reqPreference)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/users/me/preferences", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String())
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
mockService.AssertExpectations(t)
}