Skip to content

Commit bde67d1

Browse files
committed
build: jetty unit tests
1 parent 7664ebe commit bde67d1

11 files changed

Lines changed: 1712 additions & 0 deletions

modules/jooby-jetty/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,10 @@
7575
<classifier>runtime</classifier>
7676
<scope>test</scope>
7777
</dependency>
78+
<dependency>
79+
<groupId>org.mockito</groupId>
80+
<artifactId>mockito-junit-jupiter</artifactId>
81+
<scope>test</scope>
82+
</dependency>
7883
</dependencies>
7984
</project>
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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.jetty;
7+
8+
import static org.junit.jupiter.api.Assertions.assertNotNull;
9+
import static org.mockito.ArgumentMatchers.eq;
10+
import static org.mockito.ArgumentMatchers.isNull;
11+
import static org.mockito.ArgumentMatchers.same;
12+
import static org.mockito.Mockito.verify;
13+
import static org.mockito.Mockito.when;
14+
15+
import java.nio.ByteBuffer;
16+
import java.util.Arrays;
17+
import java.util.Collections;
18+
19+
import org.eclipse.jetty.server.Response;
20+
import org.eclipse.jetty.util.Callback;
21+
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.api.extension.ExtendWith;
23+
import org.mockito.Mock;
24+
import org.mockito.junit.jupiter.MockitoExtension;
25+
26+
import io.jooby.output.Output;
27+
28+
@ExtendWith(MockitoExtension.class)
29+
class JettyCallbacksTest {
30+
31+
@Mock Response response;
32+
@Mock Callback delegateCallback;
33+
@Mock Output output;
34+
35+
@Test
36+
void testByteBufferArrayCallback_SingleBuffer() {
37+
ByteBuffer buffer = ByteBuffer.allocate(10);
38+
ByteBuffer[] buffers = {buffer};
39+
40+
JettyCallbacks.ByteBufferArrayCallback cb =
41+
JettyCallbacks.fromByteBufferArray(response, delegateCallback, buffers);
42+
43+
assertNotNull(cb);
44+
45+
// With a single buffer, it should immediately write with "last = true" and use the delegate
46+
// callback
47+
cb.send();
48+
verify(response).write(eq(true), same(buffer), same(delegateCallback));
49+
}
50+
51+
@Test
52+
void testByteBufferArrayCallback_MultipleBuffers() {
53+
ByteBuffer buffer1 = ByteBuffer.allocate(10);
54+
ByteBuffer buffer2 = ByteBuffer.allocate(20);
55+
ByteBuffer[] buffers = {buffer1, buffer2};
56+
57+
JettyCallbacks.ByteBufferArrayCallback cb =
58+
JettyCallbacks.fromByteBufferArray(response, delegateCallback, buffers);
59+
60+
// Initial send should process the first buffer with "last = false" and use itself as the
61+
// callback
62+
cb.send();
63+
verify(response).write(eq(false), same(buffer1), same(cb));
64+
65+
// Simulating Jetty calling succeeded() on the callback after the first write completes
66+
cb.succeeded();
67+
68+
// It should now process the final buffer with "last = true" and use the delegate callback
69+
verify(response).write(eq(true), same(buffer2), same(delegateCallback));
70+
}
71+
72+
@Test
73+
void testByteBufferArrayCallback_Failed() {
74+
ByteBuffer[] buffers = {ByteBuffer.allocate(10)};
75+
JettyCallbacks.ByteBufferArrayCallback cb =
76+
JettyCallbacks.fromByteBufferArray(response, delegateCallback, buffers);
77+
78+
Throwable exception = new RuntimeException("Write failed");
79+
cb.failed(exception);
80+
81+
verify(delegateCallback).failed(same(exception));
82+
}
83+
84+
@Test
85+
void testOutputCallback_EmptyOutput() {
86+
when(output.iterator()).thenReturn(Collections.emptyIterator());
87+
88+
JettyCallbacks.OutputCallback cb =
89+
JettyCallbacks.fromOutput(response, delegateCallback, output);
90+
assertNotNull(cb);
91+
92+
// An empty output should immediately send a null buffer to trigger completion
93+
boolean closeOnLast = true;
94+
cb.send(closeOnLast);
95+
96+
verify(response).write(eq(true), isNull(), same(delegateCallback));
97+
}
98+
99+
@Test
100+
void testOutputCallback_SingleBuffer() {
101+
ByteBuffer buffer = ByteBuffer.allocate(10);
102+
when(output.iterator()).thenReturn(Collections.singletonList(buffer).iterator());
103+
104+
JettyCallbacks.OutputCallback cb =
105+
JettyCallbacks.fromOutput(response, delegateCallback, output);
106+
107+
boolean closeOnLast = true;
108+
cb.send(closeOnLast);
109+
110+
// It should peek ahead, see no more elements, and write with the delegate callback
111+
verify(response).write(eq(true), same(buffer), same(delegateCallback));
112+
}
113+
114+
@Test
115+
void testOutputCallback_MultipleBuffers() {
116+
ByteBuffer buffer1 = ByteBuffer.allocate(10);
117+
ByteBuffer buffer2 = ByteBuffer.allocate(20);
118+
when(output.iterator()).thenReturn(Arrays.asList(buffer1, buffer2).iterator());
119+
120+
JettyCallbacks.OutputCallback cb =
121+
JettyCallbacks.fromOutput(response, delegateCallback, output);
122+
123+
boolean closeOnLast = false;
124+
125+
// First send
126+
cb.send(closeOnLast);
127+
verify(response).write(eq(false), same(buffer1), same(cb));
128+
129+
// Jetty signals success on the first buffer, triggering the next send cycle
130+
cb.succeeded();
131+
verify(response).write(eq(false), same(buffer2), same(delegateCallback));
132+
}
133+
134+
@Test
135+
void testOutputCallback_Failed() {
136+
when(output.iterator()).thenReturn(Collections.emptyIterator());
137+
JettyCallbacks.OutputCallback cb =
138+
JettyCallbacks.fromOutput(response, delegateCallback, output);
139+
140+
Throwable exception = new RuntimeException("Chunk failed");
141+
cb.failed(exception);
142+
143+
verify(delegateCallback).failed(same(exception));
144+
}
145+
146+
@Test
147+
void testUtilityClassInstantiation() {
148+
// Included to achieve strictly 100% line coverage for JaCoCo on implicit default constructors
149+
// inside static utility classes.
150+
JettyCallbacks instance = new JettyCallbacks();
151+
assertNotNull(instance);
152+
}
153+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
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.jetty;
7+
8+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertNull;
11+
import static org.junit.jupiter.api.Assertions.assertThrows;
12+
import static org.mockito.ArgumentMatchers.any;
13+
import static org.mockito.Mockito.doThrow;
14+
import static org.mockito.Mockito.mock;
15+
import static org.mockito.Mockito.mockStatic;
16+
import static org.mockito.Mockito.verify;
17+
import static org.mockito.Mockito.when;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.nio.file.Path;
23+
24+
import org.eclipse.jetty.http.HttpFields;
25+
import org.eclipse.jetty.http.HttpHeader;
26+
import org.eclipse.jetty.http.MultiPart;
27+
import org.eclipse.jetty.io.Content;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
30+
import org.junit.jupiter.api.extension.ExtendWith;
31+
import org.mockito.Mock;
32+
import org.mockito.MockedStatic;
33+
import org.mockito.junit.jupiter.MockitoExtension;
34+
35+
import io.jooby.SneakyThrows;
36+
37+
@ExtendWith(MockitoExtension.class)
38+
class JettyFileUploadTest {
39+
40+
@Mock Path tmpdir;
41+
@Mock MultiPart.Part upload;
42+
43+
private JettyFileUpload fileUpload;
44+
45+
@BeforeEach
46+
void setup() {
47+
fileUpload = new JettyFileUpload(tmpdir, upload);
48+
}
49+
50+
@Test
51+
void testGetName() {
52+
when(upload.getName()).thenReturn("avatar");
53+
assertEquals("avatar", fileUpload.getName());
54+
}
55+
56+
@Test
57+
void testGetFileName() {
58+
when(upload.getFileName()).thenReturn("profile.png");
59+
assertEquals("profile.png", fileUpload.getFileName());
60+
}
61+
62+
@Test
63+
void testToString() {
64+
when(upload.getFileName()).thenReturn("profile.png");
65+
assertEquals("profile.png", fileUpload.toString());
66+
}
67+
68+
@Test
69+
void testGetFileSize() {
70+
when(upload.getLength()).thenReturn(1024L);
71+
assertEquals(1024L, fileUpload.getFileSize());
72+
}
73+
74+
@Test
75+
void testGetContentType() {
76+
HttpFields headers = mock(HttpFields.class);
77+
when(upload.getHeaders()).thenReturn(headers);
78+
when(headers.get(HttpHeader.CONTENT_TYPE)).thenReturn("image/png");
79+
80+
assertEquals("image/png", fileUpload.getContentType());
81+
}
82+
83+
@Test
84+
void testClose() {
85+
fileUpload.close();
86+
verify(upload).close();
87+
}
88+
89+
@Test
90+
void testStream_Success() {
91+
Content.Source contentSource = mock(Content.Source.class);
92+
when(upload.getContentSource()).thenReturn(contentSource);
93+
InputStream mockStream = mock(InputStream.class);
94+
95+
try (MockedStatic<Content.Source> sourceStatic = mockStatic(Content.Source.class)) {
96+
sourceStatic.when(() -> Content.Source.asInputStream(contentSource)).thenReturn(mockStream);
97+
98+
assertEquals(mockStream, fileUpload.stream());
99+
}
100+
}
101+
102+
@Test
103+
void testStream_ReturnsNullOnException() {
104+
// If getting the content source fails, the method gracefully returns null
105+
when(upload.getContentSource()).thenThrow(new RuntimeException("Source unavailable"));
106+
107+
assertNull(fileUpload.stream());
108+
}
109+
110+
@Test
111+
void testBytes_Success() {
112+
byte[] expectedData = {10, 20, 30};
113+
InputStream mockStream = new ByteArrayInputStream(expectedData);
114+
Content.Source contentSource = mock(Content.Source.class);
115+
when(upload.getContentSource()).thenReturn(contentSource);
116+
117+
try (MockedStatic<Content.Source> sourceStatic = mockStatic(Content.Source.class)) {
118+
sourceStatic.when(() -> Content.Source.asInputStream(contentSource)).thenReturn(mockStream);
119+
120+
assertArrayEquals(expectedData, fileUpload.bytes());
121+
}
122+
}
123+
124+
@Test
125+
void testBytes_ThrowsException() {
126+
InputStream failingStream =
127+
new InputStream() {
128+
@Override
129+
public int read() throws IOException {
130+
throw new IOException("Stream read failed");
131+
}
132+
};
133+
134+
Content.Source contentSource = mock(Content.Source.class);
135+
when(upload.getContentSource()).thenReturn(contentSource);
136+
137+
try (MockedStatic<Content.Source> sourceStatic = mockStatic(Content.Source.class);
138+
MockedStatic<SneakyThrows> sneaky = mockStatic(SneakyThrows.class)) {
139+
140+
sourceStatic
141+
.when(() -> Content.Source.asInputStream(contentSource))
142+
.thenReturn(failingStream);
143+
sneaky
144+
.when(() -> SneakyThrows.propagate(any(IOException.class)))
145+
.thenReturn(new RuntimeException("Propagated exception"));
146+
147+
RuntimeException thrown = assertThrows(RuntimeException.class, () -> fileUpload.bytes());
148+
assertEquals("Propagated exception", thrown.getMessage());
149+
}
150+
}
151+
152+
@Test
153+
void testPath_WithPathPart() {
154+
// Branch 1: If it's already a PathPart, it just returns the path
155+
MultiPart.PathPart pathPart = mock(MultiPart.PathPart.class);
156+
Path existingPath = mock(Path.class);
157+
when(pathPart.getPath()).thenReturn(existingPath);
158+
159+
JettyFileUpload pathUpload = new JettyFileUpload(tmpdir, pathPart);
160+
161+
assertEquals(existingPath, pathUpload.path());
162+
}
163+
164+
@Test
165+
void testPath_WithStandardPart_WritesToTempDir() throws Exception {
166+
// Branch 2: Standard part, creates a temp file and writes out
167+
Path resolvedPath = mock(Path.class);
168+
when(tmpdir.resolve(any(String.class))).thenReturn(resolvedPath);
169+
170+
assertEquals(resolvedPath, fileUpload.path());
171+
172+
verify(tmpdir).resolve(any(String.class));
173+
verify(upload).writeTo(resolvedPath);
174+
}
175+
176+
@Test
177+
void testPath_WithStandardPart_ThrowsException() throws Exception {
178+
Path resolvedPath = mock(Path.class);
179+
when(tmpdir.resolve(any(String.class))).thenReturn(resolvedPath);
180+
181+
IOException writeException = new IOException("Disk full");
182+
doThrow(writeException).when(upload).writeTo(resolvedPath);
183+
184+
try (MockedStatic<SneakyThrows> sneaky = mockStatic(SneakyThrows.class)) {
185+
sneaky
186+
.when(() -> SneakyThrows.propagate(writeException))
187+
.thenReturn(new RuntimeException("Propagated disk error"));
188+
189+
RuntimeException thrown = assertThrows(RuntimeException.class, () -> fileUpload.path());
190+
assertEquals("Propagated disk error", thrown.getMessage());
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)