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());
+       }
+}

Reply via email to