package authorization import ( "context" "testing" "time" "tm/pkg/logger" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) // MockRedisClient is a mock implementation of the Redis client interface type MockRedisClient struct { mock.Mock } func (m *MockRedisClient) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error { args := m.Called(ctx, key, value, expiration) return args.Error(0) } func (m *MockRedisClient) Get(ctx context.Context, key string) (string, error) { args := m.Called(ctx, key) return args.String(0), args.Error(1) } func (m *MockRedisClient) Del(ctx context.Context, keys ...string) error { args := m.Called(ctx, keys) return args.Error(0) } func (m *MockRedisClient) Exists(ctx context.Context, keys ...string) (int64, error) { args := m.Called(ctx, keys) return args.Get(0).(int64), args.Error(1) } func (m *MockRedisClient) Close() error { args := m.Called() return args.Error(0) } // MockLogger is a mock implementation of the logger interface type MockLogger struct { mock.Mock } func (m *MockLogger) Debug(msg string, fields map[string]interface{}) { m.Called(msg, fields) } func (m *MockLogger) Info(msg string, fields map[string]interface{}) { m.Called(msg, fields) } func (m *MockLogger) Warn(msg string, fields map[string]interface{}) { m.Called(msg, fields) } func (m *MockLogger) Error(msg string, fields map[string]interface{}) { m.Called(msg, fields) } func (m *MockLogger) Fatal(msg string, fields map[string]interface{}) { m.Called(msg, fields) } func (m *MockLogger) Sync() error { args := m.Called() return args.Error(0) } func (m *MockLogger) WithFields(fields map[string]interface{}) logger.Logger { args := m.Called(fields) return args.Get(0).(logger.Logger) } func TestGenerateTokenHash(t *testing.T) { service := &Service{} token1 := "test-token-1" token2 := "test-token-2" hash1 := service.generateTokenHash(token1) hash2 := service.generateTokenHash(token2) // Hash should be consistent for the same input assert.Equal(t, service.generateTokenHash(token1), hash1) // Different tokens should have different hashes assert.NotEqual(t, hash1, hash2) // Hash should be a valid hex string assert.Len(t, hash1, 64) // SHA-256 produces 32 bytes = 64 hex chars } func TestInvalidateToken_WithRedis(t *testing.T) { mockRedis := new(MockRedisClient) mockLogger := new(MockLogger) config := &AuthorizationConfig{ AccessTokenSecret: "test-secret", RefreshTokenSecret: "test-refresh-secret", AccessTokenExpiry: 15 * time.Minute, RefreshTokenExpiry: 7 * 24 * time.Hour, Issuer: "test", Audience: "test-api", } service := &Service{ config: config, logger: mockLogger, redis: mockRedis, } // Generate a valid token first token, err := service.GenerateAccessToken("user123", "test@example.com", "user", "company123") assert.NoError(t, err) // Mock Redis Set call mockRedis.On("Set", mock.Anything, mock.Anything, "1", mock.Anything).Return(nil) // Mock logger calls mockLogger.On("Info", mock.Anything, mock.Anything).Return() // Test invalidation err = service.InvalidateToken(token) assert.NoError(t, err) // Verify Redis was called mockRedis.AssertExpectations(t) } func TestInvalidateToken_WithoutRedis(t *testing.T) { mockLogger := new(MockLogger) config := &AuthorizationConfig{ AccessTokenSecret: "test-secret", RefreshTokenSecret: "test-refresh-secret", AccessTokenExpiry: 15 * time.Minute, RefreshTokenExpiry: 7 * 24 * time.Hour, Issuer: "test", Audience: "test-api", } service := &Service{ config: config, logger: mockLogger, redis: nil, // No Redis client } // Mock logger calls mockLogger.On("Warn", mock.Anything, mock.Anything).Return() // Test invalidation without Redis err := service.InvalidateToken("test-token") assert.NoError(t, err) // Verify warning was logged mockLogger.AssertExpectations(t) } func TestIsTokenBlacklisted_WithRedis(t *testing.T) { mockRedis := new(MockRedisClient) mockLogger := new(MockLogger) config := &AuthorizationConfig{ AccessTokenSecret: "test-secret", RefreshTokenSecret: "test-refresh-secret", AccessTokenExpiry: 15 * time.Minute, RefreshTokenExpiry: 7 * 24 * time.Hour, Issuer: "test", Audience: "test-api", } service := &Service{ config: config, logger: mockLogger, redis: mockRedis, } token := "test-token" tokenHash := service.generateTokenHash(token) // Test case 1: Token is blacklisted mockRedis.On("Exists", mock.Anything, []string{"blacklist:" + tokenHash}).Return(int64(1), nil).Once() mockLogger.On("Debug", mock.Anything, mock.Anything).Return() isBlacklisted, err := service.IsTokenBlacklisted(token) assert.NoError(t, err) assert.True(t, isBlacklisted) // Test case 2: Token is not blacklisted mockRedis.On("Exists", mock.Anything, []string{"blacklist:" + tokenHash}).Return(int64(0), nil).Once() mockLogger.On("Debug", mock.Anything, mock.Anything).Return() isBlacklisted, err = service.IsTokenBlacklisted(token) assert.NoError(t, err) assert.False(t, isBlacklisted) mockRedis.AssertExpectations(t) } func TestIsTokenBlacklisted_WithoutRedis(t *testing.T) { mockLogger := new(MockLogger) config := &AuthorizationConfig{ AccessTokenSecret: "test-secret", RefreshTokenSecret: "test-refresh-secret", AccessTokenExpiry: 15 * time.Minute, RefreshTokenExpiry: 7 * 24 * time.Hour, Issuer: "test", Audience: "test-api", } service := &Service{ config: config, logger: mockLogger, redis: nil, // No Redis client } // Test blacklist check without Redis isBlacklisted, err := service.IsTokenBlacklisted("test-token") assert.NoError(t, err) assert.False(t, isBlacklisted) } func TestClearExpiredTokens(t *testing.T) { mockRedis := new(MockRedisClient) mockLogger := new(MockLogger) service := &Service{ logger: mockLogger, redis: mockRedis, } // Mock logger call mockLogger.On("Info", mock.Anything, mock.Anything).Return() // Test clearing expired tokens err := service.ClearExpiredTokens() assert.NoError(t, err) mockLogger.AssertExpectations(t) } func TestGetBlacklistStats(t *testing.T) { mockRedis := new(MockRedisClient) mockLogger := new(MockLogger) service := &Service{ logger: mockLogger, redis: mockRedis, } ctx := context.Background() // Test with Redis available stats, err := service.GetBlacklistStats(ctx) assert.NoError(t, err) assert.Equal(t, true, stats["redis_available"]) // Test without Redis service.redis = nil stats, err = service.GetBlacklistStats(ctx) assert.NoError(t, err) assert.Equal(t, false, stats["redis_available"]) }