Files
traefikoidc/internal/utils/utils_test.go
T
lukaszraczylo e64fc7f730 Add redis support for distributed caching (#83)
* Add redis support for distributed caching

* Move towards the self-provided Redis connection pool and RESP protocol implementation.
Official redis client library won't work with yaegi.

* fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi.

* fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi.

* fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi.

* fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi.

* fixup! fixup! fixup! fixup! fixup! Move towards the self-provided Redis connection pool and RESP protocol implementation. Official redis client library won't work with yaegi.

* ... and another all nighter.

* fixup! ... and another all nighter.

* fixup! fixup! ... and another all nighter.

* fixup! fixup! fixup! ... and another all nighter.

* Resolve issue #85 by adding ability to set custom claims in JWT tokens

* Remove redundant validation in auth middleware ( issue #89 )

* Add ability to set cookie prefix for session cookies ( #87 )

* fixup! Add ability to set cookie prefix for session cookies ( #87 )

* Add ability to set cookie max age - issue #91

* Potential fix for code scanning alert no. 10: Size computation for allocation may overflow

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

* fixup! Merge main into 0.8.0-redis: resolve conflicts

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-11-30 02:18:46 +00:00

717 lines
19 KiB
Go

package utils
import (
"os"
"reflect"
"testing"
)
func TestCreateStringMap(t *testing.T) {
items := []string{"apple", "banana", "cherry"}
result := CreateStringMap(items)
expected := map[string]struct{}{
"apple": {},
"banana": {},
"cherry": {},
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestCreateCaseInsensitiveStringMap(t *testing.T) {
items := []string{"Apple", "BANANA", "Cherry"}
result := CreateCaseInsensitiveStringMap(items)
expected := map[string]struct{}{
"apple": {},
"banana": {},
"cherry": {},
}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestDeduplicateScopes(t *testing.T) {
scopes := []string{"openid", "profile", "email", "openid", "profile"}
result := DeduplicateScopes(scopes)
expected := []string{"openid", "profile", "email"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestMergeScopes(t *testing.T) {
defaultScopes := []string{"openid", "profile"}
userScopes := []string{"email", "offline_access"}
result := MergeScopes(defaultScopes, userScopes)
expected := []string{"openid", "profile", "email", "offline_access"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestMergeScopesWithDuplicates(t *testing.T) {
defaultScopes := []string{"openid", "profile"}
userScopes := []string{"profile", "email", "openid"}
result := MergeScopes(defaultScopes, userScopes)
expected := []string{"openid", "profile", "email"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestMergeScopesEmptyUserScopes(t *testing.T) {
defaultScopes := []string{"openid", "profile"}
userScopes := []string{}
result := MergeScopes(defaultScopes, userScopes)
expected := []string{"openid", "profile"}
if !reflect.DeepEqual(result, expected) {
t.Errorf("Expected %v, got %v", expected, result)
}
}
func TestKeysFromMap(t *testing.T) {
m := map[string]struct{}{
"key1": {},
"key2": {},
"key3": {},
}
result := KeysFromMap(m)
// Since map iteration order is not guaranteed, we need to check length and presence
if len(result) != 3 {
t.Errorf("Expected 3 keys, got %d", len(result))
}
resultMap := make(map[string]bool)
for _, key := range result {
resultMap[key] = true
}
expectedKeys := []string{"key1", "key2", "key3"}
for _, key := range expectedKeys {
if !resultMap[key] {
t.Errorf("Expected key %s not found in result", key)
}
}
}
func TestBuildFullURL(t *testing.T) {
tests := []struct {
scheme string
host string
path string
expected string
}{
{"https", "example.com", "/path", "https://example.com/path"},
{"http", "localhost:8080", "/callback", "http://localhost:8080/callback"},
{"https", "test.example.com", "/auth/callback", "https://test.example.com/auth/callback"},
}
for _, test := range tests {
result := BuildFullURL(test.scheme, test.host, test.path)
if result != test.expected {
t.Errorf("For scheme=%s, host=%s, path=%s: expected %s, got %s",
test.scheme, test.host, test.path, test.expected, result)
}
}
}
func TestIsTestMode(t *testing.T) {
// This test is challenging because IsTestMode() depends on runtime conditions.
// We'll test what we can control via environment variables.
tests := []struct {
name string
setup func()
cleanup func()
expected bool
}{
{
name: "suppress diagnostic logs enabled",
setup: func() {
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", "1")
},
cleanup: func() {
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
},
expected: true,
},
{
name: "GO_TEST environment variable set",
setup: func() {
os.Setenv("GO_TEST", "1")
},
cleanup: func() {
os.Unsetenv("GO_TEST")
},
expected: true,
},
{
name: "normal runtime conditions",
setup: func() {
// Disable runtime stack check to test fallback behavior
os.Setenv("DISABLE_RUNTIME_STACK_CHECK", "1")
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", "")
os.Setenv("GO_TEST", "")
},
cleanup: func() {
os.Unsetenv("DISABLE_RUNTIME_STACK_CHECK")
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
os.Unsetenv("GO_TEST")
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup test environment
tt.setup()
defer tt.cleanup()
result := IsTestMode()
// Note: Some test conditions may still return true due to runtime.Stack
// detecting testing context, so we check the expected behavior when possible
if tt.name == "suppress diagnostic logs enabled" || tt.name == "GO_TEST environment variable set" {
if result != tt.expected {
t.Errorf("Expected %v, got %v", tt.expected, result)
}
}
})
}
// Test that IsTestMode() returns true when called from a test context
// (which it should, since we're in a test right now)
result := IsTestMode()
if !result {
t.Log("Note: IsTestMode() returned false in test context, which may be expected depending on runtime conditions")
}
}
func TestIsTestModeEdgeCases(t *testing.T) {
// Test with various environment variable combinations
tests := []struct {
name string
env map[string]string
}{
{
name: "all env vars empty",
env: map[string]string{
"SUPPRESS_DIAGNOSTIC_LOGS": "",
"GO_TEST": "",
"DISABLE_RUNTIME_STACK_CHECK": "",
},
},
{
name: "mixed env vars",
env: map[string]string{
"SUPPRESS_DIAGNOSTIC_LOGS": "0",
"GO_TEST": "true",
"DISABLE_RUNTIME_STACK_CHECK": "1",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Save original environment
original := make(map[string]string)
for key := range tt.env {
original[key] = os.Getenv(key)
}
// Set test environment
for key, value := range tt.env {
os.Setenv(key, value)
}
// Test IsTestMode (result may vary based on runtime conditions)
result := IsTestMode()
_ = result // We just want to ensure it doesn't panic
// Restore original environment
for key, value := range original {
if value == "" {
os.Unsetenv(key)
} else {
os.Setenv(key, value)
}
}
})
}
}
func TestIsTestModeDetectionMethods(t *testing.T) {
// Test that calling IsTestMode in a test context returns true
// This should cover most of the function branches since we're in a test
result := IsTestMode()
// In a test context, IsTestMode should return true
if !result {
t.Log("IsTestMode returned false in test context - this may be due to environment settings")
}
// Test with explicit environment manipulation to force different paths
originalSuppressDiag := os.Getenv("SUPPRESS_DIAGNOSTIC_LOGS")
originalGoTest := os.Getenv("GO_TEST")
originalDisableStack := os.Getenv("DISABLE_RUNTIME_STACK_CHECK")
defer func() {
// Restore original environment
if originalSuppressDiag == "" {
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
} else {
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", originalSuppressDiag)
}
if originalGoTest == "" {
os.Unsetenv("GO_TEST")
} else {
os.Setenv("GO_TEST", originalGoTest)
}
if originalDisableStack == "" {
os.Unsetenv("DISABLE_RUNTIME_STACK_CHECK")
} else {
os.Setenv("DISABLE_RUNTIME_STACK_CHECK", originalDisableStack)
}
}()
// Test various combinations to exercise different code paths
testCases := []struct {
name string
suppressDiag string
goTest string
disableStack string
expectTrue bool
}{
{
name: "suppress_diagnostic_logs_1",
suppressDiag: "1",
goTest: "",
disableStack: "",
expectTrue: true,
},
{
name: "go_test_1",
suppressDiag: "",
goTest: "1",
disableStack: "",
expectTrue: true,
},
{
name: "runtime_detection_allowed",
suppressDiag: "",
goTest: "",
disableStack: "",
expectTrue: true, // Should detect test context from runtime stack
},
{
name: "runtime_detection_disabled",
suppressDiag: "",
goTest: "",
disableStack: "1",
expectTrue: false, // May still be true due to os.Args detection
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", tc.suppressDiag)
os.Setenv("GO_TEST", tc.goTest)
os.Setenv("DISABLE_RUNTIME_STACK_CHECK", tc.disableStack)
result := IsTestMode()
// For environment variable cases, we can assert the expected result
if tc.name == "suppress_diagnostic_logs_1" || tc.name == "go_test_1" {
if result != tc.expectTrue {
t.Errorf("Expected %v, got %v for case %s", tc.expectTrue, result, tc.name)
}
}
// For runtime detection cases, result may vary based on actual runtime conditions
})
}
}
func TestUtilsPackageComplete(t *testing.T) {
// Test edge cases to improve coverage
// Test CreateStringMap with empty slice
emptyResult := CreateStringMap([]string{})
if len(emptyResult) != 0 {
t.Errorf("Expected empty map, got %v", emptyResult)
}
// Test CreateCaseInsensitiveStringMap with empty slice
emptyInsensitiveResult := CreateCaseInsensitiveStringMap([]string{})
if len(emptyInsensitiveResult) != 0 {
t.Errorf("Expected empty map, got %v", emptyInsensitiveResult)
}
// Test DeduplicateScopes with empty slice
emptyScopes := DeduplicateScopes([]string{})
if len(emptyScopes) != 0 {
t.Errorf("Expected empty slice, got %v", emptyScopes)
}
// Test MergeScopes with nil slices
nilResult := MergeScopes(nil, nil)
if len(nilResult) != 0 {
t.Errorf("Expected empty slice, got %v", nilResult)
}
// Test KeysFromMap with empty map
emptyMapKeys := KeysFromMap(map[string]struct{}{})
if len(emptyMapKeys) != 0 {
t.Errorf("Expected empty slice, got %v", emptyMapKeys)
}
// Test BuildFullURL with empty values
emptyURL := BuildFullURL("", "", "")
expected := "://"
if emptyURL != expected {
t.Errorf("Expected '%s', got '%s'", expected, emptyURL)
}
}
func TestIsTestModeOsArgsDetection(t *testing.T) {
// Save original os.Args
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
// Test with different os.Args[0] values that should trigger test mode
testCases := []struct {
name string
args0 string
expected bool
}{
{
name: "Binary with .test suffix",
args0: "/path/to/myapp.test",
expected: true,
},
{
name: "Binary with go_build_ prefix",
args0: "/tmp/go_build_myapp",
expected: true,
},
{
name: "Binary with test in name",
args0: "/path/to/test_binary",
expected: true,
},
{
name: "Binary with __debug_bin",
args0: "/path/to/__debug_bin123",
expected: true,
},
{
name: "Regular binary name",
args0: "/path/to/myapp",
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Set up environment to avoid interference from other detection methods
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", "")
os.Setenv("GO_TEST", "")
os.Setenv("DISABLE_RUNTIME_STACK_CHECK", "1") // Disable runtime stack check
defer func() {
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
os.Unsetenv("GO_TEST")
os.Unsetenv("DISABLE_RUNTIME_STACK_CHECK")
}()
// Set os.Args
os.Args = []string{tc.args0}
result := IsTestMode()
if result != tc.expected {
t.Errorf("For args[0] = '%s': expected %v, got %v", tc.args0, tc.expected, result)
}
})
}
}
func TestIsTestModeArgsFlagDetection(t *testing.T) {
// Save original os.Args
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
testCases := []struct {
name string
args []string
expected bool
}{
{
name: "Args contain -test flag",
args: []string{"/path/to/app", "-test.v", "true"},
expected: true,
},
{
name: "Args contain -test.timeout",
args: []string{"/path/to/app", "-test.timeout", "30s"},
expected: true,
},
{
name: "Args without test flags",
args: []string{"/path/to/app", "-verbose", "-config", "file.conf"},
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Set up environment to avoid interference
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", "")
os.Setenv("GO_TEST", "")
os.Setenv("DISABLE_RUNTIME_STACK_CHECK", "1")
defer func() {
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
os.Unsetenv("GO_TEST")
os.Unsetenv("DISABLE_RUNTIME_STACK_CHECK")
}()
// Ensure args[0] doesn't trigger detection by itself
if len(tc.args) > 0 {
tc.args[0] = "/regular/app/name"
}
os.Args = tc.args
result := IsTestMode()
if result != tc.expected {
t.Errorf("For args = %v: expected %v, got %v", tc.args, tc.expected, result)
}
})
}
}
func TestIsTestModeRuntimeCompiler(t *testing.T) {
// This test verifies that the runtime.Compiler check works
// We can't easily change runtime.Compiler, but we can test the logic path
// Set up environment to isolate this test
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", "")
os.Setenv("GO_TEST", "")
os.Setenv("DISABLE_RUNTIME_STACK_CHECK", "1")
defer func() {
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
os.Unsetenv("GO_TEST")
os.Unsetenv("DISABLE_RUNTIME_STACK_CHECK")
}()
// Test with args that should trigger test mode when runtime.Compiler == "gc"
os.Args = []string{"some_test_binary", "arg1"}
result := IsTestMode()
// Since runtime.Compiler is "gc" in most cases and os.Args[0] contains "test",
// this should return true
if !result {
t.Log("Note: This test may vary depending on the actual runtime.Compiler value")
}
}
func TestIsTestModeYaegiCompiler(t *testing.T) {
// Test the yaegi compiler detection
// We can't change runtime.Compiler directly, but we can verify the GO_TEST path
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
// Test that GO_TEST=1 triggers test mode regardless of other conditions
os.Setenv("GO_TEST", "1")
os.Setenv("SUPPRESS_DIAGNOSTIC_LOGS", "")
defer func() {
os.Unsetenv("GO_TEST")
os.Unsetenv("SUPPRESS_DIAGNOSTIC_LOGS")
}()
// Use a non-test-like binary name
os.Args = []string{"/regular/binary/name"}
result := IsTestMode()
if !result {
t.Error("Expected true when GO_TEST=1 is set")
}
}
// ============================================================================
// LOGGER WRAPPER TESTS
// ============================================================================
// mockLogger is a simple mock implementation for testing
type mockLogger struct {
infoCalls int
debugCalls int
errorCalls int
lastFormat string
lastArgs []interface{}
}
func (m *mockLogger) Infof(format string, args ...interface{}) {
m.infoCalls++
m.lastFormat = format
m.lastArgs = args
}
func (m *mockLogger) Debugf(format string, args ...interface{}) {
m.debugCalls++
m.lastFormat = format
m.lastArgs = args
}
func (m *mockLogger) Errorf(format string, args ...interface{}) {
m.errorCalls++
m.lastFormat = format
m.lastArgs = args
}
// TestWrapLoggerForRecovery tests the recovery logger wrapper
func TestWrapLoggerForRecovery(t *testing.T) {
mock := &mockLogger{}
wrapper := WrapLoggerForRecovery(mock)
if wrapper == nil {
t.Fatal("WrapLoggerForRecovery should not return nil")
}
// Test Logf
wrapper.Logf("test info: %s", "value")
if mock.infoCalls != 1 {
t.Errorf("Expected 1 info call, got %d", mock.infoCalls)
}
if mock.lastFormat != "test info: %s" {
t.Errorf("Expected format 'test info: %%s', got '%s'", mock.lastFormat)
}
// Test ErrorLogf
wrapper.ErrorLogf("test error: %d", 123)
if mock.errorCalls != 1 {
t.Errorf("Expected 1 error call, got %d", mock.errorCalls)
}
if mock.lastFormat != "test error: %d" {
t.Errorf("Expected format 'test error: %%d', got '%s'", mock.lastFormat)
}
// Test DebugLogf
wrapper.DebugLogf("test debug: %v", true)
if mock.debugCalls != 1 {
t.Errorf("Expected 1 debug call, got %d", mock.debugCalls)
}
if mock.lastFormat != "test debug: %v" {
t.Errorf("Expected format 'test debug: %%v', got '%s'", mock.lastFormat)
}
}
// TestWrapLoggerForRecovery_NilLogger tests recovery wrapper with nil logger
func TestWrapLoggerForRecovery_NilLogger(t *testing.T) {
wrapper := WrapLoggerForRecovery(nil)
if wrapper == nil {
t.Fatal("WrapLoggerForRecovery should not return nil even with nil logger")
}
// These should not panic
wrapper.Logf("test")
wrapper.ErrorLogf("test")
wrapper.DebugLogf("test")
}
// TestWrapLoggerForCleanup tests the cleanup logger wrapper
func TestWrapLoggerForCleanup(t *testing.T) {
mock := &mockLogger{}
wrapper := WrapLoggerForCleanup(mock)
if wrapper == nil {
t.Fatal("WrapLoggerForCleanup should not return nil")
}
// Test Logf
wrapper.Logf("cleanup info: %s", "value")
if mock.infoCalls != 1 {
t.Errorf("Expected 1 info call, got %d", mock.infoCalls)
}
if mock.lastFormat != "cleanup info: %s" {
t.Errorf("Expected format 'cleanup info: %%s', got '%s'", mock.lastFormat)
}
// Test ErrorLogf
wrapper.ErrorLogf("cleanup error: %d", 456)
if mock.errorCalls != 1 {
t.Errorf("Expected 1 error call, got %d", mock.errorCalls)
}
if mock.lastFormat != "cleanup error: %d" {
t.Errorf("Expected format 'cleanup error: %%d', got '%s'", mock.lastFormat)
}
// Test DebugLogf
wrapper.DebugLogf("cleanup debug: %v", false)
if mock.debugCalls != 1 {
t.Errorf("Expected 1 debug call, got %d", mock.debugCalls)
}
if mock.lastFormat != "cleanup debug: %v" {
t.Errorf("Expected format 'cleanup debug: %%v', got '%s'", mock.lastFormat)
}
}
// TestWrapLoggerForCleanup_NilLogger tests cleanup wrapper with nil logger
func TestWrapLoggerForCleanup_NilLogger(t *testing.T) {
wrapper := WrapLoggerForCleanup(nil)
if wrapper == nil {
t.Fatal("WrapLoggerForCleanup should not return nil even with nil logger")
}
// These should not panic
wrapper.Logf("test")
wrapper.ErrorLogf("test")
wrapper.DebugLogf("test")
}
// TestLoggerWrappers_MultipleArgs tests wrappers with multiple arguments
func TestLoggerWrappers_MultipleArgs(t *testing.T) {
mock := &mockLogger{}
// Test recovery wrapper with multiple args
recoveryWrapper := WrapLoggerForRecovery(mock)
recoveryWrapper.Logf("format: %s %d %v", "str", 123, true)
if mock.infoCalls != 1 {
t.Errorf("Expected 1 info call, got %d", mock.infoCalls)
}
if len(mock.lastArgs) != 3 {
t.Errorf("Expected 3 args, got %d", len(mock.lastArgs))
}
// Reset mock
mock = &mockLogger{}
// Test cleanup wrapper with multiple args
cleanupWrapper := WrapLoggerForCleanup(mock)
cleanupWrapper.ErrorLogf("error: %s %d %v", "err", 456, false)
if mock.errorCalls != 1 {
t.Errorf("Expected 1 error call, got %d", mock.errorCalls)
}
if len(mock.lastArgs) != 3 {
t.Errorf("Expected 3 args, got %d", len(mock.lastArgs))
}
}