veza/veza-backend-api/tests/transactions/social_transaction_test.go
senke f84dbf5c66 test(backend): gate testcontainers tests behind VEZA_SKIP_INTEGRATION
The Forgejo runner doesn't expose /var/run/docker.sock, so anything
relying on testcontainers-go panicked with "Cannot connect to the
Docker daemon". This caused internal/testutils, tests/transactions
and tests/integration to fail wholesale, plus internal/handlers
to hit the 5min hard timeout while waiting for container startup.

Approach (least invasive):
- testutils.GetTestContainerDB short-circuits when VEZA_SKIP_INTEGRATION=1
  is set, returning a sentinel error immediately instead of attempting
  three retries against a missing Docker socket.
- Add testutils.SkipIfNoIntegration helper for granular per-test skips.
- Add TestMain to internal/testutils, tests/transactions and
  tests/integration packages that os.Exit(0) when the env var is set,
  so the entire integration-only package is silently skipped in CI.
- Wire the helper into the three setupTestDB* functions in
  tests/transactions/ for local runs (where TestMain doesn't fire when
  using -run on individual tests).

Local nightly runs / dev workstations leave VEZA_SKIP_INTEGRATION unset
and exercise the full suite against testcontainers as before.
2026-04-14 11:45:19 +02:00

325 lines
11 KiB
Go

package transactions
import (
"context"
"testing"
"veza-backend-api/internal/core/social"
"veza-backend-api/internal/models"
"veza-backend-api/internal/testutils"
"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"
)
// setupTestDB crée une DB de test avec testcontainers
func setupTestDBForSocial(t *testing.T) *gorm.DB {
testutils.SkipIfNoIntegration(t)
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")
// Note: Les migrations SQL sont déjà exécutées lors du démarrage du conteneur PostgreSQL
// via postgres.WithInitScripts dans internal/testutils/setup.go
// AutoMigrate n'est pas nécessaire et peut causer des erreurs avec les vues existantes
// 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(_ *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")
}