You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/tests/race_condition_test.go

315 lines
6.4 KiB
Go

package tests
import (
"sync"
"testing"
)
// TestConcurrentOrgCreation 测试并发创建组织
func TestConcurrentOrgCreation(t *testing.T) {
ensureUsers(t)
var wg sync.WaitGroup
errors := make(chan error, 10)
// 并发创建多个组织
for i := 0; i < 10; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
"code": "concurrent_org_" + string(rune('a'+index)),
"name": "Concurrent Org " + string(rune('A'+index)),
}, User1Token)
if resp.Code != 200 && resp.Code != 400 {
// 400 可能是重复创建,其他错误码需要记录
errors <- nil
}
}(i)
}
wg.Wait()
close(errors)
// 检查是否有错误
errorCount := 0
for range errors {
errorCount++
}
if errorCount > 0 {
t.Errorf("Got %d errors during concurrent org creation", errorCount)
}
}
// TestConcurrentMemberAddition 测试并发添加成员
func TestConcurrentMemberAddition(t *testing.T) {
ensureUsers(t)
var orgID string
// User1 创建组织
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
"code": "concurrent_member_test",
"name": "Concurrent Member Test",
}, User1Token)
if resp.Code == 200 {
var data struct {
ID string `json:"id"`
}
decodeResponse(t, resp, &data)
orgID = data.ID
}
if orgID == "" {
t.Skip("Failed to create org")
}
// 先添加 User2 为成员
resp = doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
"user_id": User2ID,
"role": "member",
}, User1Token)
if resp.Code != 200 {
t.Skip("Failed to add initial member")
}
// 并发获取组织详情
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User2Token)
if resp.Code != 200 {
t.Errorf("Concurrent access failed with code: %d", resp.Code)
}
}()
}
wg.Wait()
// 清理
doRequest(t, "DELETE", "/api/orgs/"+orgID, nil, User1Token)
}
// TestConcurrentRoleUpdate 测试并发角色更新
func TestConcurrentRoleUpdate(t *testing.T) {
ensureUsers(t)
// 创建测试角色
resp := doRequest(t, "POST", "/api/roles", map[string]string{
"code": "concurrent_role",
"name": "Concurrent Role",
"description": "Role for concurrent test",
}, AdminToken)
if resp.Code != 200 {
t.Skip("Failed to create role")
}
var data struct {
ID string `json:"id"`
}
decodeResponse(t, resp, &data)
roleID := data.ID
// 并发更新角色权限
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
var perms []string
if index%2 == 0 {
perms = []string{"vb:org:read"}
} else {
perms = []string{"vb:org:create"}
}
resp := doRequest(t, "PUT", "/api/roles/"+roleID+"/permissions", map[string]any{
"permission_ids": perms,
}, AdminToken)
if resp.Code != 200 {
t.Errorf("Concurrent role update failed with code: %d", resp.Code)
}
}(i)
}
wg.Wait()
// 清理
doRequest(t, "DELETE", "/api/roles/"+roleID, nil, AdminToken)
}
// TestConcurrentUserUpdate 测试并发用户更新
func TestConcurrentUserUpdate(t *testing.T) {
ensureUsers(t)
var wg sync.WaitGroup
// User1 并发更新自己的信息
for i := 0; i < 5; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
resp := doRequest(t, "PATCH", "/api/auth/me", map[string]string{
"nickname": "Concurrent Update " + string(rune('A'+index)),
}, User1Token)
if resp.Code != 200 {
t.Errorf("Concurrent user update failed with code: %d", resp.Code)
}
}(i)
}
wg.Wait()
}
// TestConcurrentTokenRefresh 测试并发 Token 刷新
func TestConcurrentTokenRefresh(t *testing.T) {
ensureUsers(t)
// 先获取 refresh token
resp := doRequest(t, "POST", "/api/auth/login", map[string]string{
"username": "user1_test",
"password": "password123",
}, "")
if resp.Code != 200 {
t.Skip("Failed to login")
}
var data struct {
RefreshToken string `json:"refresh_token"`
}
decodeResponse(t, resp, &data)
if data.RefreshToken == "" {
t.Skip("No refresh token available")
}
// 并发刷新 token
var wg sync.WaitGroup
tokens := make(chan string, 5)
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
resp := doRequest(t, "POST", "/api/auth/refresh", map[string]string{
"refresh_token": data.RefreshToken,
}, "")
if resp.Code == 200 {
var refreshData struct {
AccessToken string `json:"access_token"`
}
decodeResponse(t, resp, &refreshData)
if refreshData.AccessToken != "" {
tokens <- refreshData.AccessToken
}
}
}()
}
wg.Wait()
close(tokens)
// 验证至少有一个成功
tokenCount := 0
for range tokens {
tokenCount++
}
if tokenCount == 0 {
t.Errorf("All concurrent token refreshes failed")
}
}
// TestConcurrentPermissionCheck 测试并发权限检查
func TestConcurrentPermissionCheck(t *testing.T) {
ensureUsers(t)
var wg sync.WaitGroup
// 多个用户并发检查权限
for i := 0; i < 5; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
var token string
switch index % 3 {
case 0:
token = AdminToken
case 1:
token = User1Token
case 2:
token = User2Token
}
resp := doRequest(t, "GET", "/api/orgs", nil, token)
// 所有用户都应该能访问 org 列表
if resp.Code != 200 {
t.Errorf("Permission check failed with code: %d", resp.Code)
}
}(i)
}
wg.Wait()
}
// TestConcurrentOAuthClientOps 测试并发 OAuth 客户端操作
func TestConcurrentOAuthClientOps(t *testing.T) {
ensureUsers(t)
// 创建测试客户端
resp := doRequest(t, "POST", "/api/oauth/clients", map[string]any{
"name": "Concurrent Test Client",
"redirect_uris": []string{"https://example.com/callback"},
"allowed_scopes": "openid",
}, AdminToken)
if resp.Code != 200 {
t.Skip("Failed to create OAuth client")
}
var data struct {
ClientID string `json:"client_id"`
}
decodeResponse(t, resp, &data)
clientID := data.ClientID
var wg sync.WaitGroup
// 并发更新客户端
for i := 0; i < 5; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
resp := doRequest(t, "PATCH", "/api/oauth/clients/"+clientID, map[string]string{
"name": "Updated Name " + string(rune('A'+index)),
}, AdminToken)
if resp.Code != 200 {
t.Errorf("Concurrent OAuth client update failed with code: %d", resp.Code)
}
}(i)
}
wg.Wait()
// 清理
doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, AdminToken)
}