This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new dc084636ae Add TEST-04 coverage tests and fix RRPC POST dispatch
(TODO-295, TODO-296)
dc084636ae is described below
commit dc084636ae991e701886ffaa66922b7f326b9704
Author: James Bognar <[email protected]>
AuthorDate: Sun Jul 26 07:16:17 2026 -0400
Add TEST-04 coverage tests and fix RRPC POST dispatch (TODO-295, TODO-296)
Adds ~156 targeted tests closing the TEST-04 coverage gaps: juneau-bean-mcp
addX() null-init branches, juneau-marshall parser error/edge-case paths
(incl. a
new PlainTextParser test), juneau-rest-server @RestOp arg-binding, the RRPC
Policy/ContentNegotiation families, and Hyperlink/HttpPartList coverage.
While covering RRPC this surfaced and fixes a pre-existing defect: HTTP
POST to an
@RestOp(method="RRPC") operation 404'd before invocation because
RrpcRestOpSession derived the method key by splitting the path on the last
'/',
but RRPC keys (methodName/(paramTypes)) themselves contain a '/'. The key
is now
taken from the URL-path-match remainder. Includes POST round-trip regression
tests.
---
.../bean/mcp/McpBeans_AddXNullInit_Test.java | 166 ++++++++++++++
.../juneau/marshall/cbor/CborParser_Test.java | 11 +
.../juneau/marshall/hjson/HjsonEdgeCases_Test.java | 21 ++
.../juneau/marshall/hocon/HoconEdgeCases_Test.java | 28 +++
.../apache/juneau/marshall/ini/IniParser_Test.java | 54 +++++
.../juneau/marshall/ini/IniSerializer_Test.java | 38 ++++
.../marshall/markdown/MarkdownParser_Test.java | 12 ++
.../marshall/msgpack/MsgPackParser_Test.java | 14 ++
.../apache/juneau/marshall/oapi/OpenApi_Test.java | 19 ++
.../marshall/plaintext/PlainTextParser_Test.java | 53 +++++
.../marshall/prototext/PrototextParser_Test.java | 20 ++
.../apache/juneau/marshall/uon/UonParser_Test.java | 17 ++
.../rest/server/rrpc/RrpcRestOpContext_Test.java | 155 +++++++++++++
.../apache/juneau/http/part/HttpPartList_Test.java | 103 +++++++++
.../http/remote/RrpcInterfaceMethodMeta_Test.java | 105 +++++++++
.../juneau/rest/server/rrpc/RrpcRestOpSession.java | 11 +-
.../rest/server/arg/RestArgResolvers_Test.java | 14 ++
.../juneau/rest/server/beans/Hyperlink_Test.java | 128 +++++++++++
.../server/httppart/BasicNamedAttribute_Test.java | 121 +++++++++++
.../server/httppart/RequestFormParam_Test.java | 80 +++++++
.../rest/server/httppart/RequestHeader_Test.java | 93 ++++++++
.../rest/server/httppart/RequestHttpPart_Test.java | 240 +++++++++++++++++++++
22 files changed, 1499 insertions(+), 4 deletions(-)
diff --git
a/juneau-bean/juneau-bean-mcp/src/test/java/org/apache/juneau/bean/mcp/McpBeans_AddXNullInit_Test.java
b/juneau-bean/juneau-bean-mcp/src/test/java/org/apache/juneau/bean/mcp/McpBeans_AddXNullInit_Test.java
new file mode 100644
index 0000000000..891f8e2f56
--- /dev/null
+++
b/juneau-bean/juneau-bean-mcp/src/test/java/org/apache/juneau/bean/mcp/McpBeans_AddXNullInit_Test.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.bean.mcp;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Covers the {@code addX(Collection/varargs)}/{@code putX(...)} null-init
branch
+ * (<code>if (field == null) field = list()/map();</code>) across the module's
collection/map-backed beans.
+ *
+ * <p>
+ * The module's round-trip suite ({@link McpBeans_RoundTrip_Test}) only ever
calls the {@code setX(...)} form
+ * first, pre-populating the backing collection, so the null-init branch of
every {@code addX()}/{@code putX()}
+ * method was never exercised (module was 0% branch). Each test below calls
the adder/putter twice on a fresh
+ * (null-backed) instance: the first call exercises the {@code true}
(null-init) branch, the second exercises the
+ * {@code false} (already-materialized) branch, closing both sides of the
conditional.
+ */
+class McpBeans_AddXNullInit_Test {
+
+ @Test void a01_callToolRequest_putArgument_nullInit() {
+ var x = new CallToolRequest().putArgument("a",
1).putArgument("b", 2);
+ assertEquals(Map.of("a", 1, "b", 2), x.getArguments());
+ }
+
+ @Test void a02_callToolResult_addContent_varargs_nullInit() {
+ var x = new CallToolResult().addContent(new
TextContent().setText("a")).addContent(new TextContent().setText("b"));
+ assertEquals(2, x.getContent().size());
+ }
+
+ @Test void a03_callToolResult_addContent_collection_nullInit() {
+ var x = new CallToolResult()
+ .addContent(List.of(new TextContent().setText("a")))
+ .addContent(List.of(new TextContent().setText("b")));
+ assertEquals(2, x.getContent().size());
+ }
+
+ @Test void a04_clientCapabilities_putSampling_nullInit() {
+ var x = new ClientCapabilities().putSampling("a",
1).putSampling("b", 2);
+ assertEquals(Map.of("a", 1, "b", 2), x.getSampling());
+ }
+
+ @Test void a05_clientCapabilities_putExperimental_nullInit() {
+ var x = new ClientCapabilities().putExperimental("a",
1).putExperimental("b", 2);
+ assertEquals(Map.of("a", 1, "b", 2), x.getExperimental());
+ }
+
+ @Test void a06_getPromptRequest_putArgument_nullInit() {
+ var x = new GetPromptRequest().putArgument("a",
1).putArgument("b", 2);
+ assertEquals(Map.of("a", 1, "b", 2), x.getArguments());
+ }
+
+ @Test void a07_getPromptResult_addMessages_varargs_nullInit() {
+ var m1 = new PromptMessage().setRole(Role.USER);
+ var m2 = new PromptMessage().setRole(Role.ASSISTANT);
+ var x = new GetPromptResult().addMessages(m1).addMessages(m2);
+ assertEquals(2, x.getMessages().size());
+ }
+
+ @Test void a08_getPromptResult_addMessages_collection_nullInit() {
+ var m1 = new PromptMessage().setRole(Role.USER);
+ var m2 = new PromptMessage().setRole(Role.ASSISTANT);
+ var x = new
GetPromptResult().addMessages(List.of(m1)).addMessages(List.of(m2));
+ assertEquals(2, x.getMessages().size());
+ }
+
+ @Test void a09_jsonSchema_addProperty_nullInit() {
+ var x = new JsonSchema().addProperty("a", new
JsonSchema().setType("string")).addProperty("b", new
JsonSchema().setType("number"));
+ assertEquals(2, x.getProperties().size());
+ }
+
+ @Test void a10_jsonSchema_addRequired_varargs_nullInit() {
+ var x = new JsonSchema().addRequired("a").addRequired("b");
+ assertEquals(List.of("a", "b"), x.getRequired());
+ }
+
+ @Test void a11_jsonSchema_addRequired_collection_nullInit() {
+ var x = new
JsonSchema().addRequired(List.of("a")).addRequired(List.of("b"));
+ assertEquals(List.of("a", "b"), x.getRequired());
+ }
+
+ @Test void a12_jsonSchema_addDef_nullInit() {
+ var x = new JsonSchema().addDef("a", new
JsonSchema().setType("string")).addDef("b", new JsonSchema().setType("number"));
+ assertEquals(2, x.getDefs().size());
+ }
+
+ @Test void a13_listPromptsResult_addPrompts_varargs_nullInit() {
+ var x = new ListPromptsResult().addPrompts(new
Prompt().setName("a")).addPrompts(new Prompt().setName("b"));
+ assertEquals(2, x.getPrompts().size());
+ }
+
+ @Test void a14_listPromptsResult_addPrompts_collection_nullInit() {
+ var x = new ListPromptsResult().addPrompts(List.of(new
Prompt().setName("a"))).addPrompts(List.of(new Prompt().setName("b")));
+ assertEquals(2, x.getPrompts().size());
+ }
+
+ @Test void a15_listResourcesResult_addResources_varargs_nullInit() {
+ var x = new ListResourcesResult().addResources(new
Resource().setUri("a")).addResources(new Resource().setUri("b"));
+ assertEquals(2, x.getResources().size());
+ }
+
+ @Test void a16_listResourcesResult_addResources_collection_nullInit() {
+ var x = new ListResourcesResult()
+ .addResources(List.of(new Resource().setUri("a")))
+ .addResources(List.of(new Resource().setUri("b")));
+ assertEquals(2, x.getResources().size());
+ }
+
+ @Test void a17_listToolsResult_addTools_varargs_nullInit() {
+ var x = new ListToolsResult().addTools(new
Tool().setName("a")).addTools(new Tool().setName("b"));
+ assertEquals(2, x.getTools().size());
+ }
+
+ @Test void a18_listToolsResult_addTools_collection_nullInit() {
+ var x = new ListToolsResult().addTools(List.of(new
Tool().setName("a"))).addTools(List.of(new Tool().setName("b")));
+ assertEquals(2, x.getTools().size());
+ }
+
+ @Test void a19_prompt_addArguments_varargs_nullInit() {
+ var x = new Prompt().addArguments(new
PromptArgument().setName("a")).addArguments(new PromptArgument().setName("b"));
+ assertEquals(2, x.getArguments().size());
+ }
+
+ @Test void a20_prompt_addArguments_collection_nullInit() {
+ var x = new Prompt()
+ .addArguments(List.of(new
PromptArgument().setName("a")))
+ .addArguments(List.of(new
PromptArgument().setName("b")));
+ assertEquals(2, x.getArguments().size());
+ }
+
+ @Test void a21_readResourceResult_addContents_varargs_nullInit() {
+ var x = new ReadResourceResult()
+ .addContents(new TextResourceContents().setUri("a"))
+ .addContents(new TextResourceContents().setUri("b"));
+ assertEquals(2, x.getContents().size());
+ }
+
+ @Test void a22_readResourceResult_addContents_collection_nullInit() {
+ var x = new ReadResourceResult()
+ .addContents(List.of(new
TextResourceContents().setUri("a")))
+ .addContents(List.of(new
TextResourceContents().setUri("b")));
+ assertEquals(2, x.getContents().size());
+ }
+
+ @Test void a23_serverCapabilities_putExperimental_nullInit() {
+ var x = new ServerCapabilities().putExperimental("a",
1).putExperimental("b", 2);
+ assertEquals(Map.of("a", 1, "b", 2), x.getExperimental());
+ }
+}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborParser_Test.java
index 887bce6724..f1316af56c 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborParser_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/cbor/CborParser_Test.java
@@ -158,6 +158,17 @@ class CborParser_Test extends TestBase {
}
}
+
//====================================================================================================
+ // Malformed input -- CborParserSession error-path branches
+
//====================================================================================================
+
+ @Test
+ void d21_wrongDataTypeForBeanTargetThrows() {
+ // "81 01" == a 1-element array [1]; a bean target requires a
MAP wire type.
+ var ex = assertThrows(Exception.class,
()->CborParser.DEFAULT.read(fromHex("8101"), Person.class));
+ assertTrue(ex.getMessage().contains("Invalid data type"));
+ }
+
public static class Person {
public String name;
public int age;
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hjson/HjsonEdgeCases_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hjson/HjsonEdgeCases_Test.java
index e8972cb5e0..4ba423729a 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hjson/HjsonEdgeCases_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hjson/HjsonEdgeCases_Test.java
@@ -16,12 +16,14 @@
*/
package org.apache.juneau.marshall.hjson;
+import static org.apache.juneau.BasicTestUtils.*;
import static org.apache.juneau.test.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
import org.apache.juneau.*;
+import org.apache.juneau.marshall.parser.*;
import org.junit.jupiter.api.*;
/**
@@ -156,4 +158,23 @@ class HjsonEdgeCases_Test extends TestBase {
assertTrue(desc.contains("line1"));
assertTrue(desc.contains("line2"));
}
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Malformed input -- HjsonParserSession error-path branches
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test
+ void g01_missingColonAfterKeyThrows() {
+ assertThrowsWithMessage(ParseException.class, "Expected : after
key", ()->HjsonParser.DEFAULT.read("{\"a\" 1}", Map.class, String.class,
Object.class));
+ }
+
+ @Test
+ void g02_unexpectedTokenInValuePositionThrows() {
+ assertThrowsWithMessage(ParseException.class, "Unexpected
token", ()->HjsonParser.DEFAULT.read("{\"a\":}", Map.class, String.class,
Object.class));
+ }
+
+ @Test
+ void g03_invalidKeyTokenThrows() {
+ assertThrowsWithMessage(ParseException.class, "Expected key",
()->HjsonParser.DEFAULT.read("{{}:1}", Map.class, String.class, Object.class));
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hocon/HoconEdgeCases_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hocon/HoconEdgeCases_Test.java
index d429c33c85..9e412c30fd 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hocon/HoconEdgeCases_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/hocon/HoconEdgeCases_Test.java
@@ -115,4 +115,32 @@ class HoconEdgeCases_Test extends TestBase {
assertEquals(1, ((Number) m.get("a")).intValue());
assertEquals(2, ((Number) m.get("b")).intValue());
}
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Malformed input -- HoconParserSession error-path branches
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test
+ void j01_missingSeparatorAtTopLevelThrows() {
+ var ex = assertThrows(Exception.class, () ->
HoconParser.DEFAULT.read("key value", Map.class, String.class, Object.class));
+ assertTrue(ex.getMessage().contains("Expected") ||
ex.getMessage().contains("brace"));
+ }
+
+ @Test
+ void j02_missingSeparatorInNestedObjectThrows() {
+ var ex = assertThrows(Exception.class, () ->
HoconParser.DEFAULT.read("obj { key value }", Map.class, String.class,
Object.class));
+ assertTrue(ex.getMessage().contains("Expected") ||
ex.getMessage().contains("brace"));
+ }
+
+ @Test
+ void j03_missingKeyThrows() {
+ var ex = assertThrows(Exception.class, () ->
HoconParser.DEFAULT.read("= 1", Map.class, String.class, Object.class));
+ assertTrue(ex.getMessage().contains("Expected key"));
+ }
+
+ @Test
+ void j04_unexpectedTokenInValuePositionThrows() {
+ var ex = assertThrows(Exception.class, () ->
HoconParser.DEFAULT.read("key = }", Map.class, String.class, Object.class));
+ assertTrue(ex.getMessage().contains("Unexpected token"));
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniParser_Test.java
index e8180e9905..bbeaf0b207 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniParser_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniParser_Test.java
@@ -16,6 +16,7 @@
*/
package org.apache.juneau.marshall.ini;
+import static org.apache.juneau.BasicTestUtils.*;
import static org.apache.juneau.commons.utils.Shorts.*;
import static org.apache.juneau.test.bct.BctAssertions.*;
import static org.junit.jupiter.api.Assertions.*;
@@ -23,6 +24,7 @@ import static org.junit.jupiter.api.Assertions.*;
import java.util.*;
import org.apache.juneau.*;
+import org.apache.juneau.marshall.parser.*;
import org.junit.jupiter.api.*;
/**
@@ -129,4 +131,56 @@ class IniParser_Test extends TestBase {
|| cns(ex).contains("Parse"));
}
+
//====================================================================================================
+ // b - Malformed/edge-case input (IniParserSession#splitKeyValue /
#readIniContent branches)
+
//====================================================================================================
+
+ @Test
+ void b01_unterminatedSectionHeaderIgnored() throws Exception {
+ // No closing ']' -- readIniContent's "end < 0" branch skips
the line entirely (current section unchanged).
+ var ini = "[section\nkey = value";
+ var m = (Map<String,Object>) IniParser.DEFAULT.read(ini,
Map.class, String.class, Object.class);
+ assertBean(m, "key", "value");
+ assertFalse(m.containsKey("section"));
+ }
+
+ @Test
+ void b02_lineStartingWithEqualsIgnored() throws Exception {
+ // splitKeyValue's "first == '='" guard rejects the line as not
a key/value pair.
+ var ini = "=badline\nkey = value";
+ var m = (Map<String,Object>) IniParser.DEFAULT.read(ini,
Map.class, String.class, Object.class);
+ assertBean(m, "key", "value");
+ assertEquals(1, m.size());
+ }
+
+ @Test
+ void b03_lineWithNoSeparatorIgnored() throws Exception {
+ // No '=' or ':' present -- splitKeyValue's "idx < 1" guard
rejects the line.
+ var ini = "justtext\nkey = value";
+ var m = (Map<String,Object>) IniParser.DEFAULT.read(ini,
Map.class, String.class, Object.class);
+ assertBean(m, "key", "value");
+ assertEquals(1, m.size());
+ }
+
+ @Test
+ void b04_unknownBeanPropertyThrows() {
+ var ini = "name = Alice\nfoo = bar";
+ assertThrowsWithMessage(ParseException.class, "Unknown property
'foo'", () ->
+ IniParser.DEFAULT.read(ini,
IniRoundTrip_Test.Person.class));
+ }
+
+ @Test
+ void b05_unknownBeanPropertyIgnoredWhenConfigured() throws Exception {
+ var ini = "name = Alice\nage = 30\nfoo = bar";
+ var p =
IniParser.create().ignoreUnknownBeanProperties().build().read(ini,
IniRoundTrip_Test.Person.class);
+ assertBean(p, "name,age", "Alice,30");
+ }
+
+ @Test
+ void b06_inlineCommentStrippedOutsideQuotesOnly() throws Exception {
+ var ini = "a = 'has#hash'\nb = plain # comment";
+ var m = (Map<String,Object>) IniParser.DEFAULT.read(ini,
Map.class, String.class, Object.class);
+ assertBean(m, "a,b", "has#hash,plain");
+ }
+
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniSerializer_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniSerializer_Test.java
index 80371f5bc1..98f78376a9 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniSerializer_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/ini/IniSerializer_Test.java
@@ -222,4 +222,42 @@ class IniSerializer_Test {
}
enum TestEnum { ACTIVE, INACTIVE }
+
+
//====================================================================================================
+ // c - IniWriter comment/null-value branches (via @Ini(comment=...))
+
//====================================================================================================
+
+ public static class CommentedBean {
+ @Ini(comment="A single-line comment.")
+ public String name;
+ @Ini(comment="Line one.\nLine two.")
+ public String description;
+ }
+
+ @Test
+ void c01_singleLineCommentEmitted() {
+ var b = new CommentedBean();
+ b.name = "Alice";
+ var ini = IniSerializer.create().useComments().build().write(b);
+ assertTrue(ini.contains("# A single-line comment."));
+ assertTrue(ini.contains("name") && ini.contains("Alice"));
+ }
+
+ @Test
+ void c02_multiLineCommentEmittedPerLine() {
+ var b = new CommentedBean();
+ b.description = "x";
+ var ini = IniSerializer.create().useComments().build().write(b);
+ assertTrue(ini.contains("# Line one."));
+ assertTrue(ini.contains("# Line two."));
+ }
+
+ @Test
+ void c03_commentsNotEmittedByDefault() {
+ var b = new CommentedBean();
+ b.name = "Alice";
+ var ini = IniSerializer.DEFAULT.write(b);
+ assertFalse(ini.contains("#"));
+ }
+
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/markdown/MarkdownParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/markdown/MarkdownParser_Test.java
index 316ac9fa17..b7677d878d 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/markdown/MarkdownParser_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/markdown/MarkdownParser_Test.java
@@ -59,6 +59,18 @@ class MarkdownParser_Test {
public int age;
}
+
//====================================================================================================
+ // z - Malformed input -- MarkdownParserSession#readKeyValueTable JSON5
error-path branch
+
//====================================================================================================
+
+ @Test void z01_malformedInlineJson5InKeyValueTableThrows() {
+ // Backtick-wrapped cell content is embedded into the JSON5
object verbatim (unquoted); an
+ // unbalanced brace here produces invalid JSON5 syntax,
exercising the needsJson5Path(...) catch branch.
+ var md = "| Property | Value |\n|---|---|\n| a | `{invalid` |";
+ var ex =
assertThrows(org.apache.juneau.marshall.parser.ParseException.class,
()->MarkdownParser.DEFAULT.read(md, List.class));
+ assertTrue(ex.getMessage().contains("Could not parse key-value
table"));
+ }
+
//====================================================================================================
// b - Parse multi-column table to list of beans
//====================================================================================================
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackParser_Test.java
index 94dc602d1a..d8a0461be4 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackParser_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/msgpack/MsgPackParser_Test.java
@@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.*;
import java.io.*;
import org.apache.juneau.*;
+import org.apache.juneau.marshall.parser.*;
import org.junit.jupiter.api.*;
/**
@@ -125,6 +126,19 @@ class MsgPackParser_Test extends TestBase {
assertJson("{'1':2}", r);
}
+
//====================================================================================================
+ // Malformed input -- MsgPackParserSession error-path branches
+
//====================================================================================================
+
+ @Test void a03_wrongDataTypeForBeanTargetThrows() {
+ // "91 01" == a 1-element array [1]; a bean target requires a
MAP wire type.
+ assertThrowsWithMessage(ParseException.class, "Invalid data
type", ()->MsgPackParser.DEFAULT.read(is("91 01"), A03Bean.class));
+ }
+
+ public static class A03Bean {
+ public String a;
+ }
+
private static InputStream is(String spacedHex) {
return new
CloseableByteArrayInputStream(fromSpacedHex(spacedHex));
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/oapi/OpenApi_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/oapi/OpenApi_Test.java
index afe78f611f..af242fc968 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/oapi/OpenApi_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/oapi/OpenApi_Test.java
@@ -31,6 +31,7 @@ import org.apache.juneau.*;
import org.apache.juneau.commons.time.*;
import org.apache.juneau.marshall.collections.*;
import org.apache.juneau.marshall.httppart.*;
+import org.apache.juneau.marshall.parser.*;
import org.apache.juneau.marshall.serializer.*;
import org.junit.jupiter.api.*;
@@ -860,6 +861,24 @@ public class OpenApi_Test extends TestBase {
assertJson(json(in), r);
}
+
//------------------------------------------------------------------------------------------------------------------
+ // Malformed input -- OpenApiParserSession error-path branches (Type ==
OBJECT)
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void g02_objectType_noSeparatorThrows() {
+ var ps = T_OBJECT;
+ assertThrowsWithMessage(ParseException.class, "Invalid input",
()->parse(ps, "abc", JsonMap.class));
+ }
+
+ @Test void g03_objectType_unknownBeanPropertyThrows() {
+ var ps = tObject().p("a", tString()).build();
+ assertThrowsWithMessage(ParseException.class, "Cannot find
property", ()->parse(ps, "a=1,foo=2", G03Bean.class));
+ }
+
+ public static class G03Bean {
+ public String a;
+ }
+
//---------------------------------------------------------------------------------------------
// Helpers
//---------------------------------------------------------------------------------------------
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/plaintext/PlainTextParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/plaintext/PlainTextParser_Test.java
new file mode 100644
index 0000000000..8e015bc046
--- /dev/null
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/plaintext/PlainTextParser_Test.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.marshall.plaintext;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link PlainTextParser}.
+ *
+ * <p>
+ * {@link
PlainTextParserSession#doRead(org.apache.juneau.marshall.parser.ParserPipe,
ClassMeta)} is a thin
+ * wrapper delegating straight to {@code convertToType(...)}; there was no
test in this module exercising the
+ * class directly, so this file covers both the happy path and the
type-conversion error path.
+ */
+class PlainTextParser_Test extends TestBase {
+
+ @Test void a01_readString() throws Exception {
+ assertEquals("foo", PlainTextParser.DEFAULT.read("foo",
String.class));
+ }
+
+ @Test void a02_readInteger() throws Exception {
+ assertEquals(123, PlainTextParser.DEFAULT.read("123",
Integer.class));
+ }
+
+ @Test void a03_readEmptyString() throws Exception {
+ assertEquals("", PlainTextParser.DEFAULT.read("",
String.class));
+ }
+
+
//====================================================================================================
+ // Malformed input -- convertToType(...) error path for an incompatible
target type
+
//====================================================================================================
+
+ @Test void b01_unparsableNumberThrows() {
+ assertThrows(Exception.class,
()->PlainTextParser.DEFAULT.read("not-a-number", Integer.class));
+ }
+}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextParser_Test.java
index a0e4e64236..5cf8a76000 100644
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextParser_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/prototext/PrototextParser_Test.java
@@ -197,4 +197,24 @@ class PrototextParser_Test {
var result =
PrototextParser.DEFAULT.getPrototextBeanPropertyMeta(null);
assertSame(PrototextBeanPropertyMeta.DEFAULT, result);
}
+
+ //
-----------------------------------------------------------------------------------------------------------------
+ // c — Malformed input -- PrototextParserSession#populateBeanMap
error-path branch
+ //
-----------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_unknownBeanPropertyThrows() {
+ var ex =
assertThrows(org.apache.juneau.marshall.parser.ParseException.class,
+ ()->PrototextParser.DEFAULT.read("name: \"Alice\"\nfoo:
1", C01Bean.class));
+ assertTrue(ex.getMessage().contains("Unknown property 'foo'"));
+ }
+
+ @Test void c02_unknownBeanPropertyIgnoredWhenConfigured() {
+ var p =
PrototextParser.create().ignoreUnknownBeanProperties().build();
+ var bean = p.read("name: \"Alice\"\nfoo: 1", C01Bean.class);
+ assertEquals("Alice", bean.name);
+ }
+
+ public static class C01Bean {
+ public String name;
+ }
}
diff --git
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/uon/UonParser_Test.java
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/uon/UonParser_Test.java
index 766e74eefd..03e7b9a97b 100755
---
a/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/uon/UonParser_Test.java
+++
b/juneau-core/juneau-marshall/src/test/java/org/apache/juneau/marshall/uon/UonParser_Test.java
@@ -533,4 +533,21 @@ class UonParser_Test extends TestBase {
private static Reader reader(String in) {
return new CloseableStringReader(in);
}
+
+
//====================================================================================================
+ // Malformed input -- UonParserSession error-path branches
+
//====================================================================================================
+
+ @Test void a06_mapMissingOpenParenThrows() {
+ assertThrowsWithMessage(ParseException.class, "Expected '(' at
beginning of object", ()->p.read("x", Map.class));
+ }
+
+ @Test void a07_arrayMissingCloseParenThrows() {
+ assertThrowsWithMessage(ParseException.class, "Could not find
end of entry in array", ()->p.read("@(1", List.class));
+ }
+
+ @Test void a08_remainderAfterParseThrows() {
+ var p2 = UonParser.DEFAULT.copy().validateEnd().build();
+ assertThrowsWithMessage(ParseException.class, "Remainder after
parse", ()->p2.read("(a=1)extra", Map.class));
+ }
}
\ No newline at end of file
diff --git
a/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpContext_Test.java
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpContext_Test.java
new file mode 100644
index 0000000000..0e650b5a02
--- /dev/null
+++
b/juneau-integration-tests/src/test/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpContext_Test.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.rrpc;
+
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.remote.*;
+import org.apache.juneau.marshall.json.*;
+import org.apache.juneau.rest.mock.*;
+import org.apache.juneau.rest.server.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * End-to-end coverage for the {@code @RestOp(method="RRPC")} dispatch path
+ * ({@link RrpcRestOpContext} / {@link RrpcRestOpSession}).
+ *
+ * <p>
+ * Before this file, the only tests referencing these two classes were {@code
RrpcRestOpSession_Test}'s
+ * reflection-only method-signature checks -- no test actually exercised the
feature end-to-end, leaving both
+ * classes at 25-36% coverage per the TEST-04 findings.
+ *
+ * <p>
+ * Covers both halves of the dispatch path: the {@code GET} method-path
listing and the {@code POST}
+ * method-invocation round trip, plus the {@link RrpcRestOpContext}
constructor's zero-remote-method guard.
+ * The {@code POST} side previously 404'd unconditionally at dispatch (a
genuine framework defect in
+ * {@code RrpcRestOpSession.run()}'s method-signature-path extraction, since
fixed).
+ */
+class RrpcRestOpContext_Test extends TestBase {
+
+ @Remote
+ public interface Calculator {
+ int add(int a, int b);
+ String greet(String name);
+ }
+
+ public static class CalculatorImpl implements Calculator {
+ @Override public int add(int a, int b) { return a + b; }
+ @Override public String greet(String name) { return "Hello, " +
name; }
+ }
+
+ @Rest(parsers={JsonParser.class}, serializers={JsonSerializer.class})
+ public static class RrpcResource {
+ @RestOp(method="RRPC", path="/calc/*")
+ public Calculator getCalculator() {
+ return new CalculatorImpl();
+ }
+ }
+
+ @Test void a01_get_listsMethodPaths() throws Exception {
+ try (var client = MockRestClient.create(RrpcResource.class)) {
+ try (var response = client.get("/calc").run()) {
+ assertEquals(200, response.getStatusCode());
+ var body = response.getBodyAsString();
+ assertTrue(body.contains("add"), "Expected
'add' in response: " + body);
+ assertTrue(body.contains("greet"), "Expected
'greet' in response: " + body);
+ }
+ }
+ }
+
+ @Test void a02_get_listsMethodPaths_ignoresPathSuffix() throws
Exception {
+ // The RRPC GET handler always returns the full method list
regardless of any sub-path -- verify this
+ // holds even when a (structurally valid) method-signature
suffix is present in the request path.
+ try (var client = MockRestClient.create(RrpcResource.class)) {
+ try (var response =
client.get("/calc/add/(int,int)").run()) {
+ assertEquals(200, response.getStatusCode());
+ var body = response.getBodyAsString();
+ assertTrue(body.contains("add"));
+ assertTrue(body.contains("greet"));
+ }
+ }
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Interface with no remote methods -- RrpcRestOpContext's
InternalServerError guard
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Remote
+ public interface EmptyInterface { /* No methods -- exercises the "no
remote methods" guard. */ }
+
+ @Rest
+ public static class EmptyRrpcResource {
+ @RestOp(method="RRPC", path="/empty/*")
+ public EmptyInterface getEmpty() {
+ return new EmptyInterface() { /* Anonymous no-op
implementation. */ };
+ }
+ }
+
+ @Test void b01_emptyInterface_failsOnFirstRequest() throws Exception {
+ // RrpcRestOpContext's constructor throws InternalServerError
when the RRPC method's return-type
+ // interface declares zero remote methods. Per-op contexts are
built lazily (on first matching
+ // request), so MockRestClient.create() itself succeeds; the
failure surfaces as a 500 on dispatch.
+ try (var client =
MockRestClient.create(EmptyRrpcResource.class)) {
+ try (var response = client.get("/empty").run()) {
+ assertEquals(500, response.getStatusCode());
+ }
+ }
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // POST -- method invocation round trip
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_post_invokesMethod_withArgs_returnsResult() throws
Exception {
+ try (var client = MockRestClient.create(RrpcResource.class)) {
+ try (var response = client.post("/calc/" +
urlEncode("add/(int,int)"))
+ .header("Content-Type",
"application/json")
+ .bodyString("[3,4]")
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("7", response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test void c02_post_invokesMethod_withStringArg_returnsResult() throws
Exception {
+ try (var client = MockRestClient.create(RrpcResource.class)) {
+ try (var response = client.post("/calc/" +
urlEncode("greet/(java.lang.String)"))
+ .header("Content-Type",
"application/json")
+ .bodyString("[\"world\"]")
+ .run()) {
+ assertEquals(200, response.getStatusCode());
+ assertEquals("\"Hello, world\"",
response.getBodyAsString());
+ }
+ }
+ }
+
+ @Test void c03_post_unknownMethodSignature_returns404() throws
Exception {
+ // A structurally-plausible but unregistered method signature
must still 404 -- confirms the fix
+ // doesn't over-match (e.g. by falling back to the base path or
ignoring the signature entirely).
+ try (var client = MockRestClient.create(RrpcResource.class)) {
+ try (var response = client.post("/calc/" +
urlEncode("subtract/(int,int)"))
+ .header("Content-Type",
"application/json")
+ .bodyString("[3,4]")
+ .run()) {
+ assertEquals(404, response.getStatusCode());
+ }
+ }
+ }
+}
diff --git
a/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/part/HttpPartList_Test.java
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/part/HttpPartList_Test.java
index eb44529f62..a6e5d403f2 100644
---
a/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/part/HttpPartList_Test.java
+++
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/part/HttpPartList_Test.java
@@ -252,4 +252,107 @@ class HttpPartList_Test extends TestBase {
var v = new HttpPartList.Void();
assertTrue(v.isEmpty());
}
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Unmodifiable (D4 collection-mutator-override variant)
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void f01_unmodifiable_isUnmodifiable() {
+ var x = HttpPartList.create().append("a", "1");
+ assertFalse(x.isUnmodifiable());
+ assertTrue(x.unmodifiable().isUnmodifiable());
+ }
+
+ @Test void f02_unmodifiable_idempotent() {
+ var u = HttpPartList.create().append("a", "1").unmodifiable();
+ assertSame(u, u.unmodifiable()); // D1: already unmodifiable ->
returned as-is.
+ }
+
+ @Test void f03_unmodifiable_readsStillWork() {
+ var u = HttpPartList.create().append("a", "1").append("b",
"2").unmodifiable();
+ assertEquals(2, u.size());
+ assertEquals("1", u.getFirst("a").getValue());
+ assertEquals("2", u.getFirst("b").getValue());
+ var count = new AtomicInteger();
+ u.forEach("a", p -> count.incrementAndGet());
+ assertEquals(1, count.get());
+ }
+
+ @Test void f04_unmodifiable_arrayListMutatorsThrow() {
+ var u = HttpPartList.create().append("a", "1").unmodifiable();
+ var p = HttpPartBean.of("b", "2");
+ assertThrows(UnsupportedOperationException.class, () ->
u.add(p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.add(0, p));
+ var pList = List.of(p);
+ assertThrows(UnsupportedOperationException.class, () ->
u.addAll(pList));
+ assertThrows(UnsupportedOperationException.class, () ->
u.addAll(0, pList));
+ assertThrows(UnsupportedOperationException.class, () ->
u.set(0, p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.remove(0));
+ var first = u.get(0);
+ assertThrows(UnsupportedOperationException.class, () ->
u.remove((Object)first));
+ assertThrows(UnsupportedOperationException.class, u::clear);
+ var firstAsList = List.of(first);
+ assertThrows(UnsupportedOperationException.class, () ->
u.removeAll(firstAsList));
+ var emptyList = List.<HttpPart>of();
+ assertThrows(UnsupportedOperationException.class, () ->
u.retainAll(emptyList));
+ assertThrows(UnsupportedOperationException.class, () ->
u.removeIf(x -> true));
+ assertThrows(UnsupportedOperationException.class, () ->
u.replaceAll(x -> x));
+ assertThrows(UnsupportedOperationException.class, () ->
u.sort((a, b) -> 0));
+ }
+
+ @Test void f05_unmodifiable_fluentMutatorsThrow() {
+ var u = HttpPartList.create().append("a", "1").unmodifiable();
+ var p = HttpPartBean.of("b", "2");
+ assertThrows(UnsupportedOperationException.class, () ->
u.append(p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.append(p, p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.append(List.of(p)));
+ assertThrows(UnsupportedOperationException.class, () ->
u.append("c", "3"));
+ assertThrows(UnsupportedOperationException.class, () ->
u.set(p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.set(p, p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.set("c", "3"));
+ assertThrows(UnsupportedOperationException.class, () ->
u.setDefault(p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.setDefault(p, p));
+ assertThrows(UnsupportedOperationException.class, () ->
u.setDefault("c", "3"));
+ assertThrows(UnsupportedOperationException.class, () ->
u.removeAll("a"));
+ assertThrows(UnsupportedOperationException.class, () ->
u.caseInsensitive(true));
+ }
+
+ @Test void f06_unmodifiable_iteratorMutatorsThrow() {
+ var u = HttpPartList.create().append("a", "1").append("b",
"2").unmodifiable();
+ var i = u.iterator();
+ assertTrue(i.hasNext());
+ assertNotNull(i.next());
+ assertThrows(UnsupportedOperationException.class, i::remove);
+
+ var li = u.listIterator();
+ assertTrue(li.hasNext());
+ assertNotNull(li.next());
+ assertThrows(UnsupportedOperationException.class, li::remove);
+ var newPart = HttpPartBean.of("c", "3");
+ assertThrows(UnsupportedOperationException.class, () ->
li.set(newPart));
+ assertThrows(UnsupportedOperationException.class, () ->
li.add(newPart));
+
+ var li2 = u.listIterator(1);
+ assertTrue(li2.hasPrevious());
+ assertEquals(0, li2.previousIndex());
+ assertEquals(1, li2.nextIndex());
+ assertNotNull(li2.previous());
+ }
+
+ @Test void f07_unmodifiable_contentEquality() {
+ var x = HttpPartList.create().append("a", "1").append("b", "2");
+ var u = x.unmodifiable();
+ // D3 content-only equality: a modifiable list equals its
frozen snapshot (and vice versa).
+ assertEquals(x, u);
+ assertEquals(u, x);
+ assertEquals(x.hashCode(), u.hashCode());
+ }
+
+ @Test void f08_unmodifiable_snapshotIndependence() {
+ var x = HttpPartList.create().append("a", "1");
+ var u = x.unmodifiable();
+ x.append("b", "2"); // Mutate original after snapshotting.
+ assertEquals(1, u.size());
+ assertEquals("a", u.get(0).getName());
+ }
}
diff --git
a/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
index 2f0263ba07..2e02ee65c9 100644
---
a/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
+++
b/juneau-rest/juneau-rest-common/src/test/java/org/apache/juneau/http/remote/RrpcInterfaceMethodMeta_Test.java
@@ -37,4 +37,109 @@ class RrpcInterfaceMethodMeta_Test extends TestBase {
assertNull(meta.getFormDataDefault(null));
assertNull(meta.getPathDefault(null));
}
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Policy — 0% branch: null-defensive compact constructor,
defensive-copy accessor, equals()/hashCode()/toString()
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void b01_policy_none_isEmpty() {
+ var p = RrpcInterfaceMethodMeta.Policy.NONE;
+ assertEquals(0, p.interceptors().length);
+ assertEquals("", p.timeout());
+ assertEquals(0, p.retries());
+ assertFalse(p.retryNonIdempotent());
+ assertFalse(p.throwOnError());
+ }
+
+ @Test void
b02_policy_compactConstructor_nullInterceptorsDefaultsToEmptyArray() {
+ var p = new RrpcInterfaceMethodMeta.Policy(null, "5s", 3, true,
true);
+ assertEquals(0, p.interceptors().length);
+ }
+
+ @Test void b03_policy_compactConstructor_clonesInterceptorsArray() {
+ var arr = new Class<?>[]{String.class};
+ var p = new RrpcInterfaceMethodMeta.Policy(arr, "", 0, false,
false);
+ arr[0] = Integer.class; // Mutate caller's array after
construction.
+ assertEquals(String.class, p.interceptors()[0]);
+ }
+
+ @Test void b04_policy_interceptors_accessorReturnsDefensiveCopy() {
+ var p = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{String.class}, "", 0, false, false);
+ var a = p.interceptors();
+ a[0] = Integer.class; // Mutate the returned array.
+ assertEquals(String.class, p.interceptors()[0]); // Internal
state unaffected.
+ }
+
+ @Test void b05_policy_equals_reflexive() {
+ var p = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{String.class}, "5s", 3, true, true);
+ assertEquals(p, p);
+ }
+
+ @Test void b06_policy_equals_wrongType() {
+ var p = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0], "",
0, false, false);
+ assertNotEquals(p, "not a policy");
+ }
+
+ @Test void b07_policy_equals_sameValues() {
+ var p1 = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{String.class}, "5s", 3, true, true);
+ var p2 = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{String.class}, "5s", 3, true, true);
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ @Test void b08_policy_equals_differentInterceptors() {
+ var p1 = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{String.class}, "5s", 3, true, true);
+ var p2 = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{Integer.class}, "5s", 3, true, true);
+ assertNotEquals(p1, p2);
+ }
+
+ @Test void b09_policy_equals_differentTimeout() {
+ var p1 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"5s", 3, true, true);
+ var p2 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"10s", 3, true, true);
+ assertNotEquals(p1, p2);
+ }
+
+ @Test void b10_policy_equals_differentRetries() {
+ var p1 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"", 3, true, true);
+ var p2 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"", 4, true, true);
+ assertNotEquals(p1, p2);
+ }
+
+ @Test void b11_policy_equals_differentRetryNonIdempotent() {
+ var p1 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"", 0, true, true);
+ var p2 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"", 0, false, true);
+ assertNotEquals(p1, p2);
+ }
+
+ @Test void b12_policy_equals_differentThrowOnError() {
+ var p1 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"", 0, false, true);
+ var p2 = new RrpcInterfaceMethodMeta.Policy(new Class<?>[0],
"", 0, false, false);
+ assertNotEquals(p1, p2);
+ }
+
+ @Test void b13_policy_toString() {
+ var p = new RrpcInterfaceMethodMeta.Policy(new
Class<?>[]{String.class}, "5s", 3, true, false);
+ var s = p.toString();
+ assertTrue(s.contains("interceptors=[class java.lang.String]"));
+ assertTrue(s.contains("timeout=5s"));
+ assertTrue(s.contains("retries=3"));
+ assertTrue(s.contains("retryNonIdempotent=true"));
+ assertTrue(s.contains("throwOnError=false"));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // ContentNegotiation
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_contentNegotiation_none_isEmpty() {
+ var n = RrpcInterfaceMethodMeta.ContentNegotiation.NONE;
+ assertEquals("", n.accept());
+ assertEquals("", n.contentType());
+ }
+
+ @Test void c02_contentNegotiation_accessors() {
+ var n = new
RrpcInterfaceMethodMeta.ContentNegotiation("application/json",
"application/xml");
+ assertEquals("application/json", n.accept());
+ assertEquals("application/xml", n.contentType());
+ }
}
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpSession.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpSession.java
index 9911d90436..69c124a09c 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpSession.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/rrpc/RrpcRestOpSession.java
@@ -129,10 +129,13 @@ public class RrpcRestOpSession extends RestOpSession {
return;
} else if ("POST".equals(session.getMethod())) {
- var pip = session.getUrlPath().getPath();
- if (pip.indexOf('/') != -1)
- pip = pip.substring(pip.lastIndexOf('/') + 1);
- pip = urlDecode(pip);
+ // The method-signature path is whatever remains of the
URL below this operation's matched path
+ // pattern (e.g. pattern "/calc/*" matched against
"/calc/add/(int,int)" leaves "add/(int,int)"),
+ // NOT merely the last '/'-delimited segment of the
full request path. A method-signature path
+ // itself contains a '/' (name/(paramTypes)), so
truncating at the last slash strips the method
+ // name and leaves only the parameter-type token, which
can never match a registered method path.
+ var match = session.getUrlPathMatch();
+ var pip = urlDecode(emptyIfNull(nn(match) ?
match.getRemainder() : null));
RrpcInterfaceMethodMeta rmm =
ctx.getMeta().getMethodMetaByPath(pip);
if (nn(rmm)) {
Method m = rmm.getJavaMethod();
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/RestArgResolvers_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/RestArgResolvers_Test.java
index e7210fa966..66393a0e44 100644
---
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/RestArgResolvers_Test.java
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/arg/RestArgResolvers_Test.java
@@ -925,4 +925,18 @@ class RestArgResolvers_Test extends TestBase {
@Test void z11_restContextArgs_create_matchesThrownStore() {
assertNotNull(RestContextArgs.create(firstParam(Fixture.class,
"withThrownStore")));
}
+
+ //
-----------------------------------------------------------------------------------------
+ // aa — DefaultArg
+ //
-----------------------------------------------------------------------------------------
+
+ @Test void aa01_defaultArg_create_neverReturnsNull() {
+ // DefaultArg is the fallback resolver -- unlike every other
*Arg.create() above, it has no
+ // annotation/type gate and always succeeds (its "no other
resolver matched" gate lives one
+ // layer up, in the resolver-selection pipeline that calls this
as a last resort).
+ var pi = firstParam(Fixture.class, "noAnnotation");
+ var arg = DefaultArg.create(pi);
+ assertNotNull(arg);
+ assertInstanceOf(RestOpArg.class, arg);
+ }
}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/beans/Hyperlink_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/beans/Hyperlink_Test.java
new file mode 100644
index 0000000000..1a0f72fd2e
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/beans/Hyperlink_Test.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.beans;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link Hyperlink}.
+ *
+ * <p>
+ * {@link Hyperlink} is a real, reachable public API bean (see e.g. {@code
juneau-petstore}'s
+ * {@code PetInfoResource} and {@code Hyperlink_Test} in {@code
juneau-integration-tests}, which exercises it
+ * end-to-end via {@code MockRestClient}). This module ({@code
juneau-rest-server}) had no test of its own
+ * exercising the ~60 covariant-override fluent setters declared directly on
{@link Hyperlink}, so this class's
+ * own coverage was near-0% even though the type is actively used.
+ */
+class Hyperlink_Test extends TestBase {
+
+ @Test void a01_emptyBean() {
+ assertEquals("<a></a>", new Hyperlink().toString());
+ }
+
+ @Test void a02_hrefAndChildrenConstructor() {
+ assertEquals("<a href='foo'>bar</a>", new Hyperlink("foo",
"bar").toString());
+ }
+
+ @Test void a03_staticCreator() {
+ assertEquals("<a href='foo'>bar</a>", Hyperlink.create("foo",
"bar").toString());
+ }
+
+ @Test void a04_allFluentSetters() {
+ Hyperlink x = new Hyperlink()
+ .class_("a")
+ .accesskey("b")
+ .attr("data-foo", "c")
+ .attrUri("data-bar", "d")
+ .child("e1")
+ .children("e2", "e3")
+ .contenteditable("f")
+ .dir("g")
+ .download("h")
+ .hidden("i")
+ .href("j")
+ .hreflang("k")
+ .id("l")
+ .lang("m")
+ .onabort("n")
+ .onblur("o")
+ .oncancel("p")
+ .oncanplay("q")
+ .oncanplaythrough("r")
+ .onchange("s")
+ .onclick("t")
+ .oncuechange("u")
+ .ondblclick("v")
+ .ondurationchange("w")
+ .onemptied("x")
+ .onended("y")
+ .onerror("z")
+ .onfocus("aa")
+ .oninput("ab")
+ .oninvalid("ac")
+ .onkeydown("ad")
+ .onkeypress("ae")
+ .onkeyup("af")
+ .onload("ag")
+ .onloadeddata("ah")
+ .onloadedmetadata("ai")
+ .onloadstart("aj")
+ .onmousedown("ak")
+ .onmouseenter("al")
+ .onmouseleave("am")
+ .onmousemove("an")
+ .onmouseout("ao")
+ .onmouseover("ap")
+ .onmouseup("aq")
+ .onmousewheel("ar")
+ .onpause("as")
+ .onplay("at")
+ .onplaying("au")
+ .onprogress("av")
+ .onratechange("aw")
+ .onreset("ax")
+ .onresize("ay")
+ .onscroll("az")
+ .onseeked("ba")
+ .onseeking("bb")
+ .onselect("bc")
+ .onshow("bd")
+ .onstalled("be")
+ .onsubmit("bf")
+ .onsuspend("bg")
+ .ontimeupdate("bh")
+ .ontoggle("bi")
+ .onvolumechange("bj")
+ .onwaiting("bk")
+ .rel("bl")
+ .spellcheck("bm")
+ .style("bn")
+ .tabindex("bo")
+ .target("bp")
+ .title("bq")
+ .translate("br")
+ .type("bs");
+
+ assertEquals(
+ "<a class='a' accesskey='b' data-foo='c' data-bar='d'
contenteditable='f' dir='g' download='h' hidden='i' href='j' hreflang='k'
id='l' lang='m' onabort='n' onblur='o' oncancel='p' oncanplay='q'
oncanplaythrough='r' onchange='s' onclick='t' oncuechange='u' ondblclick='v'
ondurationchange='w' onemptied='x' onended='y' onerror='z' onfocus='aa'
oninput='ab' oninvalid='ac' onkeydown='ad' onkeypress='ae' onkeyup='af'
onload='ag' onloadeddata='ah' onloadedmetadata='ai' onloadstart='aj' onmou [...]
+ x.toString()
+ );
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/BasicNamedAttribute_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/BasicNamedAttribute_Test.java
new file mode 100644
index 0000000000..bb91bc75f2
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/BasicNamedAttribute_Test.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.httppart;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link BasicNamedAttribute}.
+ *
+ * <p>
+ * This class is entirely self-contained (no {@code RestRequest} dependency),
so unlike the other
+ * {@code httppart} classes in this package it can be fully unit tested
without any REST-session machinery.
+ */
+class BasicNamedAttribute_Test {
+
+ @Test void a01_of_value() {
+ var a = BasicNamedAttribute.of("foo", "bar");
+ assertEquals("foo", a.getName());
+ assertEquals("bar", a.getValue());
+ }
+
+ @Test void a02_of_supplier() {
+ var a = BasicNamedAttribute.of("foo", () -> "bar");
+ assertEquals("bar", a.getValue());
+ }
+
+ @Test void a03_of_supplier_reEvaluatedEachCall() {
+ var counter = new int[]{0};
+ var a = BasicNamedAttribute.of("foo",
(java.util.function.Supplier<Object>) () -> ++counter[0]);
+ assertEquals(1, a.getValue());
+ assertEquals(2, a.getValue());
+ }
+
+ @Test void a04_constructor() {
+ var a = new BasicNamedAttribute("foo", "bar");
+ assertEquals("foo", a.getName());
+ assertEquals("bar", a.getValue());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // ofPair
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void b01_ofPair_null() {
+ assertNull(BasicNamedAttribute.ofPair(null));
+ }
+
+ @Test void b02_ofPair_colon() {
+ var a = BasicNamedAttribute.ofPair("foo: bar");
+ assertEquals("foo", a.getName());
+ assertEquals("bar", a.getValue());
+ }
+
+ @Test void b03_ofPair_equals() {
+ var a = BasicNamedAttribute.ofPair("foo=bar");
+ assertEquals("foo", a.getName());
+ assertEquals("bar", a.getValue());
+ }
+
+ @Test void b04_ofPair_noSeparator() {
+ var a = BasicNamedAttribute.ofPair("justfoo");
+ assertEquals("justfoo", a.getName());
+ assertEquals("", a.getValue());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // get / isPresent / orElse
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_get_present() {
+ assertEquals("bar", BasicNamedAttribute.of("foo", "bar").get());
+ }
+
+ @Test void c02_get_absent_throws() {
+ assertThrows(NoSuchElementException.class, () ->
BasicNamedAttribute.of("foo", (Object)null).get());
+ }
+
+ @Test void c03_isPresent() {
+ assertTrue(BasicNamedAttribute.of("foo", "bar").isPresent());
+ assertFalse(BasicNamedAttribute.of("foo",
(Object)null).isPresent());
+ }
+
+ @Test void c04_orElse() {
+ assertEquals("bar", BasicNamedAttribute.of("foo",
"bar").orElse("def"));
+ assertEquals("def", BasicNamedAttribute.of("foo",
(Object)null).orElse("def"));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Assertions / toString
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void d01_assertName() {
+ BasicNamedAttribute.of("foo", "bar").assertName().is("foo");
+ }
+
+ @Test void d02_assertValue() {
+ BasicNamedAttribute.of("foo", "bar").assertValue().is("bar");
+ }
+
+ @Test void d03_toString() {
+ assertEquals("foo=bar", BasicNamedAttribute.of("foo",
"bar").toString());
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestFormParam_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestFormParam_Test.java
new file mode 100644
index 0000000000..3d925ad19f
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestFormParam_Test.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.httppart;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.io.*;
+
+import org.apache.juneau.commons.utils.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link RequestFormParam}.
+ *
+ * <p>
+ * The {@code (RestRequest, String, String)} constructor sets the backing
{@code jakarta.servlet.http.Part} to
+ * {@code null}; every method exercised below (mirroring {@link
RequestHttpPart_Test}'s null-request approach)
+ * is safe under that constructor because a non-null {@code value}
short-circuits before the {@code part} field
+ * is ever dereferenced. The {@code part}-backed constructor and its
exclusively part-driven methods
+ * ({@code getContentType()}, {@code getHeader(String)}, {@code getSize()},
{@code getSubmittedFileName()}, and
+ * the {@code getValue()}/{@code getStream()} lazy-read-from-part branches)
require a real multipart request and
+ * are left to the higher-level {@code MockRestClient} integration tests per
this module's established scope.
+ */
+class RequestFormParam_Test {
+
+ private static RequestFormParam formParam(String name, String value) {
+ return new RequestFormParam(null, name, value);
+ }
+
+ @Test void a01_getNameAndValue() {
+ var p = formParam("foo", "bar");
+ assertEquals("foo", p.getName());
+ assertEquals("bar", p.getValue());
+ }
+
+ @Test void a02_getContentType_nullWhenNoBackingPart() {
+ assertNull(formParam("foo", "bar").getContentType());
+ }
+
+ @Test void a03_getStream_readsFromValueWhenPresent() throws IOException
{
+ var p = formParam("foo", "bar");
+ assertEquals("bar", IoUtils.read(p.getStream()));
+ }
+
+ @Test void a04_def() {
+ assertEquals("bar", formParam("foo",
"bar").def("def").getValue());
+ assertEquals("def", formParam("foo",
null).def("def").getValue());
+ }
+
+ @Test void a05_parser() {
+ assertNotNull(formParam("foo", "bar").parser(null));
+ }
+
+ @Test void a06_schema() {
+ assertNotNull(formParam("foo", "bar").schema(null));
+ }
+
+ @Test void a07_asString() {
+ assertEquals("bar", formParam("foo", "bar").asString().get());
+ }
+
+ @Test void a08_isPresent() {
+ assertTrue(formParam("foo", "bar").isPresent());
+ assertFalse(formParam("foo", null).isPresent());
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestHeader_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestHeader_Test.java
new file mode 100644
index 0000000000..dba05f0758
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestHeader_Test.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.httppart;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link RequestHeader}.
+ *
+ * <p>
+ * None of the {@code as*Header()} convenience methods or the fluent-override
setters touch the {@code request}
+ * field, so (mirroring {@link RequestHttpPart_Test}) this file constructs
{@link RequestHeader} directly with a
+ * {@code null} request -- consistent with this module's exclusion of {@code
MockRestClient} from its test scope.
+ */
+class RequestHeader_Test {
+
+ private static RequestHeader header(String name, String value) {
+ return new RequestHeader(null, name, value);
+ }
+
+ @Test void a01_asBooleanHeader() {
+ assertNotNull(header("X-Flag", "true").asBooleanHeader());
+ }
+
+ @Test void a02_asCsvHeader() {
+ assertNotNull(header("X-List", "a,b").asCsvHeader());
+ }
+
+ @Test void a03_asDateHeader() {
+ assertNotNull(header("X-Date", null).asDateHeader());
+ }
+
+ @Test void a04_asEntityTagHeader() {
+ assertNotNull(header("ETag", "\"abc\"").asEntityTagHeader());
+ }
+
+ @Test void a05_asEntityTagsHeader() {
+ assertNotNull(header("If-Match",
"\"abc\"").asEntityTagsHeader());
+ }
+
+ @Test void a06_asIntegerHeader() {
+ assertNotNull(header("X-Count", "5").asIntegerHeader());
+ }
+
+ @Test void a07_asLongHeader() {
+ assertNotNull(header("X-Size", "5").asLongHeader());
+ }
+
+ @Test void a08_asStringHeader() {
+ assertNotNull(header("X-Foo", "bar").asStringHeader());
+ }
+
+ @Test void a09_asStringRangesHeader() {
+ assertNotNull(header("Accept",
"text/plain").asStringRangesHeader());
+ }
+
+ @Test void a10_asUriHeader() {
+ assertNotNull(header("Location",
"http://example.com").asUriHeader());
+ }
+
+ @Test void b01_def() {
+ assertEquals("def", header("X-Foo",
null).def("def").getValue());
+ assertEquals("bar", header("X-Foo",
"bar").def("def").getValue());
+ }
+
+ @Test void b02_parser() {
+ assertNotNull(header("X-Foo", "bar").parser(null));
+ }
+
+ @Test void b03_schema() {
+ assertNotNull(header("X-Foo", "bar").schema(null));
+ }
+
+ @Test void c01_toString() {
+ assertEquals("X-Foo: bar", header("X-Foo", "bar").toString());
+ }
+}
diff --git
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestHttpPart_Test.java
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestHttpPart_Test.java
new file mode 100644
index 0000000000..a3beb390f1
--- /dev/null
+++
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/httppart/RequestHttpPart_Test.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.server.httppart;
+
+import static org.apache.juneau.commons.httppart.HttpPartType.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.regex.*;
+
+import org.apache.juneau.marshall.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link RequestHttpPart}.
+ *
+ * <p>
+ * Most of this class's logic (name/value access, the {@code as*()} conversion
family, fluent assertions)
+ * operates purely off the part's own {@code name}/{@code value} state and
does not touch the {@code request}
+ * field at all -- only {@link RequestHttpPart#as(Class)} and {@link
RequestHttpPart#as(java.lang.reflect.Type,
+ * java.lang.reflect.Type...)} dereference it (via {@code
request.getMarshallingSession()}). This module
+ * intentionally excludes {@code MockRestClient} from its test scope (see
{@code RestArgResolvers_Test}), so this
+ * file constructs {@link RequestHttpPart} directly with a {@code null}
request and exercises every method that
+ * doesn't require one, leaving the two {@code request}-dependent overloads to
the higher-level integration tests.
+ */
+class RequestHttpPart_Test {
+
+ private static RequestHttpPart part(String name, String value) {
+ return new RequestHttpPart(HEADER, null, name, value);
+ }
+
+ @Test void a01_getNameAndValue() {
+ var p = part("foo", "bar");
+ assertEquals("foo", p.getName());
+ assertEquals("bar", p.getValue());
+ }
+
+ @Test void a02_isPresent() {
+ assertTrue(part("foo", "bar").isPresent());
+ assertFalse(part("foo", null).isPresent());
+ }
+
+ @Test void a03_orElse() {
+ assertEquals("bar", part("foo", "bar").orElse("def"));
+ assertEquals("def", part("foo", null).orElse("def"));
+ }
+
+ @Test void a04_get() {
+ assertEquals("bar", part("foo", "bar").get());
+ assertThrows(NoSuchElementException.class, () -> part("foo",
null).get());
+ }
+
+ @Test void a05_def_onlyAppliesWhenValueAbsent() {
+ assertEquals("bar", part("foo", "bar").def("def").getValue());
+ assertEquals("def", part("foo", null).def("def").getValue());
+ }
+
+ @Test void a06_asString() {
+ assertEquals("bar", part("foo", "bar").asString().get());
+ assertTrue(part("foo", null).asString().isEmpty());
+ }
+
+ @Test void a07_asStringPart() {
+ var sp = part("foo", "bar").asStringPart();
+ assertEquals("foo", sp.getName());
+ assertEquals("bar", sp.getValue());
+ }
+
+ @Test void a08_asUriPart() {
+ assertNotNull(part("foo", "http://example.com").asUriPart());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Boolean
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void b01_asBoolean() {
+ assertTrue(part("foo", "true").asBoolean().get());
+ assertTrue(part("foo", null).asBoolean().isEmpty());
+ }
+
+ @Test void b02_asBooleanPart() {
+ assertNotNull(part("foo", "true").asBooleanPart());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // CSV array
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void c01_asCsvArray() {
+ assertEquals(List.of("a", "b", "c"), part("foo",
"a,b,c").asCsvArray().get());
+ }
+
+ @Test void c02_asCsvArrayPart() {
+ assertNotNull(part("foo", "a,b").asCsvArrayPart());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Date
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void d01_asDate() {
+ assertTrue(part("foo", null).asDate().isEmpty());
+ }
+
+ @Test void d02_asDatePart() {
+ assertNotNull(part("foo", null).asDatePart());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Integer / Long
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void e01_asInteger() {
+ assertEquals(123, part("foo", "123").asInteger().get());
+ }
+
+ @Test void e02_asIntegerPart() {
+ assertNotNull(part("foo", "123").asIntegerPart());
+ }
+
+ @Test void e03_asLong() {
+ assertEquals(123L, part("foo", "123").asLong().get());
+ }
+
+ @Test void e04_asLongPart() {
+ assertNotNull(part("foo", "123").asLongPart());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Matcher
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void f01_asMatcher_pattern() {
+ var m = part("foo",
"bar123").asMatcher(Pattern.compile("[a-z]+(\\d+)"));
+ assertTrue(m.matches());
+ assertEquals("123", m.group(1));
+ }
+
+ @Test void f02_asMatcher_regex() {
+ assertTrue(part("foo", "bar").asMatcher("b.r").matches());
+ }
+
+ @Test void f03_asMatcher_regexAndFlags() {
+ assertTrue(part("foo", "BAR").asMatcher("bar",
Pattern.CASE_INSENSITIVE).matches());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // Assertions
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void g01_assertCsvArray() {
+ part("foo", "a,b").assertCsvArray().isHas("a", "b");
+ }
+
+ @Test void g02_assertDate() {
+ assertNotNull(part("foo", null).assertDate());
+ }
+
+ @Test void g03_assertInteger() {
+ part("foo", "5").assertInteger().isGt(1);
+ }
+
+ @Test void g04_assertLong() {
+ part("foo", "5").assertLong().isLt(100L);
+ }
+
+ @Test void g05_assertString() {
+ part("foo", "bar").assertString().isContains("ar");
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // as(ClassMeta) -- doesn't touch request at all
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void h01_asClassMeta() throws Exception {
+ var cm = MarshallingContext.DEFAULT.getClassMeta(String.class);
+ assertEquals("bar", part("foo", "bar").as(cm).get());
+ }
+
+ @Test void h02_asClassMeta_notPresent() throws Exception {
+ var cm = MarshallingContext.DEFAULT.getClassMeta(String.class);
+ assertTrue(part("foo", null).as(cm).isEmpty());
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // parser() / schema()
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void i01_parser_nullResetsToDefault() {
+ var p = part("foo", "bar").parser(null);
+ assertNotNull(p);
+ }
+
+ @Test void i02_schema() {
+ assertNotNull(part("foo", "bar").schema(null));
+ }
+
+
//------------------------------------------------------------------------------------------------------------------
+ // equals() / hashCode() / toString()
+
//------------------------------------------------------------------------------------------------------------------
+
+ @Test void j01_equals_httpPart() {
+ var p = part("foo", "bar");
+ assertEquals(p,
org.apache.juneau.http.part.HttpPartBean.of("foo", "bar"));
+ assertNotEquals(p,
org.apache.juneau.http.part.HttpPartBean.of("foo", "baz"));
+ assertNotEquals(p, "not a part");
+ }
+
+ @Test void j02_equals_httpHeader() {
+ var p = part("foo", "bar");
+ assertEquals(p,
org.apache.juneau.http.header.HttpStringHeader.of("foo", "bar"));
+ }
+
+ @Test void j03_hashCode() {
+ assertEquals(part("foo", "bar").hashCode(), part("foo",
"bar").hashCode());
+ }
+
+ @Test void j04_toString() {
+ assertEquals("foo=bar", part("foo", "bar").toString());
+ }
+
+ @Test void k01_getRequest_null() {
+ assertNull(part("foo", "bar").getRequest());
+ }
+}