Files
fn_registry/functions/infra/session_test.go
T
egutierrez 9153a20384 feat: session_create, session_validate, session_cleanup
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
2026-04-18 17:40:13 +02:00

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)
}
}