Skip to content

Commit bf3a0b1

Browse files
committed
test(profile): cover rotation and cookie isolation
1 parent 7f48a05 commit bf3a0b1

1 file changed

Lines changed: 63 additions & 0 deletions

File tree

‎src/test/java/io/github/easy4j/hermes/security/ProfileAuthenticationContractTest.java‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
import io.github.easy4j.hermes.HermesCliConfig;
66
import io.github.easy4j.hermes.HermesHttpClientConfig;
77
import io.github.easy4j.hermes.HermesOkHttpClientFactory;
8+
import io.github.easy4j.hermes.exception.HermesHttpException;
89
import io.github.easy4j.hermes.api.model.ChatRequest;
10+
import okhttp3.Cookie;
11+
import okhttp3.CookieJar;
12+
import okhttp3.HttpUrl;
913
import okhttp3.MediaType;
1014
import okhttp3.OkHttpClient;
1115
import okhttp3.Protocol;
@@ -14,9 +18,11 @@
1418
import okhttp3.mockwebserver.MockResponse;
1519
import okhttp3.mockwebserver.MockWebServer;
1620
import okhttp3.mockwebserver.RecordedRequest;
21+
import okhttp3.mockwebserver.SocketPolicy;
1722
import org.junit.jupiter.api.Test;
1823

1924
import java.util.Collections;
25+
import java.util.List;
2026
import java.util.concurrent.TimeUnit;
2127
import java.util.concurrent.atomic.AtomicReference;
2228

@@ -133,6 +139,63 @@ void credentialSnapshotDoesNotExposeSecretInToString() {
133139
assertFalse(snapshot.toString().contains("super-secret-token"));
134140
}
135141

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+
136199
@Test
137200
void statefulExternalInterceptorIsRejectedForIsolatedProfiles() {
138201
OkHttpClient external = new OkHttpClient.Builder()

0 commit comments

Comments
 (0)