Enhance cursor rules and add user management functionality
- 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.
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
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"])
|
||||
}
|
||||
Reference in New Issue
Block a user