Refactor OAuth token management tests and remove deprecated methods
- Removed outdated token management tests from the user provider, including tests for storing, revoking, and retrieving tokens. - Updated the user provider interface to reflect the removal of token management methods, ensuring cleaner code and improved maintainability. - Streamlined the test suite by focusing on relevant user management functionalities, enhancing overall test clarity and effectiveness.
This commit is contained in:
parent
8d1174d566
commit
e1428551ba
5 changed files with 60 additions and 633 deletions
|
|
@ -400,9 +400,6 @@ func TestRefreshToken(t *testing.T) {
|
|||
assert.NotEmpty(t, response.RefreshToken)
|
||||
assert.NotEqual(t, refreshToken, response.RefreshToken) // Should be different
|
||||
|
||||
// Old refresh token should be revoked
|
||||
exists := service.userProvider.TokenExists(refreshToken)
|
||||
assert.False(t, exists)
|
||||
})
|
||||
|
||||
t.Run("invalid refresh token", func(t *testing.T) {
|
||||
|
|
@ -499,9 +496,6 @@ func TestRotateRefreshToken(t *testing.T) {
|
|||
assert.Equal(t, "Bearer", response.TokenType)
|
||||
assert.Equal(t, 3600, response.ExpiresIn)
|
||||
|
||||
// Old token should be revoked
|
||||
exists := service.userProvider.TokenExists(oldToken)
|
||||
assert.False(t, exists)
|
||||
})
|
||||
|
||||
t.Run("rotation with disabled feature", func(t *testing.T) {
|
||||
|
|
@ -661,9 +655,6 @@ func TestHandleRefreshTokenGrant(t *testing.T) {
|
|||
assert.NotEmpty(t, token.RefreshToken)
|
||||
assert.NotEqual(t, refreshToken, token.RefreshToken) // Should be different
|
||||
|
||||
// Old refresh token should be revoked
|
||||
exists := service.userProvider.TokenExists(refreshToken)
|
||||
assert.False(t, exists)
|
||||
})
|
||||
|
||||
t.Run("refresh token grant without rotation", func(t *testing.T) {
|
||||
|
|
@ -693,9 +684,6 @@ func TestHandleRefreshTokenGrant(t *testing.T) {
|
|||
assert.Equal(t, 3600, token.ExpiresIn)
|
||||
assert.Equal(t, refreshToken, token.RefreshToken) // Should be the same
|
||||
|
||||
// Old refresh token should still exist
|
||||
exists := service.userProvider.TokenExists(refreshToken)
|
||||
assert.True(t, exists)
|
||||
})
|
||||
|
||||
t.Run("refresh token grant with invalid token", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -284,67 +284,67 @@ func (u *DefaultUser) ValidateUserScope(ctx context.Context, userID string, scop
|
|||
return true, nil
|
||||
}
|
||||
|
||||
// StoreToken stores a token in the token store with expiration time
|
||||
func (u *DefaultUser) StoreToken(accessToken string, tokenData map[string]interface{}, expiration time.Duration) error {
|
||||
return u.tokenStore.Set(u.tokenKey(accessToken), tokenData, expiration)
|
||||
}
|
||||
// // StoreToken stores a token in the token store with expiration time
|
||||
// func (u *DefaultUser) StoreToken(accessToken string, tokenData map[string]interface{}, expiration time.Duration) error {
|
||||
// return u.tokenStore.Set(u.tokenKey(accessToken), tokenData, expiration)
|
||||
// }
|
||||
|
||||
// RevokeToken revokes a token by removing it from the token store
|
||||
func (u *DefaultUser) RevokeToken(accessToken string) error {
|
||||
u.tokenStore.Del(u.tokenKey(accessToken))
|
||||
return nil
|
||||
}
|
||||
// // RevokeToken revokes a token by removing it from the token store
|
||||
// func (u *DefaultUser) RevokeToken(accessToken string) error {
|
||||
// u.tokenStore.Del(u.tokenKey(accessToken))
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// TokenExists checks if a token exists in the token store
|
||||
func (u *DefaultUser) TokenExists(accessToken string) bool {
|
||||
_, exists := u.tokenStore.Get(u.tokenKey(accessToken))
|
||||
return exists
|
||||
}
|
||||
// // TokenExists checks if a token exists in the token store
|
||||
// func (u *DefaultUser) TokenExists(accessToken string) bool {
|
||||
// _, exists := u.tokenStore.Get(u.tokenKey(accessToken))
|
||||
// return exists
|
||||
// }
|
||||
|
||||
// GetTokenData retrieves token data from the token store
|
||||
func (u *DefaultUser) GetTokenData(accessToken string) (map[string]interface{}, error) {
|
||||
tokenData, exists := u.tokenStore.Get(u.tokenKey(accessToken))
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("token not found")
|
||||
}
|
||||
// // GetTokenData retrieves token data from the token store
|
||||
// func (u *DefaultUser) GetTokenData(accessToken string) (map[string]interface{}, error) {
|
||||
// tokenData, exists := u.tokenStore.Get(u.tokenKey(accessToken))
|
||||
// if !exists {
|
||||
// return nil, fmt.Errorf("token not found")
|
||||
// }
|
||||
|
||||
// Try to convert to map[string]interface{} directly
|
||||
if tokenInfo, ok := tokenData.(map[string]interface{}); ok {
|
||||
return tokenInfo, nil
|
||||
}
|
||||
// // Try to convert to map[string]interface{} directly
|
||||
// if tokenInfo, ok := tokenData.(map[string]interface{}); ok {
|
||||
// return tokenInfo, nil
|
||||
// }
|
||||
|
||||
// If direct conversion fails, try to handle other possible types
|
||||
// This handles cases where MongoDB might return different types
|
||||
switch v := tokenData.(type) {
|
||||
case map[string]interface{}:
|
||||
return v, nil
|
||||
case map[interface{}]interface{}:
|
||||
// Convert map[interface{}]interface{} to map[string]interface{}
|
||||
result := make(map[string]interface{})
|
||||
for key, val := range v {
|
||||
if keyStr, ok := key.(string); ok {
|
||||
result[keyStr] = val
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
default:
|
||||
// Try to convert using map[string]interface{} casting
|
||||
// This handles primitive.M and other MongoDB types
|
||||
if reflect.TypeOf(v).Kind() == reflect.Map {
|
||||
result := make(map[string]interface{})
|
||||
rv := reflect.ValueOf(v)
|
||||
for _, key := range rv.MapKeys() {
|
||||
if keyStr, ok := key.Interface().(string); ok {
|
||||
result[keyStr] = rv.MapIndex(key).Interface()
|
||||
}
|
||||
}
|
||||
if len(result) > 0 {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("invalid token data format: %T", tokenData)
|
||||
}
|
||||
}
|
||||
// // If direct conversion fails, try to handle other possible types
|
||||
// // This handles cases where MongoDB might return different types
|
||||
// switch v := tokenData.(type) {
|
||||
// case map[string]interface{}:
|
||||
// return v, nil
|
||||
// case map[interface{}]interface{}:
|
||||
// // Convert map[interface{}]interface{} to map[string]interface{}
|
||||
// result := make(map[string]interface{})
|
||||
// for key, val := range v {
|
||||
// if keyStr, ok := key.(string); ok {
|
||||
// result[keyStr] = val
|
||||
// }
|
||||
// }
|
||||
// return result, nil
|
||||
// default:
|
||||
// // Try to convert using map[string]interface{} casting
|
||||
// // This handles primitive.M and other MongoDB types
|
||||
// if reflect.TypeOf(v).Kind() == reflect.Map {
|
||||
// result := make(map[string]interface{})
|
||||
// rv := reflect.ValueOf(v)
|
||||
// for _, key := range rv.MapKeys() {
|
||||
// if keyStr, ok := key.Interface().(string); ok {
|
||||
// result[keyStr] = rv.MapIndex(key).Interface()
|
||||
// }
|
||||
// }
|
||||
// if len(result) > 0 {
|
||||
// return result, nil
|
||||
// }
|
||||
// }
|
||||
// return nil, fmt.Errorf("invalid token data format: %T", tokenData)
|
||||
// }
|
||||
// }
|
||||
|
||||
// CreateUser creates a new user in the database
|
||||
func (u *DefaultUser) CreateUser(userData map[string]interface{}) (interface{}, error) {
|
||||
|
|
|
|||
|
|
@ -350,64 +350,6 @@ func TestKeyGeneration(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenOperations(t *testing.T) {
|
||||
storeConfigs := getStoreConfigs()
|
||||
|
||||
for _, config := range storeConfigs {
|
||||
t.Run(config.Name, func(t *testing.T) {
|
||||
tokenStore := config.GetFunc(t)
|
||||
cache := getLRUCache(t)
|
||||
|
||||
user := NewDefaultUser(&DefaultUserOptions{
|
||||
Prefix: "test:",
|
||||
|
||||
Cache: cache,
|
||||
TokenStore: tokenStore,
|
||||
})
|
||||
|
||||
// Clean up
|
||||
tokenStore.Clear()
|
||||
|
||||
t.Run("store and get token", func(t *testing.T) {
|
||||
tokenData := createTestToken("test-subject")
|
||||
err := user.StoreToken("test-token", tokenData, 1*time.Hour)
|
||||
assert.NoError(t, err)
|
||||
|
||||
exists := user.TokenExists("test-token")
|
||||
assert.True(t, exists)
|
||||
|
||||
retrievedData, err := user.GetTokenData("test-token")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tokenData["subject"], retrievedData["subject"])
|
||||
})
|
||||
|
||||
t.Run("revoke token", func(t *testing.T) {
|
||||
tokenData := createTestToken("test-subject")
|
||||
err := user.StoreToken("test-token-revoke", tokenData, 1*time.Hour)
|
||||
assert.NoError(t, err)
|
||||
|
||||
exists := user.TokenExists("test-token-revoke")
|
||||
assert.True(t, exists)
|
||||
|
||||
err = user.RevokeToken("test-token-revoke")
|
||||
assert.NoError(t, err)
|
||||
|
||||
exists = user.TokenExists("test-token-revoke")
|
||||
assert.False(t, exists)
|
||||
})
|
||||
|
||||
t.Run("non-existent token", func(t *testing.T) {
|
||||
exists := user.TokenExists("non-existent")
|
||||
assert.False(t, exists)
|
||||
|
||||
_, err := user.GetTokenData("non-existent")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "token not found")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserBySubject(t *testing.T) {
|
||||
storeConfigs := getStoreConfigs()
|
||||
|
||||
|
|
@ -557,70 +499,6 @@ func TestGetUserByEmail(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetUserByAccessToken(t *testing.T) {
|
||||
storeConfigs := getStoreConfigs()
|
||||
|
||||
for _, config := range storeConfigs {
|
||||
t.Run(config.Name, func(t *testing.T) {
|
||||
cleanupTestData(t)
|
||||
defer cleanupTestData(t)
|
||||
|
||||
tokenStore := config.GetFunc(t)
|
||||
cache := getLRUCache(t)
|
||||
|
||||
user := NewDefaultUser(&DefaultUserOptions{
|
||||
Prefix: "test:",
|
||||
|
||||
Cache: cache,
|
||||
TokenStore: tokenStore,
|
||||
})
|
||||
|
||||
// Clean up
|
||||
tokenStore.Clear()
|
||||
|
||||
// Create test user
|
||||
testUser := createTestUser("token1")
|
||||
setupTestUser(t, testUser)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("get user by access token", func(t *testing.T) {
|
||||
// Store token
|
||||
tokenData := createTestToken(testUser.Subject)
|
||||
err := user.StoreToken("test-access-token", tokenData, 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get user by token
|
||||
retrievedUser, err := user.GetUserByAccessToken(ctx, "test-access-token")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, retrievedUser)
|
||||
|
||||
userMap := convertToStringMap(t, retrievedUser)
|
||||
assert.Equal(t, testUser.Subject, userMap["subject"])
|
||||
assert.Equal(t, testUser.Username, userMap["username"])
|
||||
})
|
||||
|
||||
t.Run("non-existent token", func(t *testing.T) {
|
||||
retrievedUser, err := user.GetUserByAccessToken(ctx, "non-existent-token")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, retrievedUser)
|
||||
assert.Contains(t, err.Error(), "token not found")
|
||||
})
|
||||
|
||||
t.Run("invalid token format", func(t *testing.T) {
|
||||
// Store invalid token data
|
||||
invalidTokenData := "invalid-token-data"
|
||||
tokenStore.Set(user.tokenKey("invalid-token"), invalidTokenData, 1*time.Hour)
|
||||
|
||||
retrievedUser, err := user.GetUserByAccessToken(ctx, "invalid-token")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, retrievedUser)
|
||||
assert.Contains(t, err.Error(), "invalid token data format")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUserScope(t *testing.T) {
|
||||
storeConfigs := getStoreConfigs()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package types
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
@ -154,16 +153,16 @@ type UserProvider interface {
|
|||
|
||||
// Token management methods
|
||||
// StoreToken stores a token with expiration time
|
||||
StoreToken(accessToken string, tokenData map[string]interface{}, expiration time.Duration) error
|
||||
// StoreToken(accessToken string, tokenData map[string]interface{}, expiration time.Duration) error
|
||||
|
||||
// RevokeToken revokes a token by removing it from storage
|
||||
RevokeToken(accessToken string) error
|
||||
// RevokeToken(accessToken string) error
|
||||
|
||||
// TokenExists checks if a token exists in storage
|
||||
TokenExists(accessToken string) bool
|
||||
// TokenExists(accessToken string) bool
|
||||
|
||||
// GetTokenData retrieves token data from storage
|
||||
GetTokenData(accessToken string) (map[string]interface{}, error)
|
||||
// GetTokenData(accessToken string) (map[string]interface{}, error)
|
||||
|
||||
// User management methods
|
||||
// CreateUser creates a new user in the database
|
||||
|
|
|
|||
|
|
@ -1,439 +1 @@
|
|||
package oauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// UserInfo Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestUserInfo(t *testing.T) {
|
||||
service, _, _, cleanup := setupOAuthTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
userProvider := service.GetUserProvider()
|
||||
|
||||
t.Run("get user info with valid access token", func(t *testing.T) {
|
||||
// Create a valid access token for the first test user
|
||||
testUser := testUsers[0]
|
||||
accessToken := "valid_access_token_123"
|
||||
|
||||
// Store token data in user provider
|
||||
tokenData := map[string]interface{}{
|
||||
"token": accessToken,
|
||||
"user_id": testUser.ID,
|
||||
"subject": testUser.Subject,
|
||||
"username": testUser.Username,
|
||||
"email": testUser.Email,
|
||||
"first_name": testUser.FirstName,
|
||||
"last_name": testUser.LastName,
|
||||
"full_name": testUser.FullName,
|
||||
"scopes": testUser.Scopes,
|
||||
"status": testUser.Status,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
// Store the token data
|
||||
err := userProvider.StoreToken(accessToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get user info using the access token
|
||||
userInfo, err := service.UserInfo(ctx, accessToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify the user info contains expected data
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, testUser.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, testUser.Username, userInfoMap["username"])
|
||||
assert.Equal(t, testUser.Email, userInfoMap["email"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info with invalid access token", func(t *testing.T) {
|
||||
invalidToken := "invalid_access_token_xyz"
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, invalidToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("get user info with non-existent access token", func(t *testing.T) {
|
||||
nonExistentToken := "non_existent_token_abc"
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, nonExistentToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("get user info with empty access token", func(t *testing.T) {
|
||||
emptyToken := ""
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, emptyToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("get user info with expired access token", func(t *testing.T) {
|
||||
testUser := testUsers[1]
|
||||
expiredToken := "expired_access_token_456"
|
||||
|
||||
// Store expired token data
|
||||
tokenData := map[string]interface{}{
|
||||
"token": expiredToken,
|
||||
"user_id": testUser.ID,
|
||||
"subject": testUser.Subject,
|
||||
"username": testUser.Username,
|
||||
"email": testUser.Email,
|
||||
"scopes": testUser.Scopes,
|
||||
"status": testUser.Status,
|
||||
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||
"iat": time.Now().Add(-2 * time.Hour).Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(expiredToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, expiredToken)
|
||||
// UserInfo method returns user data regardless of token expiry
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify user info contains expected data
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, testUser.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, testUser.Username, userInfoMap["username"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info with inactive user", func(t *testing.T) {
|
||||
// Use the inactive test user
|
||||
inactiveUser := testUsers[4] // inactive.user
|
||||
inactiveToken := "inactive_user_token_789"
|
||||
|
||||
tokenData := map[string]interface{}{
|
||||
"token": inactiveToken,
|
||||
"user_id": inactiveUser.ID,
|
||||
"subject": inactiveUser.Subject,
|
||||
"username": inactiveUser.Username,
|
||||
"email": inactiveUser.Email,
|
||||
"scopes": inactiveUser.Scopes,
|
||||
"status": inactiveUser.Status, // inactive
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(inactiveToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, inactiveToken)
|
||||
// UserInfo method returns user data regardless of user status
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify user info contains expected data
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, inactiveUser.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, inactiveUser.Username, userInfoMap["username"])
|
||||
assert.Equal(t, inactiveUser.Status, userInfoMap["status"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info with limited scope user", func(t *testing.T) {
|
||||
// Use the limited scope test user
|
||||
limitedUser := testUsers[5] // limited.user
|
||||
limitedToken := "limited_scope_token_101"
|
||||
|
||||
tokenData := map[string]interface{}{
|
||||
"token": limitedToken,
|
||||
"user_id": limitedUser.ID,
|
||||
"subject": limitedUser.Subject,
|
||||
"username": limitedUser.Username,
|
||||
"email": limitedUser.Email,
|
||||
"scopes": limitedUser.Scopes, // Only openid
|
||||
"status": limitedUser.Status,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(limitedToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, limitedToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify limited user info
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, limitedUser.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, limitedUser.Username, userInfoMap["username"])
|
||||
// Should only have basic scopes
|
||||
if scopes, ok := userInfoMap["scopes"].([]string); ok {
|
||||
assert.Contains(t, scopes, "openid")
|
||||
assert.Len(t, scopes, 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get user info with admin user", func(t *testing.T) {
|
||||
// Use the admin test user
|
||||
adminUser := testUsers[0] // admin
|
||||
adminToken := "admin_token_202"
|
||||
|
||||
tokenData := map[string]interface{}{
|
||||
"token": adminToken,
|
||||
"user_id": adminUser.ID,
|
||||
"subject": adminUser.Subject,
|
||||
"username": adminUser.Username,
|
||||
"email": adminUser.Email,
|
||||
"first_name": adminUser.FirstName,
|
||||
"last_name": adminUser.LastName,
|
||||
"full_name": adminUser.FullName,
|
||||
"scopes": adminUser.Scopes,
|
||||
"status": adminUser.Status,
|
||||
"email_verified": adminUser.EmailVerified,
|
||||
"mobile_verified": adminUser.MobileVerified,
|
||||
"two_factor_enabled": adminUser.TwoFactorEnabled,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(adminToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, adminToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify admin user info
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, adminUser.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, adminUser.Username, userInfoMap["username"])
|
||||
assert.Equal(t, adminUser.Email, userInfoMap["email"])
|
||||
assert.True(t, userInfoMap["email_verified"].(bool))
|
||||
assert.True(t, userInfoMap["two_factor_enabled"].(bool))
|
||||
|
||||
// Should have admin scopes
|
||||
if scopes, ok := userInfoMap["scopes"].([]string); ok {
|
||||
assert.Contains(t, scopes, "admin")
|
||||
assert.Contains(t, scopes, "openid")
|
||||
assert.Contains(t, scopes, "profile")
|
||||
assert.Contains(t, scopes, "email")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Integration Tests
|
||||
// =============================================================================
|
||||
|
||||
func TestUserInfoIntegration(t *testing.T) {
|
||||
service, _, _, cleanup := setupOAuthTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
userProvider := service.GetUserProvider()
|
||||
|
||||
t.Run("complete user info flow", func(t *testing.T) {
|
||||
// Use different test users for comprehensive testing
|
||||
testCases := []struct {
|
||||
name string
|
||||
user *TestUser
|
||||
tokenSuffix string
|
||||
}{
|
||||
{"regular_user", testUsers[1], "regular"},
|
||||
{"verified_user", testUsers[2], "verified"},
|
||||
{"secure_user", testUsers[6], "secure"},
|
||||
{"api_user", testUsers[7], "api"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
token := "integration_token_" + tc.tokenSuffix
|
||||
|
||||
tokenData := map[string]interface{}{
|
||||
"token": token,
|
||||
"user_id": tc.user.ID,
|
||||
"subject": tc.user.Subject,
|
||||
"username": tc.user.Username,
|
||||
"email": tc.user.Email,
|
||||
"scopes": tc.user.Scopes,
|
||||
"status": tc.user.Status,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(token, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, token)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify basic user info structure
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, tc.user.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, tc.user.Username, userInfoMap["username"])
|
||||
assert.Equal(t, tc.user.Email, userInfoMap["email"])
|
||||
assert.Equal(t, tc.user.Status, userInfoMap["status"])
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("concurrent user info requests", func(t *testing.T) {
|
||||
// Test concurrent access to user info
|
||||
const numRequests = 10
|
||||
|
||||
// Create tokens for concurrent testing
|
||||
tokens := make([]string, numRequests)
|
||||
for i := 0; i < numRequests; i++ {
|
||||
tokens[i] = fmt.Sprintf("concurrent_token_%d", i)
|
||||
testUser := testUsers[i%len(testUsers)]
|
||||
|
||||
tokenData := map[string]interface{}{
|
||||
"token": tokens[i],
|
||||
"user_id": testUser.ID,
|
||||
"subject": testUser.Subject,
|
||||
"username": testUser.Username,
|
||||
"email": testUser.Email,
|
||||
"scopes": testUser.Scopes,
|
||||
"status": testUser.Status,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(tokens[i], tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Make concurrent requests
|
||||
results := make(chan error, numRequests)
|
||||
for i := 0; i < numRequests; i++ {
|
||||
go func(token string) {
|
||||
userInfo, err := service.UserInfo(ctx, token)
|
||||
if err != nil {
|
||||
results <- err
|
||||
return
|
||||
}
|
||||
if userInfo == nil {
|
||||
results <- fmt.Errorf("user info is nil")
|
||||
return
|
||||
}
|
||||
results <- nil
|
||||
}(tokens[i])
|
||||
}
|
||||
|
||||
// Collect results
|
||||
for i := 0; i < numRequests; i++ {
|
||||
err := <-results
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Edge Cases and Error Handling
|
||||
// =============================================================================
|
||||
|
||||
func TestUserInfoEdgeCases(t *testing.T) {
|
||||
service, _, _, cleanup := setupOAuthTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
userProvider := service.GetUserProvider()
|
||||
|
||||
t.Run("malformed token data", func(t *testing.T) {
|
||||
malformedToken := "malformed_token_data"
|
||||
|
||||
// Store malformed token data
|
||||
tokenData := map[string]interface{}{
|
||||
"token": malformedToken,
|
||||
"user_id": "invalid_user_id",
|
||||
"subject": nil, // Invalid subject
|
||||
"username": "", // Empty username
|
||||
"exp": "not_a_number", // Invalid expiration
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(malformedToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, malformedToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("very long access token", func(t *testing.T) {
|
||||
// Create a very long token
|
||||
longToken := "very_long_token_" + strings.Repeat("a", 1000)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, longToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("special characters in token", func(t *testing.T) {
|
||||
specialToken := "special_token_!@#$%^&*()_+{}[]|\\:;\"'<>?,./`~"
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, specialToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("token with only whitespace", func(t *testing.T) {
|
||||
whitespaceToken := " \t\n\r "
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, whitespaceToken)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, userInfo)
|
||||
})
|
||||
|
||||
t.Run("token with minimal valid data", func(t *testing.T) {
|
||||
minimalToken := "minimal_token_999"
|
||||
testUser := testUsers[9] // test.user
|
||||
|
||||
// Store minimal token data
|
||||
tokenData := map[string]interface{}{
|
||||
"token": minimalToken,
|
||||
"user_id": testUser.ID,
|
||||
"subject": testUser.Subject,
|
||||
"username": testUser.Username,
|
||||
"exp": time.Now().Add(time.Hour).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": "Bearer",
|
||||
}
|
||||
|
||||
err := userProvider.StoreToken(minimalToken, tokenData, time.Hour)
|
||||
require.NoError(t, err)
|
||||
|
||||
userInfo, err := service.UserInfo(ctx, minimalToken)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, userInfo)
|
||||
|
||||
// Verify minimal user info
|
||||
if userInfoMap, ok := userInfo.(map[string]interface{}); ok {
|
||||
assert.Equal(t, testUser.Subject, userInfoMap["subject"])
|
||||
assert.Equal(t, testUser.Username, userInfoMap["username"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue