Files
ClusterCanvas/service/internal/api/middleware_idle_test.go
T
Squid 463aa9a7a3 Add activity logs, sidebar badges, and logout with working alert colors.
Record auth audits and surface them in Activity, poll sidebar counts without resetting idle, and add a profile logout path plus theme red/green/blue tokens so failure badges render.
2026-07-18 21:43:44 +02:00

210 lines
6.2 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"codeberg.org/SquidSE/ClusterCanvas/service/internal/auth"
"codeberg.org/SquidSE/ClusterCanvas/service/internal/settings"
)
func TestMiddlewareReturnsSessionIdle(t *testing.T) {
configDir := t.TempDir()
cookie := seedCompletedSetup(t, configDir)
keyBytes := make([]byte, 32)
for index := range keyBytes {
keyBytes[index] = byte(index + 1)
}
store, err := settings.LoadSessions(configDir, keyBytes)
if err != nil {
t.Fatalf("LoadSessions: %v", err)
}
if len(store.Sessions) != 1 {
t.Fatalf("expected 1 session, got %d", len(store.Sessions))
}
store.Sessions[0].LastSeenAt = time.Now().UTC().Add(-31 * time.Minute)
if err := settings.SaveSessions(configDir, store, keyBytes); err != nil {
t.Fatalf("SaveSessions: %v", err)
}
router := NewRouter(configDir)
request := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/status", nil), cookie)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("expected status %d, got %d body=%s", http.StatusUnauthorized, recorder.Code, recorder.Body.String())
}
var payload apiErrorResponse
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload.Error != "session_idle" {
t.Fatalf("expected session_idle, got %q", payload.Error)
}
cleared := false
for _, setCookie := range recorder.Result().Cookies() {
if setCookie.Name == auth.SessionCookieName && setCookie.MaxAge < 0 {
cleared = true
}
}
if !cleared {
t.Fatal("expected session cookie to be cleared")
}
}
func TestMiddlewareReturnsAuthenticationRequiredWithoutCookie(t *testing.T) {
configDir := t.TempDir()
_ = seedCompletedSetup(t, configDir)
router := NewRouter(configDir)
request := httptest.NewRequest(http.MethodGet, "/api/v1/status", nil)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("expected status %d, got %d body=%s", http.StatusUnauthorized, recorder.Code, recorder.Body.String())
}
var payload apiErrorResponse
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload.Error != "authentication required" {
t.Fatalf("expected authentication required, got %q", payload.Error)
}
}
func TestMiddlewareIdleExemptDoesNotAdvanceLastSeenAt(t *testing.T) {
configDir := t.TempDir()
cookie := seedCompletedSetup(t, configDir)
keyBytes := make([]byte, 32)
for index := range keyBytes {
keyBytes[index] = byte(index + 1)
}
store, err := settings.LoadSessions(configDir, keyBytes)
if err != nil {
t.Fatalf("LoadSessions: %v", err)
}
originalLastSeen := time.Now().UTC().Add(-10 * time.Minute).Truncate(time.Second)
store.Sessions[0].LastSeenAt = originalLastSeen
if err := settings.SaveSessions(configDir, store, keyBytes); err != nil {
t.Fatalf("SaveSessions: %v", err)
}
router := NewRouter(configDir)
request := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/status", nil), cookie)
request.Header.Set(sessionIdleExemptHeader, "1")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, recorder.Code, recorder.Body.String())
}
store, err = settings.LoadSessions(configDir, keyBytes)
if err != nil {
t.Fatalf("LoadSessions after request: %v", err)
}
if len(store.Sessions) != 1 {
t.Fatalf("expected 1 session, got %d", len(store.Sessions))
}
if !store.Sessions[0].LastSeenAt.Equal(originalLastSeen) {
t.Fatalf(
"expected LastSeenAt %v unchanged, got %v",
originalLastSeen,
store.Sessions[0].LastSeenAt,
)
}
}
func TestMiddlewareIdleExemptStillReturnsSessionIdle(t *testing.T) {
configDir := t.TempDir()
cookie := seedCompletedSetup(t, configDir)
keyBytes := make([]byte, 32)
for index := range keyBytes {
keyBytes[index] = byte(index + 1)
}
store, err := settings.LoadSessions(configDir, keyBytes)
if err != nil {
t.Fatalf("LoadSessions: %v", err)
}
store.Sessions[0].LastSeenAt = time.Now().UTC().Add(-31 * time.Minute)
if err := settings.SaveSessions(configDir, store, keyBytes); err != nil {
t.Fatalf("SaveSessions: %v", err)
}
router := NewRouter(configDir)
request := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/status", nil), cookie)
request.Header.Set(sessionIdleExemptHeader, "1")
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusUnauthorized {
t.Fatalf("expected status %d, got %d body=%s", http.StatusUnauthorized, recorder.Code, recorder.Body.String())
}
var payload apiErrorResponse
if err := json.NewDecoder(recorder.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload.Error != "session_idle" {
t.Fatalf("expected session_idle, got %q", payload.Error)
}
}
func TestMiddlewareNormalRequestAdvancesLastSeenAt(t *testing.T) {
configDir := t.TempDir()
cookie := seedCompletedSetup(t, configDir)
keyBytes := make([]byte, 32)
for index := range keyBytes {
keyBytes[index] = byte(index + 1)
}
store, err := settings.LoadSessions(configDir, keyBytes)
if err != nil {
t.Fatalf("LoadSessions: %v", err)
}
originalLastSeen := time.Now().UTC().Add(-10 * time.Minute).Truncate(time.Second)
store.Sessions[0].LastSeenAt = originalLastSeen
if err := settings.SaveSessions(configDir, store, keyBytes); err != nil {
t.Fatalf("SaveSessions: %v", err)
}
router := NewRouter(configDir)
request := withSession(httptest.NewRequest(http.MethodGet, "/api/v1/status", nil), cookie)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d body=%s", http.StatusOK, recorder.Code, recorder.Body.String())
}
store, err = settings.LoadSessions(configDir, keyBytes)
if err != nil {
t.Fatalf("LoadSessions after request: %v", err)
}
if len(store.Sessions) != 1 {
t.Fatalf("expected 1 session, got %d", len(store.Sessions))
}
if !store.Sessions[0].LastSeenAt.After(originalLastSeen) {
t.Fatalf(
"expected LastSeenAt after %v, got %v",
originalLastSeen,
store.Sessions[0].LastSeenAt,
)
}
}