package database import ( "os" "testing" "time" "veza-backend-api/internal/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" ) // TestSessionsTableMigration teste que le fichier de migration existe et peut être lu func TestSessionsTableMigration(t *testing.T) { // Utiliser le chemin depuis le répertoire racine du projet // Le test peut être exécuté depuis différents répertoires migrationPath := "../../migrations/020_create_sessions.sql" // Essayer d'abord le chemin relatif depuis le répertoire de test content, err := os.ReadFile(migrationPath) if err != nil { // Si ça échoue, essayer depuis le répertoire racine migrationPath = "migrations/020_create_sessions.sql" content, err = os.ReadFile(migrationPath) } require.NoError(t, err, "Migration file should exist and be readable") // Vérifier que le contenu n'est pas vide assert.NotEmpty(t, content, "Migration file should not be empty") // Vérifier que le contenu contient les éléments essentiels contentStr := string(content) assert.Contains(t, contentStr, "CREATE TABLE", "Should create sessions table") assert.Contains(t, contentStr, "sessions", "Should create sessions table") // Note: user_id est BIGINT dans la migration 020, mais migré vers UUID dans 049 assert.Contains(t, contentStr, "user_id", "Should have user_id column") assert.Contains(t, contentStr, "token_hash", "Should have token_hash column") assert.Contains(t, contentStr, "ip_address", "Should have ip_address column") assert.Contains(t, contentStr, "user_agent", "Should have user_agent column") assert.Contains(t, contentStr, "expires_at", "Should have expires_at column") assert.Contains(t, contentStr, "created_at", "Should have created_at column") assert.Contains(t, contentStr, "REFERENCES users(id) ON DELETE CASCADE", "Should have foreign key constraint") assert.Contains(t, contentStr, "idx_sessions_user_id", "Should have index on user_id") assert.Contains(t, contentStr, "idx_sessions_expires_at", "Should have index on expires_at") assert.Contains(t, contentStr, "idx_sessions_revoked_at", "Should have index on revoked_at") } // TestSessionsTable_Creation teste que la table sessions est créée correctement func TestSessionsTable_Creation(t *testing.T) { // Créer une base de données en mémoire db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err, "Failed to open test database") // Créer la table users d'abord (requis pour la foreign key) err = db.AutoMigrate(&models.User{}) require.NoError(t, err, "Failed to migrate users table") // Créer la table sessions manuellement (simule la migration SQL) // Note: SQLite stocke UUIDs comme TEXT, user_id est maintenant UUID (migration 049) err = db.Exec(` CREATE TABLE sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, expires_at TIMESTAMP NOT NULL, last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) `).Error require.NoError(t, err, "Failed to create sessions table") // Créer les index err = db.Exec("CREATE INDEX idx_sessions_user_id ON sessions(user_id)").Error require.NoError(t, err) err = db.Exec("CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)").Error require.NoError(t, err) err = db.Exec("CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)").Error require.NoError(t, err) // Vérifier que la table existe hasTable := db.Migrator().HasTable("sessions") assert.True(t, hasTable, "sessions table should exist") } // TestSessionsTable_Columns teste que toutes les colonnes sont présentes func TestSessionsTable_Columns(t *testing.T) { // Créer une base de données en mémoire db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Créer la table users err = db.AutoMigrate(&models.User{}) require.NoError(t, err) // Créer un utilisateur de test user := &models.User{ Email: "test@example.com", Username: "testuser", Role: "user", IsActive: true, } err = db.Create(user).Error require.NoError(t, err) // Créer la table sessions err = db.Exec(` CREATE TABLE sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, expires_at TIMESTAMP NOT NULL, last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) `).Error require.NoError(t, err) // Vérifier que toutes les colonnes existent en insérant une session expiresAt := time.Now().Add(1 * time.Hour) err = db.Exec(` INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, user.ID, "test-token-hash-123", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error require.NoError(t, err, "Should be able to insert a session") // Vérifier que la session a été insérée var count int64 err = db.Raw("SELECT COUNT(*) FROM sessions WHERE token_hash = ?", "test-token-hash-123").Scan(&count).Error require.NoError(t, err) assert.Equal(t, int64(1), count, "Session should be inserted") } // TestSessionsTable_ForeignKey teste que la foreign key fonctionne correctement func TestSessionsTable_ForeignKey(t *testing.T) { // Créer une base de données en mémoire db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Activer les foreign keys pour SQLite (requis pour CASCADE DELETE et validation FK) err = db.Exec("PRAGMA foreign_keys = ON").Error require.NoError(t, err) // Créer la table users err = db.AutoMigrate(&models.User{}) require.NoError(t, err) // Créer la table sessions err = db.Exec(` CREATE TABLE sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, expires_at TIMESTAMP NOT NULL, last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) `).Error require.NoError(t, err) // Créer un utilisateur user := &models.User{ Email: "test@example.com", Username: "testuser", Role: "user", IsActive: true, } err = db.Create(user).Error require.NoError(t, err) // Insérer une session valide expiresAt := time.Now().Add(1 * time.Hour) err = db.Exec(` INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, user.ID, "valid-token-hash", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error require.NoError(t, err, "Should be able to insert session for existing user") // Tenter d'insérer une session avec un user_id inexistant (devrait échouer) // Utiliser un UUID valide mais inexistant fakeUserID := "00000000-0000-0000-0000-000000000999" err = db.Exec(` INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, fakeUserID, "invalid-token-hash", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error assert.Error(t, err, "Should not be able to insert session with non-existent user_id") // Vérifier que le CASCADE DELETE fonctionne // Utiliser Unscoped() pour forcer la suppression réelle (pas soft delete) err = db.Unscoped().Delete(user).Error require.NoError(t, err) // Vérifier que la session a été supprimée automatiquement var count int64 err = db.Raw("SELECT COUNT(*) FROM sessions WHERE token_hash = ?", "valid-token-hash").Scan(&count).Error require.NoError(t, err) assert.Equal(t, int64(0), count, "Session should be deleted when user is deleted") } // TestSessionsTable_UniqueTokenHash teste que le token_hash doit être unique func TestSessionsTable_UniqueTokenHash(t *testing.T) { // Créer une base de données en mémoire db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Créer la table users err = db.AutoMigrate(&models.User{}) require.NoError(t, err) // Créer la table sessions err = db.Exec(` CREATE TABLE sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, expires_at TIMESTAMP NOT NULL, last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) `).Error require.NoError(t, err) // Créer un utilisateur user := &models.User{ Email: "test@example.com", Username: "testuser", Role: "user", IsActive: true, } err = db.Create(user).Error require.NoError(t, err) // Insérer une session expiresAt := time.Now().Add(1 * time.Hour) err = db.Exec(` INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, user.ID, "unique-token-hash", "192.168.1.1", "Mozilla/5.0", expiresAt, time.Now(), time.Now()).Error require.NoError(t, err, "Should be able to insert first session") // Tenter d'insérer une session avec le même token_hash (devrait échouer) err = db.Exec(` INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at) VALUES (?, ?, ?, ?, ?, ?, ?) `, user.ID, "unique-token-hash", "192.168.1.2", "Chrome", expiresAt, time.Now(), time.Now()).Error assert.Error(t, err, "Should not be able to insert duplicate token_hash") } // TestSessionsTable_Indexes teste que les index sont créés correctement func TestSessionsTable_Indexes(t *testing.T) { // Créer une base de données en mémoire db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) // Créer la table users err = db.AutoMigrate(&models.User{}) require.NoError(t, err) // Créer la table sessions err = db.Exec(` CREATE TABLE sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, expires_at TIMESTAMP NOT NULL, last_activity TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) `).Error require.NoError(t, err) // Créer les index err = db.Exec("CREATE INDEX idx_sessions_user_id ON sessions(user_id)").Error require.NoError(t, err) err = db.Exec("CREATE INDEX idx_sessions_token_hash ON sessions(token_hash)").Error require.NoError(t, err) err = db.Exec("CREATE INDEX idx_sessions_expires_at ON sessions(expires_at)").Error require.NoError(t, err) // Vérifier que les index existent (SQLite stocke les index dans sqlite_master) var indexCount int64 err = db.Raw(` SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name IN ('idx_sessions_user_id', 'idx_sessions_token_hash', 'idx_sessions_expires_at') `).Scan(&indexCount).Error require.NoError(t, err) assert.Equal(t, int64(3), indexCount, "All three indexes should exist") }