220 lines
6.1 KiB
Go
220 lines
6.1 KiB
Go
package tests
|
||
|
||
import (
|
||
"sync"
|
||
"sync/atomic"
|
||
|
||
authpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/auth"
|
||
requestpb "git.techease.ru/Smart-search/smart-search-back/pkg/pb/request"
|
||
)
|
||
|
||
func (s *IntegrationSuite) TestConcurrentRequest_CreateTZ_LimitedBalance() {
|
||
initialBalance := 50.0
|
||
email, password, userID := s.createUniqueTestUser("limited_balance_tz", initialBalance)
|
||
|
||
loginResp, err := s.authClient.Login(s.ctx, &authpb.LoginRequest{
|
||
Email: email,
|
||
Password: password,
|
||
Ip: "127.0.0.1",
|
||
UserAgent: "test-agent",
|
||
})
|
||
s.Require().NoError(err)
|
||
|
||
validateResp, err := s.authClient.Validate(s.ctx, &authpb.ValidateRequest{
|
||
AccessToken: loginResp.AccessToken,
|
||
})
|
||
s.Require().NoError(err)
|
||
|
||
var wg sync.WaitGroup
|
||
var successCount int32
|
||
var errorCount int32
|
||
goroutines := 20
|
||
|
||
startBarrier := make(chan struct{})
|
||
|
||
for i := 0; i < goroutines; i++ {
|
||
wg.Add(1)
|
||
go func(idx int) {
|
||
defer wg.Done()
|
||
<-startBarrier
|
||
|
||
_, err := s.requestClient.CreateTZ(s.ctx, &requestpb.CreateTZRequest{
|
||
UserId: validateResp.UserId,
|
||
RequestTxt: "Параллельный CreateTZ с ограниченным балансом",
|
||
})
|
||
|
||
if err == nil {
|
||
atomic.AddInt32(&successCount, 1)
|
||
} else {
|
||
atomic.AddInt32(&errorCount, 1)
|
||
}
|
||
}(i)
|
||
}
|
||
|
||
close(startBarrier)
|
||
wg.Wait()
|
||
|
||
s.T().Logf("CreateTZ with limited balance - Success: %d, Errors: %d", successCount, errorCount)
|
||
|
||
finalBalance := s.getUserBalance(userID)
|
||
s.T().Logf("Final balance: %.4f (initial: %.4f)", finalBalance, initialBalance)
|
||
|
||
s.GreaterOrEqual(finalBalance, 0.0,
|
||
"Баланс не должен быть отрицательным")
|
||
|
||
var requestsWithTZ int
|
||
err = s.pool.QueryRow(s.ctx,
|
||
"SELECT COUNT(*) FROM requests_for_suppliers WHERE user_id = $1 AND generated_tz = true",
|
||
userID,
|
||
).Scan(&requestsWithTZ)
|
||
s.NoError(err)
|
||
|
||
s.T().Logf("Requests with generated TZ: %d", requestsWithTZ)
|
||
|
||
s.GreaterOrEqual(requestsWithTZ, 0,
|
||
"Количество запросов с TZ должно быть >= 0")
|
||
|
||
s.LessOrEqual(requestsWithTZ, int(successCount),
|
||
"Количество запросов с TZ не должно превышать успешные операции")
|
||
}
|
||
|
||
func (s *IntegrationSuite) TestConcurrentRequest_MultipleUsers_CreateTZ() {
|
||
user1Email, user1Pass, user1ID := s.createUniqueTestUser("multi_user1", 500.0)
|
||
user2Email, user2Pass, user2ID := s.createUniqueTestUser("multi_user2", 500.0)
|
||
user3Email, user3Pass, user3ID := s.createUniqueTestUser("multi_user3", 500.0)
|
||
|
||
login1, err := s.authClient.Login(s.ctx, &authpb.LoginRequest{
|
||
Email: user1Email, Password: user1Pass, Ip: "127.0.0.1", UserAgent: "test",
|
||
})
|
||
s.Require().NoError(err)
|
||
validate1, _ := s.authClient.Validate(s.ctx, &authpb.ValidateRequest{AccessToken: login1.AccessToken})
|
||
|
||
login2, err := s.authClient.Login(s.ctx, &authpb.LoginRequest{
|
||
Email: user2Email, Password: user2Pass, Ip: "127.0.0.1", UserAgent: "test",
|
||
})
|
||
s.Require().NoError(err)
|
||
validate2, _ := s.authClient.Validate(s.ctx, &authpb.ValidateRequest{AccessToken: login2.AccessToken})
|
||
|
||
login3, err := s.authClient.Login(s.ctx, &authpb.LoginRequest{
|
||
Email: user3Email, Password: user3Pass, Ip: "127.0.0.1", UserAgent: "test",
|
||
})
|
||
s.Require().NoError(err)
|
||
validate3, _ := s.authClient.Validate(s.ctx, &authpb.ValidateRequest{AccessToken: login3.AccessToken})
|
||
|
||
users := []struct {
|
||
userID int64
|
||
id int
|
||
}{
|
||
{validate1.UserId, user1ID},
|
||
{validate2.UserId, user2ID},
|
||
{validate3.UserId, user3ID},
|
||
}
|
||
|
||
var wg sync.WaitGroup
|
||
var totalSuccess int32
|
||
requestsPerUser := 5
|
||
|
||
startBarrier := make(chan struct{})
|
||
|
||
for _, user := range users {
|
||
for i := 0; i < requestsPerUser; i++ {
|
||
wg.Add(1)
|
||
go func(uid int64) {
|
||
defer wg.Done()
|
||
<-startBarrier
|
||
|
||
_, err := s.requestClient.CreateTZ(s.ctx, &requestpb.CreateTZRequest{
|
||
UserId: uid,
|
||
RequestTxt: "Multi-user concurrent CreateTZ",
|
||
})
|
||
|
||
if err == nil {
|
||
atomic.AddInt32(&totalSuccess, 1)
|
||
}
|
||
}(user.userID)
|
||
}
|
||
}
|
||
|
||
close(startBarrier)
|
||
wg.Wait()
|
||
|
||
s.T().Logf("Multi-user CreateTZ total success: %d", totalSuccess)
|
||
|
||
for _, user := range users {
|
||
balance := s.getUserBalance(user.id)
|
||
s.T().Logf("User %d final balance: %.4f", user.id, balance)
|
||
s.GreaterOrEqual(balance, 0.0,
|
||
"Баланс пользователя %d не должен быть отрицательным", user.id)
|
||
}
|
||
}
|
||
|
||
func (s *IntegrationSuite) TestConcurrentRequest_BalanceDeduction_Consistency() {
|
||
initialBalance := 1000.0
|
||
email, password, userID := s.createUniqueTestUser("balance_consistency", initialBalance)
|
||
|
||
loginResp, err := s.authClient.Login(s.ctx, &authpb.LoginRequest{
|
||
Email: email,
|
||
Password: password,
|
||
Ip: "127.0.0.1",
|
||
UserAgent: "test-agent",
|
||
})
|
||
s.Require().NoError(err)
|
||
|
||
validateResp, err := s.authClient.Validate(s.ctx, &authpb.ValidateRequest{
|
||
AccessToken: loginResp.AccessToken,
|
||
})
|
||
s.Require().NoError(err)
|
||
|
||
var wg sync.WaitGroup
|
||
var successCount int32
|
||
goroutines := 10
|
||
|
||
startBarrier := make(chan struct{})
|
||
|
||
for i := 0; i < goroutines; i++ {
|
||
wg.Add(1)
|
||
go func(idx int) {
|
||
defer wg.Done()
|
||
<-startBarrier
|
||
|
||
_, err := s.requestClient.CreateTZ(s.ctx, &requestpb.CreateTZRequest{
|
||
UserId: validateResp.UserId,
|
||
RequestTxt: "Balance consistency test",
|
||
})
|
||
|
||
if err == nil {
|
||
atomic.AddInt32(&successCount, 1)
|
||
}
|
||
}(i)
|
||
}
|
||
|
||
close(startBarrier)
|
||
wg.Wait()
|
||
|
||
s.T().Logf("Successful CreateTZ operations: %d", successCount)
|
||
|
||
finalBalance := s.getUserBalance(userID)
|
||
balanceSpent := initialBalance - finalBalance
|
||
s.T().Logf("Balance spent: %.4f", balanceSpent)
|
||
|
||
var totalTokenCost float64
|
||
err = s.pool.QueryRow(s.ctx, `
|
||
SELECT COALESCE(SUM(tu.token_cost), 0)
|
||
FROM request_token_usage tu
|
||
JOIN requests_for_suppliers r ON tu.request_id = r.id
|
||
WHERE r.user_id = $1
|
||
`, userID).Scan(&totalTokenCost)
|
||
s.NoError(err)
|
||
|
||
s.T().Logf("Total token cost from DB: %.4f", totalTokenCost)
|
||
|
||
s.GreaterOrEqual(finalBalance, 0.0,
|
||
"Баланс не должен быть отрицательным")
|
||
|
||
if totalTokenCost > 0 {
|
||
tolerance := 0.01
|
||
s.InDelta(totalTokenCost, balanceSpent, tolerance,
|
||
"Сумма token_cost должна соответствовать списанному балансу")
|
||
}
|
||
}
|