veza/veza-backend-api/internal/handlers/profile_handler_integration_test.go
senke f6ab2c6eeb [BE-API-002] api: Implement playlist collaborators endpoints
- Added routes in router.go: POST, GET, PUT, DELETE /playlists/:id/collaborators
- Applied RequireOwnershipOrAdmin middleware to POST, PUT, DELETE routes
- GET route accessible to collaborators (service layer checks permissions)
- Fixed UpdateCollaboratorPermission handler to use RespondWithAppError
- All handlers already existed in playlist_handler.go
- All endpoints properly authenticated and ownership checks enforced

Phase: PHASE-1
Priority: P0
Progress: 5/267 (1.9%)
2025-12-23 01:41:43 +01:00

266 lines
7.5 KiB
Go

//go:build integration
// +build integration
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"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"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// setupProfileIntegrationTestRouter crée un router de test pour les tests de profile
// BE-SEC-001: Tests d'intégration pour vérification ownership
func setupProfileIntegrationTestRouter(t *testing.T) (*gin.Engine, *gorm.DB, func()) {
gin.SetMode(gin.TestMode)
// Setup in-memory SQLite database
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
require.NoError(t, err)
// Enable foreign keys for SQLite
db.Exec("PRAGMA foreign_keys = ON")
// Auto-migrate - include all models needed for PermissionService
err = db.AutoMigrate(
&models.User{},
&models.Role{},
&models.Permission{},
&models.UserRole{},
&models.RolePermission{},
)
require.NoError(t, err)
// Setup logger
logger := zap.NewNop()
// Setup services
userRepo := repositories.NewGormUserRepository(db)
userService := services.NewUserServiceWithDB(userRepo, db)
profileHandler := NewProfileHandler(userService, logger)
// Setup PermissionService for admin checks
permissionService := services.NewPermissionService(db)
profileHandler.SetPermissionService(permissionService)
// Create router
router := gin.New()
v1 := router.Group("/api/v1")
{
// Protected routes with mock auth middleware
protected := v1.Group("/users")
protected.Use(func(c *gin.Context) {
// Mock auth middleware - set user_id from header
// This simulates RequireAuth() middleware
userIDStr := c.GetHeader("X-User-ID")
if userIDStr == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.Abort()
return
}
uid, err := uuid.Parse(userIDStr)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid user id"})
c.Abort()
return
}
c.Set("user_id", uid)
c.Next()
})
{
// Note: In real router, RequireOwnershipOrAdmin middleware would be here
// For this test, we're testing the handler's ownership check directly
protected.PUT("/:id", profileHandler.UpdateProfile)
}
}
cleanup := func() {
// Database will be closed automatically
}
return router, db, cleanup
}
// createTestUser crée un utilisateur de test
func createTestUserForProfile(t *testing.T, db *gorm.DB, userID uuid.UUID, username string, isAdmin bool) *models.User {
user := &models.User{
ID: userID,
Username: username,
Slug: username,
Email: username + "@example.com",
PasswordHash: "hashed_password",
IsActive: true,
CreatedAt: time.Now(),
}
if isAdmin {
user.Role = "admin"
user.IsAdmin = true
} else {
user.Role = "user"
}
// Create user first
err := db.Create(user).Error
require.NoError(t, err)
// Then create admin role and assign it if admin
if isAdmin {
// Create admin role and assign it to user
adminRole := &models.Role{
Name: "admin",
DisplayName: "Administrator",
IsSystem: true,
IsActive: true,
}
err = db.FirstOrCreate(adminRole, models.Role{Name: "admin"}).Error
require.NoError(t, err)
userRole := &models.UserRole{
UserID: userID,
RoleID: adminRole.ID,
RoleName: "admin",
IsActive: true,
}
err = db.Create(userRole).Error
require.NoError(t, err)
}
return user
}
// BE-SEC-001: Test that user cannot update another user's profile
func TestUpdateProfile_UserCannotUpdateOtherUserProfile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
router, db, cleanup := setupProfileIntegrationTestRouter(t)
defer cleanup()
// Create two users
ownerID := uuid.New()
otherUserID := uuid.New()
createTestUserForProfile(t, db, ownerID, "owner", false)
createTestUserForProfile(t, db, otherUserID, "otheruser", false)
// Try to update owner's profile as otherUser
updateReq := UpdateProfileRequest{
FirstName: "Hacked",
Bio: "I shouldn't be able to do this",
}
body, _ := json.Marshal(updateReq)
req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+ownerID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", otherUserID.String()) // otherUser is authenticated
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Should return 403 Forbidden
assert.Equal(t, http.StatusForbidden, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
// Error can be a string or a map, check both
if errorStr, ok := response["error"].(string); ok {
assert.Contains(t, errorStr, "cannot update other user's profile")
} else if errorMap, ok := response["error"].(map[string]interface{}); ok {
if message, ok := errorMap["message"].(string); ok {
assert.Contains(t, message, "cannot update other user's profile")
}
}
}
// BE-SEC-001: Test that admin can update any profile
func TestUpdateProfile_AdminCanUpdateAnyProfile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
router, db, cleanup := setupProfileIntegrationTestRouter(t)
defer cleanup()
// Create two users: owner and admin
ownerID := uuid.New()
adminID := uuid.New()
createTestUserForProfile(t, db, ownerID, "owner", false)
createTestUserForProfile(t, db, adminID, "admin", true)
// Admin tries to update owner's profile
updateReq := UpdateProfileRequest{
FirstName: "Updated by Admin",
Bio: "Admin updated this profile",
}
body, _ := json.Marshal(updateReq)
req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+ownerID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", adminID.String()) // admin is authenticated
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// Admin should be able to update
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.NotNil(t, response["data"])
}
// BE-SEC-001: Test that user can update their own profile
func TestUpdateProfile_UserCanUpdateOwnProfile(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
router, db, cleanup := setupProfileIntegrationTestRouter(t)
defer cleanup()
// Create a user
userID := uuid.New()
createTestUserForProfile(t, db, userID, "testuser", false)
// User tries to update their own profile
updateReq := UpdateProfileRequest{
FirstName: "Updated First Name",
Bio: "Updated bio",
}
body, _ := json.Marshal(updateReq)
req := httptest.NewRequest(http.MethodPut, "/api/v1/users/"+userID.String(), bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-User-ID", userID.String()) // user is authenticated
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
// User should be able to update their own profile
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.NotNil(t, response["data"])
// Verify the profile was actually updated
profileData := response["data"].(map[string]interface{})
profile := profileData["profile"].(map[string]interface{})
assert.Equal(t, "Updated First Name", profile["first_name"])
}