Skip to content

Commit 8122a27

Browse files
committed
Add unit tests for FormDataMap and BodyParserHelpers.jsValueToJavaObject
1 parent 4e33524 commit 8122a27

2 files changed

Lines changed: 245 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package datadog.trace.instrumentation.play26.appsec;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.math.BigDecimal;
8+
import java.util.List;
9+
import java.util.Map;
10+
import org.junit.jupiter.api.Test;
11+
import play.api.libs.json.JsValue;
12+
13+
class BodyParserHelpersTest {
14+
15+
private static JsValue parse(String json) {
16+
return play.api.libs.json.Json$.MODULE$.parse(json);
17+
}
18+
19+
@Test
20+
void jsValueToJavaObject_nullInputReturnsNull() {
21+
assertNull(BodyParserHelpers.jsValueToJavaObject(null));
22+
}
23+
24+
@Test
25+
void jsValueToJavaObject_jsNullReturnsNull() {
26+
assertNull(BodyParserHelpers.jsValueToJavaObject(parse("null")));
27+
}
28+
29+
@Test
30+
void jsValueToJavaObject_string() {
31+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("\"hello\""));
32+
assertEquals("hello", result);
33+
}
34+
35+
@Test
36+
void jsValueToJavaObject_number() {
37+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("42"));
38+
assertTrue(result instanceof BigDecimal);
39+
assertEquals(0, ((BigDecimal) result).compareTo(new BigDecimal("42")));
40+
}
41+
42+
@Test
43+
void jsValueToJavaObject_booleanTrue() {
44+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("true"));
45+
assertEquals(Boolean.TRUE, result);
46+
}
47+
48+
@Test
49+
void jsValueToJavaObject_booleanFalse() {
50+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("false"));
51+
assertEquals(Boolean.FALSE, result);
52+
}
53+
54+
@Test
55+
@SuppressWarnings("unchecked")
56+
void jsValueToJavaObject_object() {
57+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("{\"key\":\"value\",\"num\":1}"));
58+
assertTrue(result instanceof Map);
59+
Map<String, Object> map = (Map<String, Object>) result;
60+
assertEquals("value", map.get("key"));
61+
assertTrue(map.get("num") instanceof BigDecimal);
62+
}
63+
64+
@Test
65+
@SuppressWarnings("unchecked")
66+
void jsValueToJavaObject_array() {
67+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("[\"a\",\"b\",\"c\"]"));
68+
assertTrue(result instanceof List);
69+
List<Object> list = (List<Object>) result;
70+
assertEquals(3, list.size());
71+
assertEquals("a", list.get(0));
72+
assertEquals("b", list.get(1));
73+
assertEquals("c", list.get(2));
74+
}
75+
76+
@Test
77+
@SuppressWarnings("unchecked")
78+
void jsValueToJavaObject_nestedObject() {
79+
Object result =
80+
BodyParserHelpers.jsValueToJavaObject(parse("{\"outer\":{\"inner\":\"deep\"}}"));
81+
assertTrue(result instanceof Map);
82+
Map<String, Object> outer = (Map<String, Object>) result;
83+
assertTrue(outer.get("outer") instanceof Map);
84+
Map<String, Object> inner = (Map<String, Object>) outer.get("outer");
85+
assertEquals("deep", inner.get("inner"));
86+
}
87+
88+
@Test
89+
void jsValueToJavaObject_zeroRecursionReturnsNull() {
90+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("{\"key\":\"value\"}"), 0);
91+
assertNull(result);
92+
}
93+
94+
@Test
95+
@SuppressWarnings("unchecked")
96+
void jsValueToJavaObject_recursionLimitTruncatesNesting() {
97+
// depth=1 means the object itself is converted but children are null
98+
Object result = BodyParserHelpers.jsValueToJavaObject(parse("{\"a\":{\"b\":\"val\"}}"), 1);
99+
assertTrue(result instanceof Map);
100+
Map<String, Object> map = (Map<String, Object>) result;
101+
// inner object exceeds depth so its value is null
102+
assertNull(map.get("a"));
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package datadog.trace.instrumentation.undertow;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import io.undertow.server.handlers.form.FormData;
7+
import io.undertow.util.HeaderMap;
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.lang.reflect.Field;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.ArrayDeque;
14+
import java.util.Collection;
15+
import java.util.Deque;
16+
import java.util.Map;
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.jupiter.api.io.TempDir;
19+
20+
class FormDataMapTest {
21+
22+
@TempDir Path tempDir;
23+
24+
@Test
25+
void textFieldIsIncluded() {
26+
FormData fd = new FormData(10);
27+
fd.add("name", "John");
28+
FormDataMap map = new FormDataMap(fd);
29+
30+
assertTrue(map.containsKey("name"));
31+
Collection<String> values = map.get("name");
32+
assertEquals(1, values.size());
33+
assertTrue(values.contains("John"));
34+
}
35+
36+
@Test
37+
void multipleTextValuesForSameKey() {
38+
FormData fd = new FormData(10);
39+
fd.add("tag", "foo");
40+
fd.add("tag", "bar");
41+
FormDataMap map = new FormDataMap(fd);
42+
43+
Collection<String> values = map.get("tag");
44+
assertEquals(2, values.size());
45+
assertTrue(values.contains("foo"));
46+
assertTrue(values.contains("bar"));
47+
}
48+
49+
@Test
50+
void diskFileIsExcluded() throws IOException {
51+
Path file = Files.createTempFile(tempDir, "upload", ".txt");
52+
FormData fd = new FormData(10);
53+
fd.add("upload", file, "evil.php", new HeaderMap());
54+
FormDataMap map = new FormDataMap(fd);
55+
56+
assertTrue(map.containsKey("upload"));
57+
assertTrue(map.get("upload").isEmpty());
58+
}
59+
60+
@Test
61+
void inMemoryFileIsExcluded() throws Exception {
62+
// In undertow 2.2+, isFile() returns false for in-memory uploads, but getFileName() is still
63+
// set. Verify our check (getFileName() == null) correctly excludes these uploads too.
64+
FormData fd = new FormData(10);
65+
addInMemoryFileValue(fd, "file", "evil.php");
66+
FormDataMap map = new FormDataMap(fd);
67+
68+
assertTrue(map.containsKey("file"));
69+
assertTrue(map.get("file").isEmpty());
70+
}
71+
72+
@Test
73+
void mixedTextAndFileFields() throws IOException {
74+
Path file = Files.createTempFile(tempDir, "upload", ".txt");
75+
FormData fd = new FormData(10);
76+
fd.add("name", "John");
77+
fd.add("email", "john@example.com");
78+
fd.add("upload", file, "evil.php", new HeaderMap());
79+
FormDataMap map = new FormDataMap(fd);
80+
81+
assertEquals(3, map.size());
82+
assertTrue(map.get("name").contains("John"));
83+
assertTrue(map.get("email").contains("john@example.com"));
84+
assertTrue(map.get("upload").isEmpty());
85+
}
86+
87+
@Test
88+
void emptyFormData() {
89+
FormData fd = new FormData(10);
90+
FormDataMap map = new FormDataMap(fd);
91+
92+
assertTrue(map.isEmpty());
93+
assertEquals(0, map.size());
94+
}
95+
96+
@SuppressWarnings("unchecked")
97+
private static void addInMemoryFileValue(FormData fd, String name, String filename)
98+
throws Exception {
99+
Field valuesField = FormData.class.getDeclaredField("values");
100+
valuesField.setAccessible(true);
101+
Map<String, Deque<FormData.FormValue>> values =
102+
(Map<String, Deque<FormData.FormValue>>) valuesField.get(fd);
103+
104+
FormData.FormValue inMemory =
105+
new FormData.FormValue() {
106+
@Override
107+
public String getValue() {
108+
return "";
109+
}
110+
111+
@Override
112+
public boolean isFile() {
113+
return false;
114+
}
115+
116+
@Override
117+
public String getFileName() {
118+
return filename;
119+
}
120+
121+
@Override
122+
public Path getPath() {
123+
return null;
124+
}
125+
126+
@Override
127+
public File getFile() {
128+
return null;
129+
}
130+
131+
@Override
132+
public HeaderMap getHeaders() {
133+
return null;
134+
}
135+
};
136+
137+
Deque<FormData.FormValue> deque = new ArrayDeque<>();
138+
deque.add(inMemory);
139+
values.put(name, deque);
140+
}
141+
}

0 commit comments

Comments
 (0)