293 lines
11 KiB
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")
|
|
}
|