package handlers import ( "bytes" "encoding/json" "fmt" "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/zaptest" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // setupTestPlaylistHandler creates a test handler with real services and in-memory database func setupTestPlaylistHandler(t *testing.T) (*PlaylistHandler, *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.Track{}, &models.Playlist{}, &models.PlaylistTrack{}, &models.PlaylistCollaborator{}, &models.Role{}, &models.Permission{}, &models.UserRole{}, &models.RolePermission{}, ) require.NoError(t, err) // Setup repositories playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) // Setup services playlistService := services.NewPlaylistService( playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger, ) handler := NewPlaylistHandler(playlistService, db, logger) 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 createTestUser(id uuid.UUID) *models.User { return &models.User{ ID: id, Username: "testuser", Email: "test@example.com", IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } } // Helper to create a test playlist func createTestPlaylist(id uuid.UUID, userID uuid.UUID) *models.Playlist { return &models.Playlist{ ID: id, UserID: userID, Title: "Test Playlist", Description: "Test Description", IsPublic: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } } // Helper to create a test track for playlist tests func createTestTrackForPlaylistTest(id uuid.UUID, userID uuid.UUID) *models.Track { return &models.Track{ ID: id, UserID: userID, Title: "Test Track", Artist: "Test Artist", FilePath: "/tmp/test-uploads/test.mp3", Format: "mp3", FileSize: 1024, Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, CreatedAt: time.Now(), UpdatedAt: time.Now(), } } // TestPlaylistHandler_CreatePlaylist_Success tests successful playlist creation func TestPlaylistHandler_CreatePlaylist_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user first userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) router.POST("/playlists", handler.CreatePlaylist) createReq := CreatePlaylistRequest{ Title: "My New Playlist", Description: "A test playlist", IsPublic: true, } body, _ := json.Marshal(createReq) req := httptest.NewRequest(http.MethodPost, "/playlists", 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.StatusCreated, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestPlaylistHandler_GetPlaylist_Success tests successful playlist retrieval func TestPlaylistHandler_GetPlaylist_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user and playlist userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) playlistID := uuid.New() playlist := createTestPlaylist(playlistID, userID) err = db.Create(playlist).Error require.NoError(t, err) router.GET("/playlists/:id", handler.GetPlaylist) req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/playlists/%s", playlistID.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)) } // TestPlaylistHandler_GetPlaylist_NotFound tests playlist not found scenario func TestPlaylistHandler_GetPlaylist_NotFound(t *testing.T) { handler, _, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() router.GET("/playlists/:id", handler.GetPlaylist) nonExistentID := uuid.New() req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/playlists/%s", nonExistentID.String()), nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } // TestPlaylistHandler_GetPlaylist_InvalidID tests invalid playlist ID format func TestPlaylistHandler_GetPlaylist_InvalidID(t *testing.T) { handler, _, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() router.GET("/playlists/:id", handler.GetPlaylist) req := httptest.NewRequest(http.MethodGet, "/playlists/invalid-id", nil) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // TestPlaylistHandler_GetPlaylists_Success tests successful playlist listing func TestPlaylistHandler_GetPlaylists_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user first userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) // Create test playlists for i := 0; i < 3; i++ { playlist := createTestPlaylist(uuid.New(), userID) playlist.Title = fmt.Sprintf("Playlist %d", i+1) err := db.Create(playlist).Error require.NoError(t, err) } router.GET("/playlists", handler.GetPlaylists) req := httptest.NewRequest(http.MethodGet, "/playlists?page=1&limit=10", 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)) } // TestPlaylistHandler_UpdatePlaylist_Success tests successful playlist update func TestPlaylistHandler_UpdatePlaylist_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user and playlist userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) playlistID := uuid.New() playlist := createTestPlaylist(playlistID, userID) err = db.Create(playlist).Error require.NoError(t, err) router.PUT("/playlists/:id", handler.UpdatePlaylist) title := "Updated Title" updateReq := UpdatePlaylistRequest{ Title: &title, } body, _ := json.Marshal(updateReq) req := httptest.NewRequest(http.MethodPut, fmt.Sprintf("/playlists/%s", playlistID.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)) } // TestPlaylistHandler_DeletePlaylist_Success tests successful playlist deletion func TestPlaylistHandler_DeletePlaylist_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user and playlist userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) playlistID := uuid.New() playlist := createTestPlaylist(playlistID, userID) err = db.Create(playlist).Error require.NoError(t, err) router.DELETE("/playlists/:id", handler.DeletePlaylist) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/playlists/%s", playlistID.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)) } // TestPlaylistHandler_AddTrack_Success tests successful track addition to playlist func TestPlaylistHandler_AddTrack_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user, playlist, and track userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) playlistID := uuid.New() playlist := createTestPlaylist(playlistID, userID) err = db.Create(playlist).Error require.NoError(t, err) trackID := uuid.New() track := createTestTrackForPlaylistTest(trackID, userID) err = db.Create(track).Error require.NoError(t, err) router.POST("/playlists/:id/tracks/:trackId", handler.AddTrack) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/playlists/%s/tracks/%s", playlistID.String(), trackID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } // TestPlaylistHandler_RemoveTrack_Success tests successful track removal from playlist func TestPlaylistHandler_RemoveTrack_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test user, playlist, and track userID := uuid.New() user := createTestUser(userID) err := db.Create(user).Error require.NoError(t, err) playlistID := uuid.New() playlist := createTestPlaylist(playlistID, userID) err = db.Create(playlist).Error require.NoError(t, err) trackID := uuid.New() track := createTestTrackForPlaylistTest(trackID, userID) err = db.Create(track).Error require.NoError(t, err) // Add track to playlist first playlistTrack := &models.PlaylistTrack{ ID: uuid.New(), PlaylistID: playlistID, TrackID: trackID, Position: 0, AddedBy: userID, } err = db.Create(playlistTrack).Error require.NoError(t, err) router.DELETE("/playlists/:id/tracks/:trackId", handler.RemoveTrack) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/playlists/%s/tracks/%s", playlistID.String(), trackID.String()), nil) req.Header.Set("X-User-ID", userID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } // TestPlaylistHandler_AddCollaborator_Success tests successful collaborator addition func TestPlaylistHandler_AddCollaborator_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test users ownerID := uuid.New() owner := createTestUser(ownerID) err := db.Create(owner).Error require.NoError(t, err) collaboratorID := uuid.New() collaborator := createTestUser(collaboratorID) collaborator.Username = "collaborator" collaborator.Email = "collaborator@example.com" err = db.Create(collaborator).Error require.NoError(t, err) // Create playlist playlistID := uuid.New() playlist := createTestPlaylist(playlistID, ownerID) err = db.Create(playlist).Error require.NoError(t, err) router.POST("/playlists/:id/collaborators", handler.AddCollaborator) addReq := AddCollaboratorRequest{ UserID: collaboratorID, Permission: "write", } body, _ := json.Marshal(addReq) req := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/playlists/%s/collaborators", playlistID.String()), bytes.NewBuffer(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-User-ID", ownerID.String()) w := httptest.NewRecorder() router.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) var response map[string]interface{} err = json.Unmarshal(w.Body.Bytes(), &response) require.NoError(t, err) assert.True(t, response["success"].(bool)) } // TestPlaylistHandler_GetCollaborators_Success tests successful collaborator listing func TestPlaylistHandler_GetCollaborators_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test users ownerID := uuid.New() owner := createTestUser(ownerID) err := db.Create(owner).Error require.NoError(t, err) collaboratorID := uuid.New() collaborator := createTestUser(collaboratorID) collaborator.Username = "collaborator" collaborator.Email = "collaborator@example.com" err = db.Create(collaborator).Error require.NoError(t, err) // Create playlist playlistID := uuid.New() playlist := createTestPlaylist(playlistID, ownerID) err = db.Create(playlist).Error require.NoError(t, err) // Add collaborator playlistCollaborator := &models.PlaylistCollaborator{ PlaylistID: playlistID, UserID: collaboratorID, Permission: models.PlaylistPermissionWrite, CreatedAt: time.Now(), } err = db.Create(playlistCollaborator).Error require.NoError(t, err) router.GET("/playlists/:id/collaborators", handler.GetCollaborators) req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/playlists/%s/collaborators", playlistID.String()), nil) req.Header.Set("X-User-ID", ownerID.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)) } // TestPlaylistHandler_RemoveCollaborator_Success tests successful collaborator removal func TestPlaylistHandler_RemoveCollaborator_Success(t *testing.T) { handler, db, router, cleanup := setupTestPlaylistHandler(t) defer cleanup() // Create test users ownerID := uuid.New() owner := createTestUser(ownerID) err := db.Create(owner).Error require.NoError(t, err) collaboratorID := uuid.New() collaborator := createTestUser(collaboratorID) collaborator.Username = "collaborator" collaborator.Email = "collaborator@example.com" err = db.Create(collaborator).Error require.NoError(t, err) // Create playlist playlistID := uuid.New() playlist := createTestPlaylist(playlistID, ownerID) err = db.Create(playlist).Error require.NoError(t, err) // Add collaborator playlistCollaborator := &models.PlaylistCollaborator{ PlaylistID: playlistID, UserID: collaboratorID, Permission: models.PlaylistPermissionWrite, CreatedAt: time.Now(), } err = db.Create(playlistCollaborator).Error require.NoError(t, err) router.DELETE("/playlists/:id/collaborators/:userId", handler.RemoveCollaborator) req := httptest.NewRequest(http.MethodDelete, fmt.Sprintf("/playlists/%s/collaborators/%s", playlistID.String(), collaboratorID.String()), nil) req.Header.Set("X-User-ID", ownerID.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)) }