This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new c3e408edf15 Reduce duplicated MCP E2E coverage (#38809)
c3e408edf15 is described below
commit c3e408edf15c4262ab231d4fca73c1d27233c0cf
Author: Liang Zhang <[email protected]>
AuthorDate: Fri Jun 5 10:31:51 2026 +0800
Reduce duplicated MCP E2E coverage (#38809)
Add focused harness unit tests for deterministic MCP payload,
model-contract, and baseline assertion behavior.
Trim programmatic MCP E2E tests that duplicated unit-covered
handler, protocol, recovery, metadata, and contract branches while
keeping runtime boundary coverage for HTTP, sessions, Docker-backed
metadata/SQL, workflow state, and redaction behavior.
Document the test-layering decision with speckit spec, plan, tasks,
and verification notes.
---
.../ExecuteQueryTransactionE2ETest.java | 111 -----------------
.../HttpTransportApprovalSafetyE2ETest.java | 49 +-------
.../programmatic/HttpTransportContractE2ETest.java | 62 ----------
.../HttpTransportProtocolContractE2ETest.java | 43 -------
.../programmatic/HttpTransportRecoveryE2ETest.java | 40 ------
.../programmatic/HttpTransportSecurityE2ETest.java | 16 ---
.../programmatic/MetadataDiscoveryE2ETest.java | 86 +------------
.../MCPBaselineContractAssertionsTest.java | 42 +++++++
.../assertion/MCPModelContractAssertionsTest.java | 69 +++++++++++
.../transport/MCPInteractionPayloadsTest.java | 135 +++++++++++++++++++++
.../assertion/normalized-plan-id.yaml | 21 ++++
11 files changed, 272 insertions(+), 402 deletions(-)
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
index d3504efa8a8..447668339d2 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/ExecuteQueryTransactionE2ETest.java
@@ -20,20 +20,15 @@ package
org.apache.shardingsphere.test.e2e.mcp.runtime.programmatic;
import org.apache.shardingsphere.test.e2e.mcp.env.MCPE2ECondition;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;
-import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertFalse;
@EnabledIf("isEnabled")
class ExecuteQueryTransactionE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
@@ -67,21 +62,6 @@ class ExecuteQueryTransactionE2ETest extends
AbstractHttpProgrammaticRuntimeE2ET
assertThat(String.valueOf(payload.get("statement_class")),
is("query"));
}
- @Test
- void assertRecoverSideEffectQuery() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_query",
- createExecuteQueryArguments("logic_db", "logic_db", "UPDATE
orders SET status = status WHERE order_id = -1"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(payload,
"unsafe_sql");
- Map<String, Object> nextAction =
castToMapList(recovery.get("next_actions")).get(0);
- assertThat(String.valueOf(nextAction.get("tool_name")),
is("database_gateway_execute_update"));
-
assertThat(String.valueOf(castToMap(nextAction.get("arguments")).get("execution_mode")),
is("preview"));
- }
-
@Test
void assertRejectCrossDatabaseTransactionSwitch() throws IOException,
InterruptedException {
launchHttpTransport();
@@ -127,87 +107,6 @@ class ExecuteQueryTransactionE2ETest extends
AbstractHttpProgrammaticRuntimeE2ET
assertThat(String.valueOf(((List<?>) ((List<?>)
payload.get("rows")).get(0)).get(0)), is("NEW"));
}
- @ParameterizedTest(name = "{0}")
- @MethodSource("assertPreviewSideEffectStatementCases")
- void assertPreviewSideEffectStatement(final String name, final String sql,
final String expectedStatementClass,
- final String expectedStatementType)
throws IOException, InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
sql, "execution_mode", "preview"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- assertThat(String.valueOf(payload.get("result_kind")), is("preview"));
- assertThat(String.valueOf(payload.get("statement_class")),
is(expectedStatementClass));
- assertThat(String.valueOf(payload.get("statement_type")),
is(expectedStatementType));
- assertThat(payload.get("side_effect_scope"),
is(List.of("transaction-state")));
- assertFalse((Boolean) payload.get("would_execute"));
- }
-
- @Test
- void assertExecuteSingleStatementValidation() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_query",
- createExecuteQueryArguments("logic_db", "logic_db", "SELECT 1;
SELECT 2"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(payload,
"unsafe_sql");
- assertThat(String.valueOf(recovery.get("category")),
is("multiple_sql_statements"));
- }
-
- @Test
- void assertRejectMetadataIntrospectionSql() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_query",
- createExecuteQueryArguments("logic_db", "logic_db", "SHOW
TABLES"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(payload,
"validation");
- assertThat(String.valueOf(recovery.get("category")),
is("metadata_introspection_sql"));
- }
-
- @Test
- void assertRejectBannedSql() throws IOException, InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_query",
- createExecuteQueryArguments("logic_db", "logic_db", "USE
logic_db"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(payload,
"terminal_operator_action");
- assertThat(String.valueOf(recovery.get("category")),
is("banned_sql_statement"));
- }
-
- @Test
- void assertRejectMissingRequiredSqlArgument() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_query", Map.of("database", "logic_db",
"schema", "logic_db"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- getRecoveryPayload(payload, "missing_context");
- assertThat(String.valueOf(payload.get("message")), is("sql is
required."));
- }
-
- @Test
- void assertRejectUnsupportedTool() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "unsupported_tool", Map.of());
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- assertThat(String.valueOf(payload.get("error_code")),
is("json_rpc_error"));
- assertFalse(String.valueOf(payload.get("message")).isEmpty());
- }
-
private Map<String, Object> createExecuteSQLArguments(final String
databaseName, final String schemaName, final String sql) {
return Map.of("database", databaseName, "schema", schemaName, "sql",
sql, "execution_mode", "execute");
}
@@ -228,14 +127,4 @@ class ExecuteQueryTransactionE2ETest extends
AbstractHttpProgrammaticRuntimeE2ET
? Map.of("database", databaseName, "schema", schemaName,
"sql", sql)
: Map.of("database", databaseName, "schema", schemaName,
"sql", sql, "max_rows", maxRows);
}
-
- private List<Map<String, Object>> castToMapList(final Object value) {
- return ((List<?>) value).stream().map(this::castToMap).toList();
- }
-
- private static Stream<Arguments> assertPreviewSideEffectStatementCases() {
- return Stream.of(
- Arguments.of("transaction control", "BEGIN",
"transaction_control", "BEGIN"),
- Arguments.of("savepoint", "SAVEPOINT foo_sp_1", "savepoint",
"SAVEPOINT"));
- }
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
index 6abc482af19..0394da90ce9 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportApprovalSafetyE2ETest.java
@@ -32,7 +32,6 @@ import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertFalse;
@EnabledIf("isEnabled")
class HttpTransportApprovalSafetyE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
@@ -53,63 +52,20 @@ class HttpTransportApprovalSafetyE2ETest extends
AbstractHttpProgrammaticRuntime
assertThat(String.valueOf(payload.get("response_mode")),
is("executed"));
assertThat(String.valueOf(payload.get("result_kind")),
is("update_count"));
assertThat(String.valueOf(payload.get("affected_rows")), is("0"));
- assertModelFacingPayloadContract(payload);
- }
-
- @Test
- void assertExecuteUpdateReturnsExecutionMode() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
"UPDATE orders SET status = status WHERE order_id = -1",
- "execution_mode", "execute"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
assertThat(String.valueOf(payload.get("execution_mode")),
is("execute"));
assertModelFacingPayloadContract(payload);
}
@Test
- void assertPreviewExecuteUpdateWithoutExecution() throws IOException,
InterruptedException {
+ void assertApplyWorkflowReviewThenExecuteMode() throws IOException,
InterruptedException {
launchHttpTransport();
HttpClient httpClient = HttpClient.newHttpClient();
String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
"UPDATE orders SET status = status WHERE order_id = -1", "execution_mode",
"preview"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- assertThat(String.valueOf(payload.get("result_kind")), is("preview"));
- assertFalse((Boolean) payload.get("would_execute"));
- List<Map<String, Object>> nextActions =
castToMapList(payload.get("next_actions"));
- assertThat(nextActions.size(), is(1));
- assertThat(String.valueOf(nextActions.get(0).get("type")),
is("tool_call"));
- assertThat(String.valueOf(nextActions.get(0).get("tool_name")),
is("database_gateway_execute_update"));
-
assertThat(String.valueOf(castToMap(nextActions.get(0).get("arguments")).get("execution_mode")),
is("execute"));
- assertFalse(payload.containsKey("requires_user_approval"));
- assertFalse(nextActions.get(0).containsKey("requires_user_approval"));
- assertModelFacingPayloadContract(payload);
- }
-
- @Test
- void assertApplyWorkflowPreviewManualAndExecuteModes() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- Map<String, Object> previewPayload = callApplyWorkflow(httpClient,
sessionId, createMaskRulePlan(httpClient, sessionId), Map.of("execution_mode",
"preview"));
- assertThat(String.valueOf(previewPayload.get("status")),
is("preview"));
- assertFalse((Boolean) previewPayload.get("would_apply"));
- assertFalse(previewPayload.containsKey("requires_user_approval"));
- Map<String, Object> manualPayload = callApplyWorkflow(httpClient,
sessionId, createMaskRulePlan(httpClient, sessionId), Map.of("execution_mode",
"manual-only"));
- assertThat(String.valueOf(manualPayload.get("status")),
is("awaiting-manual-execution"));
- assertThat(String.valueOf(manualPayload.get("execution_mode")),
is("manual-only"));
Map<String, Object> executionPayload = callApplyWorkflow(httpClient,
sessionId, createMaskRulePlan(httpClient, sessionId),
Map.of("execution_mode", "review-then-execute",
"approved_steps", List.of("ddl")));
assertThat(String.valueOf(executionPayload.get("status")),
is("completed"));
assertThat(String.valueOf(executionPayload.get("execution_mode")),
is("review-then-execute"));
assertThat(((List<?>)
executionPayload.get("skipped_artifacts")).size(), is(1));
- assertModelFacingPayloadContract(previewPayload);
- assertModelFacingPayloadContract(manualPayload);
assertModelFacingPayloadContract(executionPayload);
}
@@ -156,7 +112,4 @@ class HttpTransportApprovalSafetyE2ETest extends
AbstractHttpProgrammaticRuntime
MCPModelContractAssertions.assertCanonicalNextActionLists(payload);
}
- private List<Map<String, Object>> castToMapList(final Object value) {
- return ((List<?>) value).stream().map(this::castToMap).toList();
- }
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
index 12c045fba7e..88b9bd16f48 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportContractE2ETest.java
@@ -206,68 +206,6 @@ class HttpTransportContractE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETes
assertModelFacingPayloadContract(validationPayload);
}
- @Test
- void assertRejectExecuteUpdateWithoutExecutionMode() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
"UPDATE orders SET status = 'PAID' WHERE order_id = 1"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> structuredContent =
getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(structuredContent,
"missing_context");
- assertModelFacingPayloadContract(structuredContent);
- Map<String, Object> expectedArguments = Map.of("database", "logic_db",
"schema", "logic_db",
- "sql", "UPDATE orders SET status = 'PAID' WHERE order_id = 1",
"execution_mode", "preview");
- assertThat(recovery.get("category"), is("missing_execution_mode"));
- assertThat(recovery.get("suggested_arguments"), is(expectedArguments));
- Map<String, Object> retryAction =
castToMapList(recovery.get("next_actions")).iterator().next();
- assertThat(String.valueOf(retryAction.get("type")), is("tool_call"));
- assertThat(castToMap(retryAction.get("arguments")),
is(expectedArguments));
- assertFalse(retryAction.containsKey("requires_user_approval"));
- }
-
- @Test
- void assertRejectToolCallWithInvalidInputSchema() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_search_metadata",
- Map.of("query", "order", "object_types", List.of("TABLE")));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> structuredContent =
getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(structuredContent,
"invalid_enum");
- assertModelFacingPayloadContract(structuredContent);
- assertThat(recovery.get("category"), is("invalid_enum_value"));
- assertThat(recovery.get("field"), is("object_types[0]"));
- assertThat(recovery.get("allowed_values"), is(List.of("database",
"schema", "table", "view", "column", "index", "sequence")));
- assertThat(recovery.get("suggested_arguments"), is(Map.of("query",
"order")));
- Map<String, Object> retryAction =
castToMapList(recovery.get("next_actions")).iterator().next();
- assertThat(String.valueOf(retryAction.get("type")), is("tool_call"));
- assertThat(castToMap(retryAction.get("arguments")), is(Map.of("query",
"order")));
- assertFalse(retryAction.containsKey("requires_user_approval"));
- }
-
- @Test
- void assertPreviewExecuteUpdateNextActions() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
"UPDATE orders SET status = 'PAID' WHERE order_id = 1", "execution_mode",
"preview"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> structuredContent =
getStructuredContent(actual.body());
- assertThat(String.valueOf(structuredContent.get("result_kind")),
is("preview"));
- assertFalse((Boolean) structuredContent.get("would_execute"));
- assertModelFacingPayloadContract(structuredContent);
- List<Map<String, Object>> nextActions =
castToMapList(structuredContent.get("next_actions"));
- assertThat(nextActions.stream().map(each ->
String.valueOf(each.get("type"))).toList(), is(List.of("tool_call")));
- Map<String, Object> callToolAction = nextActions.get(0);
- assertThat(String.valueOf(callToolAction.get("tool_name")),
is("database_gateway_execute_update"));
-
assertThat(String.valueOf(castToMap(callToolAction.get("arguments")).get("execution_mode")),
is("execute"));
- assertFalse(callToolAction.containsKey("requires_user_approval"));
- }
-
@Test
void assertPreserveUtf8ToolArgumentsForWorkflowIntent() throws
IOException, InterruptedException {
launchHttpTransport();
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
index ff3276a4b33..10f27c01995 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportProtocolContractE2ETest.java
@@ -82,16 +82,6 @@ class HttpTransportProtocolContractE2ETest extends
AbstractHttpProgrammaticRunti
assertFalse(actual.headers().firstValue("MCP-Session-Id").isPresent());
}
- @Test
- void assertRejectInitializeWithSseOnlyAcceptHeader() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- HttpResponse<String> actual = sendInitializeRequest(httpClient,
Map.of("Accept", "text/event-stream"),
-
MCPHttpTransportTestSupport.createInitializeRequestParams("mcp-e2e-programmatic"));
- assertThat(actual.statusCode(), is(400));
- assertFalse(actual.headers().firstValue("MCP-Session-Id").isPresent());
- }
-
@Test
void assertRejectEventStreamWithoutAcceptHeader() throws IOException,
InterruptedException {
launchHttpTransport();
@@ -101,17 +91,6 @@ class HttpTransportProtocolContractE2ETest extends
AbstractHttpProgrammaticRunti
assertThat(actual.statusCode(), is(400));
}
- @Test
- void assertRejectEventStreamWithUnsupportedAcceptHeader() throws
IOException, InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- Map<String, String> headers = new
LinkedHashMap<>(createSessionHeaders(sessionId));
- headers.put("Accept", "application/json");
- HttpResponse<String> actual = openEventStream(httpClient, headers);
- assertThat(actual.statusCode(), is(400));
- }
-
@Test
void assertAcceptInitializeWithUnsupportedProtocolVersion() throws
IOException, InterruptedException {
launchHttpTransport();
@@ -125,28 +104,6 @@ class HttpTransportProtocolContractE2ETest extends
AbstractHttpProgrammaticRunti
assertThat(String.valueOf(castToMap(actualPayload.get("result")).get("protocolVersion")),
is(getProtocolVersion()));
}
- @Test
- void assertAcceptInitializeWithoutProtocolVersion() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- Map<String, Object> initializeRequestParams = new
LinkedHashMap<>(MCPHttpTransportTestSupport.createInitializeRequestParams("mcp-e2e-programmatic"));
- initializeRequestParams.remove("protocolVersion");
- HttpResponse<String> actual = sendInitializeRequest(httpClient,
initializeRequestParams);
- assertThat(actual.statusCode(), is(200));
-
assertThat(actual.headers().firstValue("MCP-Protocol-Version").orElse(""),
is(getProtocolVersion()));
- Map<String, Object> actualPayload = parseJsonBody(actual.body());
-
assertThat(String.valueOf(castToMap(actualPayload.get("result")).get("protocolVersion")),
is(getProtocolVersion()));
- }
-
- @Test
- void assertRejectFollowUpRequestWithoutProtocolHeader() throws
IOException, InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendCapabilitiesRequest(httpClient,
Map.of("MCP-Session-Id", sessionId));
- assertThat(actual.statusCode(), is(400));
- }
-
@Test
void assertRejectFollowUpRequestWithProtocolMismatch() throws IOException,
InterruptedException {
launchHttpTransport();
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
index e4aa767fd19..02117c6a277 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportRecoveryE2ETest.java
@@ -67,46 +67,6 @@ class HttpTransportRecoveryE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETes
assertModelFacingPayloadContract(resourcePayload);
}
- @Test
- void assertRecoverUnsupportedTarget() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
"SELECT * FROM orders", "execution_mode", "preview"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(payload,
"unsupported_target");
- Map<String, Object> nextAction = getFirstNextAction(recovery);
- assertThat(String.valueOf(nextAction.get("tool_name")),
is("database_gateway_execute_query"));
- HttpResponse<String> followUp = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_query",
castToMap(nextAction.get("arguments")));
- assertThat(followUp.statusCode(), is(200));
-
assertThat(String.valueOf(getStructuredContent(followUp.body()).get("result_kind")),
is("result_set"));
- assertModelFacingPayloadContract(payload);
- }
-
- @Test
- void assertRecoverInvalidExecutionMode() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update",
- Map.of("database", "logic_db", "schema", "logic_db", "sql",
"UPDATE orders SET status = status WHERE order_id = -1", "execution_mode",
"run"));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> payload = getStructuredContent(actual.body());
- Map<String, Object> recovery = getRecoveryPayload(payload,
"invalid_enum");
- Map<String, Object> nextAction = getFirstNextAction(recovery);
- assertThat(String.valueOf(nextAction.get("tool_name")),
is("database_gateway_execute_update"));
- Map<String, Object> retryArguments =
castToMap(nextAction.get("arguments"));
- assertThat(String.valueOf(retryArguments.get("execution_mode")),
is("preview"));
- assertThat(String.valueOf(retryArguments.get("database")),
is("logic_db"));
- assertThat(String.valueOf(retryArguments.get("schema")),
is("logic_db"));
- HttpResponse<String> retry = sendToolCallRequest(httpClient,
sessionId, "database_gateway_execute_update", retryArguments);
- assertThat(retry.statusCode(), is(200));
-
assertThat(String.valueOf(getStructuredContent(retry.body()).get("result_kind")),
is("preview"));
- assertModelFacingPayloadContract(payload);
- }
-
@Test
void assertRecoverWrongSqlTool() throws IOException, InterruptedException {
launchHttpTransport();
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
index 8ae4eee531c..1f5124d3481 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSecurityE2ETest.java
@@ -81,22 +81,6 @@ class HttpTransportSecurityE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETes
assertThat(actual.statusCode(), is(403));
}
- @Test
- void assertRejectInitializeWithMalformedRemoteOrigin() throws IOException,
InterruptedException {
- launchRemoteHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- HttpResponse<String> actual = sendInitializeRequest(httpClient,
Map.of("Origin", "://bad-origin"), createInitializeRequestParams());
- assertThat(actual.statusCode(), is(403));
- }
-
- @Test
- void assertRejectInitializeWithLoopbackOriginForRemoteBinding() throws
IOException, InterruptedException {
- launchRemoteHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- HttpResponse<String> actual = sendInitializeRequest(httpClient,
Map.of("Origin", "http://127.0.0.1:8080"), createInitializeRequestParams());
- assertThat(actual.statusCode(), is(403));
- }
-
private void launchRemoteHttpTransport() throws IOException {
remoteBinding = true;
launchHttpTransport();
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
index 7e8fbae144d..85654d7847b 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/MetadataDiscoveryE2ETest.java
@@ -21,17 +21,12 @@ import
org.apache.shardingsphere.test.e2e.mcp.env.MCPE2ECondition;
import
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionPayloads;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -53,7 +48,10 @@ class MetadataDiscoveryE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
Map.of("database", "logic_db", "schema", "logic_db", "query",
"order", "object_types", List.of("table", "view")));
assertThat(actual.statusCode(), is(200));
Map<String, Object> actualPayload =
getStructuredContent(actual.body());
- List<Map<String, Object>> actualItems = getItems(actualPayload);
+ final List<Map<String, Object>> actualItems = getItems(actualPayload);
+ assertThat(String.valueOf(actualPayload.get("response_mode")),
is("search"));
+ assertThat(actualPayload.get("count"), is(3));
+ assertThat(actualPayload.get("total_match_count"), is(3));
assertThat(getItemNames(actualPayload), is(List.of("order_items",
"orders", "active_orders")));
Map<String, Object> actualResource =
MCPInteractionPayloads.castToMap(actualItems.get(1).get("resource"));
assertThat(String.valueOf(actualResource.get("uri")),
is("shardingsphere://databases/logic_db/schemas/logic_db/tables/orders"));
@@ -66,25 +64,6 @@ class MetadataDiscoveryE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
assertThat(String.valueOf(MCPInteractionPayloads.castToList(getFirstResourcePayload(tableResource.body()).get("items")).get(0).get("table")),
is("orders"));
}
- @ParameterizedTest(name = "{0}")
- @MethodSource("assertSearchMetadataObjectTypeCases")
- void assertSearchMetadataByObjectType(final String name, final Map<String,
Object> arguments, final List<String> expectedNames) throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_search_metadata", arguments);
- assertThat(actual.statusCode(), is(200));
- assertThat(getItemNames(getStructuredContent(actual.body())),
is(expectedNames));
- }
-
- private static Stream<Arguments> assertSearchMetadataObjectTypeCases() {
- return Stream.of(
- Arguments.of("database objects", createSearchArguments("", "",
"logic", List.of("database")), List.of("logic_db")),
- Arguments.of("schema objects",
createSearchArguments("logic_db", "", "logic", List.of("schema")),
List.of("logic_db")),
- Arguments.of("column objects",
createSearchArguments("logic_db", "", "status", List.of("column")),
List.of("status", "status")),
- Arguments.of("index objects",
createSearchArguments("logic_db", "", "status", List.of("index")),
List.of("idx_orders_status")));
- }
-
@Test
void assertSearchMetadataAcrossDatabasesByDefault() throws IOException,
InterruptedException {
launchHttpTransport();
@@ -95,36 +74,6 @@ class MetadataDiscoveryE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
assertThat(getItemNames(getStructuredContent(actual.body())),
is(List.of("metrics", "metric_id", "metric_name", "PRIMARY")));
}
- @Test
- void assertSearchMetadataReturnsCompleteItemList() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_search_metadata",
- createSearchArguments("logic_db", "logic_db", "order",
List.of("table", "view")));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> actualPayload =
getStructuredContent(actual.body());
- assertThat(String.valueOf(actualPayload.get("response_mode")),
is("search"));
- assertThat(actualPayload.get("count"), is(3));
- assertThat(actualPayload.get("total_match_count"), is(3));
- assertThat(getItemNames(actualPayload), is(List.of("order_items",
"orders", "active_orders")));
- }
-
- @Test
- void assertRejectUnsupportedSearchArgument() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- Map<String, Object> arguments = createSearchArguments("logic_db",
"logic_db", "order", List.of("table", "view"));
- arguments.put("client_hint", "narrow");
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_search_metadata", arguments);
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> recovery =
getRecoveryPayload(getStructuredContent(actual.body()), "validation");
- assertThat(String.valueOf(recovery.get("category")),
is("unknown_argument"));
- assertThat(String.valueOf(recovery.get("field")), is("client_hint"));
- assertThat(recovery.get("suggested_arguments"),
is(createSearchArguments("logic_db", "logic_db", "order", List.of("table",
"view"))));
- }
-
@Test
void assertRejectSchemaSearchWithoutDatabase() throws IOException,
InterruptedException {
launchHttpTransport();
@@ -137,20 +86,6 @@ class MetadataDiscoveryE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
assertThat(String.valueOf(actualPayload.get("message")), is("Schema
cannot be provided without database."));
}
- @Test
- void assertRejectUnsupportedObjectType() throws IOException,
InterruptedException {
- launchHttpTransport();
- HttpClient httpClient = HttpClient.newHttpClient();
- String sessionId = initializeSession(httpClient);
- HttpResponse<String> actual = sendToolCallRequest(httpClient,
sessionId, "database_gateway_search_metadata",
- Map.of("database", "logic_db", "schema", "logic_db", "query",
"order",
- "object_types", List.of("table", "view", "index",
"materialized_view", "sequence")));
- assertThat(actual.statusCode(), is(200));
- Map<String, Object> recovery =
getRecoveryPayload(getStructuredContent(actual.body()), "invalid_enum");
- assertThat(String.valueOf(recovery.get("category")),
is("invalid_enum_value"));
- assertThat(String.valueOf(recovery.get("field")),
is("object_types[3]"));
- }
-
@Test
void assertReadWarehouseIndexesResource() throws IOException,
InterruptedException {
launchHttpTransport();
@@ -215,17 +150,4 @@ class MetadataDiscoveryE2ETest extends
AbstractHttpProgrammaticRuntimeE2ETest {
private List<Map<String, Object>> getItems(final Map<String, Object>
payload) {
return MCPInteractionPayloads.castToList(payload.get("items"));
}
-
- private static Map<String, Object> createSearchArguments(final String
databaseName, final String schemaName, final String query, final List<String>
objectTypes) {
- Map<String, Object> result = new LinkedHashMap<>(4, 1F);
- if (!databaseName.isEmpty()) {
- result.put("database", databaseName);
- }
- if (!schemaName.isEmpty()) {
- result.put("schema", schemaName);
- }
- result.put("query", query);
- result.put("object_types", objectTypes);
- return result;
- }
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/assertion/MCPBaselineContractAssertionsTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/assertion/MCPBaselineContractAssertionsTest.java
new file mode 100644
index 00000000000..6a4f3e79110
--- /dev/null
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/assertion/MCPBaselineContractAssertionsTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.shardingsphere.test.e2e.mcp.support.assertion;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class MCPBaselineContractAssertionsTest {
+
+ @Test
+ void assertMatchesNormalizedBaselineContract() {
+ assertDoesNotThrow(() ->
MCPBaselineContractAssertions.assertMatchesNormalizedBaselineContract(
+ "baseline-contract/assertion/normalized-plan-id.yaml",
+ Map.of("plan_id", "plan-1", "nested",
List.of(Map.of("plan_id", "server_generated"), Map.of("plan_id", "plan-2")))));
+ }
+
+ @Test
+ void assertMatchesNormalizedBaselineContractWithMissingResource() {
+ assertThrows(AssertionError.class, () ->
MCPBaselineContractAssertions.assertMatchesNormalizedBaselineContract(
+ "baseline-contract/assertion/missing.yaml", Map.of()));
+ }
+}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/assertion/MCPModelContractAssertionsTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/assertion/MCPModelContractAssertionsTest.java
new file mode 100644
index 00000000000..c66b6b1247e
--- /dev/null
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/assertion/MCPModelContractAssertionsTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.shardingsphere.test.e2e.mcp.support.assertion;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class MCPModelContractAssertionsTest {
+
+ @Test
+ void assertNoBannedPublicFields() {
+ assertDoesNotThrow(() ->
MCPModelContractAssertions.assertNoBannedPublicFields(Map.of("next_actions",
List.of(Map.of("type", "terminal")))));
+ }
+
+ @Test
+ void assertNoBannedPublicFieldsWithNestedBannedField() {
+ assertThrows(AssertionError.class, () ->
MCPModelContractAssertions.assertNoBannedPublicFields(
+ Map.of("recovery", List.of(Map.of("requires_user_approval",
true)))));
+ }
+
+ @Test
+ void assertCanonicalNextActionLists() {
+ assertDoesNotThrow(() ->
MCPModelContractAssertions.assertCanonicalNextActionLists(Map.of("next_actions",
List.of(
+ Map.of("order", 1, "type", "tool_call", "title", "Retry",
"tool_name", "database_gateway_execute_update", "arguments", Map.of())))));
+ }
+
+ @Test
+ void assertCanonicalNextActionListsWithSchema() {
+ assertDoesNotThrow(() ->
MCPModelContractAssertions.assertCanonicalNextActionLists(
+ Map.of("next_actions", Map.of("type", "array", "items",
Map.of("type", "object")))));
+ }
+
+ @Test
+ void assertCanonicalNextActionListsWithUnknownType() {
+ assertThrows(AssertionError.class, () ->
MCPModelContractAssertions.assertCanonicalNextActionLists(
+ Map.of("next_actions", List.of(Map.of("order", 1, "type",
"unknown", "title", "Unknown")))));
+ }
+
+ @Test
+ void assertCanonicalNextActionListsWithNonArray() {
+ assertThrows(AssertionError.class, () ->
MCPModelContractAssertions.assertCanonicalNextActionLists(Map.of("next_actions",
"retry")));
+ }
+
+ @Test
+ void assertCanonicalNextActionListsWithMissingRequiredField() {
+ assertThrows(AssertionError.class, () ->
MCPModelContractAssertions.assertCanonicalNextActionLists(
+ Map.of("next_actions", List.of(Map.of("order", 1, "type",
"tool_call", "title", "Retry", "tool_name",
"database_gateway_execute_update")))));
+ }
+}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPInteractionPayloadsTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPInteractionPayloadsTest.java
new file mode 100644
index 00000000000..061d8fa76bd
--- /dev/null
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPInteractionPayloadsTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.shardingsphere.test.e2e.mcp.support.transport;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MCPInteractionPayloadsTest {
+
+ @Test
+ void assertParseJsonPayload() {
+ Map<String, Object> actualPayload =
MCPInteractionPayloads.parseJsonPayload("{\"result\":{\"status\":\"ok\"}}");
+
assertThat(MCPInteractionPayloads.castToMap(actualPayload.get("result")).get("status"),
is("ok"));
+ }
+
+ @Test
+ void assertParseServerSentEventPayload() {
+ Map<String, Object> actualPayload =
MCPInteractionPayloads.parseJsonPayload("event: message\ndata:
{\"result\":{\"status\":\"ok\"}}\n");
+
assertThat(MCPInteractionPayloads.castToMap(actualPayload.get("result")).get("status"),
is("ok"));
+ }
+
+ @Test
+ void assertParseJsonPayloadWithInvalidJson() {
+ assertThrows(IllegalStateException.class, () ->
MCPInteractionPayloads.parseJsonPayload("{invalid"));
+ }
+
+ @Test
+ void assertHasJsonRpcError() {
+ assertTrue(MCPInteractionPayloads.hasJsonRpcError(Map.of("error",
Map.of("message", "failed"))));
+ }
+
+ @Test
+ void assertGetJsonRpcResult() {
+ assertThat(MCPInteractionPayloads.getJsonRpcResult(Map.of("result",
Map.of("status", "ok"))).get("status"), is("ok"));
+ }
+
+ @Test
+ void assertGetJsonRpcResultWithoutResult() {
+
assertTrue(MCPInteractionPayloads.getJsonRpcResult(Map.of()).isEmpty());
+ }
+
+ @Test
+ void assertGetResultContents() {
+ Map<String, Object> payload = Map.of("result", Map.of("content",
List.of(Map.of("text", "{\"status\":\"ok\"}"))));
+
assertThat(MCPInteractionPayloads.getResultContents(payload).get(0).get("text"),
is("{\"status\":\"ok\"}"));
+ }
+
+ @Test
+ void assertGetResultContentsWithoutContent() {
+ assertTrue(MCPInteractionPayloads.getResultContents(Map.of("result",
Map.of())).isEmpty());
+ }
+
+ @Test
+ void assertGetListResourcesPayload() {
+ Map<String, Object> actualPayload =
MCPInteractionPayloads.getListResourcesPayload(Map.of("result",
Map.of("resources", List.of(Map.of("uri", "shardingsphere://capabilities")))));
+
assertThat(MCPInteractionPayloads.castToList(actualPayload.get("resources")).get(0).get("uri"),
is("shardingsphere://capabilities"));
+ }
+
+ @Test
+ void assertGetListResourcesPayloadWithError() {
+ Map<String, Object> actualPayload =
MCPInteractionPayloads.getListResourcesPayload(Map.of("error",
Map.of("message", "Resource not found")));
+ assertThat(actualPayload, is(Map.of("error_code", "json_rpc_error",
"message", "Resource not found")));
+ }
+
+ @Test
+ void assertGetStructuredContent() {
+ Map<String, Object> actualPayload =
MCPInteractionPayloads.getStructuredContent(Map.of("result",
Map.of("structuredContent", Map.of("status", "ok"))));
+ assertThat(actualPayload.get("status"), is("ok"));
+ }
+
+ @Test
+ void assertGetStructuredContentFromTextContent() {
+ Map<String, Object> payload = Map.of("result", Map.of("content",
List.of(Map.of("text", "{\"status\":\"ok\"}"))));
+
assertThat(MCPInteractionPayloads.getStructuredContent(payload).get("status"),
is("ok"));
+ }
+
+ @Test
+ void assertGetStructuredContentWithoutContent() {
+
assertTrue(MCPInteractionPayloads.getStructuredContent(Map.of("result",
Map.of())).isEmpty());
+ }
+
+ @Test
+ void assertGetFirstResourcePayload() {
+ Map<String, Object> payload = Map.of("result", Map.of("contents",
List.of(Map.of("text", "{\"item\":{\"database\":\"logic_db\"}}"))));
+
assertThat(MCPInteractionPayloads.castToMap(MCPInteractionPayloads.getFirstResourcePayload(payload).get("item")).get("database"),
is("logic_db"));
+ }
+
+ @Test
+ void assertGetFirstResourcePayloadWithoutContent() {
+
assertTrue(MCPInteractionPayloads.getFirstResourcePayload(Map.of("result",
Map.of())).isEmpty());
+ }
+
+ @Test
+ void assertGetJsonRpcErrorPayload() {
+
assertThat(MCPInteractionPayloads.getJsonRpcErrorPayload(Map.of("error",
Map.of("message", "Tool not found"))),
+ is(Map.of("error_code", "json_rpc_error", "message", "Tool not
found")));
+ }
+
+ @Test
+ void assertGetJsonRpcErrorPayloadWithoutError() {
+
assertTrue(MCPInteractionPayloads.getJsonRpcErrorPayload(Map.of()).isEmpty());
+ }
+
+ @Test
+ void assertCastToMap() {
+ assertThat(MCPInteractionPayloads.castToMap(Map.of("status",
"ok")).get("status"), is("ok"));
+ }
+
+ @Test
+ void assertCastToList() {
+ assertThat(MCPInteractionPayloads.castToList(List.of(Map.of("name",
"orders"))).get(0).get("name"), is("orders"));
+ }
+}
diff --git
a/test/e2e/mcp/src/test/resources/baseline-contract/assertion/normalized-plan-id.yaml
b/test/e2e/mcp/src/test/resources/baseline-contract/assertion/normalized-plan-id.yaml
new file mode 100644
index 00000000000..28372921f24
--- /dev/null
+++
b/test/e2e/mcp/src/test/resources/baseline-contract/assertion/normalized-plan-id.yaml
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+plan_id: <plan_id>
+nested:
+ - plan_id: server_generated
+ - plan_id: <plan_id>