package services import ( "context" "github.com/google/uuid" "testing" "github.com/stretchr/testify/assert" "go.uber.org/zap" "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" ) func setupTestTrackServiceForList(t *testing.T) (*TrackService, *gorm.DB, func()) { // Setup in-memory SQLite database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) assert.NoError(t, err) // Auto-migrate err = db.AutoMigrate(&models.Track{}, &models.User{}) assert.NoError(t, err) // Create test users user1 := &models.User{ ID: 123, Username: "testuser1", Email: "test1@example.com", IsActive: true, } err = db.Create(user1).Error assert.NoError(t, err) user2 := &models.User{ ID: 456, Username: "testuser2", Email: "test2@example.com", IsActive: true, } err = db.Create(user2).Error assert.NoError(t, err) // Setup logger logger := zap.NewNop() // Setup test service service := NewTrackService(db, logger, "test_uploads/tracks") // Cleanup function cleanup := func() { // Database will be closed automatically } return service, db, cleanup } func TestTrackService_ListTracks_Success(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer quelques tracks avec statut completed track1 := &models.Track{ UserID: 123, Title: "Track 1", FilePath: "/test/track1.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Genre: "Rock", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 10, LikeCount: 5, } err := db.Create(track1).Error assert.NoError(t, err) track2 := &models.Track{ UserID: 123, Title: "Track 2", FilePath: "/test/track2.flac", FileSize: 10 * 1024 * 1024, Format: "FLAC", Genre: "Jazz", Duration: 200, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 20, LikeCount: 10, } err = db.Create(track2).Error assert.NoError(t, err) track3 := &models.Track{ UserID: 456, Title: "Track 3", FilePath: "/test/track3.mp3", FileSize: 3 * 1024 * 1024, Format: "MP3", Genre: "Rock", Duration: 150, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 5, LikeCount: 2, } err = db.Create(track3).Error assert.NoError(t, err) // Track avec statut uploading (ne doit pas apparaître) track4 := &models.Track{ UserID: 123, Title: "Track 4", FilePath: "/test/track4.mp3", FileSize: 2 * 1024 * 1024, Format: "MP3", Duration: 100, IsPublic: true, Status: models.TrackStatusUploading, } err = db.Create(track4).Error assert.NoError(t, err) // Test: Lister tous les tracks params := TrackListParams{ Page: 1, Limit: 20, SortBy: "created_at", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(3), total) // Seulement les tracks completed assert.Len(t, tracks, 3) } func TestTrackService_ListTracks_WithPagination(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer 5 tracks for i := 1; i <= 5; i++ { track := &models.Track{ UserID: 123, Title: "Track " + string(rune('0'+i)), FilePath: "/test/track" + string(rune('0'+i)) + ".mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) } // Test: Page 1, limit 2 params := TrackListParams{ Page: 1, Limit: 2, SortBy: "created_at", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, tracks, 2) // Test: Page 2, limit 2 params.Page = 2 tracks, total, err = service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, tracks, 2) // Test: Page 3, limit 2 params.Page = 3 tracks, total, err = service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(5), total) assert.Len(t, tracks, 1) // Dernière page avec 1 track } func TestTrackService_ListTracks_WithUserFilter(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer tracks pour deux utilisateurs track1 := &models.Track{ UserID: 123, Title: "Track User 1", FilePath: "/test/track1.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error assert.NoError(t, err) track2 := &models.Track{ UserID: 456, Title: "Track User 2", FilePath: "/test/track2.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track2).Error assert.NoError(t, err) // Test: Filtrer par user_id userID := int64(123) params := TrackListParams{ Page: 1, Limit: 20, UserID: &userID, SortBy: "created_at", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(1), total) assert.Len(t, tracks, 1) assert.Equal(t, int64(123), tracks[0].UserID) } func TestTrackService_ListTracks_WithGenreFilter(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer tracks avec différents genres track1 := &models.Track{ UserID: 123, Title: "Rock Track", FilePath: "/test/track1.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Genre: "Rock", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error assert.NoError(t, err) track2 := &models.Track{ UserID: 123, Title: "Jazz Track", FilePath: "/test/track2.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Genre: "Jazz", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track2).Error assert.NoError(t, err) // Test: Filtrer par genre genre := "Rock" params := TrackListParams{ Page: 1, Limit: 20, Genre: &genre, SortBy: "created_at", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(1), total) assert.Len(t, tracks, 1) assert.Equal(t, "Rock", tracks[0].Genre) } func TestTrackService_ListTracks_WithFormatFilter(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer tracks avec différents formats track1 := &models.Track{ UserID: 123, Title: "MP3 Track", FilePath: "/test/track1.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error assert.NoError(t, err) track2 := &models.Track{ UserID: 123, Title: "FLAC Track", FilePath: "/test/track2.flac", FileSize: 10 * 1024 * 1024, Format: "FLAC", Duration: 200, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track2).Error assert.NoError(t, err) // Test: Filtrer par format format := "FLAC" params := TrackListParams{ Page: 1, Limit: 20, Format: &format, SortBy: "created_at", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(1), total) assert.Len(t, tracks, 1) assert.Equal(t, "FLAC", tracks[0].Format) } func TestTrackService_ListTracks_WithSorting(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer tracks avec différents titres track1 := &models.Track{ UserID: 123, Title: "A Track", FilePath: "/test/track1.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track1).Error assert.NoError(t, err) track2 := &models.Track{ UserID: 123, Title: "Z Track", FilePath: "/test/track2.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track2).Error assert.NoError(t, err) track3 := &models.Track{ UserID: 123, Title: "M Track", FilePath: "/test/track3.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err = db.Create(track3).Error assert.NoError(t, err) // Test: Trier par titre asc params := TrackListParams{ Page: 1, Limit: 20, SortBy: "title", SortOrder: "asc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(3), total) assert.Len(t, tracks, 3) assert.Equal(t, "A Track", tracks[0].Title) assert.Equal(t, "M Track", tracks[1].Title) assert.Equal(t, "Z Track", tracks[2].Title) } func TestTrackService_ListTracks_WithPopularitySort(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer tracks avec différentes popularités track1 := &models.Track{ UserID: 123, Title: "Low Popularity", FilePath: "/test/track1.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 5, LikeCount: 2, } err := db.Create(track1).Error assert.NoError(t, err) track2 := &models.Track{ UserID: 123, Title: "High Popularity", FilePath: "/test/track2.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 50, LikeCount: 20, } err = db.Create(track2).Error assert.NoError(t, err) track3 := &models.Track{ UserID: 123, Title: "Medium Popularity", FilePath: "/test/track3.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, PlayCount: 20, LikeCount: 10, } err = db.Create(track3).Error assert.NoError(t, err) // Test: Trier par popularité desc params := TrackListParams{ Page: 1, Limit: 20, SortBy: "popularity", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(3), total) assert.Len(t, tracks, 3) // Vérifier que le plus populaire est en premier (70 = 50 + 20) assert.Equal(t, "High Popularity", tracks[0].Title) } func TestTrackService_ListTracks_DefaultValues(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Test: Paramètres par défaut (page 0, limit 0) params := TrackListParams{ Page: 0, Limit: 0, } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(1), total) assert.Len(t, tracks, 1) // Vérifier que les valeurs par défaut sont appliquées // Page devrait être 1, limit devrait être 20 } func TestTrackService_ListTracks_MaxLimit(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer 150 tracks for i := 1; i <= 150; i++ { track := &models.Track{ UserID: 123, Title: "Track " + string(rune('0'+(i%10))), FilePath: "/test/track" + string(rune('0'+(i%10))) + ".mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) } // Test: Limit supérieur à 100 devrait être limité à 100 params := TrackListParams{ Page: 1, Limit: 200, SortBy: "created_at", SortOrder: "desc", } tracks, total, err := service.ListTracks(ctx, params) assert.NoError(t, err) assert.Equal(t, int64(150), total) assert.LessOrEqual(t, len(tracks), 100) // Maximum 100 } func TestTrackService_GetTrackByID_Success(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Genre: "Rock", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Récupérer le track retrievedTrack, err := service.GetTrackByID(ctx, track.ID) assert.NoError(t, err) assert.NotNil(t, retrievedTrack) assert.Equal(t, track.ID, retrievedTrack.ID) assert.Equal(t, track.Title, retrievedTrack.Title) assert.Equal(t, track.UserID, retrievedTrack.UserID) } func TestTrackService_GetTrackByID_NotFound(t *testing.T) { service, _, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Essayer de récupérer un track qui n'existe pas _, err := service.GetTrackByID(ctx, 99999) assert.Error(t, err) assert.Equal(t, ErrTrackNotFound, err) } func TestTrackService_UpdateTrack_Success(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Original Title", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Genre: "Rock", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Mettre à jour le track newTitle := "Updated Title" newGenre := "Jazz" params := UpdateTrackParams{ Title: &newTitle, Genre: &newGenre, } updatedTrack, err := service.UpdateTrack(ctx, track.ID, 123, params) assert.NoError(t, err) assert.NotNil(t, updatedTrack) assert.Equal(t, "Updated Title", updatedTrack.Title) assert.Equal(t, "Jazz", updatedTrack.Genre) assert.Equal(t, track.ID, updatedTrack.ID) } func TestTrackService_UpdateTrack_NotFound(t *testing.T) { service, _, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() newTitle := "Updated Title" params := UpdateTrackParams{ Title: &newTitle, } _, err := service.UpdateTrack(ctx, 99999, 123, params) assert.Error(t, err) assert.Equal(t, ErrTrackNotFound, err) } func TestTrackService_UpdateTrack_Forbidden(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track appartenant à l'utilisateur 123 track := &models.Track{ UserID: 123, Title: "Original Title", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Essayer de mettre à jour avec un autre utilisateur newTitle := "Updated Title" params := UpdateTrackParams{ Title: &newTitle, } _, err = service.UpdateTrack(ctx, track.ID, 456, params) assert.Error(t, err) assert.Equal(t, ErrForbidden, err) } func TestTrackService_UpdateTrack_EmptyTitle(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Original Title", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Essayer de mettre à jour avec un titre vide emptyTitle := "" params := UpdateTrackParams{ Title: &emptyTitle, } _, err = service.UpdateTrack(ctx, track.ID, 123, params) assert.Error(t, err) assert.Contains(t, err.Error(), "title cannot be empty") } func TestTrackService_UpdateTrack_NegativeYear(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Original Title", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Essayer de mettre à jour avec une année négative negativeYear := -1 params := UpdateTrackParams{ Year: &negativeYear, } _, err = service.UpdateTrack(ctx, track.ID, 123, params) assert.Error(t, err) assert.Contains(t, err.Error(), "year cannot be negative") } func TestTrackService_UpdateTrack_NoUpdates(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Original Title", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Mettre à jour sans aucun paramètre params := UpdateTrackParams{} updatedTrack, err := service.UpdateTrack(ctx, track.ID, 123, params) assert.NoError(t, err) assert.NotNil(t, updatedTrack) assert.Equal(t, track.Title, updatedTrack.Title) } func TestTrackService_DeleteTrack_Success(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track track := &models.Track{ UserID: 123, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Supprimer le track err = service.DeleteTrack(ctx, track.ID, 123) assert.NoError(t, err) // Vérifier que le track a été supprimé var deletedTrack models.Track err = db.First(&deletedTrack, track.ID).Error assert.Error(t, err) assert.Equal(t, gorm.ErrRecordNotFound, err) } func TestTrackService_DeleteTrack_NotFound(t *testing.T) { service, _, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Essayer de supprimer un track qui n'existe pas err := service.DeleteTrack(ctx, 99999, 123) assert.Error(t, err) assert.Equal(t, ErrTrackNotFound, err) } func TestTrackService_DeleteTrack_Forbidden(t *testing.T) { service, db, cleanup := setupTestTrackServiceForList(t) defer cleanup() ctx := context.Background() // Créer un track appartenant à l'utilisateur 123 track := &models.Track{ UserID: 123, Title: "Test Track", FilePath: "/test/track.mp3", FileSize: 5 * 1024 * 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted, } err := db.Create(track).Error assert.NoError(t, err) // Essayer de supprimer avec un autre utilisateur err = service.DeleteTrack(ctx, track.ID, 456) assert.Error(t, err) assert.Equal(t, ErrForbidden, err) // Vérifier que le track n'a pas été supprimé var existingTrack models.Track err = db.First(&existingTrack, track.ID).Error assert.NoError(t, err) assert.Equal(t, track.ID, existingTrack.ID) }