veza/veza-backend-api/internal/database/migrations_sessions_test.go

293 lines
11 KiB
Go

package database
import (
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"veza-backend-api/internal/models"
)
// TestSessionsTableMigration teste que le fichier de migration existe et peut être lu
func TestSessionsTableMigration(t *testing.T) {
migrationPath := "migrations/020_create_sessions.sql"
// Vérifier que le fichier existe
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 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 VARCHAR(255)", "Should have token_hash column")
assert.Contains(t, contentStr, "ip_address VARCHAR(45)", "Should have ip_address column")
assert.Contains(t, contentStr, "user_agent TEXT", "Should have user_agent column")
assert.Contains(t, contentStr, "expires_at TIMESTAMP", "Should have expires_at column")
assert.Contains(t, contentStr, "last_activity TIMESTAMP", "Should have last_activity column")
assert.Contains(t, contentStr, "created_at TIMESTAMP", "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_token_hash", "Should have index on token_hash")
assert.Contains(t, contentStr, "idx_sessions_expires_at", "Should have index on expires_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")
}