Skip to content

Commit 2c97882

Browse files
committed
build: more unit test for jooby.internal core package
1 parent 7aa2db7 commit 2c97882

22 files changed

Lines changed: 1878 additions & 6 deletions

jooby/src/main/java/io/jooby/internal/Chi.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -502,13 +502,13 @@ public StaticMap put(String path, StaticRoute staticRoute) {
502502
}
503503
}
504504

505-
private interface MethodMatcher {
505+
interface MethodMatcher {
506506
StaticRouterMatch get(String method);
507507

508508
void put(String method, StaticRouterMatch route);
509509
}
510510

511-
private static class SingleMethodMatcher implements MethodMatcher {
511+
static class SingleMethodMatcher implements MethodMatcher {
512512
private String method;
513513
private StaticRouterMatch route;
514514

@@ -529,7 +529,7 @@ public void clear() {
529529
}
530530
}
531531

532-
private static class MultipleMethodMatcher implements MethodMatcher {
532+
static class MultipleMethodMatcher implements MethodMatcher {
533533
private final Map<String, StaticRouterMatch> methods = new HashMap<>();
534534

535535
public MultipleMethodMatcher(SingleMethodMatcher matcher) {
@@ -549,7 +549,7 @@ public void put(String method, StaticRouterMatch route) {
549549
}
550550

551551
static class StaticRoute {
552-
private MethodMatcher matcher;
552+
MethodMatcher matcher;
553553

554554
public void put(String method, Route route) {
555555
if (matcher == null) {

jooby/src/main/java/io/jooby/internal/FlashMapImpl.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ public class FlashMapImpl extends HashMap<String, String> implements FlashMap {
2222

2323
private Context ctx;
2424

25-
private boolean keep;
26-
2725
private Cookie template;
2826

2927
private Map<String, String> initialScope;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
11+
import java.io.ByteArrayInputStream;
12+
import java.io.IOException;
13+
import java.nio.file.Files;
14+
import java.nio.file.Path;
15+
16+
import org.junit.jupiter.api.Test;
17+
18+
public class AttachedFileTest {
19+
20+
@Test
21+
public void testInputStreamConstructors() throws IOException {
22+
byte[] data = "content".getBytes();
23+
24+
// Test: InputStream, fileName, fileSize
25+
try (var is = new ByteArrayInputStream(data)) {
26+
AttachedFile file = new AttachedFile(is, "test1.txt", data.length);
27+
assertEquals("test1.txt", file.getFileName());
28+
assertEquals(data.length, file.getFileSize());
29+
assertTrue(file.getContentDisposition().startsWith("attachment"));
30+
}
31+
32+
// Test: InputStream, fileName
33+
try (var is = new ByteArrayInputStream(data)) {
34+
AttachedFile file = new AttachedFile(is, "test2.txt");
35+
assertEquals("test2.txt", file.getFileName());
36+
assertEquals(-1, file.getFileSize());
37+
assertTrue(file.getContentDisposition().startsWith("attachment"));
38+
}
39+
}
40+
41+
@Test
42+
public void testPathConstructors() throws IOException {
43+
Path tempFile = Files.createTempFile("attached-file", ".json");
44+
Files.write(tempFile, "{}".getBytes());
45+
46+
try {
47+
// Test: Path, fileName
48+
AttachedFile f1 = new AttachedFile(tempFile, "custom.json");
49+
assertEquals("custom.json", f1.getFileName());
50+
assertEquals(2, f1.getFileSize());
51+
assertTrue(f1.getContentDisposition().contains("attachment"));
52+
f1.stream().close();
53+
54+
// Test: Path
55+
AttachedFile f2 = new AttachedFile(tempFile);
56+
assertEquals(tempFile.getFileName().toString(), f2.getFileName());
57+
assertTrue(f2.getContentDisposition().contains("attachment"));
58+
f2.stream().close();
59+
} finally {
60+
Files.deleteIfExists(tempFile);
61+
}
62+
}
63+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
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;
7+
8+
import static org.junit.jupiter.api.Assertions.assertThrows;
9+
import static org.mockito.ArgumentMatchers.any;
10+
import static org.mockito.ArgumentMatchers.anyString;
11+
import static org.mockito.ArgumentMatchers.eq;
12+
import static org.mockito.Mockito.*;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.Test;
19+
import org.slf4j.Logger;
20+
21+
public class CompletionListenersTest {
22+
23+
private Context ctx;
24+
private Router router;
25+
private Logger logger;
26+
27+
@BeforeEach
28+
void setUp() {
29+
ctx = mock(Context.class);
30+
router = mock(Router.class);
31+
logger = mock(Logger.class);
32+
33+
when(ctx.getRouter()).thenReturn(router);
34+
when(router.getLog()).thenReturn(logger);
35+
when(ctx.getMethod()).thenReturn("GET");
36+
when(ctx.getRequestPath()).thenReturn("/test");
37+
}
38+
39+
@Test
40+
void shouldDoNothingWhenNoListeners() {
41+
CompletionListeners listeners = new CompletionListeners();
42+
listeners.run(ctx);
43+
verifyNoInteractions(ctx);
44+
}
45+
46+
@Test
47+
void shouldRunListenersInReverseOrder() throws Exception {
48+
CompletionListeners listeners = new CompletionListeners();
49+
List<Integer> order = new ArrayList<>();
50+
51+
listeners.addListener(c -> order.add(1));
52+
listeners.addListener(c -> order.add(2));
53+
listeners.addListener(c -> order.add(3));
54+
55+
listeners.run(ctx);
56+
57+
// Verify LIFO order
58+
java.util.List<Integer> expected = java.util.Arrays.asList(3, 2, 1);
59+
org.junit.jupiter.api.Assertions.assertEquals(expected, order);
60+
}
61+
62+
@Test
63+
void shouldLogAndSuppressMultipleExceptions() throws Exception {
64+
CompletionListeners listeners = new CompletionListeners();
65+
66+
RuntimeException ex1 = new RuntimeException("Error 1");
67+
RuntimeException ex2 = new RuntimeException("Error 2");
68+
69+
listeners.addListener(
70+
c -> {
71+
throw ex1;
72+
});
73+
listeners.addListener(
74+
c -> {
75+
throw ex2;
76+
});
77+
78+
listeners.run(ctx);
79+
80+
// Verify Error 2 was the primary (since it's last in, first out)
81+
// and Error 1 was suppressed
82+
verify(logger).error(anyString(), eq("GET"), eq("/test"), eq(ex2));
83+
org.junit.jupiter.api.Assertions.assertEquals(1, ex2.getSuppressed().length);
84+
org.junit.jupiter.api.Assertions.assertEquals(ex1, ex2.getSuppressed()[0]);
85+
}
86+
87+
@Test
88+
void shouldPropagateFatalExceptions() throws Exception {
89+
CompletionListeners listeners = new CompletionListeners();
90+
91+
// OutOfMemoryError is considered fatal
92+
OutOfMemoryError fatal = new OutOfMemoryError("Fatal");
93+
listeners.addListener(
94+
c -> {
95+
throw fatal;
96+
});
97+
98+
assertThrows(OutOfMemoryError.class, () -> listeners.run(ctx));
99+
100+
// Ensure it was still logged before being rethrown
101+
verify(logger).error(anyString(), any(), any(), eq(fatal));
102+
}
103+
104+
@Test
105+
void shouldPropagateSuppressedFatalException() throws Exception {
106+
CompletionListeners listeners = new CompletionListeners();
107+
108+
OutOfMemoryError fatal = new OutOfMemoryError("Fatal");
109+
RuntimeException normal = new RuntimeException("Normal");
110+
111+
// LIFO: normal runs first, then fatal
112+
listeners.addListener(
113+
c -> {
114+
throw fatal;
115+
});
116+
listeners.addListener(
117+
c -> {
118+
throw normal;
119+
});
120+
121+
assertThrows(OutOfMemoryError.class, () -> listeners.run(ctx));
122+
}
123+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.mockito.Mockito.mock;
10+
import static org.mockito.Mockito.when;
11+
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.List;
15+
16+
import org.junit.jupiter.api.Test;
17+
18+
public class ContextSelectorTest {
19+
20+
@Test
21+
public void testSingleApplicationSelector() {
22+
Jooby app = mock(Jooby.class);
23+
// Selector.create calls single() when list size is 1
24+
Context.Selector selector = Context.Selector.create(Collections.singletonList(app));
25+
26+
assertEquals(
27+
app, selector.select("/any/path"), "Single app selector should always return the app");
28+
assertEquals(app, selector.select("/"), "Single app selector should always return the app");
29+
}
30+
31+
@Test
32+
public void testMultipleApplicationSelectorMatching() {
33+
Jooby mainApp = mock(Jooby.class);
34+
when(mainApp.getContextPath()).thenReturn("/");
35+
36+
Jooby apiApp = mock(Jooby.class);
37+
when(apiApp.getContextPath()).thenReturn("/api");
38+
39+
Jooby adminApp = mock(Jooby.class);
40+
when(adminApp.getContextPath()).thenReturn("/admin");
41+
42+
// Selector.create calls multiple() when size > 1
43+
List<Jooby> apps = Arrays.asList(mainApp, apiApp, adminApp);
44+
Context.Selector selector = Context.Selector.create(apps);
45+
46+
// Exact matches / Prefix matches
47+
assertEquals(apiApp, selector.select("/api"), "Should match /api");
48+
assertEquals(apiApp, selector.select("/api/v1/users"), "Should match prefix /api");
49+
assertEquals(adminApp, selector.select("/admin/settings"), "Should match prefix /admin");
50+
}
51+
52+
@Test
53+
public void testMultipleApplicationSelectorFallback() {
54+
Jooby mainApp = mock(Jooby.class);
55+
when(mainApp.getContextPath()).thenReturn("/");
56+
57+
Jooby otherApp = mock(Jooby.class);
58+
when(otherApp.getContextPath()).thenReturn("/other");
59+
60+
Context.Selector selector = Context.Selector.create(Arrays.asList(mainApp, otherApp));
61+
62+
// Fallback to the app defined with "/" context path
63+
assertEquals(
64+
mainApp, selector.select("/unknown"), "Should fallback to app with '/' context path");
65+
}
66+
67+
@Test
68+
public void testMultipleApplicationSelectorFallbackToFirst() {
69+
Jooby app1 = mock(Jooby.class);
70+
when(app1.getContextPath()).thenReturn("/foo");
71+
72+
Jooby app2 = mock(Jooby.class);
73+
when(app2.getContextPath()).thenReturn("/bar");
74+
75+
// List without a "/" context path
76+
Context.Selector selector = Context.Selector.create(Arrays.asList(app1, app2));
77+
78+
// If no app has "/" and no prefix matches, it returns the first app in the list
79+
assertEquals(
80+
app1,
81+
selector.select("/baz"),
82+
"Should fallback to the first app if no '/' context path is found");
83+
}
84+
85+
@Test
86+
public void testSelectorOrderPrecedence() {
87+
Jooby app1 = mock(Jooby.class);
88+
when(app1.getContextPath()).thenReturn("/v1");
89+
90+
Jooby app2 = mock(Jooby.class);
91+
when(app2.getContextPath()).thenReturn("/v1/api");
92+
93+
// Order matters in the provided implementation. It iterates and returns the first match.
94+
Context.Selector selector = Context.Selector.create(Arrays.asList(app1, app2));
95+
96+
// Since app1 (/v1) is first, it will consume /v1/api/test because it starts with /v1
97+
assertEquals(app1, selector.select("/v1/api/test"));
98+
}
99+
}

0 commit comments

Comments
 (0)