You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/06/16 01:12:29 UTC
(shardingsphere) branch master updated: Harden shared fixture boundaries (#38852)
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 f40fd90435a Harden shared fixture boundaries (#38852)
f40fd90435a is described below
commit f40fd90435ac28ce6eca996ebe7c29cbb3973673
Author: Liang Zhang <zh...@apache.org>
AuthorDate: Tue Jun 16 09:12:22 2026 +0800
Harden shared fixture boundaries (#38852)
* Expose storage unit metadata resources
Expose Proxy-backed storage unit and single table metadata through MCP resources,
search metadata, completion targets, and recovery guidance.
Add contract and Proxy-backed E2E coverage to verify that MCP clients can read
storage unit information from a logical database.
* Expose storage unit metadata resources
Expose Proxy-backed storage unit and single table metadata through MCP resources,
search metadata, completion targets, and recovery guidance.
Add contract and Proxy-backed E2E coverage to verify that MCP clients can read
storage unit information from a logical database.
* Expose storage unit metadata resources
Expose Proxy-backed storage unit and single table metadata through MCP resources,
search metadata, completion targets, and recovery guidance.
Add contract and Proxy-backed E2E coverage to verify that MCP clients can read
storage unit information from a logical database.
* Expose storage unit metadata resources
Expose Proxy-backed storage unit and single table metadata through MCP resources,
search metadata, completion targets, and recovery guidance.
Add contract and Proxy-backed E2E coverage to verify that MCP clients can read
storage unit information from a logical database.
* Expose storage unit metadata resources
Expose Proxy-backed storage unit and single table metadata through MCP resources,
search metadata, completion targets, and recovery guidance.
Add contract and Proxy-backed E2E coverage to verify that MCP clients can read
storage unit information from a logical database.
* Expose storage unit metadata resources
Expose Proxy-backed storage unit and single table metadata through MCP resources,
search metadata, completion targets, and recovery guidance.
Add contract and Proxy-backed E2E coverage to verify that MCP clients can read
storage unit information from a logical database.
* Harden shared fixture boundaries
- Use transport-specific reusable homes for packaged distribution tests
- Rename Proxy shared fixture opt-in to make read-only scope explicit
- Keep shared fixture boundary decisions in test code and verification evidence
---
.../e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java | 8 +-
.../suite/usability/LLMUsabilitySuiteE2ETest.java | 22 +++-
.../AbstractProductionMySQLRuntimeE2ETest.java | 116 ++++++++++++++++++---
.../AbstractProductionProxyWorkflowE2ETest.java | 42 ++++++--
...sportParameterizedProductionRuntimeE2ETest.java | 6 ++
.../HttpProductionProxyEncryptWorkflowE2ETest.java | 1 +
...ductionProxyFeatureWorkflowContractE2ETest.java | 1 +
.../HttpProductionProxyMaskWorkflowE2ETest.java | 1 +
.../production/PackagedDistributionE2ETest.java | 24 +++--
.../production/ProductionMySQLRuntimeE2ETest.java | 33 +++---
.../ProductionMySQLSQLRuntimeE2ETest.java | 46 ++++----
.../ProductionRuntimeTransportSmokeE2ETest.java | 53 ++++++++++
.../AbstractHttpProgrammaticRuntimeE2ETest.java | 86 ++++++++++++---
...t.java => AbstractHttpProtocolOnlyE2ETest.java} | 99 ++++++------------
...tractSharedHttpProgrammaticRuntimeE2ETest.java} | 24 ++---
.../FeatureWorkflowContractE2ETest.java | 2 +-
.../HttpTransportApprovalSafetyE2ETest.java | 2 +-
.../HttpTransportBaselineContractE2ETest.java | 2 +-
.../HttpTransportCompletionE2ETest.java | 2 +-
.../programmatic/HttpTransportContractE2ETest.java | 2 +-
.../HttpTransportProtocolContractE2ETest.java | 9 +-
.../programmatic/HttpTransportRecoveryE2ETest.java | 2 +-
.../programmatic/HttpTransportSecurityE2ETest.java | 9 +-
.../HttpTransportSessionLifecycleE2ETest.java | 9 +-
.../programmatic/MetadataDiscoveryE2ETest.java | 8 +-
.../PackagedDistributionTestSupport.java | 26 +++++
26 files changed, 434 insertions(+), 201 deletions(-)
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
index e16872f461e..01cf010c4d3 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/smoke/LLMSmokeE2ETest.java
@@ -33,6 +33,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -48,6 +49,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("llm-e2e")
@EnabledIf("isEnabled")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LLMSmokeE2ETest extends AbstractConfigBackedRuntimeE2ETest {
private static final String DATABASE_NAME = "logic_db";
@@ -86,12 +88,16 @@ class LLMSmokeE2ETest extends AbstractConfigBackedRuntimeE2ETest {
}
}
- @AfterEach
+ @AfterAll
void closeRuntimeFixture() {
if (null != currentRuntimeFixture) {
currentRuntimeFixture.close();
currentRuntimeFixture = null;
}
+ }
+
+ @AfterEach
+ void clearCurrentTransport() {
currentTransport = null;
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
index f550e2f9018..6639df93a99 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/suite/usability/LLMUsabilitySuiteE2ETest.java
@@ -32,6 +32,7 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@@ -44,6 +45,7 @@ import java.util.stream.Stream;
@Tag("llm-e2e")
@EnabledIf("isEnabled")
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class LLMUsabilitySuiteE2ETest extends AbstractConfigBackedRuntimeE2ETest {
private static final String RUNTIME_KIND = "mysql";
@@ -54,6 +56,8 @@ class LLMUsabilitySuiteE2ETest extends AbstractConfigBackedRuntimeE2ETest {
private static final String COUNT_ORDERS_SQL = "SELECT COUNT(*) AS total_orders FROM orders";
+ private static final String FULL_TRANSPORT_MATRIX_PROPERTY = "mcp.e2e.llm.full-transport-matrix";
+
private static LLMRuntimeSupport.ModelRuntime llmRuntime;
private final LLMRuntimeFixtureFactory runtimeFixtureFactory = new LLMRuntimeFixtureFactory();
@@ -79,12 +83,16 @@ class LLMUsabilitySuiteE2ETest extends AbstractConfigBackedRuntimeE2ETest {
}
}
- @AfterEach
+ @AfterAll
void closeRuntimeFixture() {
if (null != currentRuntimeFixture) {
currentRuntimeFixture.close();
currentRuntimeFixture = null;
}
+ }
+
+ @AfterEach
+ void clearCurrentTransport() {
currentTransport = null;
}
@@ -93,9 +101,15 @@ class LLMUsabilitySuiteE2ETest extends AbstractConfigBackedRuntimeE2ETest {
}
static Stream<Arguments> getTestCases() {
- return Stream.of(
- Arguments.of("llm-usability-mysql-http", RuntimeTransport.HTTP),
- Arguments.of("llm-usability-mysql-stdio", RuntimeTransport.STDIO));
+ return isFullTransportMatrixEnabled()
+ ? Stream.of(
+ Arguments.of("llm-usability-mysql-http", RuntimeTransport.HTTP),
+ Arguments.of("llm-usability-mysql-stdio", RuntimeTransport.STDIO))
+ : Stream.of(Arguments.of("llm-usability-mysql-http", RuntimeTransport.HTTP));
+ }
+
+ private static boolean isFullTransportMatrixEnabled() {
+ return Boolean.parseBoolean(System.getProperty(FULL_TRANSPORT_MATRIX_PROPERTY, "false"));
}
@ParameterizedTest(name = "{0}")
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
index 24904c31217..325368d8077 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionMySQLRuntimeE2ETest.java
@@ -17,11 +17,6 @@
package org.apache.shardingsphere.test.e2e.mcp.runtime.production;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.hasItems;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.isA;
-
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
@@ -42,8 +37,10 @@ import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionPa
import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPPayloadAssertions;
import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPInteractionClient;
import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPStdioLogbackConfiguration;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.provider.Arguments;
import org.testcontainers.containers.GenericContainer;
@@ -59,9 +56,14 @@ import java.util.Map;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isA;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@EnabledIf("isEnabled")
abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportParameterizedProductionRuntimeE2ETest {
@@ -77,6 +79,8 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
private String physicalSchemaName;
+ private MySQLRuntimeFixture sharedRuntimeFixture;
+
protected GenericContainer<?> getContainer() {
return container;
}
@@ -87,6 +91,11 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
@AfterEach
void tearDownContainer() {
+ if (useSharedRuntimeFixture()) {
+ container = null;
+ physicalSchemaName = null;
+ return;
+ }
if (null != container) {
container.stop();
container = null;
@@ -94,21 +103,59 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
physicalSchemaName = null;
}
+ @AfterAll
+ void tearDownSharedContainer() {
+ if (null != sharedRuntimeFixture) {
+ sharedRuntimeFixture.close();
+ sharedRuntimeFixture = null;
+ }
+ }
+
@Override
protected void prepareRuntimeFixture() throws IOException {
Assumptions.assumeTrue(MySQLRuntimeTestSupport.isDockerAvailable(),
() -> MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required for the MySQL-backed production runtime E2E test."));
- container = MySQLRuntimeTestSupport.createContainer();
- container.start();
+ if (useSharedRuntimeFixture()) {
+ prepareSharedRuntimeFixture();
+ return;
+ }
+ applyRuntimeFixture(createRuntimeFixture());
+ }
+
+ private void prepareSharedRuntimeFixture() throws IOException {
+ if (null == sharedRuntimeFixture) {
+ sharedRuntimeFixture = createRuntimeFixture();
+ }
+ applyRuntimeFixture(sharedRuntimeFixture);
+ }
+
+ private MySQLRuntimeFixture createRuntimeFixture() throws IOException {
+ GenericContainer<?> result = MySQLRuntimeTestSupport.createContainer();
+ boolean success = false;
try {
- MySQLRuntimeTestSupport.initializeDatabase(container);
- String detectedSchemaName = MySQLRuntimeTestSupport.detectSchema(container);
- physicalSchemaName = detectedSchemaName.isEmpty() ? PHYSICAL_DATABASE_NAME : detectedSchemaName;
+ result.start();
+ MySQLRuntimeTestSupport.initializeDatabase(result);
+ String detectedSchemaName = MySQLRuntimeTestSupport.detectSchema(result);
+ success = true;
+ return new MySQLRuntimeFixture(result, detectedSchemaName.isEmpty() ? PHYSICAL_DATABASE_NAME : detectedSchemaName);
} catch (final SQLException ex) {
throw new IOException(ex);
+ } finally {
+ if (!success) {
+ result.stop();
+ }
}
}
+ private void applyRuntimeFixture(final MySQLRuntimeFixture fixture) {
+ container = fixture.container();
+ physicalSchemaName = fixture.physicalSchemaName();
+ }
+
+ protected boolean useSharedRuntimeFixture() {
+ return false;
+ }
+
@Override
protected Map<String, RuntimeDatabaseConfiguration> getRuntimeDatabases() {
return MySQLRuntimeTestSupport.createRuntimeDatabases(container, LOGICAL_DATABASE_NAME);
@@ -130,8 +177,16 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
return runtimeTransports().map(each -> Arguments.of(getTransportName(each), each));
}
+ protected static Stream<Arguments> dualTransports() {
+ return transports();
+ }
+
+ protected static Stream<Arguments> semanticPrimaryTransport() {
+ return semanticPrimaryRuntimeTransports().map(each -> Arguments.of(getTransportName(each), each));
+ }
+
protected static Stream<Arguments> assertReadSingleMetadataResourceCases() {
- return runtimeTransports().flatMap(each -> Stream.of(
+ return semanticPrimaryRuntimeTransports().flatMap(each -> Stream.of(
Arguments.of(getTransportName(each) + " database detail", each, "shardingsphere://databases/logic_db", "database", LOGICAL_DATABASE_NAME),
Arguments.of(getTransportName(each) + " schema detail", each, "shardingsphere://databases/logic_db/schemas/logic_db", "schema", LOGICAL_DATABASE_NAME),
Arguments.of(getTransportName(each) + " table column detail", each,
@@ -144,7 +199,7 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
}
protected static Stream<Arguments> assertReadCollectionMetadataResourceCases() {
- return runtimeTransports().flatMap(each -> Stream.of(
+ return semanticPrimaryRuntimeTransports().flatMap(each -> Stream.of(
Arguments.of(getTransportName(each) + " schemas list", each, "shardingsphere://databases/logic_db/schemas", "schema", List.of(LOGICAL_DATABASE_NAME)),
Arguments.of(getTransportName(each) + " tables list", each,
"shardingsphere://databases/logic_db/schemas/logic_db/tables", "table", List.of("order_items", "orders")),
@@ -158,6 +213,10 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
return Stream.of(RuntimeTransport.HTTP, RuntimeTransport.STDIO);
}
+ private static Stream<RuntimeTransport> semanticPrimaryRuntimeTransports() {
+ return Stream.of(RuntimeTransport.HTTP);
+ }
+
protected static String getTransportName(final RuntimeTransport transport) {
return RuntimeTransport.HTTP == transport ? "http" : "stdio";
}
@@ -274,7 +333,7 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
assertFalse((Boolean) actual.get("would_execute"));
List<Map<String, Object>> nextActions = getMapList(actual.get("next_actions"));
assertThat(nextActions.stream().map(each -> String.valueOf(each.get("type"))).toList(), is(List.of("tool_call")));
- assertFalse(nextActions.get(0).containsKey("requires_user_approval"));
+ assertFalse(nextActions.getFirst().containsKey("requires_user_approval"));
}
protected void assertAiNativeSqlResult(final MCPInteractionClient interactionClient) throws IOException, InterruptedException {
@@ -284,7 +343,7 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
assertThat(String.valueOf(actual.get("row_object_status")), is("available"));
assertThat(((List<?>) actual.get("row_objects")).size(), is(1));
assertThat(String.valueOf(actual.get("truncated")), is("true"));
- assertThat(String.valueOf(getMapList(actual.get("next_actions")).get(0).get("type")), is("ask_user"));
+ assertThat(String.valueOf(getMapList(actual.get("next_actions")).getFirst().get("type")), is("ask_user"));
}
protected McpSyncClient createElicitationClient(final RuntimeTransport transport, final List<McpSchema.ElicitRequest> elicitationRequests) throws IOException {
@@ -322,13 +381,13 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
elicitationRequests.add(request);
List<String> requiredFields = getStringList(request.requestedSchema().get("required"));
return new McpSchema.ElicitResult(McpSchema.ElicitResult.Action.ACCEPT, Map.of(
- requiredFields.get(0), "1",
+ requiredFields.getFirst(), "1",
requiredFields.get(1), "3"));
}
protected void assertElicitationRequest(final List<McpSchema.ElicitRequest> actualRequests) {
assertThat(actualRequests.size(), is(1));
- McpSchema.ElicitRequest actual = actualRequests.get(0);
+ McpSchema.ElicitRequest actual = actualRequests.getFirst();
assertThat(actual.meta().get(MCPShardingSphereMetadataKeys.TOOL), is(MASK_PLAN_TOOL_NAME));
assertFalse(String.valueOf(actual.meta().get(MCPShardingSphereMetadataKeys.PLAN_ID)).isBlank());
Map<String, Object> actualRequestedSchema = actual.requestedSchema();
@@ -376,4 +435,29 @@ abstract class AbstractProductionMySQLRuntimeE2ETest extends AbstractTransportPa
return List.of(ProtocolVersions.MCP_2025_06_18, ProtocolVersions.MCP_2025_11_25);
}
}
+
+ private static final class MySQLRuntimeFixture implements AutoCloseable {
+
+ private final GenericContainer<?> container;
+
+ private final String physicalSchemaName;
+
+ private MySQLRuntimeFixture(final GenericContainer<?> container, final String physicalSchemaName) {
+ this.container = container;
+ this.physicalSchemaName = physicalSchemaName;
+ }
+
+ private GenericContainer<?> container() {
+ return container;
+ }
+
+ private String physicalSchemaName() {
+ return physicalSchemaName;
+ }
+
+ @Override
+ public void close() {
+ container.stop();
+ }
+ }
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionProxyWorkflowE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionProxyWorkflowE2ETest.java
index cd369e42afe..93a1d77d458 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionProxyWorkflowE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractProductionProxyWorkflowE2ETest.java
@@ -22,8 +22,10 @@ import org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSu
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.ProxyEncryptWorkflowRuntimeTestSupport;
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.ProxyEncryptWorkflowRuntimeTestSupport.ProxyEncryptWorkflowRuntimeFixture;
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.TestInstance;
import java.io.IOException;
import java.sql.SQLException;
@@ -33,18 +35,36 @@ import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
abstract class AbstractProductionProxyWorkflowE2ETest extends AbstractProductionRuntimeE2ETest {
private ProxyEncryptWorkflowRuntimeFixture runtimeFixture;
+ private ProxyEncryptWorkflowRuntimeFixture sharedRuntimeFixture;
+
+ private boolean sharedRuntimeFixtureSelected;
+
@AfterEach
void tearDownFixture() {
+ if (sharedRuntimeFixtureSelected) {
+ runtimeFixture = null;
+ sharedRuntimeFixtureSelected = false;
+ return;
+ }
if (null != runtimeFixture) {
runtimeFixture.close();
runtimeFixture = null;
}
}
+ @AfterAll
+ void tearDownSharedFixture() {
+ if (null != sharedRuntimeFixture) {
+ sharedRuntimeFixture.close();
+ sharedRuntimeFixture = null;
+ }
+ }
+
@Override
protected final RuntimeTransport getTransport() {
return RuntimeTransport.HTTP;
@@ -55,12 +75,23 @@ abstract class AbstractProductionProxyWorkflowE2ETest extends AbstractProduction
Assumptions.assumeTrue(MySQLRuntimeTestSupport.isDockerAvailable(),
() -> MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required for the Proxy-backed workflow E2E tests."));
try {
+ if (sharedRuntimeFixtureSelected) {
+ prepareSharedRuntimeFixture();
+ return;
+ }
runtimeFixture = ProxyEncryptWorkflowRuntimeTestSupport.createFixture();
} catch (final SQLException ex) {
throw new IOException(ex);
}
}
+ private void prepareSharedRuntimeFixture() throws SQLException {
+ if (null == sharedRuntimeFixture) {
+ sharedRuntimeFixture = ProxyEncryptWorkflowRuntimeTestSupport.createFixture();
+ }
+ runtimeFixture = sharedRuntimeFixture;
+ }
+
@Override
protected final Map<String, RuntimeDatabaseConfiguration> getRuntimeDatabases() {
return runtimeFixture.getRuntimeDatabases();
@@ -70,10 +101,8 @@ abstract class AbstractProductionProxyWorkflowE2ETest extends AbstractProduction
return runtimeFixture.getLogicalDatabaseName();
}
- protected final int countPhysicalColumn(final String columnName) throws SQLException {
- return MySQLRuntimeTestSupport.querySingleInt(runtimeFixture.getStorageContainer(), String.format(
- "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = '%s' AND table_name = 'orders' AND column_name = '%s'",
- runtimeFixture.getPhysicalDatabaseName(), columnName));
+ protected final void useSharedReadOnlyRuntimeFixture() {
+ sharedRuntimeFixtureSelected = true;
}
protected final void assertValidationPassed(final Map<String, Object> actualValidationResponse) {
@@ -83,11 +112,6 @@ abstract class AbstractProductionProxyWorkflowE2ETest extends AbstractProduction
assertThat(actualValidationResponse.toString(), getMapList(actualValidationResponse.get("mismatches")).size(), is(0));
}
- protected final void assertValidationFailed(final Map<String, Object> actualValidationResponse) {
- assertThat(actualValidationResponse.toString(), String.valueOf(actualValidationResponse.get("status")), is("failed"));
- assertThat(actualValidationResponse.toString(), String.valueOf(actualValidationResponse.get("overall_status")), is("failed"));
- }
-
protected final void assertApplyCompleted(final Map<String, Object> actualApplyResponse) {
assertThat(actualApplyResponse.toString(), String.valueOf(actualApplyResponse.get("status")), is("completed"));
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java
index 7b6b66a1ebf..09c4245c98d 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.test.e2e.mcp.runtime.production;
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
+import org.junit.jupiter.api.AfterEach;
abstract class AbstractTransportParameterizedProductionRuntimeE2ETest extends AbstractProductionRuntimeE2ETest {
@@ -27,6 +28,11 @@ abstract class AbstractTransportParameterizedProductionRuntimeE2ETest extends Ab
this.transport = transport;
}
+ @AfterEach
+ void clearTransport() {
+ transport = null;
+ }
+
@Override
protected final RuntimeTransport getTransport() {
if (null == transport) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
index d103a343556..76943ad51b0 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyEncryptWorkflowE2ETest.java
@@ -63,6 +63,7 @@ class HttpProductionProxyEncryptWorkflowE2ETest extends AbstractProductionProxyW
@Test
void assertCompleteEncryptAlgorithmThroughProxy() throws IOException, InterruptedException {
+ useSharedReadOnlyRuntimeFixture();
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
Map<String, Object> actual = interactionClient.complete(Map.of("type", "ref/prompt", "name", PLAN_PROMPT_NAME), "algorithm_type", "AE", Map.of());
assertThat(getStringList(getMap(actual.get("completion")).get("values")), hasItem("AES"));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyFeatureWorkflowContractE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyFeatureWorkflowContractE2ETest.java
index 515ea25116c..15f3246bcc6 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyFeatureWorkflowContractE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyFeatureWorkflowContractE2ETest.java
@@ -86,6 +86,7 @@ class HttpProductionProxyFeatureWorkflowContractE2ETest extends AbstractProducti
@Test
void assertReadStorageUnitsThroughProxy() throws IOException, InterruptedException {
+ useSharedReadOnlyRuntimeFixture();
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
List<Map<String, Object>> actualStorageUnits = getPayloadItems(interactionClient.readResource(
String.format("shardingsphere://databases/%s/storage-units", getLogicalDatabaseName())));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
index 1ef2ac6ae24..68279ef4a99 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/HttpProductionProxyMaskWorkflowE2ETest.java
@@ -57,6 +57,7 @@ class HttpProductionProxyMaskWorkflowE2ETest extends AbstractProductionProxyWork
@Test
void assertCompleteMaskAlgorithmThroughProxy() throws IOException, InterruptedException {
+ useSharedReadOnlyRuntimeFixture();
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
Map<String, Object> actual = interactionClient.complete(Map.of("type", "ref/prompt", "name", PLAN_PROMPT_NAME), "algorithm_type", "KEEP", Map.of());
assertThat(getStringList(getMap(actual.get("completion")).get("values")), hasItem("KEEP_FIRST_N_LAST_M"));
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
index 2120d3144f6..17073afad68 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/PackagedDistributionE2ETest.java
@@ -17,10 +17,6 @@
package org.apache.shardingsphere.test.e2e.mcp.runtime.production;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.hasItems;
-import static org.hamcrest.Matchers.is;
-
import org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
import org.apache.shardingsphere.test.e2e.mcp.env.MCPE2ECondition;
import org.apache.shardingsphere.test.e2e.mcp.support.OfficialMCPToolNames;
@@ -50,10 +46,14 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -105,7 +105,7 @@ class PackagedDistributionE2ETest {
@Test
void assertLaunchPackagedDistributionOverHttp() throws IOException, InterruptedException, SQLException {
- PreparedPackagedDistribution distribution = preparePackagedDistribution("http", RuntimeTransport.HTTP);
+ PreparedPackagedDistribution distribution = prepareReusableOfficialPackagedDistribution(RuntimeTransport.HTTP);
try (
PackagedDistributionHttpRuntime runtime = new PackagedDistributionHttpRuntime(distribution);
MCPInteractionClient interactionClient = runtime.openInteractionClient()) {
@@ -117,7 +117,7 @@ class PackagedDistributionE2ETest {
@Test
void assertLaunchPackagedDistributionOverStdio() throws IOException, InterruptedException, SQLException {
- PreparedPackagedDistribution distribution = preparePackagedDistribution("stdio", RuntimeTransport.STDIO);
+ PreparedPackagedDistribution distribution = prepareReusableOfficialPackagedDistribution(RuntimeTransport.STDIO);
try (MCPInteractionClient interactionClient = new PackagedDistributionStdioInteractionClient(distribution.home(), distribution.configFile())) {
interactionClient.open();
assertOfficialRuntime(distribution.home(), RuntimeTransport.STDIO, interactionClient);
@@ -174,9 +174,15 @@ class PackagedDistributionE2ETest {
MySQLRuntimeTestSupport.createRuntimeDatabases(mysqlContainer, LOGICAL_DATABASE_NAME));
}
+ private PreparedPackagedDistribution prepareReusableOfficialPackagedDistribution(final RuntimeTransport transport) throws IOException, SQLException {
+ prepareMySQLContainer();
+ return PackagedDistributionTestSupport.prepareReusable(tempDir.resolve("official-distribution-home-" + transport.name().toLowerCase(Locale.ENGLISH)), transport,
+ MySQLRuntimeTestSupport.createRuntimeDatabases(mysqlContainer, LOGICAL_DATABASE_NAME));
+ }
+
private Path createDockerConfigurationFile(final RuntimeTransport transport) throws IOException, SQLException {
prepareMySQLContainer();
- return PackagedDistributionTestSupport.createDockerConfigurationFile(tempDir.resolve("container-" + transport.name().toLowerCase() + ".yaml"), transport,
+ return PackagedDistributionTestSupport.createDockerConfigurationFile(tempDir.resolve("container-" + transport.name().toLowerCase(Locale.ENGLISH) + ".yaml"), transport,
MySQLRuntimeTestSupport.createDockerHostRuntimeDatabases(mysqlContainer, LOGICAL_DATABASE_NAME));
}
@@ -307,8 +313,8 @@ class PackagedDistributionE2ETest {
private void assertFixtureResource(final Map<String, Object> payload) {
List<Map<String, Object>> actualItems = MCPInteractionPayloads.castToList(payload.get("items"));
assertThat(actualItems.size(), is(1));
- assertThat(actualItems.get(0).get("feature"), is("test-fixture"));
- assertThat(actualItems.get(0).get("status"), is("ready"));
+ assertThat(actualItems.getFirst().get("feature"), is("test-fixture"));
+ assertThat(actualItems.getFirst().get("status"), is("ready"));
}
private void assertCapabilities(final Map<String, Object> payload) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
index 165bcf7f0e0..f9ae572a6ba 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLRuntimeE2ETest.java
@@ -40,8 +40,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledIf("isEnabled")
class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETest {
+ @Override
+ protected boolean useSharedRuntimeFixture() {
+ return true;
+ }
+
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertReadCapabilitiesWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -52,7 +57,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertReadDatabasesResourceWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -61,7 +66,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertServiceCapabilitiesResourceWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -70,7 +75,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertListToolsWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -84,7 +89,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertListResourcesWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -93,7 +98,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertListResourceTemplatesWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -106,7 +111,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectUnsupportedResourceUriWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -119,7 +124,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectUnsupportedToolNameWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -131,7 +136,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertInitializeExposesMarkdownInstructionsWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -143,7 +148,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertServerInstructionsAreNotListedAsResourceWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -173,7 +178,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertReadTableDetailWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -188,7 +193,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertSearchMetadataTablesAndViewsWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -199,7 +204,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertReadViewsWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -211,7 +216,7 @@ class ProductionMySQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETes
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertReadIndexesWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLSQLRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLSQLRuntimeE2ETest.java
index f3b1f79528f..a25269bb910 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLSQLRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionMySQLSQLRuntimeE2ETest.java
@@ -46,7 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2ETest {
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteSelectWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -57,7 +57,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteSelectWithTruncationWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -70,7 +70,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteExplainAnalyzeSelectWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -83,7 +83,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteQueryTimeoutWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -94,7 +94,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectExecuteMultiStatementWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -105,7 +105,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectExplainAnalyzeUpdateFromReadOnlyToolWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -119,7 +119,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectLockingReadFromReadOnlyToolWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -130,7 +130,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectLockingReadFromUpdateToolWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -141,7 +141,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("dualTransports")
void assertElicitMaskPlanningWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException {
useTransport(transport);
List<McpSchema.ElicitRequest> actualElicitationRequests = new CopyOnWriteArrayList<>();
@@ -170,7 +170,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteUpdateWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws SQLException, IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -183,7 +183,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteUpdateWithoutApprovalArgumentWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -195,7 +195,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectSequenceResourceWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -207,7 +207,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteRollbackWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws SQLException, IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -222,7 +222,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectBlankSavepointNameWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -232,7 +232,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteSavepointFlowWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws SQLException, IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -252,7 +252,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteReleaseSavepointWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -267,7 +267,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertExecuteDdlRefreshesMetadataWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -281,7 +281,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertCloseRollsBackPendingTransactionWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws SQLException, IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -294,7 +294,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertAiNativeDeterministicInteractionLoopWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
@@ -313,7 +313,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertListDatabasesWithMultipleRuntimeDatabasesWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient(createPreparedProgrammaticRuntimeDatabases())) {
@@ -324,7 +324,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRefreshMetadataVisibleForTargetDatabaseOnlyWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient firstInteractionClient = createOpenedInteractionClient(createPreparedProgrammaticRuntimeDatabases())) {
@@ -343,7 +343,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("semanticPrimaryTransport")
void assertRejectCrossDatabaseTransactionSwitchWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
useTransport(transport);
try (MCPInteractionClient interactionClient = createOpenedInteractionClient(createPreparedProgrammaticRuntimeDatabases())) {
@@ -355,7 +355,7 @@ class ProductionMySQLSQLRuntimeE2ETest extends AbstractProductionMySQLRuntimeE2E
}
@ParameterizedTest(name = "{0}")
- @MethodSource("transports")
+ @MethodSource("dualTransports")
void assertRejectMismatchedDatabaseTypeWithActualMySQLBackend(final String name, final RuntimeTransport transport) {
useTransport(transport);
if (RuntimeTransport.HTTP == transport) {
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionRuntimeTransportSmokeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionRuntimeTransportSmokeE2ETest.java
new file mode 100644
index 00000000000..11f3920d6b5
--- /dev/null
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/ProductionRuntimeTransportSmokeE2ETest.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.shardingsphere.test.e2e.mcp.runtime.production;
+
+import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
+import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPPayloadAssertions;
+import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPInteractionClient;
+import org.junit.jupiter.api.condition.EnabledIf;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+@EnabledIf("isEnabled")
+class ProductionRuntimeTransportSmokeE2ETest extends AbstractProductionMySQLRuntimeE2ETest {
+
+ @Override
+ protected boolean useSharedRuntimeFixture() {
+ return true;
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("dualTransports")
+ void assertTransportLifecycleWithActualMySQLBackend(final String name, final RuntimeTransport transport) throws IOException, InterruptedException {
+ useTransport(transport);
+ try (MCPInteractionClient interactionClient = createOpenedInteractionClient()) {
+ assertOfficialToolNames(interactionClient.listTools().stream().map(each -> String.valueOf(each.get("name"))).toList());
+ MCPPayloadAssertions.assertSingleItemValue(interactionClient.readResource("shardingsphere://databases"), "database", LOGICAL_DATABASE_NAME);
+ Map<String, Object> actual = interactionClient.call("database_gateway_execute_query",
+ Map.of("database", LOGICAL_DATABASE_NAME, "schema", LOGICAL_DATABASE_NAME, "sql", "SELECT status FROM orders WHERE order_id = 1", "max_rows", 1));
+ assertThat(String.valueOf(actual.get("result_kind")), is("result_set"));
+ }
+ }
+}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
index f7584ad3eca..b73b2efc603 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
@@ -47,8 +47,15 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
private Map<String, RuntimeDatabaseConfiguration> runtimeDatabases;
+ private ProgrammaticRuntimeFixture sharedRuntimeFixture;
+
@AfterEach
void tearDownContainer() {
+ if (useSharedDatabaseBackedRuntime()) {
+ container = null;
+ runtimeDatabases = null;
+ return;
+ }
if (null != container) {
container.stop();
container = null;
@@ -96,10 +103,6 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
return sendJsonRpcRequest(httpClient, createSessionHeaders(sessionId), "resource-1", "resources/read", Map.of("uri", resourceUri));
}
- protected final HttpResponse<String> sendCapabilitiesRequest(final HttpClient httpClient, final Map<String, String> headers) throws IOException, InterruptedException {
- return sendJsonRpcRequest(httpClient, headers, "resource-1", "resources/read", Map.of("uri", "shardingsphere://capabilities"));
- }
-
protected final HttpResponse<String> sendDeleteRequest(final HttpClient httpClient, final Map<String, String> headers) throws IOException, InterruptedException {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(getEndpointUri()).DELETE();
applyHeaders(requestBuilder, headers);
@@ -114,12 +117,6 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
return httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
}
- protected final HttpResponse<String> openEventStream(final HttpClient httpClient, final Map<String, String> headers) throws IOException, InterruptedException {
- HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(getEndpointUri()).GET();
- applyHeaders(requestBuilder, headers);
- return httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
- }
-
protected final Map<String, Object> getStructuredContent(final String responseBody) {
Map<String, Object> payload = MCPInteractionPayloads.parseJsonPayload(responseBody);
return MCPInteractionPayloads.hasJsonRpcError(payload) ? MCPInteractionPayloads.getJsonRpcErrorPayload(payload) : MCPInteractionPayloads.getStructuredContent(payload);
@@ -174,12 +171,50 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
protected final void prepareRuntimeFixture() throws IOException {
Assumptions.assumeTrue(MySQLRuntimeTestSupport.isDockerAvailable(),
() -> MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required for the MySQL-backed MCP programmatic contract E2E test."));
- container = MySQLRuntimeTestSupport.createContainer();
- container.start();
+ if (useSharedDatabaseBackedRuntime()) {
+ prepareSharedDatabaseBackedRuntime();
+ return;
+ }
+ applyRuntimeFixture(createRuntimeFixture());
+ }
+
+ private void prepareSharedDatabaseBackedRuntime() throws IOException {
+ if (null == sharedRuntimeFixture) {
+ sharedRuntimeFixture = createRuntimeFixture();
+ }
+ applyRuntimeFixture(sharedRuntimeFixture);
+ }
+
+ private ProgrammaticRuntimeFixture createRuntimeFixture() throws IOException {
+ GenericContainer<?> result = MySQLRuntimeTestSupport.createContainer();
+ boolean success = false;
try {
- runtimeDatabases = MySQLRuntimeTestSupport.createPreparedProgrammaticRuntimeDatabases(container);
+ result.start();
+ Map<String, RuntimeDatabaseConfiguration> actualRuntimeDatabases = MySQLRuntimeTestSupport.createPreparedProgrammaticRuntimeDatabases(result);
+ success = true;
+ return new ProgrammaticRuntimeFixture(result, actualRuntimeDatabases);
} catch (final SQLException ex) {
throw new IOException("Failed to initialize MCP E2E runtime databases.", ex);
+ } finally {
+ if (!success) {
+ result.stop();
+ }
+ }
+ }
+
+ private void applyRuntimeFixture(final ProgrammaticRuntimeFixture fixture) {
+ container = fixture.container();
+ runtimeDatabases = fixture.runtimeDatabases();
+ }
+
+ protected boolean useSharedDatabaseBackedRuntime() {
+ return false;
+ }
+
+ protected void closeSharedDatabaseBackedRuntime() {
+ if (null != sharedRuntimeFixture) {
+ sharedRuntimeFixture.close();
+ sharedRuntimeFixture = null;
}
}
@@ -191,4 +226,29 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
private void applyHeaders(final HttpRequest.Builder requestBuilder, final Map<String, String> headers) {
headers.forEach(requestBuilder::setHeader);
}
+
+ private static final class ProgrammaticRuntimeFixture implements AutoCloseable {
+
+ private final GenericContainer<?> container;
+
+ private final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases;
+
+ private ProgrammaticRuntimeFixture(final GenericContainer<?> container, final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases) {
+ this.container = container;
+ this.runtimeDatabases = runtimeDatabases;
+ }
+
+ private GenericContainer<?> container() {
+ return container;
+ }
+
+ private Map<String, RuntimeDatabaseConfiguration> runtimeDatabases() {
+ return runtimeDatabases;
+ }
+
+ @Override
+ public void close() {
+ container.stop();
+ }
+ }
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProtocolOnlyE2ETest.java
similarity index 62%
copy from test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
copy to test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProtocolOnlyE2ETest.java
index f7584ad3eca..2dc53a275bd 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProgrammaticRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractHttpProtocolOnlyE2ETest.java
@@ -17,47 +17,51 @@
package org.apache.shardingsphere.test.e2e.mcp.runtime.programmatic;
-import org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
-import org.apache.shardingsphere.test.e2e.mcp.support.runtime.AbstractConfigBackedRuntimeE2ETest;
-import org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport;
-import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
+import org.apache.shardingsphere.mcp.bootstrap.config.HttpTransportConfiguration;
+import org.apache.shardingsphere.mcp.bootstrap.transport.server.http.StreamableHttpMCPServer;
+import org.apache.shardingsphere.mcp.core.context.MCPRuntimeContext;
+import org.apache.shardingsphere.mcp.core.session.MCPSessionManager;
+import org.apache.shardingsphere.mcp.support.database.capability.MCPDatabaseCapabilityProvider;
import org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionPayloads;
import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPHttpTransportTestSupport;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assumptions;
-import org.testcontainers.containers.GenericContainer;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
-import java.sql.SQLException;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBackedRuntimeE2ETest {
+abstract class AbstractHttpProtocolOnlyE2ETest {
- private static final String CLIENT_NAME = "mcp-e2e-programmatic";
+ private static final String LOOPBACK_BIND_HOST = "127.0.0.1";
+
+ private static final String ENDPOINT_PATH = "/gateway";
- private GenericContainer<?> container;
+ private static final String CLIENT_NAME = "mcp-e2e-programmatic";
- private Map<String, RuntimeDatabaseConfiguration> runtimeDatabases;
+ private StreamableHttpMCPServer httpServer;
@AfterEach
- void tearDownContainer() {
- if (null != container) {
- container.stop();
- container = null;
+ void tearDownHttpServer() {
+ if (null != httpServer) {
+ httpServer.stop();
+ httpServer = null;
}
- runtimeDatabases = null;
}
protected final void launchHttpTransport() throws IOException {
- prepareRuntime();
+ if (null != httpServer) {
+ return;
+ }
+ httpServer = new StreamableHttpMCPServer(createHttpTransportConfiguration(), createRuntimeContext());
+ httpServer.start();
}
protected final String initializeSession(final HttpClient httpClient) throws IOException, InterruptedException {
@@ -86,16 +90,6 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
MCPHttpTransportTestSupport.createJsonRpcNotificationBody("notifications/initialized", Map.of()));
}
- protected final HttpResponse<String> sendToolCallRequest(final HttpClient httpClient, final String sessionId,
- final String toolName, final Map<String, Object> arguments) throws IOException, InterruptedException {
- return sendJsonRpcRequest(httpClient, createSessionHeaders(sessionId), toolName + "-1", "tools/call", Map.of("name", toolName, "arguments", arguments));
- }
-
- protected final HttpResponse<String> sendResourceReadRequest(final HttpClient httpClient, final String sessionId,
- final String resourceUri) throws IOException, InterruptedException {
- return sendJsonRpcRequest(httpClient, createSessionHeaders(sessionId), "resource-1", "resources/read", Map.of("uri", resourceUri));
- }
-
protected final HttpResponse<String> sendCapabilitiesRequest(final HttpClient httpClient, final Map<String, String> headers) throws IOException, InterruptedException {
return sendJsonRpcRequest(httpClient, headers, "resource-1", "resources/read", Map.of("uri", "shardingsphere://capabilities"));
}
@@ -120,16 +114,6 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
return httpClient.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
}
- protected final Map<String, Object> getStructuredContent(final String responseBody) {
- Map<String, Object> payload = MCPInteractionPayloads.parseJsonPayload(responseBody);
- return MCPInteractionPayloads.hasJsonRpcError(payload) ? MCPInteractionPayloads.getJsonRpcErrorPayload(payload) : MCPInteractionPayloads.getStructuredContent(payload);
- }
-
- protected final Map<String, Object> getFirstResourcePayload(final String responseBody) {
- Map<String, Object> payload = MCPInteractionPayloads.parseJsonPayload(responseBody);
- return MCPInteractionPayloads.hasJsonRpcError(payload) ? MCPInteractionPayloads.getJsonRpcErrorPayload(payload) : MCPInteractionPayloads.getFirstResourcePayload(payload);
- }
-
protected final Map<String, Object> parseJsonBody(final String responseBody) {
return MCPInteractionPayloads.parseJsonPayload(responseBody);
}
@@ -138,15 +122,11 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
return MCPInteractionPayloads.castToMap(value);
}
- protected final Map<String, Object> getRecoveryPayload(final Map<String, Object> payload, final String expectedRecoveryCategory) {
- assertThat(String.valueOf(payload.get("response_mode")), is("recovery"));
- Map<String, Object> result = castToMap(payload.get("recovery"));
- assertThat(String.valueOf(result.get("recovery_category")), is(expectedRecoveryCategory));
- return result;
- }
-
- protected final URI getEndpointUri() throws IOException {
- return getHttpEndpointUri();
+ protected final URI getEndpointUri() {
+ if (null == httpServer) {
+ throw new IllegalStateException("HTTP transport is not enabled for current protocol E2E test.");
+ }
+ return URI.create(String.format("http://%s:%d%s", LOOPBACK_BIND_HOST, httpServer.getLocalPort(), getHttpEndpointPath()));
}
protected final String getProtocolVersion() {
@@ -160,27 +140,12 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
return result;
}
- @Override
- protected final RuntimeTransport getTransport() {
- return RuntimeTransport.HTTP;
+ protected HttpTransportConfiguration createHttpTransportConfiguration() {
+ return new HttpTransportConfiguration(LOOPBACK_BIND_HOST, 0, getHttpEndpointPath());
}
- @Override
- protected final Map<String, RuntimeDatabaseConfiguration> getRuntimeDatabases() {
- return runtimeDatabases;
- }
-
- @Override
- protected final void prepareRuntimeFixture() throws IOException {
- Assumptions.assumeTrue(MySQLRuntimeTestSupport.isDockerAvailable(),
- () -> MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required for the MySQL-backed MCP programmatic contract E2E test."));
- container = MySQLRuntimeTestSupport.createContainer();
- container.start();
- try {
- runtimeDatabases = MySQLRuntimeTestSupport.createPreparedProgrammaticRuntimeDatabases(container);
- } catch (final SQLException ex) {
- throw new IOException("Failed to initialize MCP E2E runtime databases.", ex);
- }
+ protected String getHttpEndpointPath() {
+ return ENDPOINT_PATH;
}
private HttpResponse<String> sendJsonRpcRequest(final HttpClient httpClient, final Map<String, String> headers, final String requestId,
@@ -191,4 +156,8 @@ abstract class AbstractHttpProgrammaticRuntimeE2ETest extends AbstractConfigBack
private void applyHeaders(final HttpRequest.Builder requestBuilder, final Map<String, String> headers) {
headers.forEach(requestBuilder::setHeader);
}
+
+ private MCPRuntimeContext createRuntimeContext() {
+ return new MCPRuntimeContext(new MCPSessionManager(Collections.emptyMap()), new MCPDatabaseCapabilityProvider(Collections.emptyMap()), "http");
+ }
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractSharedHttpProgrammaticRuntimeE2ETest.java
similarity index 56%
copy from test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java
copy to test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractSharedHttpProgrammaticRuntimeE2ETest.java
index 7b6b66a1ebf..a2728e221a5 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/production/AbstractTransportParameterizedProductionRuntimeE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/AbstractSharedHttpProgrammaticRuntimeE2ETest.java
@@ -15,23 +15,21 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.test.e2e.mcp.runtime.production;
+package org.apache.shardingsphere.test.e2e.mcp.runtime.programmatic;
-import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.TestInstance;
-abstract class AbstractTransportParameterizedProductionRuntimeE2ETest extends AbstractProductionRuntimeE2ETest {
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+abstract class AbstractSharedHttpProgrammaticRuntimeE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
- private RuntimeTransport transport;
-
- protected final void useTransport(final RuntimeTransport transport) {
- this.transport = transport;
+ @Override
+ protected boolean useSharedDatabaseBackedRuntime() {
+ return true;
}
- @Override
- protected final RuntimeTransport getTransport() {
- if (null == transport) {
- throw new IllegalStateException("Runtime transport is not selected for current production E2E test.");
- }
- return transport;
+ @AfterAll
+ void tearDownSharedDatabaseBackedRuntime() {
+ closeSharedDatabaseBackedRuntime();
}
}
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/FeatureWorkflowContractE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/FeatureWorkflowContractE2ETest.java
index 8a59e4faae2..91513ca75c0 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/FeatureWorkflowContractE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/FeatureWorkflowContractE2ETest.java
@@ -38,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledIf("isEnabled")
-class FeatureWorkflowContractE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class FeatureWorkflowContractE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static final List<String> FORBIDDEN_ARTIFACT_TOKENS = List.of(
"create table", "alter table", "drop table", "create index", "drop index", "migrate", "migration", "backfill", "data probe", "physical metadata",
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 1020d66662d..2b97d688b4b 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
@@ -35,7 +35,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@EnabledIf("isEnabled")
-class HttpTransportApprovalSafetyE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class HttpTransportApprovalSafetyE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static boolean isEnabled() {
return MCPE2ECondition.isDockerEnabled();
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
index fef006781dd..469d5e2322d 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportBaselineContractE2ETest.java
@@ -35,7 +35,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@EnabledIf("isEnabled")
-class HttpTransportBaselineContractE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class HttpTransportBaselineContractE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static final String BASELINE_RESOURCE_PATH = "baseline-contract/model-contract/";
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
index 55ef7d8cadc..038284dcf3e 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportCompletionE2ETest.java
@@ -36,7 +36,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledIf("isEnabled")
-class HttpTransportCompletionE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class HttpTransportCompletionE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static final String PLAN_MASK_PROMPT_NAME = "plan_mask_rule";
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 341749ca36c..9c6dcc7282a 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
@@ -41,7 +41,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledIf("isEnabled")
-class HttpTransportContractE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class HttpTransportContractE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static final List<String> OFFICIAL_TOOL_NAMES = OfficialMCPToolNames.getAll();
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 10f27c01995..05db95835de 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
@@ -17,10 +17,8 @@
package org.apache.shardingsphere.test.e2e.mcp.runtime.programmatic;
-import org.apache.shardingsphere.test.e2e.mcp.env.MCPE2ECondition;
import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPHttpTransportTestSupport;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledIf;
import java.io.IOException;
import java.net.http.HttpClient;
@@ -34,12 +32,7 @@ import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
-@EnabledIf("isEnabled")
-class HttpTransportProtocolContractE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
-
- private static boolean isEnabled() {
- return MCPE2ECondition.isDockerEnabled();
- }
+class HttpTransportProtocolContractE2ETest extends AbstractHttpProtocolOnlyE2ETest {
@Test
void assertInitializeSessionAndProtocolHeaders() throws IOException, InterruptedException {
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 833e7776d83..b5b1d10603d 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
@@ -38,7 +38,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@EnabledIf("isEnabled")
-class HttpTransportRecoveryE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class HttpTransportRecoveryE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static final String RECOVERY_SECRET = "recovery-secret-value";
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 1f5124d3481..9d02d1fcb85 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
@@ -18,10 +18,8 @@
package org.apache.shardingsphere.test.e2e.mcp.runtime.programmatic;
import org.apache.shardingsphere.mcp.bootstrap.config.HttpTransportConfiguration;
-import org.apache.shardingsphere.test.e2e.mcp.env.MCPE2ECondition;
import org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPHttpTransportTestSupport;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.condition.EnabledIf;
import java.io.IOException;
import java.net.http.HttpClient;
@@ -33,15 +31,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
-@EnabledIf("isEnabled")
-class HttpTransportSecurityE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class HttpTransportSecurityE2ETest extends AbstractHttpProtocolOnlyE2ETest {
private boolean remoteBinding;
- private static boolean isEnabled() {
- return MCPE2ECondition.isDockerEnabled();
- }
-
@Override
protected HttpTransportConfiguration createHttpTransportConfiguration() {
return remoteBinding ? new HttpTransportConfiguration("0.0.0.0", 0, getHttpEndpointPath()) : super.createHttpTransportConfiguration();
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
index ff41ec73476..86671d5cf54 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/runtime/programmatic/HttpTransportSessionLifecycleE2ETest.java
@@ -17,9 +17,7 @@
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 java.io.IOException;
import java.net.http.HttpClient;
@@ -29,12 +27,7 @@ import java.util.Map;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
-@EnabledIf("isEnabled")
-class HttpTransportSessionLifecycleE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
-
- private static boolean isEnabled() {
- return MCPE2ECondition.isDockerEnabled();
- }
+class HttpTransportSessionLifecycleE2ETest extends AbstractHttpProtocolOnlyE2ETest {
@Test
void assertRejectOpenStreamAfterDelete() throws IOException, InterruptedException {
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 85654d7847b..bb6a2e84005 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
@@ -33,7 +33,7 @@ import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertFalse;
@EnabledIf("isEnabled")
-class MetadataDiscoveryE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
+class MetadataDiscoveryE2ETest extends AbstractSharedHttpProgrammaticRuntimeE2ETest {
private static boolean isEnabled() {
return MCPE2ECondition.isDockerEnabled();
@@ -61,7 +61,7 @@ class MetadataDiscoveryE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
"shardingsphere://databases/logic_db/schemas/logic_db/tables/orders/indexes")));
HttpResponse<String> tableResource = sendResourceReadRequest(httpClient, sessionId, String.valueOf(actualResource.get("uri")));
assertThat(tableResource.statusCode(), is(200));
- assertThat(String.valueOf(MCPInteractionPayloads.castToList(getFirstResourcePayload(tableResource.body()).get("items")).get(0).get("table")), is("orders"));
+ assertThat(String.valueOf(MCPInteractionPayloads.castToList(getFirstResourcePayload(tableResource.body()).get("items")).getFirst().get("table")), is("orders"));
}
@Test
@@ -96,8 +96,8 @@ class MetadataDiscoveryE2ETest extends AbstractHttpProgrammaticRuntimeE2ETest {
assertThat(actual.statusCode(), is(200));
List<Map<String, Object>> actualItems = MCPInteractionPayloads.castToList(getFirstResourcePayload(actual.body()).get("items"));
assertThat(actualItems.size(), is(1));
- assertThat(String.valueOf(actualItems.get(0).get("table")), is("facts"));
- assertThat(String.valueOf(actualItems.get(0).get("index")), is("PRIMARY"));
+ assertThat(String.valueOf(actualItems.getFirst().get("table")), is("facts"));
+ assertThat(String.valueOf(actualItems.getFirst().get("index")), is("PRIMARY"));
}
@Test
diff --git a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java
index 0b00365e19a..95343e05c36 100644
--- a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java
+++ b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionTestSupport.java
@@ -89,6 +89,23 @@ public final class PackagedDistributionTestSupport {
return new PreparedPackagedDistribution(workingHome, configFile, transport, httpPort);
}
+ /**
+ * Prepare a reusable packaged MCP distribution home with supplied runtime databases.
+ *
+ * @param workingHome reusable working home
+ * @param transport runtime transport
+ * @param runtimeDatabases runtime databases
+ * @return prepared packaged distribution
+ * @throws IOException I/O exception
+ */
+ public static PreparedPackagedDistribution prepareReusable(final Path workingHome, final RuntimeTransport transport,
+ final Map<String, RuntimeDatabaseConfiguration> runtimeDatabases) throws IOException {
+ Path actualWorkingHome = prepareReusableWorkingHome(workingHome);
+ int httpPort = resolveHttpPort(transport);
+ Path configFile = rewriteConfiguration(actualWorkingHome, transport, httpPort, runtimeDatabases);
+ return new PreparedPackagedDistribution(actualWorkingHome, configFile, transport, httpPort);
+ }
+
/**
* Create a mounted configuration file for a Dockerized MCP distribution.
*
@@ -130,6 +147,15 @@ public final class PackagedDistributionTestSupport {
return workingHome;
}
+ private static Path prepareReusableWorkingHome(final Path workingHome) throws IOException {
+ if (!Files.exists(workingHome)) {
+ copyDistributionHome(findRequiredDistributionHome(), workingHome);
+ }
+ resetRuntimeDirectories(workingHome);
+ makeStartScriptExecutable(workingHome);
+ return workingHome;
+ }
+
private static Path findRequiredDistributionHome() throws IOException {
return findDistributionHome().orElseThrow(PackagedDistributionTestSupport::createMissingDistributionHomeException);
}