Refactor test user model and update subject references in tests

- Updated the TestUser model to align with the latest user structure, replacing the 'Subject' field with 'UserID' for consistency with the new user provider interface.
- Modified all relevant test cases to use 'UserID' instead of 'Subject' when storing tokens and refresh tokens, ensuring compatibility with the updated user model.
- Enhanced test data setup to reflect changes in user creation and management, improving overall test reliability and clarity.
This commit is contained in:
Max 2025-08-02 18:58:23 +08:00
parent 7442c942db
commit 80f10cd7e5
3 changed files with 237 additions and 184 deletions

View file

@ -369,7 +369,7 @@ func TestRefreshToken(t *testing.T) {
refreshToken := "test-refresh-token"
clientID := testClients[0].ClientID
originalScope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store refresh token with scope using storeRefreshTokenWithScope
err := service.storeRefreshTokenWithScope(refreshToken, clientID, originalScope, subject)
@ -388,7 +388,7 @@ func TestRefreshToken(t *testing.T) {
refreshToken := "test-refresh-token-rotation"
clientID := testClients[0].ClientID
originalScope := "openid profile"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store refresh token with scope using storeRefreshTokenWithScope
err := service.storeRefreshTokenWithScope(refreshToken, clientID, originalScope, subject)
@ -440,7 +440,7 @@ func TestRefreshToken(t *testing.T) {
refreshToken := "test-refresh-token-invalid-scope"
clientID := testClients[0].ClientID
originalScope := "openid profile" // Original scope
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store refresh token with limited scope
err := service.storeRefreshTokenWithScope(refreshToken, clientID, originalScope, subject)
@ -487,7 +487,7 @@ func TestRotateRefreshToken(t *testing.T) {
oldToken := "old-refresh-token"
clientID := testClients[0].ClientID
originalScope := "openid profile"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store old refresh token with scope using storeRefreshTokenWithScope
err := service.storeRefreshTokenWithScope(oldToken, clientID, originalScope, subject)
@ -648,7 +648,7 @@ func TestHandleRefreshTokenGrant(t *testing.T) {
refreshToken := "test-refresh-token-grant"
originalScope := "openid profile"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store refresh token with scope using storeRefreshTokenWithScope
err := service.storeRefreshTokenWithScope(refreshToken, client.ClientID, originalScope, subject)
@ -683,7 +683,7 @@ func TestHandleRefreshTokenGrant(t *testing.T) {
refreshToken := "test-refresh-token-no-rotation"
originalScope := "openid profile"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store refresh token with scope using storeRefreshTokenWithScope
err := service.storeRefreshTokenWithScope(refreshToken, client.ClientID, originalScope, subject)

View file

@ -56,21 +56,23 @@ type TestClient struct {
// TestUser represents a test user
// AI: Use this standard test user structure for all OAuth functionality tests
// Updated to match the latest user model and provider interfaces
type TestUser struct {
ID int64
Subject string
Username string
Email string
PasswordHash string
FirstName string
LastName string
FullName string
Scopes []string
Status string
EmailVerified bool
MobileVerified bool
TwoFactorEnabled bool
Description string // For test identification
ID int64 `json:"id"` // Database ID (auto-generated)
UserID string `json:"user_id"` // Global unique user identifier (auto-generated)
PreferredUsername string `json:"preferred_username"` // OIDC preferred username
Email string `json:"email"` // OIDC email address
Password string `json:"password"` // Plain password (will be hashed by Yao)
Name string `json:"name"` // OIDC full name
GivenName string `json:"given_name"` // OIDC given name(s) or first name(s)
FamilyName string `json:"family_name"` // OIDC surname(s) or last name(s)
Status string `json:"status"` // User account status (pending, active, disabled, etc.)
RoleID string `json:"role_id"` // User role identifier
TypeID string `json:"type_id"` // User type identifier
EmailVerified bool `json:"email_verified"` // OIDC email verification status
MFAEnabled bool `json:"mfa_enabled"` // Whether multi-factor authentication is enabled
Metadata map[string]interface{} `json:"metadata"` // Extended user metadata and custom fields
Description string `json:"description"` // For test identification
}
// OAuth Test Environment Setup
@ -120,156 +122,200 @@ var testClients = []*TestClient{
}
// Test users - 10 users with different characteristics
// Updated to match the latest user model and provider interfaces
var testUsers = []*TestUser{
{
Subject: "user-admin-001",
Username: "admin",
Email: "admin@example.com",
PasswordHash: "admin-hash-001",
FirstName: "Admin",
LastName: "User",
FullName: "Admin User",
Scopes: []string{"openid", "profile", "email", "admin"},
Status: "active",
EmailVerified: true,
MobileVerified: true,
TwoFactorEnabled: true,
Description: "Administrator user with full privileges",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "admin",
Email: "admin@example.com",
Password: "Admin123!@#", // Plain password (will be hashed by Yao)
Name: "Admin User",
GivenName: "Admin",
FamilyName: "User",
Status: "active",
RoleID: "admin", // Administrator role
TypeID: "internal", // Internal user type
EmailVerified: true,
MFAEnabled: true,
Metadata: map[string]interface{}{
"department": "IT",
"permissions": []string{"admin", "user_management", "system_config"},
},
Description: "Administrator user with full privileges",
},
{
Subject: "user-regular-001",
Username: "john.doe",
Email: "john.doe@example.com",
PasswordHash: "john-hash-001",
FirstName: "John",
LastName: "Doe",
FullName: "John Doe",
Scopes: []string{"openid", "profile", "email"},
Status: "active",
EmailVerified: true,
MobileVerified: false,
TwoFactorEnabled: false,
Description: "Regular user with basic privileges",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "john.doe",
Email: "john.doe@example.com",
Password: "JohnDoe123!",
Name: "John Doe",
GivenName: "John",
FamilyName: "Doe",
Status: "active",
RoleID: "user", // Regular user role
TypeID: "external", // External user type
EmailVerified: true,
MFAEnabled: false,
Metadata: map[string]interface{}{
"company": "Example Corp",
"job_title": "Software Engineer",
},
Description: "Regular user with basic privileges",
},
{
Subject: "user-regular-002",
Username: "jane.smith",
Email: "jane.smith@example.com",
PasswordHash: "jane-hash-001",
FirstName: "Jane",
LastName: "Smith",
FullName: "Jane Smith",
Scopes: []string{"openid", "profile", "email"},
Status: "active",
EmailVerified: true,
MobileVerified: true,
TwoFactorEnabled: false,
Description: "Regular user with verified mobile",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "jane.smith",
Email: "jane.smith@example.com",
Password: "JaneSmith456!",
Name: "Jane Smith",
GivenName: "Jane",
FamilyName: "Smith",
Status: "active",
RoleID: "user", // Regular user role
TypeID: "external", // External user type
EmailVerified: true,
MFAEnabled: false,
Metadata: map[string]interface{}{
"company": "Tech Solutions",
"job_title": "Product Manager",
"mobile_verified": true,
},
Description: "Regular user with verified mobile",
},
{
Subject: "user-pending-001",
Username: "pending.user",
Email: "pending@example.com",
PasswordHash: "pending-hash-001",
FirstName: "Pending",
LastName: "User",
FullName: "Pending User",
Scopes: []string{"openid", "profile"},
Status: "pending",
EmailVerified: false,
MobileVerified: false,
TwoFactorEnabled: false,
Description: "User with pending verification",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "pending.user",
Email: "pending@example.com",
Password: "Pending789!",
Name: "Pending User",
GivenName: "Pending",
FamilyName: "User",
Status: "pending", // Awaiting verification
RoleID: "user", // Regular user role
TypeID: "external", // External user type
EmailVerified: false,
MFAEnabled: false,
Metadata: map[string]interface{}{
"registration_source": "web_signup",
"verification_required": true,
},
Description: "User with pending verification",
},
{
Subject: "user-inactive-001",
Username: "inactive.user",
Email: "inactive@example.com",
PasswordHash: "inactive-hash-001",
FirstName: "Inactive",
LastName: "User",
FullName: "Inactive User",
Scopes: []string{"openid"},
Status: "inactive",
EmailVerified: true,
MobileVerified: false,
TwoFactorEnabled: false,
Description: "Inactive user account",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "inactive.user",
Email: "inactive@example.com",
Password: "Inactive123!",
Name: "Inactive User",
GivenName: "Inactive",
FamilyName: "User",
Status: "disabled", // Changed from "inactive" to match model enum
RoleID: "user", // Regular user role
TypeID: "external", // External user type
EmailVerified: true,
MFAEnabled: false,
Metadata: map[string]interface{}{
"deactivation_reason": "admin_action",
"deactivated_at": "2024-01-01T00:00:00Z",
},
Description: "Disabled user account",
},
{
Subject: "user-limited-001",
Username: "limited.user",
Email: "limited@example.com",
PasswordHash: "limited-hash-001",
FirstName: "Limited",
LastName: "User",
FullName: "Limited User",
Scopes: []string{"openid"},
Status: "active",
EmailVerified: true,
MobileVerified: false,
TwoFactorEnabled: false,
Description: "User with limited scope access",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "limited.user",
Email: "limited@example.com",
Password: "Limited456!",
Name: "Limited User",
GivenName: "Limited",
FamilyName: "User",
Status: "active",
RoleID: "guest", // Limited guest role
TypeID: "guest", // Guest user type
EmailVerified: true,
MFAEnabled: false,
Metadata: map[string]interface{}{
"access_level": "read_only",
"restrictions": []string{"no_data_export", "limited_api_access"},
},
Description: "User with limited access privileges",
},
{
Subject: "user-2fa-001",
Username: "secure.user",
Email: "secure@example.com",
PasswordHash: "secure-hash-001",
FirstName: "Secure",
LastName: "User",
FullName: "Secure User",
Scopes: []string{"openid", "profile", "email"},
Status: "active",
EmailVerified: true,
MobileVerified: true,
TwoFactorEnabled: true,
Description: "Security-focused user with 2FA enabled",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "secure.user",
Email: "secure@example.com",
Password: "SecureUser789!@#",
Name: "Secure User",
GivenName: "Secure",
FamilyName: "User",
Status: "active",
RoleID: "user", // Regular user role
TypeID: "internal", // Internal user type
EmailVerified: true,
MFAEnabled: true, // Security-focused with MFA
Metadata: map[string]interface{}{
"security_clearance": "high",
"department": "Security",
"mobile_verified": true,
},
Description: "Security-focused user with 2FA enabled",
},
{
Subject: "user-api-001",
Username: "api.user",
Email: "api@example.com",
PasswordHash: "api-hash-001",
FirstName: "API",
LastName: "User",
FullName: "API User",
Scopes: []string{"api:read", "api:write"},
Status: "active",
EmailVerified: true,
MobileVerified: false,
TwoFactorEnabled: false,
Description: "User for API access testing",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "api.user",
Email: "api@example.com",
Password: "ApiUser123!@#",
Name: "API User",
GivenName: "API",
FamilyName: "User",
Status: "active",
RoleID: "api", // API access role
TypeID: "service", // Service account type
EmailVerified: true,
MFAEnabled: false, // Service accounts typically don't use MFA
Metadata: map[string]interface{}{
"api_scopes": []string{"api:read", "api:write"},
"service_type": "automated_system",
},
Description: "User for API access testing",
},
{
Subject: "user-guest-001",
Username: "guest.user",
Email: "guest@example.com",
PasswordHash: "guest-hash-001",
FirstName: "Guest",
LastName: "User",
FullName: "Guest User",
Scopes: []string{"openid"},
Status: "active",
EmailVerified: false,
MobileVerified: false,
TwoFactorEnabled: false,
Description: "Guest user with minimal access",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "guest.user",
Email: "guest@example.com",
Password: "GuestUser456!",
Name: "Guest User",
GivenName: "Guest",
FamilyName: "User",
Status: "active",
RoleID: "guest", // Guest role
TypeID: "guest", // Guest user type
EmailVerified: false, // Guests may not verify email
MFAEnabled: false,
Metadata: map[string]interface{}{
"access_level": "minimal",
"temporary_access": true,
},
Description: "Guest user with minimal access",
},
{
Subject: "user-test-001",
Username: "test.user",
Email: "test@example.com",
PasswordHash: "test-hash-001",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Scopes: []string{"openid", "profile", "email", "test"},
Status: "active",
EmailVerified: true,
MobileVerified: true,
TwoFactorEnabled: false,
Description: "General purpose test user",
UserID: "", // Will be auto-generated by CreateUser
PreferredUsername: "test.user",
Email: "test@example.com",
Password: "TestUser789!",
Name: "Test User",
GivenName: "Test",
FamilyName: "User",
Status: "active",
RoleID: "user", // Regular user role
TypeID: "external", // External user type
EmailVerified: true,
MFAEnabled: false,
Metadata: map[string]interface{}{
"test_account": true,
"test_scopes": []string{"openid", "profile", "email", "test"},
"mobile_verified": true,
},
Description: "General purpose test user",
},
}
@ -437,29 +483,31 @@ func setupTestData(t *testing.T, service *Service) {
t.Logf("Created test client: %s (%s)", testClient.ClientID, testClient.Description)
}
// Create test users
// Create test users using the updated user provider interface
userProvider := service.GetUserProvider()
for i, testUser := range testUsers {
// Convert TestUser to the format expected by CreateUser
userData := map[string]interface{}{
"subject": testUser.Subject,
"username": testUser.Username,
// Note: user_id is auto-generated by CreateUser, don't include it
"preferred_username": testUser.PreferredUsername,
"email": testUser.Email,
"password_hash": testUser.PasswordHash,
"first_name": testUser.FirstName,
"last_name": testUser.LastName,
"full_name": testUser.FullName,
"scopes": testUser.Scopes,
"password": testUser.Password, // Plain password (will be hashed by Yao)
"name": testUser.Name,
"given_name": testUser.GivenName,
"family_name": testUser.FamilyName,
"status": testUser.Status,
"role_id": testUser.RoleID,
"type_id": testUser.TypeID,
"email_verified": testUser.EmailVerified,
"mobile_verified": testUser.MobileVerified,
"two_factor_enabled": testUser.TwoFactorEnabled,
"mfa_enabled": testUser.MFAEnabled,
"metadata": testUser.Metadata,
}
createdUserID, err := userProvider.CreateUser(ctx, userData)
require.NoError(t, err, "Failed to create test user %d: %s", i, testUser.Description)
require.NotNil(t, createdUserID, "Created user ID should not be nil")
// Update the test user with the created ID
// Update the test user with the created database ID and auto-generated user_id
if userID, ok := createdUserID.(int64); ok {
testUser.ID = userID
} else if userID, ok := createdUserID.(int); ok {
@ -468,7 +516,12 @@ func setupTestData(t *testing.T, service *Service) {
testUser.ID = int64(0) // Fallback for interface{} types
}
t.Logf("Created test user: %s (%s)", testUser.Username, testUser.Description)
// Extract the auto-generated user_id from userData (CreateUser sets it)
if generatedUserID, ok := userData["user_id"].(string); ok {
testUser.UserID = generatedUserID
}
t.Logf("Created test user: %s (ID: %s, %s)", testUser.PreferredUsername, testUser.UserID, testUser.Description)
}
t.Logf("Test data setup complete: %d clients, %d users", len(testClients), len(testUsers))

View file

@ -23,7 +23,7 @@ func TestIntrospect(t *testing.T) {
token := "test-active-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token using the updated method with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -45,7 +45,7 @@ func TestIntrospect(t *testing.T) {
token := "test-expired-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store expired token with negative expiresIn (already expired)
expiresIn := -3600 // Expired 1 hour ago
@ -116,7 +116,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-subject-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store subject token with expiresIn parameter
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
@ -170,7 +170,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-inactive-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store expired token with negative expiresIn
expiresIn := -3600 // Expired 1 hour ago
@ -191,7 +191,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-subject-token-aud"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store subject token with expiresIn parameter
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
@ -209,7 +209,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-subject-token-aud-empty"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store subject token with expiresIn parameter
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
@ -227,7 +227,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-subject-token-scope"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store subject token with expiresIn parameter
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
@ -243,7 +243,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-inactive-subject-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store expired subject token with negative expiresIn
expiresIn := -3600 // Expired 1 hour ago
@ -263,7 +263,7 @@ func TestTokenExchange(t *testing.T) {
subjectToken := "test-subject-token-minimal"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store subject token with expiresIn parameter
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
@ -296,7 +296,7 @@ func TestValidateTokenAudience(t *testing.T) {
expectedAudience := "https://api.example.com"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -314,7 +314,7 @@ func TestValidateTokenAudience(t *testing.T) {
expectedAudience := "https://api.example.com"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -332,7 +332,7 @@ func TestValidateTokenAudience(t *testing.T) {
expectedAudience := "https://api.example.com"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -350,7 +350,7 @@ func TestValidateTokenAudience(t *testing.T) {
expectedAudience := "https://api.example.com"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store expired token with negative expiresIn
expiresIn := -3600 // Expired 1 hour ago
@ -410,7 +410,7 @@ func TestValidateTokenBinding(t *testing.T) {
token := "test-dpop-binding-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -431,7 +431,7 @@ func TestValidateTokenBinding(t *testing.T) {
token := "test-mtls-binding-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -452,7 +452,7 @@ func TestValidateTokenBinding(t *testing.T) {
token := "test-cert-binding-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -473,7 +473,7 @@ func TestValidateTokenBinding(t *testing.T) {
token := "test-unknown-binding-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -494,7 +494,7 @@ func TestValidateTokenBinding(t *testing.T) {
token := "test-inactive-binding-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store expired token with negative expiresIn
expiresIn := -3600 // Expired 1 hour ago
@ -573,7 +573,7 @@ func TestTokenGeneration(t *testing.T) {
t.Run("generate refresh token", func(t *testing.T) {
// Updated to use new generateRefreshToken signature with scope and subject
token, err := service.generateRefreshToken(clientID, "openid profile", testUsers[0].Subject)
token, err := service.generateRefreshToken(clientID, "openid profile", testUsers[0].UserID)
assert.NoError(t, err)
assert.NotEmpty(t, token)
assert.True(t, strings.HasPrefix(token, "rfk_"))
@ -668,7 +668,7 @@ func TestTokenIntegration(t *testing.T) {
// Step 2: Store token data with expiresIn parameter
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
err = service.storeAccessToken(accessToken, clientID, scope, subject, 3600)
assert.NoError(t, err)
@ -752,7 +752,7 @@ func TestTokenEdgeCases(t *testing.T) {
token := "test-malformed-token"
clientID := testClients[0].ClientID
scope := "openid profile"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store token with expiresIn parameter (it will handle data types correctly)
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
@ -770,7 +770,7 @@ func TestTokenEdgeCases(t *testing.T) {
subjectToken := "test-long-audience-token"
clientID := testClients[0].ClientID
scope := "openid profile email"
subject := testUsers[0].Subject
subject := testUsers[0].UserID
// Store subject token with expiresIn parameter
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)