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 29f273621e3 Refine MCP E2E coverage and diagnostics (#39281)
29f273621e3 is described below
commit 29f273621e304c1d7133f7bc919c9b8b61100f9e
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Jul 30 21:59:36 2026 +0800
Refine MCP E2E coverage and diagnostics (#39281)
* Refine MCP E2E coverage and diagnostics
- limit PR triggers to MCP-owned paths
- add STDIO parity and persist bounded redacted runtime artifacts
- simplify E2E fixtures and remove low-value test-only checks
* Refine MCP E2E coverage and diagnostics
- limit PR triggers to MCP-owned paths
- add STDIO parity and persist bounded redacted runtime artifacts
- simplify E2E fixtures and remove low-value test-only checks
---
.github/workflows/e2e-mcp.yml | 13 +--
.../HttpProxySecretReferenceWorkflowE2ETest.java | 19 +++-
.../mcp/functionality/MySQLReadOnlySQLE2ETest.java | 2 +-
.../functionality/PackagedDistributionE2ETest.java | 9 +-
.../test/e2e/mcp/llm/LLMHttpE2ETest.java | 60 +++++++---
.../artifact/LLMConversationArtifactWriter.java | 30 ++---
.../artifact/LLME2EArtifactRedactor.java | 35 ------
.../mcp/llm/fixture/LLMRuntimeFixtureFactory.java | 101 -----------------
.../llm/fixture/LLMRuntimeFixtureFactoryTest.java | 42 -------
.../test/e2e/mcp/support/OfficialMCPToolNames.java | 43 --------
.../e2e/mcp/support/artifact/MCPArtifactUtils.java | 121 +++++++++++++++++++++
.../distribution/DockerImageHttpRuntime.java | 2 +
.../PackagedDistributionProcessSupport.java | 3 +
...PWorkflowCustomEncryptAlgorithmFixtureTest.java | 65 -----------
.../MCPWorkflowCustomMaskAlgorithmFixtureTest.java | 36 ------
.../fixture/MCPWorkflowSecretReferenceFixture.java | 56 ----------
.../plugin/PluginFixtureHandlerProviderTest.java | 51 ---------
.../plugin/PluginFixturePingToolHandlerTest.java | 34 ------
.../PluginFixtureStatusResourceHandlerTest.java | 36 ------
.../transport/MCPPayloadAssertionsTest.java | 53 ---------
.../AbstractProcessMCPStdioInteractionClient.java | 2 +
.../resources/proxy/workflow/cluster/global.yaml | 2 +
22 files changed, 214 insertions(+), 601 deletions(-)
diff --git a/.github/workflows/e2e-mcp.yml b/.github/workflows/e2e-mcp.yml
index a81396d0815..bd470111a7c 100644
--- a/.github/workflows/e2e-mcp.yml
+++ b/.github/workflows/e2e-mcp.yml
@@ -27,15 +27,8 @@ on:
- ready_for_review
paths:
- '.github/workflows/e2e-mcp.yml'
- - '.github/workflows/resources/actions/download-e2e-artifacts/**'
- - '.github/workflows/resources/actions/setup-build-environment/**'
- - '.github/workflows/resources/actions/upload-e2e-artifacts/**'
- - 'pom.xml'
- - 'database/connector/**'
- 'distribution/mcp/**'
- 'mcp/**'
- - 'test/e2e/pom.xml'
- - 'test/e2e/env/**'
- 'test/e2e/mcp/**'
schedule:
- cron: '0 19 * * 1-5'
@@ -161,13 +154,15 @@ jobs:
mkdir -p distribution/mcp/target
tar -xzf
"${RUNNER_TEMP}/mcp-e2e-build-artifacts/mcp-distribution-home.tar.gz" -C
distribution/mcp/target
- name: Run MCP Functionality E2E
- run: ./mvnw -pl test/e2e/mcp test -Pe2e.mcp.functionality
-Dmcp.e2e.container.image=${{ env.MCP_DISTRIBUTION_IMAGE }} -DskipITs
-Dspotless.skip=true -B -ntp
+ run: ./mvnw -pl test/e2e/mcp test -Pe2e.mcp.functionality
-Dmcp.e2e.container.image=${{ env.MCP_DISTRIBUTION_IMAGE }}
-Dmcp.e2e.artifact-root="${{ github.workspace
}}/test/e2e/mcp/target/functionality-e2e" -DskipITs -Dspotless.skip=true -B -ntp
- name: Upload MCP Functionality E2E Artifacts
if: always()
uses: actions/upload-artifact@v6
with:
name: e2e-mcp-functionality-${{ github.run_id }}-${{
github.run_attempt }}
- path: test/e2e/mcp/target/surefire-reports
+ path: |
+ test/e2e/mcp/target/surefire-reports
+ test/e2e/mcp/target/functionality-e2e
if-no-files-found: warn
e2e-mcp-conformance:
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
index a4a23652c91..f53ad351484 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/HttpProxySecretReferenceWorkflowE2ETest.java
@@ -19,7 +19,6 @@ package org.apache.shardingsphere.test.e2e.mcp.functionality;
import org.apache.shardingsphere.mcp.support.diagnostic.MCPDiagnosticCategory;
import
org.apache.shardingsphere.mcp.support.workflow.descriptor.WorkflowToolDescriptors;
-import
org.apache.shardingsphere.test.e2e.mcp.support.fixture.MCPWorkflowSecretReferenceFixture;
import
org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPInteractionClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
@@ -43,6 +42,10 @@ class HttpProxySecretReferenceWorkflowE2ETest extends
AbstractHttpProxyWorkflowE
private static final String RULES_RESOURCE_URI =
"shardingsphere://features/encrypt/databases/%s/rules";
+ private static final String SECRET_REF = "placeholder://secret-value-1";
+
+ private static final String INPUT_LABEL = "placeholder-secret-1";
+
@Test
void assertSecretReferenceApplyRequiresManualExecution() throws
IOException, InterruptedException {
try (MCPInteractionClient interactionClient =
createOpenedInteractionClient()) {
@@ -57,7 +60,7 @@ class HttpProxySecretReferenceWorkflowE2ETest extends
AbstractHttpProxyWorkflowE
assertThat(String.valueOf(applyResponse.get("category")),
is(MCPDiagnosticCategory.SECRET_REFERENCE_MANUAL_EXECUTION_REQUIRED));
assertModelFacingPayloadContract(applyResponse);
assertThat(getObjectListOrEmpty(applyResponse.get("step_results")).size(),
is(0));
-
MCPWorkflowSecretReferenceFixture.assertSecretReferenceRedacted(applyResponse);
+ assertSecretReferenceRedacted(applyResponse);
assertTrue(getPayloadItems(interactionClient.readResource(String.format(RULES_RESOURCE_URI,
getLogicalDatabaseName()))).isEmpty());
}
}
@@ -67,7 +70,7 @@ class HttpProxySecretReferenceWorkflowE2ETest extends
AbstractHttpProxyWorkflowE
Map.of("database", getLogicalDatabaseName(), "table",
"orders", "column", "status",
"natural_language_intent", "encrypt status with
reversible encryption, no equality, no like", "algorithm_type", "AES",
"cipher_column_name", "status_cipher",
- "primary_algorithm_properties",
Map.of("aes-key-value",
MCPWorkflowSecretReferenceFixture.createSecretReferenceInput())));
+ "primary_algorithm_properties",
Map.of("aes-key-value", Map.of("secret_ref", SECRET_REF, "label",
INPUT_LABEL))));
}
private void assertPlannedSecretReferencePayload(final Map<String, Object>
planResponse) {
@@ -81,7 +84,7 @@ class HttpProxySecretReferenceWorkflowE2ETest extends
AbstractHttpProxyWorkflowE
assertThat(String.valueOf(secretReference.get("property_key")),
is("aes-key-value"));
assertThat(String.valueOf(secretReference.get("label")),
is("secret_placeholder:primary.aes-key-value"));
assertThat(String.valueOf(secretReference.get("manual_placeholder")),
is("<SECRET_VALUE_PRIMARY_AES_KEY_VALUE>"));
-
MCPWorkflowSecretReferenceFixture.assertSecretReferenceRedacted(planResponse);
+ assertSecretReferenceRedacted(planResponse);
}
private void assertSecretReferencedPreview(final Map<String, Object>
previewResponse) {
@@ -91,6 +94,12 @@ class HttpProxySecretReferenceWorkflowE2ETest extends
AbstractHttpProxyWorkflowE
assertThat(previewArtifacts.size(), is(1));
assertThat(String.valueOf(previewArtifacts.getFirst().get("sql")),
containsString("'aes-key-value'='<SECRET_VALUE_PRIMARY_AES_KEY_VALUE>'"));
assertFalse(String.valueOf(previewArtifacts.getFirst().get("sql")).contains("secret_reference:primary.aes-key-value"));
-
MCPWorkflowSecretReferenceFixture.assertSecretReferenceRedacted(previewResponse);
+ assertSecretReferenceRedacted(previewResponse);
+ }
+
+ private void assertSecretReferenceRedacted(final Object payload) {
+ String actual = String.valueOf(payload);
+ assertFalse(actual.contains(SECRET_REF));
+ assertFalse(actual.contains(INPUT_LABEL));
}
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
index e631abd8775..02364479502 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/MySQLReadOnlySQLE2ETest.java
@@ -138,7 +138,7 @@ class MySQLReadOnlySQLE2ETest extends
AbstractMySQLRuntimeE2ETest {
}
@ParameterizedTest(name = "{0}")
- @MethodSource("httpTransportCase")
+ @MethodSource("allTransportCases")
void assertAiNativeDeterministicInteractionLoop(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/functionality/PackagedDistributionE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
index d69b8b50240..7ee136b7b66 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/functionality/PackagedDistributionE2ETest.java
@@ -17,8 +17,9 @@
package org.apache.shardingsphere.test.e2e.mcp.functionality;
+import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
+import org.apache.shardingsphere.mcp.core.tool.handler.ToolDefinitionRegistry;
import
org.apache.shardingsphere.test.e2e.env.runtime.EnvironmentPropertiesLoader;
-import org.apache.shardingsphere.test.e2e.mcp.support.OfficialMCPToolNames;
import
org.apache.shardingsphere.test.e2e.mcp.support.distribution.DockerImageHttpRuntime;
import
org.apache.shardingsphere.test.e2e.mcp.support.distribution.PackagedDistributionHttpRuntime;
import
org.apache.shardingsphere.test.e2e.mcp.support.distribution.PackagedDistributionPluginFixtureSupport;
@@ -75,7 +76,9 @@ class PackagedDistributionE2ETest {
"database_gateway_execute_query",
"database_gateway_execute_explain_query", "database_gateway_execute_update",
"database_gateway_apply_workflow",
"database_gateway_validate_workflow");
- private static final List<String> REMOVED_FEATURE_TOOL_NAMES =
OfficialMCPToolNames.getAll().stream().filter(each ->
!CORE_TOOL_NAMES.contains(each)).toList();
+ private static final List<String> OFFICIAL_TOOL_NAMES =
ToolDefinitionRegistry.getSupportedToolDescriptors().stream().map(MCPToolDescriptor::getName).toList();
+
+ private static final List<String> REMOVED_FEATURE_TOOL_NAMES =
OFFICIAL_TOOL_NAMES.stream().filter(each ->
!CORE_TOOL_NAMES.contains(each)).toList();
private static final String FIXTURE_RESOURCE_URI =
"shardingsphere://features/test-fixture/status";
@@ -314,7 +317,7 @@ class PackagedDistributionE2ETest {
}
private void assertOfficialToolNames(final List<String> actualToolNames) {
- assertThat(actualToolNames,
containsInAnyOrder(OfficialMCPToolNames.getAll().toArray()));
+ assertThat(actualToolNames,
containsInAnyOrder(OFFICIAL_TOOL_NAMES.toArray()));
}
private void assertMySQLMetadata(final MCPInteractionClient
interactionClient) throws IOException, InterruptedException {
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
index de9aa1d9393..b2d9983af22 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/LLMHttpE2ETest.java
@@ -25,11 +25,11 @@ import
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.LLMConversationRu
import
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact.LLMConversationArtifactWriter;
import
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact.LLME2EAssertionReport;
import
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.client.LLMChatModelClient;
-import
org.apache.shardingsphere.test.e2e.mcp.llm.fixture.LLMRuntimeFixtureFactory;
-import
org.apache.shardingsphere.test.e2e.mcp.llm.fixture.LLMRuntimeFixtureFactory.Fixture;
import org.apache.shardingsphere.test.e2e.mcp.llm.fixture.LLMRuntimeSupport;
import
org.apache.shardingsphere.test.e2e.mcp.support.assertion.MCPModelContractAssertions;
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.MySQLRuntimeTestSupport.LLMMySQLRuntimeFixture;
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.RuntimeTransport;
import
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionTraceRecord;
import org.junit.jupiter.api.AfterAll;
@@ -81,15 +81,14 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
"interaction-trace.json", "mcp-runtime.log",
"assertion-report.json");
private static final Pattern UNREDACTED_SECRET_PATTERN = Pattern.compile(
-
"(?i)(\"(?:api[_-]?key|token|password|authorization|secret)\"\\s*:\\s*\")(?!<redacted>\")([^\"]+)(\")|(Bearer\\s+)(?!<redacted>)[A-Za-z0-9._~+/=-]+");
+
"(?i)(?<![a-z0-9_])\"?(?:api[_-]?key|access[_-]?token|token|authorization|password|passwd|pwd|secret)\"?\\s*[:=]\\s*[\"']?(?!<redacted>)[^\\s,\"'}]+"
+ + "|(Bearer\\s+)(?!<redacted>)[A-Za-z0-9._~+/=-]+|jdbc:");
private static LLMRuntimeSupport.ModelRuntime llmRuntime;
- private final LLMRuntimeFixtureFactory runtimeFixtureFactory = new
LLMRuntimeFixtureFactory();
-
private final LLMConversationArtifactWriter artifactWriter = new
LLMConversationArtifactWriter();
- private Fixture runtimeFixture;
+ private LLMMySQLRuntimeFixture runtimeFixture;
@BeforeAll
static void prepareLLMRuntime() throws InterruptedException {
@@ -162,8 +161,9 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
createInteractionClient(),
modelRuntime.getConfiguration().getModelName()).run(scenario);
Path artifactDirectory =
modelRuntime.getConfiguration().createArtifactDirectory("llm-http/" +
scenario.id());
- artifactWriter.write(artifactDirectory, actualResult,
modelRuntime.getEvidence());
- assertArtifacts(artifactDirectory);
+ Collection<String> sensitiveValues = getArtifactSensitiveValues();
+ artifactWriter.write(artifactDirectory, actualResult,
modelRuntime.getEvidence(), sensitiveValues);
+ assertArtifacts(artifactDirectory, sensitiveValues);
assertTrue(actualResult.assertionReport().isSuccess(), () ->
createFailureMessage(
scenario.id(), actualResult.assertionReport(),
artifactDirectory));
assertFalse(actualResult.evidence().interactionTrace().isEmpty(),
scenario.id() + " must capture MCP evidence.");
@@ -172,7 +172,7 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
private LLME2EAssertionReport evaluateReadOnlyQuery(final String answer,
final List<MCPInteractionTraceRecord> trace) {
Optional<Integer> actualCount = findQueryCount(trace, 0);
- if (actualCount.isEmpty() || getRequiredRuntimeFixture().totalOrders()
!= actualCount.get()) {
+ if (actualCount.isEmpty() ||
getRequiredRuntimeFixture().getTotalOrders() != actualCount.get()) {
return LLME2EAssertionReport.failure("query_evidence_mismatch",
"The MCP query response did not contain the fixture row count.");
}
return containsStandaloneNumber(answer, actualCount.get())
@@ -247,7 +247,7 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
return LLME2EAssertionReport.failure("missing_resource_recovery",
"The model did not follow the stale response recovery action to a live resource
containing orders.");
}
Optional<Integer> actualCount = findQueryCount(trace,
trace.get(recoveryResourceIndex).getModelTurn());
- if (actualCount.isEmpty() || getRequiredRuntimeFixture().totalOrders()
!= actualCount.get()) {
+ if (actualCount.isEmpty() ||
getRequiredRuntimeFixture().getTotalOrders() != actualCount.get()) {
return LLME2EAssertionReport.failure("query_evidence_mismatch",
"The recovered conversation did not obtain the fixture row count from MCP.");
}
return containsStandaloneNumber(answer, actualCount.get())
@@ -363,7 +363,23 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
return Pattern.compile("(?<![a-z0-9_])" + Pattern.quote(identifier) +
"(?![a-z0-9_])").matcher(answer).find();
}
- private void assertArtifacts(final Path artifactDirectory) throws
IOException {
+ private Collection<String> getArtifactSensitiveValues() {
+ Set<String> result = new LinkedHashSet<>();
+ addArtifactSensitiveValue(result,
getRequiredLLMRuntime().getConfiguration().getApiKey());
+ for (RuntimeDatabaseConfiguration each :
getRequiredRuntimeFixture().getRuntimeDatabases().values()) {
+ addArtifactSensitiveValue(result, each.getJdbcUrl());
+ addArtifactSensitiveValue(result, each.getPassword());
+ }
+ return result;
+ }
+
+ private void addArtifactSensitiveValue(final Collection<String>
sensitiveValues, final String value) {
+ if (8 <= value.length()) {
+ sensitiveValues.add(value);
+ }
+ }
+
+ private void assertArtifacts(final Path artifactDirectory, final
Collection<String> sensitiveValues) throws IOException {
assertTrue(Files.isDirectory(artifactDirectory), () -> "Missing LLM
artifact directory: " + artifactDirectory);
for (String each : ARTIFACT_FILES) {
assertTrue(Files.isRegularFile(artifactDirectory.resolve(each)),
() -> "Missing LLM artifact: " + artifactDirectory.resolve(each));
@@ -372,7 +388,9 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
for (Path each : paths.filter(Files::isRegularFile).toList()) {
String content = Files.readString(each);
assertFalse(UNREDACTED_SECRET_PATTERN.matcher(content).find(),
() -> "Unredacted secret-like value in LLM artifact: " + each);
-
assertFalse(content.contains(getRequiredLLMRuntime().getConfiguration().getApiKey()),
() -> "Known model API key leaked into LLM artifact: " + each);
+ for (String sensitiveValue : sensitiveValues) {
+ assertFalse(content.contains(sensitiveValue), () -> "Known
sensitive value leaked into LLM artifact: " + each);
+ }
}
}
}
@@ -389,7 +407,7 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
}
private String queryOrderStatus() throws SQLException {
- RuntimeDatabaseConfiguration databaseConfig =
getRequiredRuntimeFixture().runtimeDatabases().get(DATABASE_NAME);
+ RuntimeDatabaseConfiguration databaseConfig =
getRequiredRuntimeFixture().getRuntimeDatabases().get(DATABASE_NAME);
try (
Connection connection =
databaseConfig.openConnection(DATABASE_NAME);
PreparedStatement statement =
connection.prepareStatement("SELECT status FROM orders WHERE order_id = 1");
@@ -427,17 +445,25 @@ class LLMHttpE2ETest extends
AbstractConfigBackedRuntimeE2ETest {
@Override
protected Map<String, RuntimeDatabaseConfiguration> getRuntimeDatabases() {
- return getRequiredRuntimeFixture().runtimeDatabases();
+ return getRequiredRuntimeFixture().getRuntimeDatabases();
}
@Override
protected void prepareRuntimeFixture() throws IOException {
- if (null == runtimeFixture) {
- runtimeFixture =
runtimeFixtureFactory.createMySQLFixture(DATABASE_NAME, "Docker is required for
the MySQL-backed LLM E2E test.");
+ if (null != runtimeFixture) {
+ return;
+ }
+ if (!MySQLRuntimeTestSupport.isDockerAvailable()) {
+ throw new
IllegalStateException(MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker
is required for the MySQL-backed LLM E2E test."));
+ }
+ try {
+ runtimeFixture =
MySQLRuntimeTestSupport.createLLMRuntimeFixture(DATABASE_NAME);
+ } catch (final SQLException ex) {
+ throw new IOException(ex);
}
}
- private Fixture getRequiredRuntimeFixture() {
+ private LLMMySQLRuntimeFixture getRequiredRuntimeFixture() {
if (null == runtimeFixture) {
throw new IllegalStateException("LLM E2E runtime fixture was not
initialized.");
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
index 36df0250b4a..a5c83c0b473 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLMConversationArtifactWriter.java
@@ -19,10 +19,12 @@ package
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.artifact;
import org.apache.shardingsphere.infra.util.json.JsonUtils;
import
org.apache.shardingsphere.test.e2e.mcp.llm.conversation.LLMConversationRunner.Result;
+import
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.Map;
/**
@@ -30,8 +32,6 @@ import java.util.Map;
*/
public final class LLMConversationArtifactWriter {
- private final LLME2EArtifactRedactor redactor = new
LLME2EArtifactRedactor();
-
private final LLME2ERuntimeEvidenceValidator runtimeEvidenceValidator =
new LLME2ERuntimeEvidenceValidator();
/**
@@ -40,20 +40,22 @@ public final class LLMConversationArtifactWriter {
* @param artifactDirectory artifact directory
* @param conversationResult conversation result
* @param runtimeEvidence runtime evidence
+ * @param sensitiveValues concrete sensitive values
* @throws IOException IO exception
*/
- public void write(final Path artifactDirectory, final Result
conversationResult, final Map<String, Object> runtimeEvidence) throws
IOException {
+ public void write(final Path artifactDirectory, final Result
conversationResult, final Map<String, Object> runtimeEvidence,
+ final Collection<String> sensitiveValues) throws
IOException {
runtimeEvidenceValidator.validate(runtimeEvidence);
- writeContent(artifactDirectory.resolve("run-context.json"),
JsonUtils.toJsonString(createRunContext(conversationResult, runtimeEvidence)));
- writeContent(artifactDirectory.resolve("system-prompt.md"),
conversationResult.systemPrompt());
- writeContent(artifactDirectory.resolve("question.txt"),
conversationResult.scenario().question());
- writeContent(artifactDirectory.resolve("answer.txt"),
conversationResult.actualAnswer());
+ writeContent(artifactDirectory.resolve("run-context.json"),
JsonUtils.toJsonString(createRunContext(conversationResult, runtimeEvidence)),
sensitiveValues);
+ writeContent(artifactDirectory.resolve("system-prompt.md"),
conversationResult.systemPrompt(), sensitiveValues);
+ writeContent(artifactDirectory.resolve("question.txt"),
conversationResult.scenario().question(), sensitiveValues);
+ writeContent(artifactDirectory.resolve("answer.txt"),
conversationResult.actualAnswer(), sensitiveValues);
writeContent(artifactDirectory.resolve("raw-model-output.txt"),
- String.join(System.lineSeparator() + System.lineSeparator(),
conversationResult.evidence().rawModelOutputs()));
- writeContent(artifactDirectory.resolve("available-tools.json"),
JsonUtils.toJsonString(conversationResult.evidence().toolDefinitions()));
- writeContent(artifactDirectory.resolve("interaction-trace.json"),
JsonUtils.toJsonString(conversationResult.evidence().interactionTrace()));
- writeContent(artifactDirectory.resolve("mcp-runtime.log"),
String.join(System.lineSeparator(),
conversationResult.evidence().runtimeLogLines()));
- writeContent(artifactDirectory.resolve("assertion-report.json"),
JsonUtils.toJsonString(conversationResult.assertionReport()));
+ String.join(System.lineSeparator() + System.lineSeparator(),
conversationResult.evidence().rawModelOutputs()), sensitiveValues);
+ writeContent(artifactDirectory.resolve("available-tools.json"),
JsonUtils.toJsonString(conversationResult.evidence().toolDefinitions()),
sensitiveValues);
+ writeContent(artifactDirectory.resolve("interaction-trace.json"),
JsonUtils.toJsonString(conversationResult.evidence().interactionTrace()),
sensitiveValues);
+ writeContent(artifactDirectory.resolve("mcp-runtime.log"),
String.join(System.lineSeparator(),
conversationResult.evidence().runtimeLogLines()), sensitiveValues);
+ writeContent(artifactDirectory.resolve("assertion-report.json"),
JsonUtils.toJsonString(conversationResult.assertionReport()), sensitiveValues);
}
private Map<String, Object> createRunContext(final Result
conversationResult, final Map<String, Object> runtimeEvidence) {
@@ -65,8 +67,8 @@ public final class LLMConversationArtifactWriter {
"failureType",
conversationResult.assertionReport().getFailureType());
}
- private void writeContent(final Path file, final String content) throws
IOException {
+ private void writeContent(final Path file, final String content, final
Collection<String> sensitiveValues) throws IOException {
Files.createDirectories(file.getParent());
- Files.writeString(file, redactor.redact(content));
+ Files.writeString(file, MCPArtifactUtils.redact(content,
sensitiveValues));
}
}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactRedactor.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactRedactor.java
deleted file mode 100644
index 19f7625850d..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/conversation/artifact/LLME2EArtifactRedactor.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.llm.conversation.artifact;
-
-import java.util.regex.Pattern;
-
-final class LLME2EArtifactRedactor {
-
- private static final Pattern JSON_SECRET_FIELD_PATTERN =
Pattern.compile("(?i)(\"(?:api[_-]?key|token|password|authorization|secret)\"\\s*:\\s*\")([^\"]+)(\")");
-
- private static final Pattern BEARER_TOKEN_PATTERN =
Pattern.compile("(?i)(Bearer\\s+)[A-Za-z0-9._~+/=-]+");
-
- private static final Pattern ENV_SECRET_ASSIGNMENT_PATTERN =
Pattern.compile("(?i)((?:MCP_LLM_API_KEY|HF_TOKEN|HUGGING_FACE_HUB_TOKEN|LLAMA_API_KEY)\\s*=\\s*)\\S+");
-
- String redact(final String value) {
- String result =
JSON_SECRET_FIELD_PATTERN.matcher(value).replaceAll("$1<redacted>$3");
- result =
BEARER_TOKEN_PATTERN.matcher(result).replaceAll("$1<redacted>");
- return
ENV_SECRET_ASSIGNMENT_PATTERN.matcher(result).replaceAll("$1<redacted>");
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactory.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactory.java
deleted file mode 100644
index e2b9caa8c63..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactory.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.llm.fixture;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import
org.apache.shardingsphere.mcp.support.database.metadata.jdbc.RuntimeDatabaseConfiguration;
-import
org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport;
-
-import java.io.IOException;
-import java.sql.SQLException;
-import java.util.Map;
-
-/**
- * Create runtime fixtures for LLM E2E tests.
- */
-public final class LLMRuntimeFixtureFactory {
-
- /**
- * Create one MySQL runtime fixture.
- *
- * @param logicalDatabase logical database
- * @param dockerRequiredMessage failure message when Docker is unavailable
- * @return runtime fixture
- * @throws IOException IO exception
- * @throws IllegalStateException Docker is unavailable
- */
- public Fixture createMySQLFixture(final String logicalDatabase, final
String dockerRequiredMessage) throws IOException {
- if (!MySQLRuntimeTestSupport.isDockerAvailable()) {
- throw new
IllegalStateException(MySQLRuntimeTestSupport.createDockerRequiredMessage(dockerRequiredMessage));
- }
- try {
- MySQLRuntimeTestSupport.LLMMySQLRuntimeFixture actualFixture =
MySQLRuntimeTestSupport.createLLMRuntimeFixture(logicalDatabase);
- return new Fixture(actualFixture.getSchemaName(),
actualFixture.getTotalOrders(), actualFixture.getRuntimeDatabases(),
actualFixture::close);
- } catch (final SQLException ex) {
- throw new IOException(ex);
- }
- }
-
- /**
- * Runtime fixture.
- */
- @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
- public static final class Fixture implements AutoCloseable {
-
- private final String schemaName;
-
- private final int totalOrders;
-
- private final Map<String, RuntimeDatabaseConfiguration>
runtimeDatabases;
-
- private final Runnable closeAction;
-
- /**
- * Get schema name.
- *
- * @return schema name
- */
- public String schemaName() {
- return schemaName;
- }
-
- /**
- * Get total orders.
- *
- * @return total orders
- */
- public int totalOrders() {
- return totalOrders;
- }
-
- /**
- * Get runtime databases.
- *
- * @return runtime databases
- */
- public Map<String, RuntimeDatabaseConfiguration> runtimeDatabases() {
- return runtimeDatabases;
- }
-
- @Override
- public void close() {
- closeAction.run();
- }
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactoryTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactoryTest.java
deleted file mode 100644
index 053e03c5a78..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/llm/fixture/LLMRuntimeFixtureFactoryTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.llm.fixture;
-
-import
org.apache.shardingsphere.test.e2e.mcp.support.runtime.MySQLRuntimeTestSupport;
-import org.junit.jupiter.api.Test;
-import org.mockito.MockedStatic;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.mockStatic;
-
-class LLMRuntimeFixtureFactoryTest {
-
- @Test
- void assertDockerUnavailableFails() {
- try (MockedStatic<MySQLRuntimeTestSupport> mocked =
mockStatic(MySQLRuntimeTestSupport.class)) {
-
mocked.when(MySQLRuntimeTestSupport::isDockerAvailable).thenReturn(false);
- mocked.when(() ->
MySQLRuntimeTestSupport.createDockerRequiredMessage("Docker is required."))
- .thenReturn("Docker is required.");
- IllegalStateException actual =
assertThrows(IllegalStateException.class,
- () -> new
LLMRuntimeFixtureFactory().createMySQLFixture("logic_db", "Docker is
required."));
- assertThat(actual.getMessage(), is("Docker is required."));
- }
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
deleted file mode 100644
index f760c8efdea..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/OfficialMCPToolNames.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolDescriptor;
-import org.apache.shardingsphere.mcp.core.tool.handler.ToolDefinitionRegistry;
-
-import java.util.List;
-
-/**
- * Official MCP tool names packaged by default.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class OfficialMCPToolNames {
-
- private static final List<String> ALL =
ToolDefinitionRegistry.getSupportedToolDescriptors().stream().map(MCPToolDescriptor::getName).toList();
-
- /**
- * Get official MCP tool names.
- *
- * @return official MCP tool names
- */
- public static List<String> getAll() {
- return ALL;
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/artifact/MCPArtifactUtils.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/artifact/MCPArtifactUtils.java
new file mode 100644
index 00000000000..db638aeb388
--- /dev/null
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/artifact/MCPArtifactUtils.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.test.e2e.mcp.support.artifact;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * MCP E2E artifact utilities.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class MCPArtifactUtils {
+
+ private static final String ARTIFACT_ROOT_PROPERTY =
"mcp.e2e.artifact-root";
+
+ private static final int MAX_RUNTIME_LOG_CHARS = 4096;
+
+ private static final String TRUNCATION_MARKER = "...<truncated>";
+
+ private static final Pattern JSON_SECRET_FIELD_PATTERN = Pattern.compile(
+
"(?i)(\"(?:api[_-]?key|access[_-]?token|token|authorization|password|passwd|pwd|secret)\"\\s*:\\s*\")((?:\\\\.|[^\"\\\\])*)(\")");
+
+ private static final Pattern SECRET_ASSIGNMENT_PATTERN = Pattern.compile(
+
"(?i)(?<![a-z0-9_])((?:api[_-]?key|access[_-]?token|token|authorization|password|passwd|pwd|secret)\\s*[:=]\\s*)([^\\r\\n]+)");
+
+ private static final Pattern BEARER_TOKEN_PATTERN =
Pattern.compile("(?i)(Bearer\\s+)[A-Za-z0-9._~+/=-]+");
+
+ private static final Pattern JDBC_URL_PATTERN =
Pattern.compile("(?i)jdbc:[^\\s\"']+");
+
+ private static final Pattern URI_USER_INFO_PATTERN =
Pattern.compile("(?i)([a-z][a-z0-9+.-]*://[^:/\\s\"']+:)[^@\\s\"']+(@)");
+
+ /**
+ * Redact secret-shaped fields and concrete sensitive values.
+ *
+ * @param value artifact value
+ * @param sensitiveValues concrete sensitive values
+ * @return redacted value
+ */
+ public static String redact(final String value, final Collection<String>
sensitiveValues) {
+ String result = value;
+ for (String each : sensitiveValues) {
+ if (!each.isBlank()) {
+ result = result.replace(each, "<redacted>");
+ }
+ }
+ result =
JSON_SECRET_FIELD_PATTERN.matcher(result).replaceAll("$1<redacted>$3");
+ result =
SECRET_ASSIGNMENT_PATTERN.matcher(result).replaceAll("$1<redacted>");
+ result =
BEARER_TOKEN_PATTERN.matcher(result).replaceAll("$1<redacted>");
+ result =
JDBC_URL_PATTERN.matcher(result).replaceAll("<redacted-jdbc-url>");
+ return
URI_USER_INFO_PATTERN.matcher(result).replaceAll("$1<redacted>$2");
+ }
+
+ /**
+ * Write one bounded and redacted runtime log when the artifact root is
configured.
+ *
+ * @param filePrefix artifact file prefix
+ * @param outputMessages runtime output messages
+ */
+ public static void writeRuntimeLogIfConfigured(final String filePrefix,
final Collection<String> outputMessages) {
+ String artifactRoot = System.getProperty(ARTIFACT_ROOT_PROPERTY,
"").trim();
+ if (artifactRoot.isEmpty() || outputMessages.isEmpty()) {
+ return;
+ }
+ try {
+ writeArtifact(Path.of(artifactRoot), filePrefix,
redactAndBoundRuntimeLog(String.join(System.lineSeparator(), outputMessages)));
+ } catch (final IOException ignored) {
+ }
+ }
+
+ /**
+ * Copy one bounded and redacted runtime log when the artifact root is
configured.
+ *
+ * @param filePrefix artifact file prefix
+ * @param runtimeLog runtime log
+ */
+ public static void copyRuntimeLogIfConfigured(final String filePrefix,
final Path runtimeLog) {
+ String artifactRoot = System.getProperty(ARTIFACT_ROOT_PROPERTY,
"").trim();
+ if (artifactRoot.isEmpty() || !Files.isRegularFile(runtimeLog)) {
+ return;
+ }
+ try {
+ writeArtifact(Path.of(artifactRoot), filePrefix,
redactAndBoundRuntimeLog(Files.readString(runtimeLog, StandardCharsets.UTF_8)));
+ } catch (final IOException ignored) {
+ }
+ }
+
+ private static void writeArtifact(final Path artifactDirectory, final
String filePrefix, final String content) throws IOException {
+ Files.createDirectories(artifactDirectory);
+ Files.writeString(Files.createTempFile(artifactDirectory, filePrefix,
".log"), content);
+ }
+
+ private static String redactAndBoundRuntimeLog(final String content) {
+ String redactedContent = redact(content, List.of());
+ return redactedContent.length() <= MAX_RUNTIME_LOG_CHARS
+ ? redactedContent
+ : TRUNCATION_MARKER +
redactedContent.substring(redactedContent.length() - MAX_RUNTIME_LOG_CHARS);
+ }
+}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
index 2acd33151dd..198283acfc3 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/DockerImageHttpRuntime.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.test.e2e.mcp.support.distribution;
import lombok.RequiredArgsConstructor;
+import
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
import org.apache.shardingsphere.test.e2e.mcp.support.runtime.ReadinessProbe;
import
org.apache.shardingsphere.test.e2e.mcp.support.runtime.ReadinessProbe.ReadinessResult;
import
org.apache.shardingsphere.test.e2e.mcp.support.transport.client.MCPHttpInteractionClient;
@@ -95,6 +96,7 @@ public final class DockerImageHttpRuntime implements
AutoCloseable {
} catch (final InterruptedException ignored) {
Thread.currentThread().interrupt();
} finally {
+ MCPArtifactUtils.writeRuntimeLogIfConfigured("docker-image-http-",
outputMessages);
process = null;
outputCollector = null;
httpPort = 0;
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
index bf8b6986b51..735c01556a1 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/distribution/PackagedDistributionProcessSupport.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.test.e2e.mcp.support.distribution;
import lombok.Getter;
+import
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
import
org.apache.shardingsphere.test.e2e.mcp.support.distribution.PackagedDistributionTestSupport.PreparedPackagedDistribution;
import java.io.BufferedReader;
@@ -123,6 +124,8 @@ public final class PackagedDistributionProcessSupport
implements AutoCloseable {
} catch (final InterruptedException ignored) {
Thread.currentThread().interrupt();
} finally {
+
MCPArtifactUtils.writeRuntimeLogIfConfigured(outputCollectorThreadName +
"-process-", outputMessages);
+
MCPArtifactUtils.copyRuntimeLogIfConfigured(outputCollectorThreadName +
"-server-", distributionHome.resolve("logs/mcp.log"));
process = null;
outputCollector = null;
outputMessages.clear();
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomEncryptAlgorithmFixtureTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomEncryptAlgorithmFixtureTest.java
deleted file mode 100644
index f392cf3d6c0..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomEncryptAlgorithmFixtureTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.fixture;
-
-import org.apache.shardingsphere.encrypt.spi.EncryptAlgorithmMetaData;
-import
org.apache.shardingsphere.infra.algorithm.core.config.AlgorithmConfiguration;
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class MCPWorkflowCustomEncryptAlgorithmFixtureTest {
-
- @Test
- void assertEncrypt() {
- assertThat(new
MCPWorkflowCustomEncryptAlgorithmFixture().encrypt("plain", null),
is("mcp_custom:plain"));
- }
-
- @Test
- void assertDecrypt() {
- assertThat(new
MCPWorkflowCustomEncryptAlgorithmFixture().decrypt("mcp_custom:plain", null),
is("plain"));
- }
-
- @Test
- void assertDecryptWithRawValue() {
- assertThat(new
MCPWorkflowCustomEncryptAlgorithmFixture().decrypt("plain", null), is("plain"));
- }
-
- @Test
- void assertGetMetaData() {
- EncryptAlgorithmMetaData actual = new
MCPWorkflowCustomEncryptAlgorithmFixture().getMetaData();
- assertTrue(actual.isSupportDecrypt());
- assertFalse(actual.isSupportEquivalentFilter());
- assertFalse(actual.isSupportLike());
- }
-
- @Test
- void assertToConfiguration() {
- AlgorithmConfiguration actual = new
MCPWorkflowCustomEncryptAlgorithmFixture().toConfiguration();
- assertThat(actual.getType(), is("MCP_CUSTOM_REVERSIBLE"));
- assertTrue(actual.getProps().isEmpty());
- }
-
- @Test
- void assertGetType() {
- assertThat(new MCPWorkflowCustomEncryptAlgorithmFixture().getType(),
is("MCP_CUSTOM_REVERSIBLE"));
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomMaskAlgorithmFixtureTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomMaskAlgorithmFixtureTest.java
deleted file mode 100644
index 3dcc2341802..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowCustomMaskAlgorithmFixtureTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.fixture;
-
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class MCPWorkflowCustomMaskAlgorithmFixtureTest {
-
- @Test
- void assertMask() {
- assertThat(new MCPWorkflowCustomMaskAlgorithmFixture().mask("plain"),
is("mask:plain"));
- }
-
- @Test
- void assertGetType() {
- assertThat(new MCPWorkflowCustomMaskAlgorithmFixture().getType(),
is("MCP_MASK_CUSTOM"));
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowSecretReferenceFixture.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowSecretReferenceFixture.java
deleted file mode 100644
index 12459d78d7d..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/MCPWorkflowSecretReferenceFixture.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.fixture;
-
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-import java.util.Map;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-
-/**
- * Secret reference fixture for MCP workflow E2E tests.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class MCPWorkflowSecretReferenceFixture {
-
- public static final String SECRET_REF = "placeholder://secret-value-1";
-
- public static final String INPUT_LABEL = "placeholder-secret-1";
-
- /**
- * Create secret reference input object.
- *
- * @return secret reference input object
- */
- public static Map<String, Object> createSecretReferenceInput() {
- return Map.of("secret_ref", SECRET_REF, "label", INPUT_LABEL);
- }
-
- /**
- * Assert model-facing payload does not expose reference details.
- *
- * @param payload model-facing payload
- */
- public static void assertSecretReferenceRedacted(final Object payload) {
- String actual = String.valueOf(payload);
- assertFalse(actual.contains(SECRET_REF));
- assertFalse(actual.contains(INPUT_LABEL));
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureHandlerProviderTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureHandlerProviderTest.java
deleted file mode 100644
index b9996681bcf..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureHandlerProviderTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.fixture.plugin;
-
-import
org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceHandler;
-import org.apache.shardingsphere.mcp.api.capability.tool.MCPToolHandler;
-import org.apache.shardingsphere.mcp.api.MCPRequestContext;
-import org.junit.jupiter.api.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-class PluginFixtureHandlerProviderTest {
-
- @Test
- void assertGetToolHandlers() {
- MCPToolHandler<?> actual = new
PluginFixtureHandlerProvider().getToolHandlers().iterator().next();
- assertThat(actual.getClass(), is(PluginFixturePingToolHandler.class));
- assertThat(actual.getToolName(), is("fixture_ping"));
- assertThat(actual.getContextType(), is(MCPRequestContext.class));
- }
-
- @Test
- void assertGetResourceHandlers() {
- MCPResourceHandler<?> actual = new
PluginFixtureHandlerProvider().getResourceHandlers().iterator().next();
- assertThat(actual.getClass(),
is(PluginFixtureStatusResourceHandler.class));
- assertThat(actual.getResourceUriTemplate(),
is("shardingsphere://features/test-fixture/status"));
- assertThat(actual.getContextType(), is(MCPRequestContext.class));
- }
-
- @Test
- void assertGetCompletionHandlers() {
- assertTrue(new
PluginFixtureHandlerProvider().getCompletionHandlers().isEmpty());
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixturePingToolHandlerTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixturePingToolHandlerTest.java
deleted file mode 100644
index c683f127532..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixturePingToolHandlerTest.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.fixture.plugin;
-
-import org.junit.jupiter.api.Test;
-
-import java.util.Map;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-
-class PluginFixturePingToolHandlerTest {
-
- @Test
- void assertHandle() {
- assertThat(new PluginFixturePingToolHandler().handle(null,
Map.of("message", "hello")).toPayload(),
- is(Map.of("status", "ready", "echo", "hello")));
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureStatusResourceHandlerTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureStatusResourceHandlerTest.java
deleted file mode 100644
index c9ac7f19d25..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/fixture/plugin/PluginFixtureStatusResourceHandlerTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.fixture.plugin;
-
-import
org.apache.shardingsphere.mcp.api.capability.resource.MCPResourceURIVariables;
-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;
-
-class PluginFixtureStatusResourceHandlerTest {
-
- @Test
- void assertHandle() {
- assertThat(new PluginFixtureStatusResourceHandler().handle(null, new
MCPResourceURIVariables(Map.of())).toPayload(),
- is(Map.of("items", List.of(Map.of("feature", "test-fixture",
"status", "ready")))));
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPPayloadAssertionsTest.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPPayloadAssertionsTest.java
deleted file mode 100644
index dbf091aca5a..00000000000
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/MCPPayloadAssertionsTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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;
-
-class MCPPayloadAssertionsTest {
-
- @Test
- void assertAssertSingleItemValue() {
- MCPPayloadAssertions.assertSingleItemValue(Map.of("items",
List.of(Map.of("name", "orders"))), "name", "orders");
- }
-
- @Test
- void assertAssertItemValues() {
- MCPPayloadAssertions.assertItemValues(createPayload(), "name",
List.of("orders", "users"));
- }
-
- @Test
- void assertGetSingleItem() {
- assertThat(MCPPayloadAssertions.getSingleItem(Map.of("items",
List.of(Map.of("name", "orders")))), is(Map.of("name", "orders")));
- }
-
- @Test
- void assertFindItem() {
- assertThat(MCPPayloadAssertions.findItem(createPayload(), "name",
"users"), is(Map.of("name", "users", "type", "table")));
- }
-
- private Map<String, Object> createPayload() {
- return Map.of("items", List.of(Map.of("name", "orders", "type",
"table"), Map.of("name", "users", "type", "table")));
- }
-}
diff --git
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
index 0c7087cc417..0e86085c37d 100644
---
a/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
+++
b/test/e2e/mcp/src/test/java/org/apache/shardingsphere/test/e2e/mcp/support/transport/client/AbstractProcessMCPStdioInteractionClient.java
@@ -19,6 +19,7 @@ package
org.apache.shardingsphere.test.e2e.mcp.support.transport.client;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
+import
org.apache.shardingsphere.test.e2e.mcp.support.artifact.MCPArtifactUtils;
import
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionPayloads;
import
org.apache.shardingsphere.test.e2e.mcp.support.transport.MCPInteractionProtocolSupport;
@@ -277,6 +278,7 @@ abstract class AbstractProcessMCPStdioInteractionClient
extends AbstractMCPInter
writer = null;
stdErrorCollector = null;
initializePayload = Map.of();
+ MCPArtifactUtils.writeRuntimeLogIfConfigured(getClientName() + "-",
stdErrorMessages);
stdErrorMessages.clear();
}
diff --git a/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
b/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
index f4a368cc40d..bb470af23e4 100644
--- a/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
+++ b/test/e2e/mcp/src/test/resources/proxy/workflow/cluster/global.yaml
@@ -22,6 +22,8 @@ mode:
props:
namespace: mcp_workflow
server-lists: zk.workflow.host:2181
+ maxRetries: 10
+ operationTimeoutMilliseconds: 5000
authority:
users: