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/core/social" "veza-backend-api/internal/models" "veza-backend-api/internal/testutils" ) // setupTestDB crée une DB de test avec testcontainers func setupTestDBForSocial(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 // Note: On suppose que les tables likes, comments, posts existent // Si elles n'existent pas, il faudra les créer via migrations err = db.AutoMigrate( &models.User{}, ) require.NoError(t, err, "Failed to migrate database") // Créer les tables si elles n'existent pas (simplifié pour les tests) db.Exec(` CREATE TABLE IF NOT EXISTS likes ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL, target_id UUID NOT NULL, target_type VARCHAR(50) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, target_id, target_type) ) `) db.Exec(` CREATE TABLE IF NOT EXISTS posts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL, content TEXT, like_count INTEGER DEFAULT 0, comment_count INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `) db.Exec(` CREATE TABLE IF NOT EXISTS comments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL, target_id UUID NOT NULL, target_type VARCHAR(20) NOT NULL, content TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, deleted_at TIMESTAMP ) `) return db } // cleanupTestDB nettoie la DB entre les tests func cleanupTestDBForSocial(t *testing.T, db *gorm.DB) { db.Exec("TRUNCATE TABLE likes CASCADE") db.Exec("TRUNCATE TABLE comments CASCADE") db.Exec("TRUNCATE TABLE posts CASCADE") db.Exec("TRUNCATE TABLE users CASCADE") } // createTestUser crée un utilisateur de test func createTestUserForSocial(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 } // createTestPost crée un post de test func createTestPost(t *testing.T, db *gorm.DB, userID uuid.UUID) uuid.UUID { // Scan UUID as string to avoid driver type conversion issues var postIDStr string err := db.Raw(` INSERT INTO posts (id, user_id, content, like_count, comment_count) VALUES (gen_random_uuid(), ?, ?, 0, 0) RETURNING id `, userID, "Test post content").Scan(&postIDStr).Error require.NoError(t, err) postID, err := uuid.Parse(postIDStr) require.NoError(t, err) return postID } // TestToggleLike_Success vérifie que le like fonctionne correctement func TestToggleLike_Success(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user := createTestUserForSocial(t, db) postID := createTestPost(t, db, user.ID) // Liker le post liked, err := socialService.ToggleLike(context.Background(), user.ID, postID, "post") require.NoError(t, err, "ToggleLike should succeed") assert.True(t, liked, "Post should be liked") // Vérifier que le like existe var likeCount int64 db.Raw("SELECT COUNT(*) FROM likes WHERE user_id = ? AND target_id = ? AND target_type = ?", user.ID, postID, "post").Scan(&likeCount) assert.Equal(t, int64(1), likeCount, "Like should exist") // Vérifier que le compteur est incrémenté var postLikeCount int db.Raw("SELECT like_count FROM posts WHERE id = ?", postID).Scan(&postLikeCount) assert.Equal(t, 1, postLikeCount, "Post like_count should be 1") } // TestToggleLike_Unlike vérifie que l'unlike fonctionne correctement func TestToggleLike_Unlike(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user := createTestUserForSocial(t, db) postID := createTestPost(t, db, user.ID) // Liker d'abord liked, err := socialService.ToggleLike(context.Background(), user.ID, postID, "post") require.NoError(t, err) assert.True(t, liked) // Unliker liked, err = socialService.ToggleLike(context.Background(), user.ID, postID, "post") require.NoError(t, err, "ToggleLike (unlike) should succeed") assert.False(t, liked, "Post should be unliked") // Vérifier que le like n'existe plus var likeCount int64 db.Raw("SELECT COUNT(*) FROM likes WHERE user_id = ? AND target_id = ? AND target_type = ?", user.ID, postID, "post").Scan(&likeCount) assert.Equal(t, int64(0), likeCount, "Like should be removed") // Vérifier que le compteur est décrémenté var postLikeCount int db.Raw("SELECT like_count FROM posts WHERE id = ?", postID).Scan(&postLikeCount) assert.Equal(t, 0, postLikeCount, "Post like_count should be 0") } // TestToggleLike_RollbackOnError vérifie le rollback si une erreur survient func TestToggleLike_RollbackOnError(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user := createTestUserForSocial(t, db) postID := createTestPost(t, db, user.ID) // Supprimer le post pour forcer une erreur lors de l'UPDATE du compteur db.Exec("DELETE FROM posts WHERE id = ?", postID) // Tenter de liker (devrait échouer car le post n'existe plus) _, err := socialService.ToggleLike(context.Background(), user.ID, postID, "post") require.Error(t, err, "ToggleLike should fail") // Vérifier qu'aucun like n'a été créé (rollback) var likeCount int64 db.Raw("SELECT COUNT(*) FROM likes WHERE user_id = ? AND target_id = ?", user.ID, postID).Scan(&likeCount) assert.Equal(t, int64(0), likeCount, "No like should be created on error") } // TestToggleLike_Coherence vérifie la cohérence entre likes et compteurs func TestToggleLike_Coherence(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user1 := createTestUserForSocial(t, db) user2 := createTestUserForSocial(t, db) postID := createTestPost(t, db, user1.ID) // User1 like _, err := socialService.ToggleLike(context.Background(), user1.ID, postID, "post") require.NoError(t, err) // User2 like _, err = socialService.ToggleLike(context.Background(), user2.ID, postID, "post") require.NoError(t, err) // Vérifier la cohérence var actualLikeCount int64 db.Raw("SELECT COUNT(*) FROM likes WHERE target_id = ? AND target_type = ?", postID, "post").Scan(&actualLikeCount) var postLikeCount int db.Raw("SELECT like_count FROM posts WHERE id = ?", postID).Scan(&postLikeCount) assert.Equal(t, int64(postLikeCount), actualLikeCount, "Like count should match actual likes") assert.Equal(t, int64(2), actualLikeCount, "Should have 2 likes") } // TestAddComment_Success vérifie que l'ajout de commentaire fonctionne func TestAddComment_Success(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user := createTestUserForSocial(t, db) postID := createTestPost(t, db, user.ID) // Ajouter un commentaire comment, err := socialService.AddComment( context.Background(), user.ID, postID, "post", "Test comment", ) require.NoError(t, err, "AddComment should succeed") require.NotNil(t, comment, "Comment should be created") assert.Equal(t, "Test comment", comment.Content, "Comment content should match") // Vérifier que le commentaire existe var commentCount int64 db.Raw("SELECT COUNT(*) FROM comments WHERE user_id = ? AND target_id = ? AND target_type = ?", user.ID, postID, "post").Scan(&commentCount) assert.Equal(t, int64(1), commentCount, "Comment should exist") // Vérifier que le compteur est incrémenté var postCommentCount int db.Raw("SELECT comment_count FROM posts WHERE id = ?", postID).Scan(&postCommentCount) assert.Equal(t, 1, postCommentCount, "Post comment_count should be 1") } // TestAddComment_RollbackOnError vérifie le rollback si une erreur survient func TestAddComment_RollbackOnError(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user := createTestUserForSocial(t, db) postID := createTestPost(t, db, user.ID) // Supprimer le post pour forcer une erreur lors de l'UPDATE du compteur db.Exec("DELETE FROM posts WHERE id = ?", postID) // Tenter d'ajouter un commentaire (devrait échouer) _, err := socialService.AddComment( context.Background(), user.ID, postID, "post", "Test comment", ) require.Error(t, err, "AddComment should fail") // Vérifier qu'aucun commentaire n'a été créé (rollback) var commentCount int64 db.Raw("SELECT COUNT(*) FROM comments WHERE user_id = ? AND target_id = ?", user.ID, postID).Scan(&commentCount) assert.Equal(t, int64(0), commentCount, "No comment should be created on error") } // TestAddComment_Coherence vérifie la cohérence entre comments et compteurs func TestAddComment_Coherence(t *testing.T) { db := setupTestDBForSocial(t) defer cleanupTestDBForSocial(t, db) logger := zaptest.NewLogger(t) socialService := social.NewService(db, logger) user1 := createTestUserForSocial(t, db) user2 := createTestUserForSocial(t, db) postID := createTestPost(t, db, user1.ID) // User1 commente _, err := socialService.AddComment(context.Background(), user1.ID, postID, "post", "Comment 1") require.NoError(t, err) // User2 commente _, err = socialService.AddComment(context.Background(), user2.ID, postID, "post", "Comment 2") require.NoError(t, err) // Vérifier la cohérence var actualCommentCount int64 db.Raw("SELECT COUNT(*) FROM comments WHERE target_id = ? AND target_type = ?", postID, "post").Scan(&actualCommentCount) var postCommentCount int db.Raw("SELECT comment_count FROM posts WHERE id = ?", postID).Scan(&postCommentCount) assert.Equal(t, int64(postCommentCount), actualCommentCount, "Comment count should match actual comments") assert.Equal(t, int64(2), actualCommentCount, "Should have 2 comments") }