multipart parsing, request body access, and chunked input stream support
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
package dev.relism;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ChunkedInputStreamTest {
|
||||
|
||||
private static ChunkedInputStream wrap(String chunkedEncoded) {
|
||||
byte[] bytes = chunkedEncoded.getBytes(StandardCharsets.UTF_8);
|
||||
return new ChunkedInputStream(new ByteArrayInputStream(bytes), null, 0, 0);
|
||||
}
|
||||
|
||||
private static String readAll(ChunkedInputStream in) throws IOException {
|
||||
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// --- bulk reads ---
|
||||
|
||||
@Test
|
||||
void singleChunk() throws IOException {
|
||||
assertEquals("hello", readAll(wrap("5\r\nhello\r\n0\r\n\r\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void multipleChunks() throws IOException {
|
||||
assertEquals("hello world", readAll(wrap("5\r\nhello\r\n6\r\n world\r\n0\r\n\r\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyBody_terminatorOnly() throws IOException {
|
||||
assertEquals("", readAll(wrap("0\r\n\r\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hexDigitsUppercase() throws IOException {
|
||||
// "A" = 10 bytes
|
||||
String data = "0123456789";
|
||||
assertEquals(data, readAll(wrap("A\r\n" + data + "\r\n0\r\n\r\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hexDigitsLowercase() throws IOException {
|
||||
// "a" = 10 bytes
|
||||
String data = "0123456789";
|
||||
assertEquals(data, readAll(wrap("a\r\n" + data + "\r\n0\r\n\r\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void chunkExtension_ignored() throws IOException {
|
||||
// semicolon and extension are discarded, only size matters
|
||||
assertEquals("hello", readAll(wrap("5;ext=val\r\nhello\r\n0\r\n\r\n")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void trailers_consumed() throws IOException {
|
||||
// trailing headers after 0-chunk must be consumed
|
||||
assertEquals("hi", readAll(wrap("2\r\nhi\r\n0\r\nTrailer: value\r\n\r\n")));
|
||||
}
|
||||
|
||||
// --- byte-by-byte read ---
|
||||
|
||||
@Test
|
||||
void byteByByteRead_singleChunk() throws IOException {
|
||||
ChunkedInputStream in = wrap("3\r\nabc\r\n0\r\n\r\n");
|
||||
assertEquals('a', in.read());
|
||||
assertEquals('b', in.read());
|
||||
assertEquals('c', in.read());
|
||||
assertEquals(-1, in.read());
|
||||
}
|
||||
|
||||
@Test
|
||||
void byteByByteRead_multipleChunks() throws IOException {
|
||||
ChunkedInputStream in = wrap("2\r\nhi\r\n2\r\n!!\r\n0\r\n\r\n");
|
||||
assertEquals('h', in.read());
|
||||
assertEquals('i', in.read());
|
||||
assertEquals('!', in.read());
|
||||
assertEquals('!', in.read());
|
||||
assertEquals(-1, in.read());
|
||||
}
|
||||
|
||||
// --- EOF behaviour ---
|
||||
|
||||
@Test
|
||||
void readAfterEof_returnsMinusOne() throws IOException {
|
||||
ChunkedInputStream in = wrap("0\r\n\r\n");
|
||||
assertEquals(-1, in.read());
|
||||
assertEquals(-1, in.read()); // idempotent
|
||||
}
|
||||
|
||||
@Test
|
||||
void readArrayAfterEof_returnsMinusOne() throws IOException {
|
||||
ChunkedInputStream in = wrap("0\r\n\r\n");
|
||||
assertEquals(-1, in.read(new byte[8], 0, 8));
|
||||
}
|
||||
|
||||
// --- pre-buffered data ---
|
||||
|
||||
@Test
|
||||
void preBuf_prependedBeforeSocket() throws IOException {
|
||||
// "5\r\nhello" in preBuf, "\r\n0\r\n\r\n" in socket
|
||||
byte[] preBuf = "5\r\nhello".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] socket = "\r\n0\r\n\r\n".getBytes(StandardCharsets.UTF_8);
|
||||
ChunkedInputStream in = new ChunkedInputStream(new ByteArrayInputStream(socket), preBuf, 0, preBuf.length);
|
||||
assertEquals("hello", new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
void preBuf_withOffset() throws IOException {
|
||||
byte[] preBuf = "XX2\r\nhi\r\n0\r\n\r\n".getBytes(StandardCharsets.UTF_8);
|
||||
// offset=2, len=preBuf.length-2 — skip "XX"
|
||||
ChunkedInputStream in = new ChunkedInputStream(
|
||||
new ByteArrayInputStream(new byte[0]), preBuf, 2, preBuf.length - 2);
|
||||
assertEquals("hi", new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package dev.relism.api.multipart;
|
||||
|
||||
import dev.relism.fpr.core.ByteView;
|
||||
import dev.relism.http.HttpMethod;
|
||||
import dev.relism.models.HeaderMap;
|
||||
import dev.relism.models.Request;
|
||||
import dev.relism.models.RequestLine;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class MultipartTest {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private static final String BOUNDARY = "testboundary";
|
||||
|
||||
private static byte[] body(String... parts) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String part : parts)
|
||||
sb.append("--").append(BOUNDARY).append("\r\n").append(part).append("\r\n");
|
||||
sb.append("--").append(BOUNDARY).append("--\r\n");
|
||||
return sb.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static String textPart(String name, String value) {
|
||||
return "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n" + value;
|
||||
}
|
||||
|
||||
private static String filePart(String name, String filename, String contentType, String value) {
|
||||
return "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + filename + "\"\r\n"
|
||||
+ "Content-Type: " + contentType + "\r\n\r\n" + value;
|
||||
}
|
||||
|
||||
private static Request request(byte[] bodyBytes) {
|
||||
String ct = "multipart/form-data; boundary=" + BOUNDARY;
|
||||
byte[] headerBuf = ("Content-Type: " + ct).getBytes(StandardCharsets.US_ASCII);
|
||||
HeaderMap headers = new HeaderMap();
|
||||
headers.reset(headerBuf, 0, headerBuf.length);
|
||||
RequestLine line = new RequestLine(HttpMethod.POST, viewOf("/upload"), null, viewOf("HTTP/1.1"), headers);
|
||||
return new Request(line, bodyBytes);
|
||||
}
|
||||
|
||||
private static ByteView viewOf(String s) {
|
||||
byte[] b = s.getBytes(StandardCharsets.UTF_8);
|
||||
return new ByteView() {
|
||||
public int length() { return b.length; }
|
||||
public byte byteAt(int i) { return b[i]; }
|
||||
};
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// field()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void field_singleField_returnsValue() throws IOException {
|
||||
assertEquals("alice", Multipart.of(request(body(textPart("username", "alice")))).field("username"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void field_absent_returnsNull() throws IOException {
|
||||
assertNull(Multipart.of(request(body(textPart("username", "alice")))).field("missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void field_filePart_returnsNull() throws IOException {
|
||||
// file parts must not be returned by field()
|
||||
assertNull(Multipart.of(request(body(filePart("photo", "img.png", "image/png", "data")))).field("photo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void field_multipleFields_anyOrder() throws IOException {
|
||||
// Text fields are buffered eagerly — accessible in any call order regardless of declaration order
|
||||
Multipart mp = Multipart.of(request(body(textPart("a", "1"), textPart("b", "2"), textPart("c", "3"))));
|
||||
assertEquals("3", mp.field("c"));
|
||||
assertEquals("1", mp.field("a")); // already in cache
|
||||
assertEquals("2", mp.field("b")); // already in cache
|
||||
}
|
||||
|
||||
@Test
|
||||
void field_accessibleAfterFilePart() throws IOException {
|
||||
// "text" comes AFTER "file" — Multipart must drain the file body silently
|
||||
Multipart mp = Multipart.of(request(body(
|
||||
filePart("file", "f.bin", "application/octet-stream", "binary"),
|
||||
textPart("text", "hello"))));
|
||||
assertEquals("hello", mp.field("text"));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// file()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void file_filePart_returnsPartWithMetadata() throws IOException {
|
||||
Part p = Multipart.of(request(body(filePart("avatar", "me.jpg", "image/jpeg", "JFIF")))).file("avatar");
|
||||
|
||||
assertNotNull(p);
|
||||
assertTrue(p.isFile());
|
||||
assertEquals("avatar", p.name());
|
||||
assertEquals("me.jpg", p.filename());
|
||||
assertEquals("image/jpeg", p.contentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void file_absent_returnsNull() throws IOException {
|
||||
assertNull(Multipart.of(request(body(textPart("x", "y")))).file("photo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void file_fieldPart_returnsNull() throws IOException {
|
||||
// text fields must not be returned by file()
|
||||
assertNull(Multipart.of(request(body(textPart("name", "bob")))).file("name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void file_accessibleAfterTextField() throws IOException {
|
||||
// "file" comes AFTER "userId" — text field is buffered while scanning toward file
|
||||
Multipart mp = Multipart.of(request(body(
|
||||
textPart("userId", "42"),
|
||||
filePart("file", "doc.pdf", "application/pdf", "PDF"))));
|
||||
|
||||
Part file = mp.file("file");
|
||||
assertNotNull(file);
|
||||
assertEquals("doc.pdf", file.filename());
|
||||
assertEquals("42", mp.field("userId")); // already cached
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Body access
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void part_text_returnsUtf8Value() throws IOException {
|
||||
Part p = Multipart.of(request(body(textPart("note", "héllo")))).parts("note").getFirst();
|
||||
assertEquals("héllo", p.text());
|
||||
}
|
||||
|
||||
@Test
|
||||
void part_bytes_returnsCorrectCopy() throws IOException {
|
||||
byte[] expected = "binary\0data".getBytes(StandardCharsets.UTF_8);
|
||||
Part p = Multipart.of(request(body(
|
||||
filePart("f", "f.bin", "application/octet-stream",
|
||||
new String(expected, StandardCharsets.UTF_8))))).parts("f").getFirst();
|
||||
assertArrayEquals(expected, p.materialize());
|
||||
}
|
||||
|
||||
@Test
|
||||
void part_stream_hasCorrectContent() throws IOException {
|
||||
Part p = Multipart.of(request(body(textPart("data", "streamed")))).parts("data").getFirst();
|
||||
assertArrayEquals("streamed".getBytes(StandardCharsets.UTF_8), p.stream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void part_stream_isRepeatable_forBufferedParts() throws IOException {
|
||||
// Buffered parts (text fields, parts() results) can be read multiple times
|
||||
Part p = Multipart.of(request(body(textPart("k", "v")))).parts("k").getFirst();
|
||||
assertNotSame(p.stream(), p.stream()); // fresh ByteArrayInputStream each call
|
||||
assertArrayEquals("v".getBytes(StandardCharsets.UTF_8), p.stream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void part_bytes_isCached() throws IOException {
|
||||
Part p = Multipart.of(request(body(textPart("k", "v")))).parts("k").getFirst();
|
||||
assertSame(p.materialize(), p.materialize()); // second call returns cached array
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Multiple parts / parts()
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void parts_sameNameMultiple_allReturned() throws IOException {
|
||||
Multipart mp = Multipart.of(request(body(
|
||||
textPart("tag", "alpha"),
|
||||
textPart("tag", "beta"),
|
||||
textPart("tag", "gamma"))));
|
||||
|
||||
List<Part> tags = mp.parts("tag");
|
||||
assertEquals(3, tags.size());
|
||||
assertEquals("alpha", tags.get(0).text());
|
||||
assertEquals("beta", tags.get(1).text());
|
||||
assertEquals("gamma", tags.get(2).text());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parts_allParts_inDeclarationOrder() throws IOException {
|
||||
List<Part> all = Multipart.of(request(body(
|
||||
textPart("first", "1"),
|
||||
filePart("second", "s.bin", "application/octet-stream", "2"),
|
||||
textPart("third", "3")))).parts();
|
||||
|
||||
assertEquals(3, all.size());
|
||||
assertEquals("first", all.get(0).name());
|
||||
assertEquals("second", all.get(1).name());
|
||||
assertEquals("third", all.get(2).name());
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Large-body simulation (streaming correctness)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void file_bodyLargerThanWindow_streamedCorrectly() throws IOException {
|
||||
// Produce a body > 8 KB to exercise multi-refill window logic
|
||||
String large = "x".repeat(20_000);
|
||||
Part p = Multipart.of(request(body(
|
||||
filePart("big", "big.txt", "text/plain", large)))).file("big");
|
||||
|
||||
assertNotNull(p);
|
||||
byte[] got = p.materialize();
|
||||
assertArrayEquals(large.getBytes(StandardCharsets.UTF_8), got);
|
||||
}
|
||||
|
||||
@Test
|
||||
void field_afterLargeFile_drained_thenAccessible() throws IOException {
|
||||
// File part (>8 KB) before a text field — file must be drained, text must be accessible
|
||||
String large = "y".repeat(20_000);
|
||||
Multipart mp = Multipart.of(request(body(
|
||||
filePart("file", "f.bin", "application/octet-stream", large),
|
||||
textPart("meta", "value"))));
|
||||
|
||||
assertEquals("value", mp.field("meta"));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Error cases
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
void of_notMultipart_throws() {
|
||||
byte[] headerBuf = "Content-Type: application/json".getBytes(StandardCharsets.US_ASCII);
|
||||
HeaderMap headers = new HeaderMap();
|
||||
headers.reset(headerBuf, 0, headerBuf.length);
|
||||
RequestLine line = new RequestLine(HttpMethod.POST, viewOf("/"), null, viewOf("HTTP/1.1"), headers);
|
||||
Request req = new Request(line, new byte[0]);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> Multipart.of(req));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package dev.relism.models;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RequestBodyTest {
|
||||
|
||||
// --- static factories ---
|
||||
|
||||
@Test
|
||||
void of_resolvedImmediately() {
|
||||
byte[] data = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = RequestBody.of(data);
|
||||
assertArrayEquals(data, body.bytes());
|
||||
assertFalse(body.isEmpty());
|
||||
assertEquals(5, body.contentLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
void empty_isEmptyAndZeroLength() {
|
||||
RequestBody body = RequestBody.empty();
|
||||
assertTrue(body.isEmpty());
|
||||
assertEquals(0, body.contentLength());
|
||||
assertEquals(0, body.bytes().length);
|
||||
}
|
||||
|
||||
// --- bytes() ---
|
||||
|
||||
@Test
|
||||
void bytes_fromPreBufOnly() {
|
||||
byte[] preBuf = "world".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(null, 5, preBuf, 0, 5);
|
||||
assertArrayEquals(preBuf, body.bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bytes_fromPreBufWithOffset() {
|
||||
byte[] preBuf = "xxhelloxx".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(null, 5, preBuf, 2, 5);
|
||||
assertArrayEquals("hello".getBytes(StandardCharsets.UTF_8), body.bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bytes_fromSocketOnly() {
|
||||
byte[] data = "socket".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(new ByteArrayInputStream(data), 6, new byte[0], 0, 0);
|
||||
assertArrayEquals(data, body.bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bytes_fromPreBufAndSocket() {
|
||||
byte[] preBuf = "hel".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(
|
||||
new ByteArrayInputStream("lo".getBytes(StandardCharsets.UTF_8)),
|
||||
5, preBuf, 0, 3);
|
||||
assertArrayEquals("hello".getBytes(StandardCharsets.UTF_8), body.bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bytes_cached_returnsSameReference() {
|
||||
RequestBody body = RequestBody.of("cached".getBytes(StandardCharsets.UTF_8));
|
||||
assertSame(body.bytes(), body.bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bytes_chunked_readsAllFromSocket() {
|
||||
// contentLength == -1 → bytes() calls socket.readAllBytes()
|
||||
byte[] data = "chunked content".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(new ByteArrayInputStream(data), -1L, null, 0, 0);
|
||||
assertArrayEquals(data, body.bytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void bytes_tooLarge_throwsIllegalStateException() {
|
||||
RequestBody body = new RequestBody(InputStream.nullInputStream(), (long) Integer.MAX_VALUE + 1, null, 0, 0);
|
||||
assertThrows(IllegalStateException.class, body::bytes);
|
||||
}
|
||||
|
||||
// --- stream() ---
|
||||
|
||||
@Test
|
||||
void stream_onResolved_returnsBytesWrapped() throws IOException {
|
||||
byte[] data = "stream".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = RequestBody.of(data);
|
||||
assertArrayEquals(data, body.stream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stream_fromPreBufOnly() throws IOException {
|
||||
byte[] preBuf = "buf".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(null, 3, preBuf, 0, 3);
|
||||
assertArrayEquals(preBuf, body.stream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stream_fromPreBufAndSocket() throws IOException {
|
||||
byte[] preBuf = "hel".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(
|
||||
new ByteArrayInputStream("lo".getBytes(StandardCharsets.UTF_8)),
|
||||
5, preBuf, 0, 3);
|
||||
assertArrayEquals("hello".getBytes(StandardCharsets.UTF_8), body.stream().readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stream_chunked_returnsSocketDirectly() {
|
||||
InputStream socket = InputStream.nullInputStream();
|
||||
RequestBody body = new RequestBody(socket, -1L, null, 0, 0);
|
||||
assertSame(socket, body.stream());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stream_afterBytes_returnsCachedBytes() throws IOException {
|
||||
byte[] data = "data".getBytes(StandardCharsets.UTF_8);
|
||||
RequestBody body = new RequestBody(new ByteArrayInputStream(data), 4, new byte[0], 0, 0);
|
||||
body.bytes(); // resolves and caches
|
||||
assertArrayEquals(data, body.stream().readAllBytes()); // wraps cached bytes
|
||||
}
|
||||
|
||||
// --- drain() ---
|
||||
|
||||
@Test
|
||||
void drain_empty_noOp() {
|
||||
assertDoesNotThrow(RequestBody.empty()::drain);
|
||||
}
|
||||
|
||||
@Test
|
||||
void drain_resolved_noOp() {
|
||||
assertDoesNotThrow(RequestBody.of("data".getBytes())::drain);
|
||||
}
|
||||
|
||||
@Test
|
||||
void drain_skipsUnreadSocketBytes() throws IOException {
|
||||
byte[] payload = "helloNEXT".getBytes(StandardCharsets.UTF_8);
|
||||
ByteArrayInputStream socket = new ByteArrayInputStream(payload);
|
||||
RequestBody body = new RequestBody(socket, 5, new byte[0], 0, 0);
|
||||
body.drain();
|
||||
assertArrayEquals("NEXT".getBytes(StandardCharsets.UTF_8), socket.readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void drain_skipsOnlyRemainingAfterPartialPreBuf() throws IOException {
|
||||
byte[] preBuf = "hel".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] rest = "loNEXT".getBytes(StandardCharsets.UTF_8);
|
||||
ByteArrayInputStream socket = new ByteArrayInputStream(rest);
|
||||
// body = "hello" (5 bytes), 3 in preBuf, 2 from socket
|
||||
RequestBody body = new RequestBody(socket, 5, preBuf, 0, 3);
|
||||
body.drain();
|
||||
// drain should skip 2 socket bytes ("lo"), leaving "NEXT"
|
||||
assertArrayEquals("NEXT".getBytes(StandardCharsets.UTF_8), socket.readAllBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void drain_chunked_drainsSocket() throws IOException {
|
||||
byte[] data = "some chunked data".getBytes(StandardCharsets.UTF_8);
|
||||
ByteArrayInputStream socket = new ByteArrayInputStream(data);
|
||||
RequestBody body = new RequestBody(socket, -1L, null, 0, 0);
|
||||
body.drain();
|
||||
assertEquals(0, socket.available());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user