|
22 | 22 | import java.nio.charset.StandardCharsets; |
23 | 23 | import java.time.Instant; |
24 | 24 | import java.util.List; |
| 25 | +import java.util.concurrent.CountDownLatch; |
| 26 | +import java.util.concurrent.ExecutorService; |
| 27 | +import java.util.concurrent.Executors; |
| 28 | +import java.util.concurrent.TimeUnit; |
25 | 29 |
|
26 | 30 | import static org.assertj.core.api.Assertions.assertThat; |
27 | 31 | import static org.junit.jupiter.api.Assertions.assertEquals; |
28 | 32 | import static org.junit.jupiter.api.Assertions.assertFalse; |
29 | 33 | import static org.junit.jupiter.api.Assertions.assertNull; |
30 | 34 | import static org.junit.jupiter.api.Assertions.assertThrows; |
| 35 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
31 | 36 | import static org.mockito.ArgumentMatchers.any; |
| 37 | +import static org.mockito.Mockito.doAnswer; |
| 38 | +import static org.mockito.Mockito.spy; |
32 | 39 | import static org.mockito.Mockito.times; |
33 | 40 | import static org.mockito.Mockito.verify; |
34 | 41 | import static org.mockito.Mockito.when; |
@@ -116,6 +123,54 @@ public void authenticate() throws Exception { |
116 | 123 | }); |
117 | 124 | } |
118 | 125 |
|
| 126 | + @Test |
| 127 | + public void shouldCallGetTokenOnlyOnceUnderContention() throws Exception { |
| 128 | + OAuthClient authClient = spy(new OAuthClient(null, "client", "secret", null, Scope.GIGACHAT_API_B2B, "url")); |
| 129 | + |
| 130 | + AccessTokenResponse mockResponse = AccessTokenResponse.builder() |
| 131 | + .accessToken(token) |
| 132 | + .expiresAt(Instant.now() |
| 133 | + .plusSeconds(1000L).toEpochMilli()) |
| 134 | + .build(); |
| 135 | + |
| 136 | + // Имитируем задержку сети, чтобы увеличить окно конкуренции |
| 137 | + doAnswer(invocation -> { |
| 138 | + Thread.sleep(100); |
| 139 | + return mockResponse; |
| 140 | + }).when(authClient).oauth(); |
| 141 | + |
| 142 | + int threadCount = 10; |
| 143 | + ExecutorService executor = Executors.newFixedThreadPool(threadCount); |
| 144 | + CountDownLatch startLatch = new CountDownLatch(1); |
| 145 | + CountDownLatch finishLatch = new CountDownLatch(threadCount); |
| 146 | + HttpRequestBuilder requestBuilder = HttpRequest.builder() |
| 147 | + .url("test.ru/models"); |
| 148 | + |
| 149 | + for (int i = 0; i < threadCount; i++) { |
| 150 | + executor.submit(() -> { |
| 151 | + try { |
| 152 | + startLatch.await(); // Ждем сигнала к одновременному старту |
| 153 | + authClient.authenticate(requestBuilder); |
| 154 | + } catch (InterruptedException e) { |
| 155 | + Thread.currentThread().interrupt(); |
| 156 | + } finally { |
| 157 | + finishLatch.countDown(); |
| 158 | + } |
| 159 | + }); |
| 160 | + } |
| 161 | + |
| 162 | + startLatch.countDown(); |
| 163 | + |
| 164 | + // Ждем завершения всех потоков |
| 165 | + boolean completed = finishLatch.await(2, TimeUnit.SECONDS); |
| 166 | + assertTrue(completed, "Threads did not finish in time"); |
| 167 | + |
| 168 | + // Проверяем, что сетевой запрос был выполнен ровно 1 раз |
| 169 | + verify(authClient, times(1)).getToken(); |
| 170 | + |
| 171 | + executor.shutdown(); |
| 172 | + } |
| 173 | + |
119 | 174 | @Test |
120 | 175 | public void authenticateByKey() throws Exception { |
121 | 176 | AuthClient authClient = AuthClientBuilder.builder() |
|
0 commit comments