Skip to content

Commit 16eb8ea

Browse files
committed
fixup! STF-322: Add tests for transport-failure retry
1 parent 4542332 commit 16eb8ea

1 file changed

Lines changed: 60 additions & 14 deletions

File tree

src/test/java/com/maxmind/geoip2/WebServiceClientTest.java

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -635,9 +635,8 @@ public void testNoRetryOn4xx() {
635635
// as plain HTTP/1.1. The HTTP/1.1 path then triggers the JDK's own
636636
// idempotent-GET retry inside HttpClient.send(), producing two wire
637637
// requests where the test expects one. This is platform-specific JDK
638-
// behavior we cannot disable from application code; the equivalent
639-
// POST-based test in minfraud-api-java is unaffected because the JDK
640-
// does not internally retry non-idempotent requests.
638+
// behavior we cannot disable from application code; only idempotent
639+
// methods (GET / HEAD) are affected.
641640
@Test
642641
@DisabledOnOs(OS.WINDOWS)
643642
public void testMaxRetriesZeroDisablesRetry() {
@@ -658,9 +657,12 @@ public void testMaxRetriesZeroDisablesRetry() {
658657
wireMock.verify(1, getRequestedFor(urlEqualTo(url)));
659658
}
660659

661-
// Disabled on Windows for the same reason as testMaxRetriesZeroDisablesRetry:
662-
// the JDK's internal idempotent-GET retry can stack on top of our retry
663-
// loop on the HTTP/1.1 fallback path, multiplying wire counts.
660+
// Disabled on Windows: the JDK's internal idempotent-GET retry on the
661+
// HTTP/1.1 fallback path (triggered by Windows-specific h2c upgrade
662+
// failures against an immediate RST) stacks on top of our retry loop,
663+
// multiplying wire counts (each of our 3 attempts becomes 2 wire
664+
// requests, so the count assertion sees 6 instead of 3). This is JDK
665+
// behavior we cannot disable from application code.
664666
@Test
665667
@DisabledOnOs(OS.WINDOWS)
666668
public void testRetriesExhausted() {
@@ -675,11 +677,51 @@ public void testRetriesExhausted() {
675677
.maxRetries(2)
676678
.build();
677679

678-
assertThrows(IOException.class,
680+
IOException ex = assertThrows(IOException.class,
679681
() -> client.insights(InetAddress.getByName("1.2.3.4")));
680682

681683
// 1 initial attempt + 2 retries.
682684
wireMock.verify(3, getRequestedFor(urlEqualTo(url)));
685+
// The final exception should carry the prior failures as suppressed
686+
// exceptions so the full retry history is visible in stack traces.
687+
assertEquals(2, ex.getSuppressed().length,
688+
"expected the 2 prior IOExceptions to be attached as suppressed");
689+
}
690+
691+
@Test
692+
public void testCustomHttpClientStillRetries() throws Exception {
693+
// The Javadoc on Builder.httpClient(HttpClient) promises that the SDK's
694+
// transport-failure retry wraps any supplied client. Verify it.
695+
String url = "/geoip/v2.1/country/1.2.3.4";
696+
String body = "{\"traits\":{\"ip_address\":\"1.2.3.4\"}}";
697+
698+
wireMock.stubFor(get(urlEqualTo(url))
699+
.inScenario("retry-custom-client")
700+
.whenScenarioStateIs(Scenario.STARTED)
701+
.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))
702+
.willSetStateTo("succeeded"));
703+
704+
wireMock.stubFor(get(urlEqualTo(url))
705+
.inScenario("retry-custom-client")
706+
.whenScenarioStateIs("succeeded")
707+
.willReturn(aResponse()
708+
.withStatus(200)
709+
.withHeader("Content-Type",
710+
"application/vnd.maxmind.com-country+json; charset=UTF-8; version=2.1")
711+
.withBody(body)));
712+
713+
HttpClient customClient = HttpClient.newBuilder().build();
714+
WebServiceClient client = new WebServiceClient.Builder(6, "0123456789")
715+
.host("localhost")
716+
.port(wireMock.getPort())
717+
.disableHttps()
718+
.httpClient(customClient)
719+
.build();
720+
721+
CountryResponse response = client.country(InetAddress.getByName("1.2.3.4"));
722+
assertNotNull(response);
723+
724+
wireMock.verify(2, getRequestedFor(urlEqualTo(url)));
683725
}
684726

685727
@Test
@@ -689,13 +731,17 @@ public void testNegativeMaxRetriesThrows() {
689731
}
690732

691733
@Test
692-
public void testInterruptDuringRetry() {
693-
// Pre-interrupting the calling thread aborts the call before any wire
694-
// request is dispatched: HttpClient.send checks the interrupt status
695-
// and throws InterruptedException, which is caught and rewrapped as
696-
// GeoIp2Exception with the interrupt flag restored. The wire-count
697-
// assertion (zero) guards against a regression where pre-interrupt
698-
// would silently let the request proceed.
734+
public void testInterruptedThreadAbortsBeforeSend() {
735+
// When the calling thread is already interrupted, HttpClient.send
736+
// checks the interrupt status and throws InterruptedException before
737+
// dispatching any wire request. The exception is caught and rewrapped
738+
// as GeoIp2Exception, with the interrupt flag restored on the calling
739+
// thread. The wire-count assertion (zero) guards against a regression
740+
// where a pre-interrupt would silently let the request proceed.
741+
// NOTE: this test does not exercise the predicate's own
742+
// Thread.currentThread().isInterrupted() short-circuit, since the JDK
743+
// aborts before that branch can be reached; a true mid-flight
744+
// interrupt is hard to test deterministically.
699745
String url = "/geoip/v2.1/insights/1.2.3.4";
700746
wireMock.stubFor(get(urlEqualTo(url))
701747
.willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));

0 commit comments

Comments
 (0)