|
5 | 5 | import io.github.easy4j.hermes.HermesCliConfig; |
6 | 6 | import io.github.easy4j.hermes.HermesHttpClientConfig; |
7 | 7 | import io.github.easy4j.hermes.HermesOkHttpClientFactory; |
| 8 | +import io.github.easy4j.hermes.exception.HermesHttpException; |
8 | 9 | import io.github.easy4j.hermes.api.model.ChatRequest; |
| 10 | +import okhttp3.Cookie; |
| 11 | +import okhttp3.CookieJar; |
| 12 | +import okhttp3.HttpUrl; |
9 | 13 | import okhttp3.MediaType; |
10 | 14 | import okhttp3.OkHttpClient; |
11 | 15 | import okhttp3.Protocol; |
|
14 | 18 | import okhttp3.mockwebserver.MockResponse; |
15 | 19 | import okhttp3.mockwebserver.MockWebServer; |
16 | 20 | import okhttp3.mockwebserver.RecordedRequest; |
| 21 | +import okhttp3.mockwebserver.SocketPolicy; |
17 | 22 | import org.junit.jupiter.api.Test; |
18 | 23 |
|
19 | 24 | import java.util.Collections; |
| 25 | +import java.util.List; |
20 | 26 | import java.util.concurrent.TimeUnit; |
21 | 27 | import java.util.concurrent.atomic.AtomicReference; |
22 | 28 |
|
@@ -133,6 +139,63 @@ void credentialSnapshotDoesNotExposeSecretInToString() { |
133 | 139 | assertFalse(snapshot.toString().contains("super-secret-token")); |
134 | 140 | } |
135 | 141 |
|
| 142 | + @Test |
| 143 | + void credentialRotationAfterLostWriteDoesNotResubmitUnderNewIdentity() throws Exception { |
| 144 | + try (MockWebServer server = new MockWebServer()) { |
| 145 | + server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AFTER_REQUEST)); |
| 146 | + server.start(); |
| 147 | + |
| 148 | + AtomicReference<CredentialSnapshot> credential = |
| 149 | + new AtomicReference<>(CredentialSnapshot.of("token-a", "generation-a")); |
| 150 | + ProfileBinding binding = ProfileBinding.of( |
| 151 | + "team-a", "credential-a", identity -> credential.get()); |
| 152 | + |
| 153 | + try (HermesClient root = new HermesClient(http(server), disabledCli())) { |
| 154 | + HermesClient profile = root.forProfile(binding); |
| 155 | + ChatRequest request = new ChatRequest(); |
| 156 | + request.setMessages(Collections.singletonList( |
| 157 | + new ChatRequest.Message("user", "side-effecting request"))); |
| 158 | + |
| 159 | + assertThrows(HermesHttpException.class, () -> profile.chatCompletion(request)); |
| 160 | + credential.set(CredentialSnapshot.of("token-b", "generation-b")); |
| 161 | + |
| 162 | + RecordedRequest accepted = server.takeRequest(3, TimeUnit.SECONDS); |
| 163 | + assertNotNull(accepted); |
| 164 | + assertEquals("Bearer token-a", accepted.getHeader("Authorization")); |
| 165 | + assertEquals(1, server.getRequestCount(), |
| 166 | + "an outcome-unknown write must not be recreated under rotated credentials"); |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + @Test |
| 172 | + void externalCookieJarIsRejectedForIsolatedProfiles() { |
| 173 | + CookieJar statefulJar = new CookieJar() { |
| 174 | + @Override |
| 175 | + public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { |
| 176 | + } |
| 177 | + |
| 178 | + @Override |
| 179 | + public List<Cookie> loadForRequest(HttpUrl url) { |
| 180 | + return Collections.emptyList(); |
| 181 | + } |
| 182 | + }; |
| 183 | + OkHttpClient external = new OkHttpClient.Builder().cookieJar(statefulJar).build(); |
| 184 | + HermesHttpClientConfig http = new HermesHttpClientConfig(); |
| 185 | + http.markUnsafeBaseUrlOverriddenForTest(true); |
| 186 | + http.setBaseUrl("http://127.0.0.1:8642"); |
| 187 | + |
| 188 | + ProfileBinding binding = ProfileBinding.of( |
| 189 | + "team-a", "credential-a", |
| 190 | + identity -> CredentialSnapshot.of("profile-token", "1")); |
| 191 | + |
| 192 | + try (HermesClient root = new HermesClient(http, disabledCli(), external)) { |
| 193 | + assertThrows(IllegalStateException.class, () -> root.forProfile(binding)); |
| 194 | + } finally { |
| 195 | + HermesOkHttpClientFactory.shutdown(external); |
| 196 | + } |
| 197 | + } |
| 198 | + |
136 | 199 | @Test |
137 | 200 | void statefulExternalInterceptorIsRejectedForIsolatedProfiles() { |
138 | 201 | OkHttpClient external = new OkHttpClient.Builder() |
|
0 commit comments