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
commit 96b51db18a72389fc5f56c0d2ec764dbcdb6221f Author: James Bognar <[email protected]> AuthorDate: Tue Jul 28 18:21:40 2026 -0400 test: cover the capabilities hook's auto-derive and override paths on both integration paths Co-authored-by: Cursor <[email protected]> --- .../mcp/v20250618/McpCapabilityHook_Test.java | 133 +++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpCapabilityHook_Test.java b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpCapabilityHook_Test.java new file mode 100644 index 0000000000..13e0e78588 --- /dev/null +++ b/juneau-rest/juneau-rest-server-mcp-2025-06-18/src/test/java/org/apache/juneau/rest/server/mcp/v20250618/McpCapabilityHook_Test.java @@ -0,0 +1,133 @@ +/* + * 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.mcp.v20250618; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; + +import org.apache.juneau.*; +import org.apache.juneau.bean.mcp.v20250618.*; +import org.apache.juneau.commons.inject.*; +import org.apache.juneau.marshall.collections.*; +import org.apache.juneau.marshall.json.*; +import org.apache.juneau.rest.mock.classic.*; +import org.apache.juneau.rest.server.*; +import org.apache.juneau.rest.server.mcp.*; +import org.apache.juneau.rest.server.servlet.*; +import org.junit.jupiter.api.*; + +/** + * Coverage for the explicit-capabilities hook on both integration paths. + * + * <p> + * The hook replaces the pre-re-layering {@code McpServerConfig.setCapabilities(...)} setter. Both of + * its states are real, distinct code paths: a {@code null} return auto-derives from the handler + * registry (unchanged behavior), and a non-{@code null} return is advertised as-is. + */ +@SuppressWarnings({ + "resource" // MockRestClient is a Closeable test helper; lifetime is bounded by the test method. +}) +class McpCapabilityHook_Test extends TestBase { + + private static McpToolHandler tool() { + return new McpToolHandler() { + @Override public McpToolSpec descriptor() { return new McpToolSpec().setName("t"); } + @Override public McpToolOutcome call(Map<String,Object> arguments, BeanStore ctx) { return new McpToolOutcome(); } + }; + } + + private static ServerCapabilities explicit() { + return new ServerCapabilities() + .setLogging(new LoggingCapability().setLevel("debug")) + .setResources(new ResourceCapability().setSubscribe(true)) + .setExperimental(JsonMap.of("x", 1)); + } + + @Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json") + public static class A_ServletDefault extends McpRestServlet20250618 { + private static final long serialVersionUID = 1L; + @Override protected McpServerConfig createMcpConfig() { return new McpServerConfig().addTool(tool()); } + } + + @Rest(serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json") + public static class A_ServletOverride extends McpRestServlet20250618 { + private static final long serialVersionUID = 1L; + @Override protected McpServerConfig createMcpConfig() { return new McpServerConfig().addTool(tool()); } + @Override protected ServerCapabilities capabilities() { return explicit(); } + } + + @Rest(path = "/api", serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json") + @org.apache.juneau.marshall.serializer.SerializerConfig(addBeanTypes = "true") + public static class B_MixinDefault extends BasicRestServlet implements McpEndpoint20250618 { + private static final long serialVersionUID = 1L; + @Override public McpServerConfig getMcpConfig() { return new McpServerConfig().addTool(tool()); } + } + + @Rest(path = "/api", serializers = JsonSerializer.class, parsers = JsonParser.class, defaultAccept = "application/json") + @org.apache.juneau.marshall.serializer.SerializerConfig(addBeanTypes = "true") + public static class B_MixinOverride extends BasicRestServlet implements McpEndpoint20250618 { + private static final long serialVersionUID = 1L; + @Override public McpServerConfig getMcpConfig() { return new McpServerConfig().addTool(tool()); } + // public here is required by the JLS (an interface method, default or not, is always public; + // an overriding class member cannot narrow that) — it is not a departure from C8's "no public + // hook" ruling, which is about McpRevision never having cross-package access to the hook. + @Override public ServerCapabilities capabilities() { return explicit(); } + } + + @Test + void a01_servlet_defaultHook_autoDerives() throws Exception { + var c = MockRestClient.create(A_ServletDefault.class).json().contentType("application/json").accept("application/json").build(); + var body = c.post("/").contentString("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"}").run().assertStatus(200).getContent().asString(); + assertTrue(body.contains("\"tools\":{}"), body); + assertFalse(body.contains("logging"), body); + assertFalse(body.contains("subscribe"), body); + } + + @Test + void a02_servlet_overriddenHook_bypassesAutoDerivation() throws Exception { + var c = MockRestClient.create(A_ServletOverride.class).json().contentType("application/json").accept("application/json").build(); + var body = c.post("/").contentString("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"}").run().assertStatus(200).getContent().asString(); + assertTrue(body.contains("\"level\":\"debug\""), body); + assertTrue(body.contains("\"subscribe\":true"), body); + assertTrue(body.contains("\"x\":1"), body); + assertFalse(body.contains("\"tools\":{}"), "an explicit override must bypass auto-derivation entirely: " + body); + } + + @Test + void b01_mixin_defaultHook_autoDerives() throws Exception { + var c = MockRestClient.create(B_MixinDefault.class).json().contentType("application/json").accept("application/json").build(); + var body = c.post("/mcp").contentString("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"}").run().assertStatus(200).getContent().asString(); + assertTrue(body.contains("\"tools\":{}"), body); + assertFalse(body.contains("logging"), body); + } + + @Test + void b02_mixin_overriddenHook_bypassesAutoDerivation() throws Exception { + var c = MockRestClient.create(B_MixinOverride.class).json().contentType("application/json").accept("application/json").build(); + var body = c.post("/mcp").contentString("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\"}").run().assertStatus(200).getContent().asString(); + assertTrue(body.contains("\"level\":\"debug\""), body); + assertTrue(body.contains("\"subscribe\":true"), body); + assertFalse(body.contains("\"tools\":{}"), body); + } + + @Test + void c01_defaultHookReturnsNullOnBothPaths() { + assertNull(new A_ServletDefault().capabilities()); + assertNull(new B_MixinDefault().capabilities()); + } +}
