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 должна соответствовать списанному балансу") } }