package handlers import ( "bytes" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" "veza-backend-api/internal/database" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // setupTestProfileHandler creates a test handler with real services and in-memory database func setupTestProfileHandler(t *testing.T) (*ProfileHandler, *gorm.DB, *gin.Engine, func()) { gin.SetMode(gin.TestMode) logger := zaptest.NewLogger(t) // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) db.Exec("PRAGMA foreign_keys = ON") // Auto-migrate models err = db.AutoMigrate( &models.User{}, &models.Role{}, &models.Permission{}, &models.UserRole{}, &models.RolePermission{}, ) require.NoError(t, err) // Create follows and user_blocks tables for SocialService (uses raw SQL) sqlDB, err := db.DB() require.NoError(t, err) _, err = sqlDB.Exec(` CREATE TABLE IF NOT EXISTS follows ( id TEXT PRIMARY KEY, follower_id TEXT NOT NULL, followed_id TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(follower_id, followed_id) ) `) require.NoError(t, err) _, err = sqlDB.Exec(` CREATE TABLE IF NOT EXISTS user_blocks ( id TEXT PRIMARY KEY, blocker_id TEXT NOT NULL, blocked_id TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(blocker_id, blocked_id) ) `) require.NoError(t, err) // Setup repositories userRepo := repositories.NewGormUserRepository(db) // Setup services userService := services.NewUserServiceWithDB(userRepo, db) permissionService := services.NewPermissionService(db) // Create database wrapper for SocialService // Note: SocialService uses raw SQL, so we need to get the underlying sql.DB sqlDB, err = db.DB() require.NoError(t, err) dbWrapper := &database.Database{} dbWrapper.DB = sqlDB dbWrapper.GormDB = db socialService := services.NewSocialService(dbWrapper, logger) handler := NewProfileHandler(userService, logger) handler.SetSocialService(socialService) handler.SetPermissionService(permissionService) router := gin.New() router.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 { c.Set("user_id", uid) } } c.Next() }) cleanup := func() { // Database cleanup handled by test } return handler, db, router, cleanup } // Helper to create a test user func createTestUserForProfile(id uuid.UUID, username string) *models.User { return &models.User{ ID: id, Username: username, Email: fmt.Sprintf("%s@example.com", username), IsActive: true, IsVerified: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } } // TestProfileHandler_GetProfile_Success tests successful profile retrieval func TestProfileHandler_GetProfile_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.GET("/users/:id", handler.GetProfile) req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s", userID.String()), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_GetProfile_NotFound tests profile not found scenario func TestProfileHandler_GetProfile_NotFound(t *testing.T) { handler, _, router, cleanup := setupTestProfileHandler(t) defer cleanup() router.GET("/users/:id", handler.GetProfile) nonExistentID := uuid.New() req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s", nonExistentID.String()), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // TestProfileHandler_GetProfile_InvalidID tests invalid user ID format func TestProfileHandler_GetProfile_InvalidID(t *testing.T) { handler, _, router, cleanup := setupTestProfileHandler(t) defer cleanup() router.GET("/users/:id", handler.GetProfile) req := httptest.NewRequest(http.MethodGet, "/users/invalid-id", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestProfileHandler_GetProfileByUsername_Success tests successful profile retrieval by username func TestProfileHandler_GetProfileByUsername_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.GET("/users/by-username/:username", handler.GetProfileByUsername) req := httptest.NewRequest(http.MethodGet, "/users/by-username/testuser", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_GetProfileByUsername_NotFound tests username not found scenario func TestProfileHandler_GetProfileByUsername_NotFound(t *testing.T) { handler, _, router, cleanup := setupTestProfileHandler(t) defer cleanup() router.GET("/users/by-username/:username", handler.GetProfileByUsername) req := httptest.NewRequest(http.MethodGet, "/users/by-username/nonexistent", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // TestProfileHandler_ListUsers_Success tests successful user listing func TestProfileHandler_ListUsers_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users for i := 0; i < 3; i++ { user := createTestUserForProfile(uuid.New(), fmt.Sprintf("user%d", i+1)) err := db.Create(user).Error require.NoError(t, err) } router.GET("/users", handler.ListUsers) req := httptest.NewRequest(http.MethodGet, "/users?page=1&limit=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_SearchUsers_Success tests successful user search func TestProfileHandler_SearchUsers_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.GET("/users/search", handler.SearchUsers) req := httptest.NewRequest(http.MethodGet, "/users/search?q=test&page=1&limit=10", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_UpdateProfile_Success tests successful profile update func TestProfileHandler_UpdateProfile_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.PUT("/users/:id", handler.UpdateProfile) updateReq := UpdateProfileRequest{ FirstName: "John", LastName: "Doe", Bio: "Test bio", } body, _ := json.Marshal(updateReq) req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/users/%s", userID.String()), bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_UpdateProfile_Forbidden tests forbidden profile update (different user) func TestProfileHandler_UpdateProfile_Forbidden(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users userID1 := uuid.New() user1 := createTestUserForProfile(userID1, "user1") err := db.Create(user1).Error require.NoError(t, err) userID2 := uuid.New() user2 := createTestUserForProfile(userID2, "user2") err = db.Create(user2).Error require.NoError(t, err) router.PUT("/users/:id", handler.UpdateProfile) updateReq := UpdateProfileRequest{ FirstName: "John", } body, _ := json.Marshal(updateReq) // Try to update user1's profile as user2 req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/users/%s", userID1.String()), bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", userID2.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) } // TestProfileHandler_GetProfileCompletion_Success tests successful profile completion retrieval func TestProfileHandler_GetProfileCompletion_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.GET("/users/:id/completion", handler.GetProfileCompletion) req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s/completion", userID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_GetProfileCompletion_Forbidden tests forbidden access to other user's completion func TestProfileHandler_GetProfileCompletion_Forbidden(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users userID1 := uuid.New() user1 := createTestUserForProfile(userID1, "user1") err := db.Create(user1).Error require.NoError(t, err) userID2 := uuid.New() user2 := createTestUserForProfile(userID2, "user2") err = db.Create(user2).Error require.NoError(t, err) router.GET("/users/:id/completion", handler.GetProfileCompletion) // Try to access user1's completion as user2 req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/users/%s/completion", userID1.String()), nil) req.Header.Set("X-User-ID", userID2.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) } // TestProfileHandler_FollowUser_Success tests successful user follow func TestProfileHandler_FollowUser_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users followerID := uuid.New() follower := createTestUserForProfile(followerID, "follower") err := db.Create(follower).Error require.NoError(t, err) followedID := uuid.New() followed := createTestUserForProfile(followedID, "followed") err = db.Create(followed).Error require.NoError(t, err) router.POST("/users/:id/follow", handler.FollowUser) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/follow", followedID.String()), nil) req.Header.Set("X-User-ID", followerID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_FollowUser_CannotFollowSelf tests that user cannot follow themselves func TestProfileHandler_FollowUser_CannotFollowSelf(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/users/:id/follow", handler.FollowUser) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/follow", userID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestProfileHandler_UnfollowUser_Success tests successful user unfollow func TestProfileHandler_UnfollowUser_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users followerID := uuid.New() follower := createTestUserForProfile(followerID, "follower") err := db.Create(follower).Error require.NoError(t, err) followedID := uuid.New() followed := createTestUserForProfile(followedID, "followed") err = db.Create(followed).Error require.NoError(t, err) // Create follow relationship using raw SQL (follows table) sqlDB, err := db.DB() require.NoError(t, err) _, err = sqlDB.Exec("INSERT INTO follows (follower_id, followed_id) VALUES (?, ?)", followerID, followedID) require.NoError(t, err) router.DELETE("/users/:id/follow", handler.UnfollowUser) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s/follow", followedID.String()), nil) req.Header.Set("X-User-ID", followerID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_BlockUser_Success tests successful user block func TestProfileHandler_BlockUser_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users blockerID := uuid.New() blocker := createTestUserForProfile(blockerID, "blocker") err := db.Create(blocker).Error require.NoError(t, err) blockedID := uuid.New() blocked := createTestUserForProfile(blockedID, "blocked") err = db.Create(blocked).Error require.NoError(t, err) router.POST("/users/:id/block", handler.BlockUser) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/block", blockedID.String()), nil) req.Header.Set("X-User-ID", blockerID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_BlockUser_CannotBlockSelf tests that user cannot block themselves func TestProfileHandler_BlockUser_CannotBlockSelf(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.POST("/users/:id/block", handler.BlockUser) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/users/%s/block", userID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestProfileHandler_UnblockUser_Success tests successful user unblock func TestProfileHandler_UnblockUser_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users blockerID := uuid.New() blocker := createTestUserForProfile(blockerID, "blocker") err := db.Create(blocker).Error require.NoError(t, err) blockedID := uuid.New() blocked := createTestUserForProfile(blockedID, "blocked") err = db.Create(blocked).Error require.NoError(t, err) // Create block relationship using raw SQL (user_blocks table) sqlDB, err := db.DB() require.NoError(t, err) _, err = sqlDB.Exec("INSERT INTO user_blocks (blocker_id, blocked_id) VALUES (?, ?)", blockerID, blockedID) require.NoError(t, err) router.DELETE("/users/:id/block", handler.UnblockUser) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s/block", blockedID.String()), nil) req.Header.Set("X-User-ID", blockerID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_DeleteUser_Success tests successful user deletion func TestProfileHandler_DeleteUser_Success(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test user userID := uuid.New() user := createTestUserForProfile(userID, "testuser") err := db.Create(user).Error require.NoError(t, err) router.DELETE("/users/:id", handler.DeleteUser) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s", userID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestProfileHandler_DeleteUser_Forbidden tests forbidden user deletion (different user) func TestProfileHandler_DeleteUser_Forbidden(t *testing.T) { handler, db, router, cleanup := setupTestProfileHandler(t) defer cleanup() // Create test users userID1 := uuid.New() user1 := createTestUserForProfile(userID1, "user1") err := db.Create(user1).Error require.NoError(t, err) userID2 := uuid.New() user2 := createTestUserForProfile(userID2, "user2") err = db.Create(user2).Error require.NoError(t, err) router.DELETE("/users/:id", handler.DeleteUser) // Try to delete user1 as user2 req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/users/%s", userID1.String()), nil) req.Header.Set("X-User-ID", userID2.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusForbidden, w.Code) }