Skip to content

Commit 8f83fd7

Browse files
author
wlanboy
committed
Added and updated tests
1 parent 94c692a commit 8f83fd7

4 files changed

Lines changed: 922 additions & 1 deletion

File tree

src/test/java/com/wlanboy/javahttpclient/client/ClientServiceTest.java

Lines changed: 264 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ClientServiceTest {
2525
static void startServer() throws IOException {
2626
server = HttpServer.create(new InetSocketAddress(0), 0);
2727
port = server.getAddress().getPort();
28-
server.setExecutor(Executors.newFixedThreadPool(2));
28+
server.setExecutor(Executors.newFixedThreadPool(4));
2929

3030
// GET endpoint
3131
server.createContext("/get", exchange -> {
@@ -77,6 +77,50 @@ static void startServer() throws IOException {
7777
}
7878
});
7979

80+
// Redirect endpoints
81+
server.createContext("/redirect/301", exchange -> {
82+
exchange.getResponseHeaders().set("Location", "/get");
83+
exchange.sendResponseHeaders(301, -1);
84+
exchange.getResponseBody().close();
85+
});
86+
87+
server.createContext("/redirect/302", exchange -> {
88+
exchange.getResponseHeaders().set("Location", "/get");
89+
exchange.sendResponseHeaders(302, -1);
90+
exchange.getResponseBody().close();
91+
});
92+
93+
server.createContext("/redirect/303", exchange -> {
94+
exchange.getResponseHeaders().set("Location", "/get");
95+
exchange.sendResponseHeaders(303, -1);
96+
exchange.getResponseBody().close();
97+
});
98+
99+
server.createContext("/redirect/307", exchange -> {
100+
exchange.getResponseHeaders().set("Location", "/post");
101+
exchange.sendResponseHeaders(307, -1);
102+
exchange.getResponseBody().close();
103+
});
104+
105+
server.createContext("/redirect/308", exchange -> {
106+
exchange.getResponseHeaders().set("Location", "/post");
107+
exchange.sendResponseHeaders(308, -1);
108+
exchange.getResponseBody().close();
109+
});
110+
111+
server.createContext("/redirect/circular", exchange -> {
112+
exchange.getResponseHeaders().set("Location", "/redirect/circular");
113+
exchange.sendResponseHeaders(302, -1);
114+
exchange.getResponseBody().close();
115+
});
116+
117+
// /redirect/chain → 301 → /redirect/302 → 302 → /get
118+
server.createContext("/redirect/chain", exchange -> {
119+
exchange.getResponseHeaders().set("Location", "/redirect/302");
120+
exchange.sendResponseHeaders(301, -1);
121+
exchange.getResponseBody().close();
122+
});
123+
80124
server.start();
81125
}
82126

@@ -331,4 +375,223 @@ void sendRequest_withDifferentHttpMethods_worksCorrectly() {
331375
assertNotNull(response);
332376
}
333377
}
378+
379+
// -------------------------------------------------------------------------
380+
// Redirect tests
381+
// -------------------------------------------------------------------------
382+
383+
@Test
384+
void sendRequest_with301Redirect_followsRedirect() {
385+
JavaHttpRequest request = new JavaHttpRequest(
386+
baseUrl() + "/redirect/301",
387+
HttpMethod.GET,
388+
null,
389+
null,
390+
false,
391+
null
392+
);
393+
394+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
395+
396+
assertEquals(200, response.getStatusCode().value());
397+
assertEquals("GET Success", response.getBody());
398+
399+
// The redirect chain header must be present and mention status 301
400+
String chain = response.getHeaders().getFirst("X-Redirect-Chain");
401+
assertNotNull(chain, "X-Redirect-Chain header should be present after a redirect");
402+
assertTrue(chain.contains("\"status\":301"),
403+
"Redirect chain should contain a step with status 301, got: " + chain);
404+
}
405+
406+
@Test
407+
void sendRequest_with302Redirect_followsRedirect() {
408+
JavaHttpRequest request = new JavaHttpRequest(
409+
baseUrl() + "/redirect/302",
410+
HttpMethod.GET,
411+
null,
412+
null,
413+
false,
414+
null
415+
);
416+
417+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
418+
419+
assertEquals(200, response.getStatusCode().value());
420+
assertEquals("GET Success", response.getBody());
421+
422+
String chain = response.getHeaders().getFirst("X-Redirect-Chain");
423+
assertNotNull(chain, "X-Redirect-Chain header should be present after a redirect");
424+
assertTrue(chain.contains("\"status\":302"),
425+
"Redirect chain should contain a step with status 302, got: " + chain);
426+
}
427+
428+
@Test
429+
void sendRequest_with303Redirect_fromPost_becomeGet() {
430+
// POST to a 303 redirect; the 303 spec mandates the follow-up is always GET
431+
JavaHttpRequest request = new JavaHttpRequest(
432+
baseUrl() + "/redirect/303",
433+
HttpMethod.POST,
434+
"{\"data\":\"value\"}",
435+
"application/json",
436+
false,
437+
null
438+
);
439+
440+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
441+
442+
// Final destination is /get which returns "GET Success"
443+
assertEquals(200, response.getStatusCode().value());
444+
assertEquals("GET Success", response.getBody());
445+
446+
String chain = response.getHeaders().getFirst("X-Redirect-Chain");
447+
assertNotNull(chain, "X-Redirect-Chain header should be present after a 303 redirect");
448+
assertTrue(chain.contains("\"status\":303"),
449+
"Redirect chain should contain a step with status 303, got: " + chain);
450+
}
451+
452+
@Test
453+
void sendRequest_with307Redirect_keepsPOSTMethod() {
454+
// 307 must keep the original method (POST) and body
455+
JavaHttpRequest request = new JavaHttpRequest(
456+
baseUrl() + "/redirect/307",
457+
HttpMethod.POST,
458+
"hello-body",
459+
"text/plain",
460+
false,
461+
null
462+
);
463+
464+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
465+
466+
// Final destination is /post which echoes the body
467+
assertEquals(200, response.getStatusCode().value());
468+
assertNotNull(response.getBody());
469+
assertTrue(response.getBody().contains("Received: hello-body"),
470+
"307 redirect must preserve the POST body, got: " + response.getBody());
471+
472+
String chain = response.getHeaders().getFirst("X-Redirect-Chain");
473+
assertNotNull(chain, "X-Redirect-Chain header should be present after a 307 redirect");
474+
assertTrue(chain.contains("\"status\":307"),
475+
"Redirect chain should contain a step with status 307, got: " + chain);
476+
}
477+
478+
@Test
479+
void sendRequest_withRedirectChain_capturesAllHops() {
480+
// /redirect/chain → 301 → /redirect/302 → 302 → /get (2 hops total)
481+
JavaHttpRequest request = new JavaHttpRequest(
482+
baseUrl() + "/redirect/chain",
483+
HttpMethod.GET,
484+
null,
485+
null,
486+
false,
487+
null
488+
);
489+
490+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
491+
492+
assertEquals(200, response.getStatusCode().value());
493+
assertEquals("GET Success", response.getBody());
494+
495+
String chain = response.getHeaders().getFirst("X-Redirect-Chain");
496+
assertNotNull(chain, "X-Redirect-Chain header should be present after a redirect chain");
497+
498+
// Two redirect steps must appear – count occurrences of "\"status\":"
499+
long hopCount = chain.chars()
500+
.filter(c -> c == '{')
501+
.count();
502+
assertEquals(2, hopCount,
503+
"Redirect chain should capture exactly 2 hops for /redirect/chain, got chain: " + chain);
504+
}
505+
506+
@Test
507+
void sendRequest_withCircularRedirect_stopsAtMaxRedirects() {
508+
// /redirect/circular points back to itself – ClientService must stop after MAX_REDIRECTS
509+
JavaHttpRequest request = new JavaHttpRequest(
510+
baseUrl() + "/redirect/circular",
511+
HttpMethod.GET,
512+
null,
513+
null,
514+
false,
515+
null
516+
);
517+
518+
// Must not throw and must return a response within a reasonable time
519+
ResponseEntity<String> response = assertDoesNotThrow(
520+
() -> clientService.sendRequest(request, new HttpHeaders()),
521+
"Circular redirect must not cause an infinite loop or exception"
522+
);
523+
524+
assertNotNull(response, "Response must not be null for circular redirect");
525+
// After MAX_REDIRECTS the service returns whatever the last response was (another 302)
526+
// or a 502 – either way it must be a valid HTTP status code
527+
assertTrue(response.getStatusCode().value() >= 100 && response.getStatusCode().value() < 600,
528+
"Response status must be a valid HTTP status code, got: " + response.getStatusCode().value());
529+
}
530+
531+
// -------------------------------------------------------------------------
532+
// Diagnostic header tests
533+
// -------------------------------------------------------------------------
534+
535+
@Test
536+
void sendRequest_success_hasProtocolVersionHeader() {
537+
JavaHttpRequest request = new JavaHttpRequest(
538+
baseUrl() + "/get",
539+
HttpMethod.GET,
540+
null,
541+
null,
542+
false,
543+
null
544+
);
545+
546+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
547+
548+
assertEquals(200, response.getStatusCode().value());
549+
String protocolVersion = response.getHeaders().getFirst("X-Protocol-Version");
550+
assertNotNull(protocolVersion, "X-Protocol-Version header must be present");
551+
// The embedded test server only speaks HTTP/1.1; the Java HttpClient may
552+
// negotiate HTTP/2 but will fall back to HTTP/1.1 for plain HTTP.
553+
assertTrue(
554+
protocolVersion.equals("HTTP/1.1") || protocolVersion.equals("HTTP/2"),
555+
"X-Protocol-Version must be HTTP/1.1 or HTTP/2, got: " + protocolVersion
556+
);
557+
}
558+
559+
@Test
560+
void sendRequest_success_hasResolvedIpHeader() {
561+
JavaHttpRequest request = new JavaHttpRequest(
562+
baseUrl() + "/get",
563+
HttpMethod.GET,
564+
null,
565+
null,
566+
false,
567+
null
568+
);
569+
570+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
571+
572+
assertEquals(200, response.getStatusCode().value());
573+
String resolvedIp = response.getHeaders().getFirst("X-Resolved-IP");
574+
assertNotNull(resolvedIp, "X-Resolved-IP header must be present when targeting localhost");
575+
assertFalse(resolvedIp.isBlank(), "X-Resolved-IP header must not be empty");
576+
}
577+
578+
@Test
579+
void sendRequest_withNoRedirect_hasNoRedirectChainHeader() {
580+
JavaHttpRequest request = new JavaHttpRequest(
581+
baseUrl() + "/get",
582+
HttpMethod.GET,
583+
null,
584+
null,
585+
false,
586+
null
587+
);
588+
589+
ResponseEntity<String> response = clientService.sendRequest(request, new HttpHeaders());
590+
591+
assertEquals(200, response.getStatusCode().value());
592+
assertNull(
593+
response.getHeaders().getFirst("X-Redirect-Chain"),
594+
"X-Redirect-Chain header must NOT be present when no redirect occurred"
595+
);
596+
}
334597
}

0 commit comments

Comments
 (0)