package transactions import ( "context" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" "gorm.io/driver/postgres" "gorm.io/gorm" "veza-backend-api/internal/models" "veza-backend-api/internal/repositories" "veza-backend-api/internal/services" "veza-backend-api/internal/testutils" ) // setupTestDB crée une DB de test avec testcontainers func setupTestDBForPlaylist(t *testing.T) *gorm.DB { ctx := context.Background() dsn, err := testutils.GetTestContainerDB(ctx) require.NoError(t, err, "Failed to setup test database") db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) require.NoError(t, err, "Failed to open database connection") // Auto-migrate models nécessaires err = db.AutoMigrate( &models.User{}, &models.Track{}, &models.Playlist{}, &models.PlaylistTrack{}, &models.PlaylistCollaborator{}, ) require.NoError(t, err, "Failed to migrate database") return db } // cleanupTestDB nettoie la DB entre les tests func cleanupTestDBForPlaylist(t *testing.T, db *gorm.DB) { db.Exec("TRUNCATE TABLE playlist_tracks CASCADE") db.Exec("TRUNCATE TABLE playlist_collaborators CASCADE") db.Exec("TRUNCATE TABLE playlists CASCADE") db.Exec("TRUNCATE TABLE tracks CASCADE") db.Exec("TRUNCATE TABLE users CASCADE") } // createTestUser crée un utilisateur de test func createTestUserForPlaylist(t *testing.T, db *gorm.DB) *models.User { user := &models.User{ Username: "testuser_" + uuid.New().String()[:8], Slug: "testuser_" + uuid.New().String()[:8], // Unique slug Email: "test_" + uuid.New().String()[:8] + "@example.com", PasswordHash: "$2a$10$examplehash", IsActive: true, IsVerified: true, } err := db.Create(user).Error require.NoError(t, err) return user } // createTestTrack crée un track de test func createTestTrack(t *testing.T, db *gorm.DB, userID uuid.UUID) uuid.UUID { // Create a dummy file first (required by FK) fileID := uuid.New() err := db.Exec(` INSERT INTO files (id, user_id, filename, original_filename, mime_type, file_size, storage_path, url, is_public) VALUES (?, ?, 'test_track.mp3', 'test_track.mp3', 'audio/mpeg', 5242880, '/test/track.mp3', 'http://example.com/test.mp3', true) `, fileID, userID).Error require.NoError(t, err, "Failed to create dummy file for track") trackID := uuid.New() track := &models.Track{ ID: trackID, UserID: userID, // Maps to creator_id now FileID: fileID, Title: "Test Track " + uuid.New().String()[:8], Artist: "Test Artist", Duration: 180, FilePath: "/test/track.mp3", FileSize: 5242880, Format: "mp3", IsPublic: true, Status: "completed", } err = db.Create(track).Error require.NoError(t, err) return track.ID } // createTestPlaylistWithTracks crée une playlist avec des tracks func createTestPlaylistWithTracks(t *testing.T, db *gorm.DB, userID uuid.UUID, trackCount int) *models.Playlist { playlist := &models.Playlist{ UserID: userID, Title: "Original Playlist", Description: "Test playlist", IsPublic: false, TrackCount: 0, } err := db.Create(playlist).Error require.NoError(t, err) tracks := make([]uuid.UUID, trackCount) for i := 0; i < trackCount; i++ { tracks[i] = createTestTrack(t, db, userID) } // Ajouter les tracks à la playlist for i, trackID := range tracks { playlistTrack := &models.PlaylistTrack{ PlaylistID: playlist.ID, TrackID: trackID, Position: i + 1, AddedBy: userID, } err := db.Create(playlistTrack).Error require.NoError(t, err) } // Mettre à jour le compteur playlist.TrackCount = trackCount db.Model(playlist).Update("track_count", trackCount) return playlist } // TestDuplicatePlaylist_Success vérifie que la duplication fonctionne correctement func TestDuplicatePlaylist_Success(t *testing.T) { db := setupTestDBForPlaylist(t) defer cleanupTestDBForPlaylist(t, db) logger := zaptest.NewLogger(t) // Create repositories playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger) user := createTestUserForPlaylist(t, db) originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 5) // Dupliquer la playlist request := services.DuplicatePlaylistRequest{ NewTitle: "Duplicated Playlist", } newPlaylist, err := duplicateService.DuplicatePlaylist( context.Background(), originalPlaylist.ID, user.ID, request, ) require.NoError(t, err, "DuplicatePlaylist should succeed") require.NotNil(t, newPlaylist, "New playlist should be created") // Vérifier que la nouvelle playlist existe var playlistCount int64 db.Model(&models.Playlist{}).Where("id = ?", newPlaylist.ID).Count(&playlistCount) assert.Equal(t, int64(1), playlistCount, "New playlist should exist") // Vérifier que tous les tracks sont dupliqués var trackCount int64 db.Model(&models.PlaylistTrack{}). Where("playlist_id = ?", newPlaylist.ID). Count(&trackCount) assert.Equal(t, int64(5), trackCount, "All tracks should be duplicated") // Vérifier que le compteur est cohérent assert.Equal(t, int(5), newPlaylist.TrackCount, "Track count should match") } // TestDuplicatePlaylist_RollbackOnPlaylistNotFound vérifie le rollback si la playlist n'existe pas func TestDuplicatePlaylist_RollbackOnPlaylistNotFound(t *testing.T) { db := setupTestDBForPlaylist(t) defer cleanupTestDBForPlaylist(t, db) logger := zaptest.NewLogger(t) // Create repositories playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger) user := createTestUserForPlaylist(t, db) fakePlaylistID := uuid.New() request := services.DuplicatePlaylistRequest{ NewTitle: "Duplicated Playlist", } _, err := duplicateService.DuplicatePlaylist( context.Background(), fakePlaylistID, user.ID, request, ) require.Error(t, err, "DuplicatePlaylist should fail") assert.Contains(t, err.Error(), "playlist not found", "Error should mention playlist not found") // Vérifier qu'aucune playlist n'a été créée var playlistCount int64 db.Model(&models.Playlist{}).Where("user_id = ?", user.ID).Count(&playlistCount) assert.Equal(t, int64(0), playlistCount, "No playlist should be created on error") } // TestDuplicatePlaylist_RollbackOnTrackError vérifie le rollback si un track échoue func TestDuplicatePlaylist_RollbackOnTrackError(t *testing.T) { db := setupTestDBForPlaylist(t) defer cleanupTestDBForPlaylist(t, db) logger := zaptest.NewLogger(t) // Create repositories playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger) user := createTestUserForPlaylist(t, db) originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 3) // Supprimer un track pour forcer une erreur FK lors de la duplication // (simulation d'une erreur au milieu de la transaction) var firstTrack models.Track db.Model(&models.PlaylistTrack{}). Where("playlist_id = ?", originalPlaylist.ID). Order("position ASC"). Limit(1). First(&models.PlaylistTrack{}). Association("Track").Find(&firstTrack) // Supprimer le track db.Delete(&firstTrack) // Tenter de dupliquer (devrait échouer car le track n'existe plus) request := services.DuplicatePlaylistRequest{ NewTitle: "Duplicated Playlist", } _, err := duplicateService.DuplicatePlaylist( context.Background(), originalPlaylist.ID, user.ID, request, ) require.Error(t, err, "DuplicatePlaylist should fail") // Vérifier qu'aucune playlist n'a été créée (rollback complet) var playlistCount int64 db.Model(&models.Playlist{}). Where("user_id = ? AND title = ?", user.ID, "Duplicated Playlist"). Count(&playlistCount) assert.Equal(t, int64(0), playlistCount, "No playlist should be created on error") // Vérifier qu'aucun track n'a été ajouté var trackCount int64 db.Model(&models.PlaylistTrack{}). Where("playlist_id != ?", originalPlaylist.ID). Count(&trackCount) assert.Equal(t, int64(0), trackCount, "No tracks should be created on error") } // TestDuplicatePlaylist_Coherence vérifie la cohérence des données après duplication func TestDuplicatePlaylist_Coherence(t *testing.T) { db := setupTestDBForPlaylist(t) defer cleanupTestDBForPlaylist(t, db) logger := zaptest.NewLogger(t) // Create repositories playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger) user := createTestUserForPlaylist(t, db) originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 10) // Dupliquer request := services.DuplicatePlaylistRequest{ NewTitle: "Duplicated Playlist", } newPlaylist, err := duplicateService.DuplicatePlaylist( context.Background(), originalPlaylist.ID, user.ID, request, ) require.NoError(t, err) // Vérifier que le compteur correspond au nombre réel de tracks var actualTrackCount int64 db.Model(&models.PlaylistTrack{}). Where("playlist_id = ?", newPlaylist.ID). Count(&actualTrackCount) assert.Equal(t, int64(newPlaylist.TrackCount), actualTrackCount, "Track count should match actual tracks") // Vérifier que les positions sont cohérentes var playlistTracks []models.PlaylistTrack db.Where("playlist_id = ?", newPlaylist.ID). Order("position ASC"). Find(&playlistTracks) for i, pt := range playlistTracks { assert.Equal(t, i+1, pt.Position, "Position should be sequential") } } // TestDuplicatePlaylist_EmptyPlaylist vérifie la duplication d'une playlist vide func TestDuplicatePlaylist_EmptyPlaylist(t *testing.T) { db := setupTestDBForPlaylist(t) defer cleanupTestDBForPlaylist(t, db) logger := zaptest.NewLogger(t) // Create repositories playlistRepo := repositories.NewPlaylistRepository(db) playlistTrackRepo := repositories.NewPlaylistTrackRepository(db) playlistCollaboratorRepo := repositories.NewPlaylistCollaboratorRepository(db) userRepo := repositories.NewGormUserRepository(db) playlistService := services.NewPlaylistService(playlistRepo, playlistTrackRepo, playlistCollaboratorRepo, userRepo, logger) duplicateService := services.NewPlaylistDuplicateService(playlistService, db, logger) user := createTestUserForPlaylist(t, db) originalPlaylist := createTestPlaylistWithTracks(t, db, user.ID, 0) // Playlist vide request := services.DuplicatePlaylistRequest{ NewTitle: "Duplicated Empty Playlist", } newPlaylist, err := duplicateService.DuplicatePlaylist( context.Background(), originalPlaylist.ID, user.ID, request, ) require.NoError(t, err, "Duplicating empty playlist should succeed") assert.Equal(t, 0, newPlaylist.TrackCount, "Empty playlist should have 0 tracks") // Vérifier qu'aucun track n'a été créé var trackCount int64 db.Model(&models.PlaylistTrack{}). Where("playlist_id = ?", newPlaylist.ID). Count(&trackCount) assert.Equal(t, int64(0), trackCount, "No tracks should be created for empty playlist") }