9153a20384
Fase 3 del issue 0010 — sesiones SQLite como alternativa a JWT. - Tabla sessions creada con CREATE TABLE IF NOT EXISTS (autosetup) - Tokens de 32 bytes aleatorios (crypto/rand) codificados en hex (256 bits) - Indices en user_id y expires_at - Prepared statements para evitar SQL injection - SessionCleanup para eliminar expiradas periodicamente
127 lines
3.3 KiB
Go
127 lines
3.3 KiB
Go
package infra
|
|
|
|
import (
|
|
"database/sql"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
)
|
|
|
|
func openSessionTestDB(t *testing.T) *sql.DB {
|
|
t.Helper()
|
|
db, err := sql.Open("sqlite3", ":memory:")
|
|
if err != nil {
|
|
t.Fatalf("sql.Open: %v", err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
return db
|
|
}
|
|
|
|
func TestSessionCreate_PersistsSession(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
s, err := SessionCreate(db, "user-1", time.Hour, map[string]any{"role": "admin"})
|
|
if err != nil {
|
|
t.Fatalf("SessionCreate: %v", err)
|
|
}
|
|
if s.Token == "" || len(s.Token) != 64 {
|
|
t.Errorf("token de largo incorrecto: len=%d", len(s.Token))
|
|
}
|
|
if s.UserID != "user-1" {
|
|
t.Errorf("UserID = %q", s.UserID)
|
|
}
|
|
if s.ExpiresAt <= s.CreatedAt {
|
|
t.Errorf("ExpiresAt %d <= CreatedAt %d", s.ExpiresAt, s.CreatedAt)
|
|
}
|
|
|
|
// Verificar persistencia
|
|
var count int
|
|
if err := db.QueryRow("SELECT COUNT(*) FROM sessions").Scan(&count); err != nil {
|
|
t.Fatalf("query: %v", err)
|
|
}
|
|
if count != 1 {
|
|
t.Errorf("esperaba 1 fila, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestSessionCreate_GeneratesDistinctTokens(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
s1, _ := SessionCreate(db, "u", time.Hour, nil)
|
|
s2, _ := SessionCreate(db, "u", time.Hour, nil)
|
|
if s1.Token == s2.Token {
|
|
t.Fatal("dos sesiones no pueden tener el mismo token")
|
|
}
|
|
}
|
|
|
|
func TestSessionCreate_RejectsEmptyUserID(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
if _, err := SessionCreate(db, "", time.Hour, nil); err == nil {
|
|
t.Fatal("esperaba error con user_id vacio")
|
|
}
|
|
}
|
|
|
|
func TestSessionValidate_ValidSession(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
s, _ := SessionCreate(db, "user-7", time.Hour, map[string]any{"k": "v"})
|
|
|
|
got, err := SessionValidate(db, s.Token)
|
|
if err != nil {
|
|
t.Fatalf("SessionValidate: %v", err)
|
|
}
|
|
if got.UserID != "user-7" {
|
|
t.Errorf("UserID = %q", got.UserID)
|
|
}
|
|
if got.Metadata["k"] != "v" {
|
|
t.Errorf("metadata[k] = %v", got.Metadata["k"])
|
|
}
|
|
}
|
|
|
|
func TestSessionValidate_MissingToken(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
_ = sessionEnsureTable(db)
|
|
if _, err := SessionValidate(db, "nope"); err == nil {
|
|
t.Fatal("esperaba error con token inexistente")
|
|
}
|
|
}
|
|
|
|
func TestSessionValidate_ExpiredSession(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
_ = sessionEnsureTable(db)
|
|
// Insertar manualmente una sesion ya expirada
|
|
past := time.Now().Unix() - 60
|
|
_, err := db.Exec(
|
|
"INSERT INTO sessions (token, user_id, expires_at, created_at, metadata) VALUES (?, ?, ?, ?, ?)",
|
|
"expired-tok", "u", past, past-100, "{}",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("insert: %v", err)
|
|
}
|
|
if _, err := SessionValidate(db, "expired-tok"); err == nil {
|
|
t.Fatal("esperaba error con sesion expirada")
|
|
}
|
|
}
|
|
|
|
func TestSessionCleanup_RemovesOnlyExpired(t *testing.T) {
|
|
db := openSessionTestDB(t)
|
|
active, _ := SessionCreate(db, "active-user", time.Hour, nil)
|
|
_ = sessionEnsureTable(db)
|
|
past := time.Now().Unix() - 60
|
|
_, _ = db.Exec(
|
|
"INSERT INTO sessions (token, user_id, expires_at, created_at, metadata) VALUES (?, ?, ?, ?, ?)",
|
|
"expired", "u", past, past, "{}",
|
|
)
|
|
|
|
n, err := SessionCleanup(db)
|
|
if err != nil {
|
|
t.Fatalf("SessionCleanup: %v", err)
|
|
}
|
|
if n != 1 {
|
|
t.Errorf("esperaba 1 eliminada, got %d", n)
|
|
}
|
|
|
|
// La activa sigue ahi
|
|
if _, err := SessionValidate(db, active.Token); err != nil {
|
|
t.Errorf("sesion activa no deberia haberse borrado: %v", err)
|
|
}
|
|
}
|