Skip to content

Commit 253ec9b

Browse files
committed
build: more unit tests for io.jooby.handler, internal.unbescape
1 parent e4bae2e commit 253ec9b

21 files changed

Lines changed: 2738 additions & 186 deletions

jooby/src/main/java/io/jooby/handler/Cors.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ boolean wild() {
4646
return values.contains("*");
4747
}
4848

49-
@Override
5049
public String toString() {
5150
return values.toString();
5251
}

jooby/src/main/java/io/jooby/internal/handler/SendDirect.java

Lines changed: 0 additions & 33 deletions
This file was deleted.

jooby/src/test/java/io/jooby/CorsTest.java

Lines changed: 0 additions & 152 deletions
This file was deleted.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Jooby https://jooby.io
3+
* Apache License Version 2.0 https://jooby.io/LICENSE.txt
4+
* Copyright 2014 Edgar Espina
5+
*/
6+
package io.jooby.handler;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertNotNull;
10+
import static org.junit.jupiter.api.Assertions.assertTrue;
11+
import static org.mockito.Mockito.mock;
12+
import static org.mockito.Mockito.verify;
13+
import static org.mockito.Mockito.when;
14+
15+
import java.time.ZoneId;
16+
import java.time.format.DateTimeFormatter;
17+
import java.util.concurrent.atomic.AtomicReference;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.DisplayName;
21+
import org.junit.jupiter.api.Test;
22+
import org.mockito.ArgumentCaptor;
23+
24+
import io.jooby.Context;
25+
import io.jooby.Route;
26+
import io.jooby.Router;
27+
import io.jooby.StatusCode;
28+
import io.jooby.value.Value;
29+
30+
public class AccessLogHandlerTest {
31+
32+
private Context ctx;
33+
private Route.Handler next;
34+
35+
@BeforeEach
36+
void setUp() throws Exception {
37+
ctx = mock(Context.class);
38+
next = mock(Route.Handler.class);
39+
when(next.apply(ctx)).thenReturn("OK");
40+
41+
// Default mock behavior for standard request
42+
when(ctx.getRemoteAddress()).thenReturn("192.168.1.1");
43+
when(ctx.getMethod()).thenReturn(Router.GET);
44+
when(ctx.getRequestPath()).thenReturn("/api/users");
45+
when(ctx.queryString()).thenReturn("?status=active");
46+
when(ctx.getProtocol()).thenReturn("HTTP/1.1");
47+
when(ctx.getResponseCode()).thenReturn(StatusCode.OK);
48+
when(ctx.getResponseLength()).thenReturn(512L);
49+
}
50+
51+
@Test
52+
@DisplayName("Verify default NCSA log formatting without an authenticated user")
53+
void testDefaultLogFormat() throws Exception {
54+
when(ctx.getUser()).thenReturn(null);
55+
56+
AtomicReference<String> logResult = new AtomicReference<>();
57+
AccessLogHandler handler = new AccessLogHandler().log(logResult::set);
58+
59+
triggerAndCaptureLog(handler);
60+
61+
String result = logResult.get();
62+
assertNotNull(result);
63+
// 192.168.1.1 - - [Date] "GET /api/users?status=active HTTP/1.1" 200 512 {latency}
64+
assertTrue(result.startsWith("192.168.1.1 - - ["));
65+
assertTrue(result.contains("] \"GET /api/users?status=active HTTP/1.1\" 200 512 "));
66+
}
67+
68+
@Test
69+
@DisplayName("Verify NCSA log formatting with an authenticated user")
70+
void testAuthenticatedUserLogFormat() throws Exception {
71+
when(ctx.getUser()).thenReturn("admin-user");
72+
73+
AtomicReference<String> logResult = new AtomicReference<>();
74+
AccessLogHandler handler = new AccessLogHandler().log(logResult::set);
75+
76+
triggerAndCaptureLog(handler);
77+
78+
String result = logResult.get();
79+
assertNotNull(result);
80+
// 192.168.1.1 - admin-user [Date] ...
81+
assertTrue(result.startsWith("192.168.1.1 - admin-user ["));
82+
}
83+
84+
@Test
85+
@DisplayName("Verify custom user ID provider")
86+
void testCustomUserIdProvider() throws Exception {
87+
AtomicReference<String> logResult = new AtomicReference<>();
88+
AccessLogHandler handler = new AccessLogHandler(c -> "custom-id").log(logResult::set);
89+
90+
triggerAndCaptureLog(handler);
91+
92+
String result = logResult.get();
93+
assertTrue(result.startsWith("192.168.1.1 - custom-id ["));
94+
}
95+
96+
@Test
97+
@DisplayName("Verify missing response length and extended headers logic")
98+
void testMissingResponseLengthAndHeaders() throws Exception {
99+
when(ctx.getUser()).thenReturn(null);
100+
when(ctx.getResponseLength()).thenReturn(-1L); // Triggers DASH for length
101+
102+
// Mock headers
103+
Value userAgent = mock(Value.class);
104+
when(userAgent.valueOrNull()).thenReturn("Mozilla/5.0");
105+
when(ctx.header("User-Agent")).thenReturn(userAgent);
106+
107+
Value referer = mock(Value.class);
108+
when(referer.valueOrNull()).thenReturn(null); // Triggers DASH for missing header
109+
when(ctx.header("Referer")).thenReturn(referer);
110+
111+
when(ctx.getResponseHeader("X-Request-Id")).thenReturn("req-123");
112+
113+
AtomicReference<String> logResult = new AtomicReference<>();
114+
AccessLogHandler handler =
115+
new AccessLogHandler()
116+
.extended() // Adds User-Agent and Referer
117+
.responseHeader("X-Request-Id")
118+
.log(logResult::set);
119+
120+
triggerAndCaptureLog(handler);
121+
122+
String result = logResult.get();
123+
// Validate length is DASH (-)
124+
assertTrue(result.contains("\" 200 - "));
125+
126+
// Validate appended headers: "Mozilla/5.0" "-" "req-123"
127+
assertTrue(result.endsWith(" \"Mozilla/5.0\" \"-\" \"req-123\""));
128+
}
129+
130+
@Test
131+
@DisplayName("Verify custom request headers configuration")
132+
void testRequestHeaderConfiguration() throws Exception {
133+
Value customHeader = mock(Value.class);
134+
when(customHeader.valueOrNull()).thenReturn("custom-value");
135+
when(ctx.header("X-Custom")).thenReturn(customHeader);
136+
137+
AtomicReference<String> logResult = new AtomicReference<>();
138+
AccessLogHandler handler = new AccessLogHandler().requestHeader("X-Custom").log(logResult::set);
139+
140+
triggerAndCaptureLog(handler);
141+
142+
String result = logResult.get();
143+
assertTrue(result.endsWith(" \"custom-value\""));
144+
}
145+
146+
@Test
147+
@DisplayName("Verify various dateFormatter overrides")
148+
void testDateFormatterOverrides() throws Exception {
149+
// 1. Test ZoneId
150+
AccessLogHandler h1 = new AccessLogHandler().dateFormatter(ZoneId.of("UTC"));
151+
assertNotNull(h1);
152+
153+
// 2. Test DateTimeFormatter
154+
AccessLogHandler h2 = new AccessLogHandler().dateFormatter(DateTimeFormatter.ISO_INSTANT);
155+
assertNotNull(h2);
156+
157+
// 3. Test Function<Long, String>
158+
AtomicReference<String> logResult = new AtomicReference<>();
159+
AccessLogHandler h3 =
160+
new AccessLogHandler().dateFormatter(ts -> "STATIC_DATE").log(logResult::set);
161+
162+
triggerAndCaptureLog(h3);
163+
String result = logResult.get();
164+
assertTrue(result.contains(" [STATIC_DATE] "));
165+
}
166+
167+
/** Helper to execute the pipeline and manually trigger the context.onComplete callback. */
168+
private void triggerAndCaptureLog(AccessLogHandler handler) throws Exception {
169+
Route.Handler pipeline = handler.apply(next);
170+
171+
// Execute route
172+
Object response = pipeline.apply(ctx);
173+
assertEquals("OK", response);
174+
175+
// Capture and execute the onComplete callback
176+
ArgumentCaptor<Route.Complete> captor = ArgumentCaptor.forClass(Route.Complete.class);
177+
verify(ctx).onComplete(captor.capture());
178+
179+
Route.Complete completeCallback = captor.getValue();
180+
completeCallback.apply(ctx);
181+
}
182+
}

0 commit comments

Comments
 (0)