98f581ec97
- Updated cursor rules to emphasize the importance of dependency injection and logging practices. - Introduced a new user management domain, including entities, services, handlers, and validation. - Implemented user authentication features such as login, registration, and role-based access control. - Added Redis integration for token management and caching. - Enhanced API documentation with Swagger for user-related endpoints. - Updated configuration to support new JWT settings and Redis connection parameters.
269 lines
6.6 KiB
Go
269 lines
6.6 KiB
Go
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"])
|
|
}
|