Skip to content

Commit 7664ebe

Browse files
committed
build: add netty/undertow unit tests
1 parent cd28548 commit 7664ebe

26 files changed

Lines changed: 5350 additions & 5 deletions

modules/jooby-netty/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@
8989
<version>1.37</version>
9090
<scope>test</scope>
9191
</dependency>
92+
<dependency>
93+
<groupId>org.mockito</groupId>
94+
<artifactId>mockito-junit-jupiter</artifactId>
95+
<scope>test</scope>
96+
</dependency>
9297
</dependencies>
9398

9499
<build>

modules/jooby-netty/src/main/java/io/jooby/internal/netty/HeadersMultiMap.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ public HeadersMultiMap add(CharSequence name, Iterable values) {
105105
int h = hashCode(name);
106106
int i = h & 0x0000000F;
107107
for (Object vstr : values) {
108+
if (vstr == null) {
109+
break;
110+
}
108111
add0(h, i, name, toValidCharSequence(vstr));
109112
}
110113
return this;
@@ -549,7 +552,8 @@ public CharSequence getValue() {
549552
public CharSequence setValue(CharSequence value) {
550553
Objects.requireNonNull(value, "value");
551554
if (validator != null) {
552-
validator.accept("", value);
555+
// BUGFIX: Pass the actual key instead of an empty string
556+
validator.accept(this.key, value);
553557
}
554558
CharSequence oldValue = this.value;
555559
this.value = value;

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyWriter.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ public void write(String str) throws IOException {
3535

3636
@Override
3737
public void write(String str, int off, int len) throws IOException {
38-
write(str.substring(off, len));
38+
// FIX: Standard Java Writer contract defines 'len' as the number of characters, not endIndex
39+
write(str.substring(off, off + len));
3940
}
4041

4142
@Override
4243
public void write(int c) throws IOException {
43-
out.write((char) c);
44+
// FIX: Route through String to ensure the Charset properly encodes multi-byte characters
45+
write(String.valueOf((char) c));
4446
}
4547

4648
@Override
@@ -50,7 +52,8 @@ public void write(char[] cbuf) throws IOException {
5052

5153
@Override
5254
public Writer append(char c) throws IOException {
53-
out.write(c);
55+
// FIX: Route through the fixed write(int) method to apply proper encoding
56+
write(c);
5457
return this;
5558
}
5659

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
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.internal.netty;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertFalse;
10+
import static org.junit.jupiter.api.Assertions.assertNotNull;
11+
import static org.junit.jupiter.api.Assertions.assertNull;
12+
import static org.junit.jupiter.api.Assertions.assertThrows;
13+
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
15+
import java.util.Arrays;
16+
import java.util.Iterator;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Set;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import io.netty.buffer.ByteBuf;
24+
import io.netty.buffer.Unpooled;
25+
import io.netty.util.AsciiString;
26+
27+
class HeadersMultiMapTest {
28+
29+
@Test
30+
void shouldAddAndGetHeaders() {
31+
HeadersMultiMap headers = new HeadersMultiMap(null); // Disable validation for pure logic tests
32+
33+
headers.add("Content-Type", "application/json");
34+
headers.add(new AsciiString("X-Custom"), new AsciiString("CustomValue"));
35+
36+
assertEquals(2, headers.size());
37+
assertFalse(headers.isEmpty());
38+
assertEquals("application/json", headers.get("Content-Type"));
39+
assertEquals("CustomValue", headers.get(new AsciiString("X-Custom")));
40+
}
41+
42+
@Test
43+
void shouldAddAndIterateHeaders() {
44+
HeadersMultiMap headers = new HeadersMultiMap(null); // Disable validation for pure logic tests
45+
46+
headers.add("Content-Type", "application/json");
47+
for (Map.Entry<String, String> header : headers) {
48+
assertEquals("Content-Type", header.getKey());
49+
assertEquals("application/json", header.getValue());
50+
}
51+
}
52+
53+
@Test
54+
void shouldSetHeadersOverwritingExisting() {
55+
HeadersMultiMap headers = new HeadersMultiMap(null);
56+
headers.add("X-Multi", "Value1");
57+
headers.add("X-Multi", "Value2");
58+
59+
assertEquals(2, headers.getAll("X-Multi").size());
60+
61+
headers.set("X-Multi", "Value3");
62+
List<String> all = headers.getAll("X-Multi");
63+
64+
assertEquals(1, all.size());
65+
assertEquals("Value3", all.get(0));
66+
}
67+
68+
@Test
69+
void shouldRemoveHeaders() {
70+
HeadersMultiMap headers = new HeadersMultiMap(null);
71+
headers.add("A", "1");
72+
headers.add("B", "2");
73+
74+
headers.remove("A");
75+
76+
assertNull(headers.get("A"));
77+
assertEquals("2", headers.get("B"));
78+
assertEquals(1, headers.size());
79+
}
80+
81+
@Test
82+
void shouldHandleHashCollisionsAndLinkedListRemoval() {
83+
HeadersMultiMap headers = new HeadersMultiMap(null);
84+
for (int i = 0; i < 20; i++) {
85+
headers.add("Key-" + i, "Val-" + i);
86+
}
87+
88+
headers.remove("Key-5");
89+
assertNull(headers.get("Key-5"));
90+
assertEquals(19, headers.size());
91+
92+
headers.clear();
93+
assertTrue(headers.isEmpty());
94+
assertEquals(0, headers.size());
95+
}
96+
97+
@Test
98+
void shouldParseCsvForContainsValue() {
99+
HeadersMultiMap headers = new HeadersMultiMap(null);
100+
headers.add("Accept", "text/html, application/xhtml+xml, application/xml;q=0.9");
101+
102+
assertFalse(headers.contains("Accept", "application/xhtml+xml", true));
103+
104+
assertTrue(headers.containsValue("Accept", "application/xhtml+xml", true));
105+
assertTrue(headers.containsValue("Accept", "text/html", true));
106+
107+
assertTrue(headers.containsValue("Accept", "APPLICATION/XML;Q=0.9", true));
108+
assertFalse(headers.containsValue("Accept", "APPLICATION/XML;Q=0.9", false));
109+
}
110+
111+
@Test
112+
void shouldAddAndSetIterables() {
113+
HeadersMultiMap headers = new HeadersMultiMap(null);
114+
headers.add("List", Arrays.asList("A", "B", "C"));
115+
assertEquals(3, headers.getAll("List").size());
116+
117+
headers.set("List", Arrays.asList("X", "Y"));
118+
assertEquals(2, headers.getAll("List").size());
119+
assertEquals("X", headers.getAll("List").get(0));
120+
}
121+
122+
@Test
123+
void iterableShouldBreakOnNullElement() {
124+
HeadersMultiMap headers = new HeadersMultiMap(null);
125+
126+
// Will not throw NullPointerException anymore, it will gracefully break
127+
headers.add("List", Arrays.asList("A", null, "B"));
128+
129+
List<String> values = headers.getAll("List");
130+
assertEquals(1, values.size());
131+
assertEquals("A", values.get(0));
132+
}
133+
134+
@Test
135+
void shouldThrowNpeOnNullObjectValue() {
136+
HeadersMultiMap headers = new HeadersMultiMap(null);
137+
assertThrows(NullPointerException.class, () -> headers.add("Key", (Object) null));
138+
}
139+
140+
@Test
141+
void testPrimitiveGetters() {
142+
HeadersMultiMap headers = new HeadersMultiMap(null);
143+
headers.addInt("X-Int", 42);
144+
headers.addShort("X-Short", (short) 8);
145+
headers.add("X-Date", "Wed, 21 Oct 2015 07:28:00 GMT");
146+
147+
assertEquals(42, headers.getInt("X-Int"));
148+
assertEquals(42, headers.getInt("X-Int", 0));
149+
assertEquals(99, headers.getInt("X-Missing", 99));
150+
151+
assertEquals((short) 8, headers.getShort("X-Short"));
152+
assertEquals((short) 8, headers.getShort("X-Short", (short) 0));
153+
154+
assertNotNull(headers.getTimeMillis("X-Date"));
155+
assertEquals(100L, headers.getTimeMillis("X-Missing-Date", 100L));
156+
}
157+
158+
@Test
159+
void testPrimitiveSetters() {
160+
HeadersMultiMap headers = new HeadersMultiMap(null);
161+
headers.setInt("X-Int", 100);
162+
headers.setShort("X-Short", (short) 5);
163+
164+
assertEquals(100, headers.getInt("X-Int"));
165+
assertEquals((short) 5, headers.getShort("X-Short"));
166+
}
167+
168+
@Test
169+
void shouldEncodeToByteBuf() {
170+
HeadersMultiMap headers = new HeadersMultiMap(null);
171+
headers.add("A", "1");
172+
headers.add(new AsciiString("B"), new AsciiString("2"));
173+
174+
ByteBuf buf = Unpooled.buffer();
175+
headers.encode(buf);
176+
177+
String encoded = buf.toString(io.netty.util.CharsetUtil.US_ASCII);
178+
assertTrue(encoded.contains("A: 1\r\n"));
179+
assertTrue(encoded.contains("B: 2\r\n"));
180+
181+
buf.release();
182+
}
183+
184+
@Test
185+
void testIteratorsAndForEach() {
186+
HeadersMultiMap headers = new HeadersMultiMap(null);
187+
headers.add("A", "1");
188+
headers.add("B", "2");
189+
190+
Set<String> names = headers.names();
191+
assertTrue(names.contains("A"));
192+
assertTrue(names.contains("B"));
193+
194+
List<Map.Entry<String, String>> entries = headers.entries();
195+
assertEquals(2, entries.size());
196+
197+
Iterator<Map.Entry<CharSequence, CharSequence>> charSeqIter = headers.iteratorCharSequence();
198+
assertTrue(charSeqIter.hasNext());
199+
assertNotNull(charSeqIter.next());
200+
}
201+
202+
@Test
203+
void mapEntrySetValueShouldUpdateValueSuccessfully() {
204+
// Re-create the strict environment independently of the system property.
205+
HeadersMultiMap headers =
206+
new HeadersMultiMap(
207+
(name, value) -> {
208+
if (name == null || name.length() == 0) {
209+
throw new IllegalArgumentException("empty header name");
210+
}
211+
});
212+
headers.add("Valid-Key", "Valid-Value");
213+
214+
Map.Entry<String, String> entry = headers.iterator().next();
215+
216+
// Because we patched MapEntry.setValue to pass "this.key" instead of "",
217+
// this will successfully pass validation and update the entry.
218+
String oldVal = entry.setValue("New-Value");
219+
220+
assertEquals("Valid-Value", oldVal);
221+
assertEquals("New-Value", entry.getValue());
222+
assertEquals("New-Value", headers.get("Valid-Key"));
223+
}
224+
}

0 commit comments

Comments
 (0)