Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
312 lines
9.8 KiB
Go
312 lines
9.8 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/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],
|
|
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 createTestTrackForPlaylist(t *testing.T, db *gorm.DB, userID uuid.UUID) *models.Track {
|
|
track := &models.Track{
|
|
UserID: userID,
|
|
Title: "Test Track " + uuid.New().String()[:8],
|
|
Artist: "Test Artist",
|
|
Duration: 180,
|
|
FilePath: "/test/track.mp3",
|
|
FileSize: 1024 * 1024 * 5,
|
|
Format: "mp3",
|
|
Status: models.TrackStatusCompleted,
|
|
}
|
|
err := db.Create(track).Error
|
|
require.NoError(t, err)
|
|
return track
|
|
}
|
|
|
|
// 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([]*models.Track, trackCount)
|
|
for i := 0; i < trackCount; i++ {
|
|
tracks[i] = createTestTrackForPlaylist(t, db, userID)
|
|
}
|
|
|
|
// Ajouter les tracks à la playlist
|
|
for i, track := range tracks {
|
|
playlistTrack := &models.PlaylistTrack{
|
|
PlaylistID: playlist.ID,
|
|
TrackID: track.ID,
|
|
Position: i + 1,
|
|
}
|
|
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)
|
|
playlistService := services.NewPlaylistService(db, 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)
|
|
playlistService := services.NewPlaylistService(db, 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)
|
|
playlistService := services.NewPlaylistService(db, 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)
|
|
playlistService := services.NewPlaylistService(db, 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)
|
|
playlistService := services.NewPlaylistService(db, 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")
|
|
}
|
|
|
|
|