package traefikoidc import ( "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/stretchr/testify/suite" ) // SessionBehaviourSuite tests session management behavior type SessionBehaviourSuite struct { suite.Suite logger *Logger sessionManager *SessionManager } func (s *SessionBehaviourSuite) SetupTest() { s.logger = NewLogger("error") var err error s.sessionManager, err = NewSessionManager( "test-encryption-key-32-bytes-long!!", false, "", "", 0, s.logger, ) s.Require().NoError(err) } func (s *SessionBehaviourSuite) TearDownTest() { if s.sessionManager != nil { s.sessionManager.Shutdown() } } // TestValidateSessionHealth_NilSession tests validation with nil session func (s *SessionBehaviourSuite) TestValidateSessionHealth_NilSession() { err := s.sessionManager.ValidateSessionHealth(nil) s.Error(err) s.Contains(err.Error(), "session data is nil") } // TestValidateSessionHealth_NotAuthenticated tests validation with unauthenticated session func (s *SessionBehaviourSuite) TestValidateSessionHealth_NotAuthenticated() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Session is not authenticated by default err = s.sessionManager.ValidateSessionHealth(session) s.Error(err) s.Contains(err.Error(), "session is not authenticated") } // TestValidateSessionHealth_AuthenticatedSession tests validation with authenticated session func (s *SessionBehaviourSuite) TestValidateSessionHealth_AuthenticatedSession() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set session as authenticated err = session.SetAuthenticated(true) s.Require().NoError(err) // Validate health - should pass err = s.sessionManager.ValidateSessionHealth(session) s.NoError(err) } // TestValidateSessionHealth_WithValidAccessToken tests validation with valid access token func (s *SessionBehaviourSuite) TestValidateSessionHealth_WithValidAccessToken() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set session as authenticated err = session.SetAuthenticated(true) s.Require().NoError(err) // Set a valid-format access token (opaque token format) session.SetAccessToken("valid-access-token-with-sufficient-length-for-testing") // Validate health - should pass err = s.sessionManager.ValidateSessionHealth(session) s.NoError(err) } // TestValidateSessionHealth_CorruptedAccessToken tests validation with corrupted access token func (s *SessionBehaviourSuite) TestValidateSessionHealth_CorruptedAccessToken() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set session as authenticated err = session.SetAuthenticated(true) s.Require().NoError(err) // Manually set a corrupted access token session.accessSession.Values["token"] = "__CORRUPTION_MARKER_TEST__" session.accessSession.Values["compressed"] = false // Validate health - should fail err = s.sessionManager.ValidateSessionHealth(session) s.Error(err) s.Contains(err.Error(), "access token validation failed") } // TestValidateSessionHealth_PathTraversalAttempt tests detection of path traversal in session func (s *SessionBehaviourSuite) TestValidateSessionHealth_PathTraversalAttempt() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set session as authenticated err = session.SetAuthenticated(true) s.Require().NoError(err) // Inject path traversal attempt in session value session.mainSession.Values["malicious"] = "../../../etc/passwd" // Validate health - should detect tampering err = s.sessionManager.ValidateSessionHealth(session) s.Error(err) s.Contains(err.Error(), "tampering detected") } // TestValidateSessionHealth_XSSAttempt tests detection of XSS attempt in session func (s *SessionBehaviourSuite) TestValidateSessionHealth_XSSAttempt() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set session as authenticated err = session.SetAuthenticated(true) s.Require().NoError(err) // Inject XSS attempt in session value session.mainSession.Values["xss"] = "" // Validate health - should detect tampering err = s.sessionManager.ValidateSessionHealth(session) s.Error(err) s.Contains(err.Error(), "tampering detected") } // TestValidateSessionHealth_SuspiciouslyLongValue tests detection of suspiciously long values func (s *SessionBehaviourSuite) TestValidateSessionHealth_SuspiciouslyLongValue() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set session as authenticated err = session.SetAuthenticated(true) s.Require().NoError(err) // Inject suspiciously long value session.mainSession.Values["long_value"] = strings.Repeat("x", 15000) // Validate health - should detect suspicious value err = s.sessionManager.ValidateSessionHealth(session) s.Error(err) s.Contains(err.Error(), "suspiciously long") } // TestValidateTokenFormat_EmptyToken tests validation of empty token func (s *SessionBehaviourSuite) TestValidateTokenFormat_EmptyToken() { err := s.sessionManager.validateTokenFormat("", "access_token") s.NoError(err) // Empty tokens are valid (just not present) } // TestValidateTokenFormat_ValidJWT tests validation of valid JWT format func (s *SessionBehaviourSuite) TestValidateTokenFormat_ValidJWT() { // Valid JWT format (header.payload.signature) jwt := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature" err := s.sessionManager.validateTokenFormat(jwt, "id_token") s.NoError(err) } // TestValidateTokenFormat_InvalidJWTWithEmptyPart tests JWT with empty part func (s *SessionBehaviourSuite) TestValidateTokenFormat_InvalidJWTWithEmptyPart() { // JWT with empty part invalidJWT := "header..signature" err := s.sessionManager.validateTokenFormat(invalidJWT, "id_token") s.Error(err) s.Contains(err.Error(), "empty part") } // TestValidateTokenFormat_CorruptionMarker tests detection of corruption marker func (s *SessionBehaviourSuite) TestValidateTokenFormat_CorruptionMarker() { err := s.sessionManager.validateTokenFormat("__CORRUPTION_MARKER_TEST__", "access_token") s.Error(err) s.Contains(err.Error(), "corruption marker") } // TestPeriodicChunkCleanup tests the periodic cleanup function func (s *SessionBehaviourSuite) TestPeriodicChunkCleanup() { // This should not panic or error s.sessionManager.PeriodicChunkCleanup() // Verify it can be called multiple times s.sessionManager.PeriodicChunkCleanup() s.sessionManager.PeriodicChunkCleanup() } // TestPeriodicChunkCleanup_WithCanceledContext tests cleanup with canceled context func (s *SessionBehaviourSuite) TestPeriodicChunkCleanup_WithCanceledContext() { // Cancel the context s.sessionManager.cancel() // Should return early without panicking s.sessionManager.PeriodicChunkCleanup() } // TestGetSessionStats tests session statistics retrieval func (s *SessionBehaviourSuite) TestGetSessionStats() { stats := s.sessionManager.GetSessionStats() s.NotNil(stats) s.Contains(stats, "active_sessions") s.Contains(stats, "pool_hits") s.Contains(stats, "pool_misses") } // TestGetSessionMetrics tests session metrics retrieval func (s *SessionBehaviourSuite) TestGetSessionMetrics() { metrics := s.sessionManager.GetSessionMetrics() s.NotNil(metrics) s.Equal("CookieStore", metrics["session_manager_type"]) s.Contains(metrics, "force_https") s.Contains(metrics, "absolute_timeout_hours") s.Contains(metrics, "max_cookie_size") s.Contains(metrics, "has_encryption") } // TestEnhanceSessionSecurity_NilOptions tests enhancing nil options func (s *SessionBehaviourSuite) TestEnhanceSessionSecurity_NilOptions() { req := httptest.NewRequest(http.MethodGet, "/test", nil) options := s.sessionManager.EnhanceSessionSecurity(nil, req) s.NotNil(options) s.True(options.HttpOnly) s.Equal("/", options.Path) } // TestEnhanceSessionSecurity_WithHTTPS tests enhancing with HTTPS request func (s *SessionBehaviourSuite) TestEnhanceSessionSecurity_WithHTTPS() { req := httptest.NewRequest(http.MethodGet, "https://example.com/test", nil) req.Header.Set("X-Forwarded-Proto", "https") options := s.sessionManager.EnhanceSessionSecurity(nil, req) s.True(options.Secure) s.Equal(http.SameSiteLaxMode, options.SameSite) } // TestEnhanceSessionSecurity_MissingUserAgent tests handling of missing User-Agent func (s *SessionBehaviourSuite) TestEnhanceSessionSecurity_MissingUserAgent() { req := httptest.NewRequest(http.MethodGet, "/test", nil) // Explicitly remove User-Agent req.Header.Del("User-Agent") options := s.sessionManager.EnhanceSessionSecurity(nil, req) // Should have reduced MaxAge for suspicious requests s.NotNil(options) } // TestCleanupOldCookies tests cookie cleanup func (s *SessionBehaviourSuite) TestCleanupOldCookies() { req := httptest.NewRequest(http.MethodGet, "/test", nil) req.Header.Set("Host", "example.com") rw := httptest.NewRecorder() // Add some cookies that match the prefix req.AddCookie(&http.Cookie{Name: "_oidc_raczylo_m", Value: "test"}) req.AddCookie(&http.Cookie{Name: "_oidc_raczylo_a", Value: "test"}) // Should not panic s.sessionManager.CleanupOldCookies(rw, req) } // TestSessionData_DirtyTracking tests dirty flag tracking func (s *SessionBehaviourSuite) TestSessionData_DirtyTracking() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Initially not dirty (fresh session from pool) s.False(session.IsDirty()) // Mark dirty session.MarkDirty() s.True(session.IsDirty()) // Reset should clear dirty flag session.Reset() s.False(session.IsDirty()) } // TestSessionData_SetEmail tests email setter with dirty tracking func (s *SessionBehaviourSuite) TestSessionData_SetEmail() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set email session.SetEmail("test@example.com") s.Equal("test@example.com", session.GetEmail()) s.True(session.IsDirty()) } // TestSessionData_SetCSRF tests CSRF setter with dirty tracking func (s *SessionBehaviourSuite) TestSessionData_SetCSRF() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set CSRF session.SetCSRF("csrf-token-value") s.Equal("csrf-token-value", session.GetCSRF()) s.True(session.IsDirty()) // Setting same value should not trigger dirty again session.dirty = false session.SetCSRF("csrf-token-value") s.False(session.IsDirty()) } // TestSessionData_SetNonce tests nonce setter with dirty tracking func (s *SessionBehaviourSuite) TestSessionData_SetNonce() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set nonce session.SetNonce("nonce-value") s.Equal("nonce-value", session.GetNonce()) s.True(session.IsDirty()) } // TestSessionData_SetCodeVerifier tests code verifier setter func (s *SessionBehaviourSuite) TestSessionData_SetCodeVerifier() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set code verifier session.SetCodeVerifier("pkce-code-verifier") s.Equal("pkce-code-verifier", session.GetCodeVerifier()) s.True(session.IsDirty()) } // TestSessionData_SetIncomingPath tests incoming path setter func (s *SessionBehaviourSuite) TestSessionData_SetIncomingPath() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set incoming path session.SetIncomingPath("/original/path?query=value") s.Equal("/original/path?query=value", session.GetIncomingPath()) s.True(session.IsDirty()) } // TestSessionData_RedirectCount tests redirect count operations func (s *SessionBehaviourSuite) TestSessionData_RedirectCount() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Initial count should be 0 s.Equal(0, session.GetRedirectCount()) // Increment session.IncrementRedirectCount() s.Equal(1, session.GetRedirectCount()) session.IncrementRedirectCount() s.Equal(2, session.GetRedirectCount()) // Reset session.ResetRedirectCount() s.Equal(0, session.GetRedirectCount()) } // TestSessionData_SetAccessToken tests access token storage func (s *SessionBehaviourSuite) TestSessionData_SetAccessToken() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set a valid opaque access token token := "opaque-access-token-with-sufficient-length-for-testing" session.SetAccessToken(token) // Get the token back retrieved := session.GetAccessToken() s.Equal(token, retrieved) } // TestSessionData_SetAccessToken_InvalidFormat tests rejection of invalid token format func (s *SessionBehaviourSuite) TestSessionData_SetAccessToken_InvalidFormat() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set a token with invalid format (exactly 1 dot is invalid) session.SetAccessToken("invalid.token") // Should be rejected retrieved := session.GetAccessToken() s.Empty(retrieved) } // TestSessionData_SetAccessToken_TooShortOpaque tests rejection of too short opaque token func (s *SessionBehaviourSuite) TestSessionData_SetAccessToken_TooShortOpaque() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set a very short opaque token (less than 20 chars) session.SetAccessToken("short") // Should be rejected retrieved := session.GetAccessToken() s.Empty(retrieved) } // TestSessionData_SetIDToken_ValidJWT tests ID token storage with valid JWT func (s *SessionBehaviourSuite) TestSessionData_SetIDToken_ValidJWT() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set a valid JWT format ID token token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.signature" session.SetIDToken(token) // The ID token should be stored - verify it directly from the session // since GetIDToken uses ChunkManager which may apply additional validation storedToken, _ := session.idTokenSession.Values["token"].(string) s.NotEmpty(storedToken) s.True(session.IsDirty()) } // TestSessionData_SetIDToken_InvalidFormat tests rejection of invalid ID token format func (s *SessionBehaviourSuite) TestSessionData_SetIDToken_InvalidFormat() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set a non-JWT format (ID tokens must be JWT) session.SetIDToken("not-a-jwt-token") // Should be rejected retrieved := session.GetIDToken() s.Empty(retrieved) } // TestSessionData_SetRefreshToken tests refresh token storage func (s *SessionBehaviourSuite) TestSessionData_SetRefreshToken() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Set refresh token (opaque format is valid) token := "refresh-token-opaque-format-value" session.SetRefreshToken(token) // Get the token back retrieved := session.GetRefreshToken() s.Equal(token, retrieved) } // TestSessionData_SetRefreshToken_TooLarge tests rejection of too large refresh token func (s *SessionBehaviourSuite) TestSessionData_SetRefreshToken_TooLarge() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Create a very large token (over 50KB) largeToken := strings.Repeat("x", 60*1024) session.SetRefreshToken(largeToken) // Should be rejected retrieved := session.GetRefreshToken() s.Empty(retrieved) } // TestSessionData_GetRefreshTokenIssuedAt tests refresh token issued timestamp func (s *SessionBehaviourSuite) TestSessionData_GetRefreshTokenIssuedAt() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Before setting refresh token, issued_at should be zero issuedAt := session.GetRefreshTokenIssuedAt() s.True(issuedAt.IsZero()) // Set refresh token (this sets issued_at) session.SetRefreshToken("refresh-token-value-here") // Now issued_at should be set issuedAt = session.GetRefreshTokenIssuedAt() s.False(issuedAt.IsZero()) s.True(time.Since(issuedAt) < 5*time.Second) // Should be very recent } // TestSessionData_Clear tests session clearing func (s *SessionBehaviourSuite) TestSessionData_Clear() { req := httptest.NewRequest(http.MethodGet, "/test", nil) rw := httptest.NewRecorder() session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) // Set some data err = session.SetAuthenticated(true) s.Require().NoError(err) session.SetEmail("test@example.com") session.SetCSRF("csrf-token") // Clear session err = session.Clear(req, rw) s.NoError(err) // After clear, session is returned to pool, so we shouldn't use it } // TestSessionData_Save tests session saving func (s *SessionBehaviourSuite) TestSessionData_Save() { req := httptest.NewRequest(http.MethodGet, "/test", nil) rw := httptest.NewRecorder() session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) defer session.returnToPoolSafely() // Modify session session.SetEmail("test@example.com") s.True(session.IsDirty()) // Save session err = session.Save(req, rw) s.NoError(err) // After save, dirty flag should be cleared s.False(session.IsDirty()) // Response should have cookies cookies := rw.Result().Cookies() s.NotEmpty(cookies) } // TestSessionData_ReturnToPool tests returning session to pool func (s *SessionBehaviourSuite) TestSessionData_ReturnToPool() { req := httptest.NewRequest(http.MethodGet, "/test", nil) session, err := s.sessionManager.GetSession(req) s.Require().NoError(err) // Initially in use s.True(session.inUse) // Return to pool safely session.returnToPoolSafely() // Should no longer be in use s.False(session.inUse) } // TestTokenCompression tests token compression functionality func (s *SessionBehaviourSuite) TestTokenCompression() { // A typical JWT token that could benefit from compression token := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InRlc3Qta2V5LWlkIn0.eyJpc3MiOiJodHRwczovL3Rlc3QtaXNzdWVyLmNvbSIsInN1YiI6InRlc3Qtc3ViamVjdCIsImF1ZCI6InRlc3QtY2xpZW50LWlkIiwiZXhwIjoxNzAyNDE2MDAwLCJpYXQiOjE3MDI0MTI0MDAsIm5vbmNlIjoidGVzdC1ub25jZSIsImVtYWlsIjoidXNlckBleGFtcGxlLmNvbSJ9.signature_data_here" compressed := compressToken(token) // Decompress and verify decompressed := decompressToken(compressed) s.Equal(token, decompressed) } // TestTokenCompression_EmptyToken tests compression of empty token func (s *SessionBehaviourSuite) TestTokenCompression_EmptyToken() { compressed := compressToken("") s.Empty(compressed) decompressed := decompressToken("") s.Empty(decompressed) } // TestTokenCompression_InvalidFormat tests compression of non-JWT token func (s *SessionBehaviourSuite) TestTokenCompression_InvalidFormat() { // Token without proper JWT format (wrong number of dots) token := "not-a-jwt" compressed := compressToken(token) // Should return original (not compressed) s.Equal(token, compressed) } // TestSplitIntoChunks tests chunk splitting functionality func (s *SessionBehaviourSuite) TestSplitIntoChunks() { // Test with a string that needs splitting data := strings.Repeat("x", 3000) chunks := splitIntoChunks(data, 1000) s.Equal(3, len(chunks)) s.Equal(1000, len(chunks[0])) s.Equal(1000, len(chunks[1])) s.Equal(1000, len(chunks[2])) // Verify reassembly reassembled := strings.Join(chunks, "") s.Equal(data, reassembled) } // TestSplitIntoChunks_SmallData tests chunk splitting with data smaller than chunk size func (s *SessionBehaviourSuite) TestSplitIntoChunks_SmallData() { data := "small" chunks := splitIntoChunks(data, 1000) s.Equal(1, len(chunks)) s.Equal(data, chunks[0]) } // TestValidateChunkSize tests chunk size validation func (s *SessionBehaviourSuite) TestValidateChunkSize() { // Small chunk should be valid s.True(validateChunkSize("small_chunk_data")) // Very large chunk should be invalid largeChunk := strings.Repeat("x", 5000) s.False(validateChunkSize(largeChunk)) } // TestIsCorruptionMarker tests corruption marker detection func (s *SessionBehaviourSuite) TestIsCorruptionMarker() { // Known corruption markers s.True(isCorruptionMarker("__CORRUPTION_MARKER_TEST__")) s.True(isCorruptionMarker("__INVALID_BASE64_DATA__")) s.True(isCorruptionMarker("<<>>")) // Normal data s.False(isCorruptionMarker("normal-data")) s.False(isCorruptionMarker("eyJhbGciOiJSUzI1NiJ9")) s.False(isCorruptionMarker("")) // Data with special characters (in long strings) s.True(isCorruptionMarker("long-string-with!special@chars")) } // TestSessionManager_Shutdown tests graceful shutdown func (s *SessionBehaviourSuite) TestSessionManager_Shutdown() { // Create a new session manager for this test sm, err := NewSessionManager( "test-encryption-key-32-bytes-long!!", false, "", "", 0, s.logger, ) s.Require().NoError(err) // Shutdown should complete without error err = sm.Shutdown() s.NoError(err) // Second shutdown should also be safe (idempotent) err = sm.Shutdown() s.NoError(err) } // TestCookieNameHelpers tests cookie name helper methods func (s *SessionBehaviourSuite) TestCookieNameHelpers() { s.Equal("_oidc_raczylo_m", s.sessionManager.mainCookieName()) s.Equal("_oidc_raczylo_a", s.sessionManager.accessTokenCookieName()) s.Equal("_oidc_raczylo_r", s.sessionManager.refreshTokenCookieName()) s.Equal("_oidc_raczylo_id", s.sessionManager.idTokenCookieName()) } // TestSessionManager_CustomCookiePrefix tests custom cookie prefix func (s *SessionBehaviourSuite) TestSessionManager_CustomCookiePrefix() { customSM, err := NewSessionManager( "test-encryption-key-32-bytes-long!!", false, "", "custom_prefix_", 0, s.logger, ) s.Require().NoError(err) defer customSM.Shutdown() s.Equal("custom_prefix_m", customSM.mainCookieName()) s.Equal("custom_prefix_a", customSM.accessTokenCookieName()) s.Equal("custom_prefix_r", customSM.refreshTokenCookieName()) s.Equal("custom_prefix_id", customSM.idTokenCookieName()) } // TestSessionManager_ShortEncryptionKey tests rejection of short encryption key func (s *SessionBehaviourSuite) TestSessionManager_ShortEncryptionKey() { _, err := NewSessionManager( "short", // Too short false, "", "", 0, s.logger, ) s.Error(err) s.Contains(err.Error(), "encryption key must be at least") } // TestGenerateSecureRandomString tests secure random string generation func (s *SessionBehaviourSuite) TestGenerateSecureRandomString() { // Generate two random strings str1, err := generateSecureRandomString(32) s.NoError(err) s.Equal(64, len(str1)) // Hex encoding doubles length str2, err := generateSecureRandomString(32) s.NoError(err) s.Equal(64, len(str2)) // They should be different s.NotEqual(str1, str2) } // TestConstantTimeStringCompare tests constant-time string comparison func (s *SessionBehaviourSuite) TestConstantTimeStringCompare() { s.True(constantTimeStringCompare("hello", "hello")) s.False(constantTimeStringCompare("hello", "world")) s.False(constantTimeStringCompare("hello", "hell")) s.False(constantTimeStringCompare("", "hello")) s.True(constantTimeStringCompare("", "")) } func TestSessionBehaviourSuite(t *testing.T) { suite.Run(t, new(SessionBehaviourSuite)) }