Pulse/internal/api/session_oidc_test.go
2026-07-07 16:35:07 +01:00

239 lines
7 KiB
Go

package api
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestCreateOIDCSession(t *testing.T) {
// Create a temporary directory for test sessions
tmpDir, err := os.MkdirTemp("", "pulse-session-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
store := NewSessionStore(tmpDir)
// Create an OIDC session with token info
token := "test-session-token-123"
oidcInfo := &OIDCTokenInfo{
RefreshToken: "test-refresh-token",
AccessTokenExp: time.Now().Add(1 * time.Hour),
Issuer: "https://example.com",
ClientID: "test-client-id",
}
store.CreateOIDCSession(token, 24*time.Hour, "TestAgent", "127.0.0.1", "testuser", oidcInfo)
// Verify session was created
if !store.ValidateSession(token) {
t.Error("Created OIDC session should be valid")
}
// Verify OIDC token info was stored
session := store.GetSession(token)
if session == nil {
t.Fatal("GetSession returned nil for valid session")
}
if session.OIDCRefreshToken != oidcInfo.RefreshToken {
t.Errorf("RefreshToken mismatch: got %q, want %q", session.OIDCRefreshToken, oidcInfo.RefreshToken)
}
if session.OIDCIssuer != oidcInfo.Issuer {
t.Errorf("Issuer mismatch: got %q, want %q", session.OIDCIssuer, oidcInfo.Issuer)
}
if session.OIDCClientID != oidcInfo.ClientID {
t.Errorf("ClientID mismatch: got %q, want %q", session.OIDCClientID, oidcInfo.ClientID)
}
if session.OIDCAccessTokenExp.IsZero() {
t.Error("AccessTokenExp should not be zero")
}
if session.OIDCAccessTokenIssuedAt.IsZero() {
t.Error("AccessTokenIssuedAt should not be zero")
}
if !session.OIDCAccessTokenIssuedAt.Before(session.OIDCAccessTokenExp) {
t.Error("AccessTokenIssuedAt should be before AccessTokenExp")
}
}
func TestUpdateOIDCTokens(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "pulse-session-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
store := NewSessionStore(tmpDir)
token := "test-session-token-456"
originalExpiry := time.Now().Add(1 * time.Hour)
oidcInfo := &OIDCTokenInfo{
RefreshToken: "original-refresh-token",
AccessTokenExp: originalExpiry,
Issuer: "https://example.com",
ClientID: "test-client-id",
}
store.CreateOIDCSession(token, 24*time.Hour, "TestAgent", "127.0.0.1", "testuser", oidcInfo)
// Update the tokens (simulating a refresh)
beforeRefresh := time.Now()
newExpiry := time.Now().Add(2 * time.Hour)
store.UpdateOIDCTokens(token, "new-refresh-token", newExpiry)
// Verify tokens were updated
session := store.GetSession(token)
if session == nil {
t.Fatal("GetSession returned nil for valid session")
}
if session.OIDCRefreshToken != "new-refresh-token" {
t.Errorf("RefreshToken should be updated: got %q, want %q", session.OIDCRefreshToken, "new-refresh-token")
}
if !session.OIDCAccessTokenExp.After(originalExpiry) {
t.Error("AccessTokenExp should be updated to new expiry")
}
if session.OIDCAccessTokenIssuedAt.Before(beforeRefresh) {
t.Error("AccessTokenIssuedAt should update when tokens refresh")
}
}
func TestOIDCSessionPersistence(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "pulse-session-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
token := "test-session-token-persist"
oidcInfo := &OIDCTokenInfo{
RefreshToken: "persist-refresh-token",
AccessTokenExp: time.Now().Add(1 * time.Hour),
Issuer: "https://example.com",
ClientID: "test-client-id",
}
// Create session in first store instance
store1 := NewSessionStore(tmpDir)
store1.CreateOIDCSession(token, 24*time.Hour, "TestAgent", "127.0.0.1", "testuser", oidcInfo)
// Verify file was written
sessionFile := filepath.Join(tmpDir, "sessions.json")
if _, err := os.Stat(sessionFile); os.IsNotExist(err) {
t.Fatal("Session file should exist after creating session")
}
// Create new store instance (simulates restart)
store2 := NewSessionStore(tmpDir)
// Verify session was loaded with OIDC info
session := store2.GetSession(token)
if session == nil {
t.Fatal("Session should be restored after reload")
}
if session.OIDCRefreshToken != oidcInfo.RefreshToken {
t.Errorf("RefreshToken should persist: got %q, want %q", session.OIDCRefreshToken, oidcInfo.RefreshToken)
}
if session.OIDCIssuer != oidcInfo.Issuer {
t.Errorf("Issuer should persist: got %q, want %q", session.OIDCIssuer, oidcInfo.Issuer)
}
}
func TestOIDCSessionPersistsDisplayUsernameSeparatelyFromPrincipal(t *testing.T) {
tmpDir := t.TempDir()
token := "test-session-token-display"
principal := "sso:oidc:test-oidc:stable-principal"
displayUsername := "alice@example.com"
store1 := NewSessionStore(tmpDir)
store1.CreateOIDCSessionWithDisplayName(token, 24*time.Hour, "TestAgent", "127.0.0.1", principal, displayUsername, &OIDCTokenInfo{
RefreshToken: "display-refresh-token",
AccessTokenExp: time.Now().Add(1 * time.Hour),
Issuer: "https://example.com",
ClientID: "test-client-id",
})
store2 := NewSessionStore(tmpDir)
session := store2.GetSession(token)
if session == nil {
t.Fatal("Session should be restored after reload")
}
if session.Username != principal {
t.Fatalf("session principal = %q, want %q", session.Username, principal)
}
if session.DisplayUsername != displayUsername {
t.Fatalf("session display username = %q, want %q", session.DisplayUsername, displayUsername)
}
}
func TestCreateOIDCSession_NilTokenInfo(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "pulse-session-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
store := NewSessionStore(tmpDir)
// Create OIDC session with nil token info (no refresh token available)
token := "test-session-no-refresh"
store.CreateOIDCSession(token, 24*time.Hour, "TestAgent", "127.0.0.1", "testuser", nil)
// Verify session was created
if !store.ValidateSession(token) {
t.Error("Session should be valid even without OIDC tokens")
}
session := store.GetSession(token)
if session == nil {
t.Fatal("GetSession returned nil")
}
if session.OIDCRefreshToken != "" {
t.Errorf("RefreshToken should be empty when nil info passed: got %q", session.OIDCRefreshToken)
}
}
func TestInvalidateSession(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "pulse-session-test-*")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
store := NewSessionStore(tmpDir)
token := "test-session-invalidate"
oidcInfo := &OIDCTokenInfo{
RefreshToken: "refresh-token",
AccessTokenExp: time.Now().Add(1 * time.Hour),
Issuer: "https://example.com",
ClientID: "test-client-id",
}
store.CreateOIDCSession(token, 24*time.Hour, "TestAgent", "127.0.0.1", "testuser", oidcInfo)
// Verify session exists
if !store.ValidateSession(token) {
t.Error("Session should be valid before invalidation")
}
// Invalidate the session
store.InvalidateSession(token)
// Verify session no longer exists
if store.ValidateSession(token) {
t.Error("Session should be invalid after invalidation")
}
}