Files
pixa/internal/session/session_test.go
clawbot 2d805125ee
All checks were successful
check / check (push) Successful in 4s
chore: update golangci-lint to v2.12.2 with canonical config (#54)
Canonical v2-schema `.golangci.yml`, golangci-lint pins bumped to v2.12.2 in `Dockerfile` and `script/bootstrap`, and the tree brought to `0 issues.` under it.

Three behaviour deltas: `Cache.StoreVariant` takes a context (cancelled requests skip the accounting row, recovered by reconciliation); `MetadataStorage.Store` no longer leaks `.tmp-*.json` on Write/Close/Rename failure (dead-defer bug fix); the `signing_key` too-short error text gained a `value too short:` prefix.

Eviction-loop context cancellation deferred to #102.
2026-08-10 16:12:22 +02:00

241 lines
5.1 KiB
Go

package session_test
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"sneak.berlin/go/pixa/internal/session"
)
func TestManager_CreateAndValidate(t *testing.T) {
t.Parallel()
mgr, err := session.NewManager("test-signing-key-12345")
if err != nil {
t.Fatalf("NewManager() error = %v", err)
}
// Create a session
w := httptest.NewRecorder()
err = mgr.CreateSession(w)
if err != nil {
t.Fatalf("CreateSession() error = %v", err)
}
// Extract the cookie from response
resp := w.Result()
cookies := resp.Cookies()
if len(cookies) == 0 {
t.Fatal("CreateSession() did not set a cookie")
}
var sessionCookie *http.Cookie
for _, c := range cookies {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
if sessionCookie == nil {
t.Fatalf("CreateSession() did not set cookie named %q", session.CookieName)
}
// Validate the session
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
data, err := mgr.ValidateSession(req)
if err != nil {
t.Fatalf("ValidateSession() error = %v", err)
}
if !data.Authenticated {
t.Error("ValidateSession() returned unauthenticated session")
}
if data.ExpiresAt.Before(time.Now()) {
t.Error("ValidateSession() returned already-expired session")
}
}
func TestManager_ValidateSession_NoCookie(t *testing.T) {
t.Parallel()
mgr, _ := session.NewManager("test-signing-key-12345")
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
_, err := mgr.ValidateSession(req)
if err == nil {
t.Error("ValidateSession() should fail with no cookie")
}
if !errors.Is(err, session.ErrNoSession) {
t.Errorf("ValidateSession() error = %v, want %v", err, session.ErrNoSession)
}
}
func TestManager_ValidateSession_TamperedCookie(t *testing.T) {
t.Parallel()
mgr, _ := session.NewManager("test-signing-key-12345")
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(&http.Cookie{
Name: session.CookieName,
Value: "tampered-invalid-cookie-value",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
})
_, err := mgr.ValidateSession(req)
if err == nil {
t.Error("ValidateSession() should fail with tampered cookie")
}
if !errors.Is(err, session.ErrInvalidSession) {
t.Errorf("ValidateSession() error = %v, want %v", err, session.ErrInvalidSession)
}
}
func TestManager_ValidateSession_WrongKey(t *testing.T) {
t.Parallel()
mgr1, _ := session.NewManager("signing-key-1")
mgr2, _ := session.NewManager("signing-key-2")
// Create session with mgr1
w := httptest.NewRecorder()
_ = mgr1.CreateSession(w)
resp := w.Result()
var sessionCookie *http.Cookie
for _, c := range resp.Cookies() {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
// Try to validate with mgr2 (different key)
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
_, err := mgr2.ValidateSession(req)
if err == nil {
t.Error("ValidateSession() should fail with different signing key")
}
}
func TestManager_ClearSession(t *testing.T) {
t.Parallel()
mgr, _ := session.NewManager("test-signing-key-12345")
w := httptest.NewRecorder()
mgr.ClearSession(w)
resp := w.Result()
cookies := resp.Cookies()
var sessionCookie *http.Cookie
for _, c := range cookies {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
if sessionCookie == nil {
t.Fatal("ClearSession() did not set a cookie")
}
if sessionCookie.MaxAge != -1 {
t.Errorf("ClearSession() cookie MaxAge = %d, want -1", sessionCookie.MaxAge)
}
}
func TestManager_IsAuthenticated(t *testing.T) {
t.Parallel()
mgr, _ := session.NewManager("test-signing-key-12345")
// No session - should return false
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
if mgr.IsAuthenticated(req) {
t.Error("IsAuthenticated() should return false with no session")
}
// Create session
w := httptest.NewRecorder()
_ = mgr.CreateSession(w)
resp := w.Result()
var sessionCookie *http.Cookie
for _, c := range resp.Cookies() {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
// With valid session - should return true
req = httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
req.AddCookie(sessionCookie)
if !mgr.IsAuthenticated(req) {
t.Error("IsAuthenticated() should return true with valid session")
}
}
func TestManager_CookieAttributes(t *testing.T) {
t.Parallel()
mgr, _ := session.NewManager("test-key")
w := httptest.NewRecorder()
_ = mgr.CreateSession(w)
resp := w.Result()
var sessionCookie *http.Cookie
for _, c := range resp.Cookies() {
if c.Name == session.CookieName {
sessionCookie = c
break
}
}
if !sessionCookie.HttpOnly {
t.Error("Cookie should have HttpOnly flag")
}
if !sessionCookie.Secure {
t.Error("Cookie should have Secure flag when manager created with secure=true")
}
if sessionCookie.SameSite != http.SameSiteStrictMode {
t.Errorf("Cookie SameSite = %v, want %v",
sessionCookie.SameSite, http.SameSiteStrictMode)
}
}