veza/veza-backend-api/tests/transactions/playlist_duplicate_transaction_test.go

355 lines
12 KiB
Go

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")
}