From 2a2cd23a575c0f80c1515b7269b45811b591b637 Mon Sep 17 00:00:00 2001 From: Hazel Date: Wed, 4 Mar 2026 11:44:16 -0800 Subject: [PATCH 1/8] Add integration tests for GeneralCommandTools --- ...GeneralCommandToolsMcpIntegrationTest.java | 100 +++++++++++++++ .../api/v1/mcp/McpIntegrationTestBase.java | 114 ++++++++++++++++++ .../jsonapi/testresource/DseTestResource.java | 11 ++ 3 files changed, 225 insertions(+) create mode 100644 src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java create mode 100644 src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java new file mode 100644 index 0000000000..d218859b94 --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java @@ -0,0 +1,100 @@ +package io.stargate.sgv2.jsonapi.api.v1.mcp; + +import static org.junit.jupiter.api.Assertions.*; + +import io.quarkiverse.mcp.server.MetaKey; +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.stargate.sgv2.jsonapi.api.model.command.CommandName; +import io.stargate.sgv2.jsonapi.testresource.DseTestResource; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import java.util.Map; +import org.junit.jupiter.api.*; + +/** + * MCP integration tests for {@link GeneralCommandTools}. Uses the Streamable HTTP transport via + * McpAssured to test all general-level MCP tools end-to-end. + */ +@QuarkusIntegrationTest +@WithTestResource(value = DseTestResource.class) +class GeneralCommandToolsMcpIntegrationTest extends McpIntegrationTestBase { + + private static final String EXTRA_KEYSPACE = "new_ks"; + + @Nested + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class CreateFindAndDropKeyspaceToolCall { + @Test + @Order(1) + void testCreateKeyspaceToolCall() { + callToolAndAssert( + CommandName.Names.CREATE_KEYSPACE, + Map.of("name", EXTRA_KEYSPACE), + response -> { + assertFalse(response.isError()); + assertNotNull(response._meta()); + assertNull(response.structuredContent()); + }); + } + + @Test + @Order(2) + void testFindKeyspaceToolCall() { + callToolAndAssert( + CommandName.Names.FIND_KEYSPACES, + Map.of(), + response -> { + // check mcp response + assertFalse(response.isError()); + assertNotNull(response._meta()); + assertNull(response.structuredContent()); + + // check the new keyspace is there + var status = (JsonObject) response._meta().get(MetaKey.of("status")); + assertNotNull(status, "Status should not be null"); + JsonArray keyspaces = status.getJsonArray("keyspaces"); + assertNotNull(keyspaces, "Keyspaces array should not be null"); + assertTrue( + keyspaces.contains(EXTRA_KEYSPACE), "New created Keyspace should be in the list"); + }); + } + + @Test + @Order(3) + void testDropKeyspaceToolCall() { + callToolAndAssert( + CommandName.Names.DROP_KEYSPACE, + Map.of("name", EXTRA_KEYSPACE), + response -> { + assertFalse(response.isError()); + assertNotNull(response._meta()); + assertNull(response.structuredContent()); + }); + } + } + + @Test + void findEmbeddingProvidersToolCall() { + callToolAndAssert( + "findEmbeddingProviders", + Map.of(), + response -> { + assertFalse(response.isError()); + assertNotNull(response._meta()); + assertNull(response.structuredContent()); + }); + } + + @Test + void findRerankingProvidersToolCall() { + callToolAndAssert( + "findRerankingProviders", + Map.of(), + response -> { + assertFalse(response.isError()); + assertNotNull(response._meta()); + assertNull(response.structuredContent()); + }); + } +} diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java new file mode 100644 index 0000000000..e6eba16539 --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java @@ -0,0 +1,114 @@ +package io.stargate.sgv2.jsonapi.api.v1.mcp; + +import static io.stargate.sgv2.jsonapi.api.v1.util.IntegrationTestUtils.*; +import static org.assertj.core.api.Assertions.assertThat; + +import io.quarkiverse.mcp.server.ToolResponse; +import io.quarkiverse.mcp.server.test.McpAssured; +import io.quarkiverse.mcp.server.test.McpAssured.McpStreamableTestClient; +import io.stargate.sgv2.jsonapi.config.constants.HttpConstants; +import io.vertx.core.MultiMap; +import java.net.URI; +import java.util.Base64; +import java.util.Map; +import java.util.function.Consumer; +import org.apache.commons.lang3.RandomStringUtils; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; + +/** + * Abstract base class for MCP integration tests. Provides a shared MCP client instance, + * authentication, and utility methods for invoking MCP tools via the Streamable HTTP transport. + */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class McpIntegrationTestBase { + + private static final String MCP_PATH = "/v1/mcp"; + + /** MCP Assured cannot automatically resolve the URI (like Rest Assured) */ + private static final String MCP_HOSTNAME = "http://localhost:"; + + /** Test keyspace name, with a random suffix for isolation. */ + protected final String keyspaceName = + "mcp_ks_" + RandomStringUtils.insecure().nextAlphanumeric(8).toLowerCase(); + + /** Test collection name, with a random suffix for isolation. */ + protected final String collectionName = + "mcp_col_" + RandomStringUtils.insecure().nextAlphanumeric(8).toLowerCase(); + + /** Shared MCP client instance, connected once per test class. */ + protected McpStreamableTestClient mcpClient; + + @BeforeAll + void setUpMcpClient() { + mcpClient = + McpAssured.newStreamableClient() + .setBaseUri(URI.create(MCP_HOSTNAME + getTestPort())) + .setMcpPath(MCP_PATH) + .setAdditionalHeaders(msg -> authHeaders()) + .build() + .connect(); + } + + @AfterAll + void tearDownMcpClient() { + if (mcpClient != null) { + mcpClient.disconnect(); + mcpClient = null; + } + } + + protected int getTestPort() { + try { + return ConfigProvider.getConfig().getValue("quarkus.http.test-port", Integer.class); + } catch (Exception e) { + return Integer.parseInt(System.getProperty("quarkus.http.test-port")); + } + } + + /** Build authentication headers matching the Token header format used by the REST API. */ + protected MultiMap authHeaders() { + String credential = + "Cassandra:" + + Base64.getEncoder().encodeToString(getCassandraUsername().getBytes()) + + ":" + + Base64.getEncoder().encodeToString(getCassandraPassword().getBytes()); + return MultiMap.caseInsensitiveMultiMap() + .add(HttpConstants.AUTHENTICATION_TOKEN_HEADER_NAME, credential); + } + + /** Create a keyspace via the MCP createKeyspace tool. */ + protected void createKeyspace(String name) { + callToolExpectSuccess("createKeyspace", Map.of("name", name)); + } + + /** Drop a keyspace via the MCP dropKeyspace tool. */ + protected void dropKeyspace(String name) { + callToolExpectSuccess("dropKeyspace", Map.of("name", name)); + } + + /** Create a collection via the MCP createCollection tool. */ + protected void createCollection(String keyspace, String collection) { + callToolExpectSuccess( + "createCollection", Map.of("keyspace", keyspace, "collection", collection)); + } + + /** + * Execute an MCP tool call and assert the response using the shared client. + * + * @param toolName the MCP tool name to invoke + * @param args the tool arguments + * @param assertFn assertion function for the ToolResponse + */ + protected void callToolAndAssert( + String toolName, Map args, Consumer assertFn) { + mcpClient.when().toolsCall(toolName, args, assertFn).thenAssertResults(); + } + + /** Execute an MCP tool call expecting success (no error) using the shared client. */ + protected void callToolExpectSuccess(String toolName, Map args) { + callToolAndAssert(toolName, args, response -> assertThat(response.isError()).isFalse()); + } +} diff --git a/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java b/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java index 20fd958dbd..3e42772e34 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/testresource/DseTestResource.java @@ -113,6 +113,11 @@ public String getFeatureFlagReranking() { return "true"; } + // By default, we enable the feature flag for MCP + public String getFeatureFlagMcp() { + return "true"; + } + @Override public Map start() { Map env = super.start(); @@ -140,6 +145,12 @@ public Map start() { propsBuilder.put("stargate.feature.flags.reranking", featureFlagReranking); } + // MCP feature flag: + String featureFlagMcp = getFeatureFlagMcp(); + if (featureFlagMcp != null) { + propsBuilder.put("stargate.feature.flags.mcp", featureFlagMcp); + } + propsBuilder.put( "stargate.jsonapi.custom.embedding.clazz", "io.stargate.sgv2.jsonapi.service.embedding.operation.test.CustomITEmbeddingProvider"); From 18a3d717a00bf06bd81f175885590800dc236f7b Mon Sep 17 00:00:00 2001 From: Hazel Date: Thu, 5 Mar 2026 18:17:28 -0800 Subject: [PATCH 2/8] format --- .../service/operation/collections/DeleteOperationPage.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/DeleteOperationPage.java b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/DeleteOperationPage.java index 18dab2f6e3..96451ce4cd 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/DeleteOperationPage.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/service/operation/collections/DeleteOperationPage.java @@ -36,8 +36,7 @@ public CommandResult get() { // when we move to use OperationAttempt for the collection commands we can refactor if (deletedInformation == null) { // when returnDocument is set this means we are runnning findOneAndDelete, so we have to - // return a - // data and documents section + // return a data and documents section // aaron - this is a giant hack 21 oct 2024 if (returnDocument()) { if (singleDocument()) { From 92735e51e124ad984828c6cd1739c55772bf581b Mon Sep 17 00:00:00 2001 From: Hazel Date: Thu, 5 Mar 2026 18:17:50 -0800 Subject: [PATCH 3/8] add one more IT --- ...GeneralCommandToolsMcpIntegrationTest.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java index d218859b94..3d981cfc21 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java @@ -40,7 +40,7 @@ void testCreateKeyspaceToolCall() { @Test @Order(2) - void testFindKeyspaceToolCall() { + void testFindKeyspaceToolCallAfterCreateKeyspace() { callToolAndAssert( CommandName.Names.FIND_KEYSPACES, Map.of(), @@ -72,6 +72,29 @@ void testDropKeyspaceToolCall() { assertNull(response.structuredContent()); }); } + + @Test + @Order(2) + void testFindKeyspaceToolCallAfterDropKeyspace() { + callToolAndAssert( + CommandName.Names.FIND_KEYSPACES, + Map.of(), + response -> { + // check mcp response + assertFalse(response.isError()); + assertNotNull(response._meta()); + assertNull(response.structuredContent()); + + // check the new keyspace is there + var status = (JsonObject) response._meta().get(MetaKey.of("status")); + assertNotNull(status, "Status should not be null"); + JsonArray keyspaces = status.getJsonArray("keyspaces"); + assertNotNull(keyspaces, "Keyspaces array should not be null"); + assertFalse( + keyspaces.contains(EXTRA_KEYSPACE), + "Keyspace should be dropped and not in the list"); + }); + } } @Test From 8f21cf01994246a837318310b66b4250d6994c14 Mon Sep 17 00:00:00 2001 From: Hazel Date: Thu, 5 Mar 2026 18:18:14 -0800 Subject: [PATCH 4/8] fix null in ToolResponse --- .../io/stargate/sgv2/jsonapi/api/v1/mcp/McpResource.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResource.java b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResource.java index 41b3af4ac0..c7c96c52b6 100644 --- a/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResource.java +++ b/src/main/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResource.java @@ -142,7 +142,9 @@ public Uni processCollectionCommand( .addThrowable(throwable) .build(); return Uni.createFrom() - .item(new ToolResponse(true, null, errorResult.errors(), Map.of())); + .item( + new ToolResponse( + true, List.of(), Map.of("errors", errorResult.errors()), Map.of())); } else { VectorColumnDefinition vectorColDef = null; if (schemaObject.type() == SchemaObject.SchemaObjectType.COLLECTION) { @@ -218,7 +220,7 @@ public Uni processCommand(CommandContext context, Command comma Map meta = (result.status() != null && !result.status().isEmpty()) ? Map.of(MetaKey.of("status"), result.status()) - : null; + : Map.of(); // Map "errors" or "data" to structuredContent // Also, structuredContent is expected to be a Record (a plain JSON object {}) From f9a9665e3898b010a5ee66e35d994809f2bf6df2 Mon Sep 17 00:00:00 2001 From: Hazel Date: Thu, 5 Mar 2026 18:18:29 -0800 Subject: [PATCH 5/8] add unit test for McpResource --- .../jsonapi/api/v1/mcp/McpResourceTest.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java new file mode 100644 index 0000000000..c56f718373 --- /dev/null +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java @@ -0,0 +1,192 @@ +package io.stargate.sgv2.jsonapi.api.v1.mcp; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import io.quarkiverse.mcp.server.MetaKey; +import io.quarkiverse.mcp.server.ToolResponse; +import io.quarkus.test.InjectMock; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import io.smallrye.mutiny.Uni; +import io.stargate.sgv2.jsonapi.api.model.command.*; +import io.stargate.sgv2.jsonapi.api.model.command.tracing.RequestTracing; +import io.stargate.sgv2.jsonapi.config.feature.ApiFeature; +import io.stargate.sgv2.jsonapi.config.feature.ApiFeatures; +import io.stargate.sgv2.jsonapi.service.cqldriver.executor.DatabaseSchemaObject; +import io.stargate.sgv2.jsonapi.service.processor.MeteredCommandProcessor; +import io.stargate.sgv2.jsonapi.testresource.NoGlobalResourcesTestProfile; +import jakarta.inject.Inject; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link McpResource#processCommand(CommandContext, Command)} verifying the + * CommandResult → ToolResponse mapping logic. + */ +@QuarkusTest +@TestProfile(NoGlobalResourcesTestProfile.Impl.class) +class McpResourceTest { + + @Inject McpResource mcpResource; + + @InjectMock MeteredCommandProcessor meteredCommandProcessor; + + @Test + void successWithStatusOnly() { + // Arrange: a DDL-style result with status and no errors and data + var commandResult = + CommandResult.statusOnlyBuilder(RequestTracing.NO_OP) + .addStatus(CommandStatus.OK, 1) + .build(); + + when(meteredCommandProcessor.processCommand(any(), any())) + .thenReturn(Uni.createFrom().item(commandResult)); + + // Act + ToolResponse response = + mcpResource + .processCommand(mockContextWithMcpEnabled(), mock(Command.class)) + .await() + .indefinitely(); + + // Assert: no error, no content and structuredContent, status should be mapped into _meta + assertFalse(response.isError()); + assertThat(response.content()).isEmpty(); + assertNull(response.structuredContent()); + assertNotNull(response._meta()); + assertNotNull(response._meta().get(MetaKey.of("status"))); + } + + @Test + void successWithStatusAndData() { + // Arrange: a result with data and status + var commandResult = + CommandResult.singleDocumentBuilder(RequestTracing.NO_OP) + .addStatus(CommandStatus.DELETED_COUNT, 0) + .addDocument(null) + .build(); + + when(meteredCommandProcessor.processCommand(any(), any())) + .thenReturn(Uni.createFrom().item(commandResult)); + + // Act + ToolResponse response = + mcpResource + .processCommand(mockContextWithMcpEnabled(), mock(Command.class)) + .await() + .indefinitely(); + + // Assert: no error, no content, data in structuredContent, status should be mapped into _meta + assertFalse(response.isError()); + assertThat(response.content()).isEmpty(); + assertThat(response.structuredContent()).isNotNull(); + assertInstanceOf(ResponseData.class, response.structuredContent()); + assertNotNull(response._meta()); + assertNotNull(response._meta().get(MetaKey.of("status"))); + } + + @Test + void failWithErrorOnly() { + // Arrange: a result that contains errors + var commandResult = + CommandResult.statusOnlyBuilder(RequestTracing.NO_OP) + .addThrowable(new RuntimeException("test error")) + .build(); + + when(meteredCommandProcessor.processCommand(any(), any())) + .thenReturn(Uni.createFrom().item(commandResult)); + + // Act + ToolResponse response = + mcpResource + .processCommand(mockContextWithMcpEnabled(), mock(Command.class)) + .await() + .indefinitely(); + + // Assert: should be an error in structuredContent, no meta_ and content + assertThat(response.content()).isEmpty(); + assertThat(response._meta()).isEmpty(); + assertTrue(response.isError()); + assertThat(response.structuredContent()).isNotNull(); + @SuppressWarnings("unchecked") + var errorContent = (Map) response.structuredContent(); + assertThat(errorContent).containsKey("errors"); + } + + @Test + void failWithErrorAndStatus() { + // Arrange: a result that contains errors + var commandResult = + CommandResult.statusOnlyBuilder(RequestTracing.NO_OP) + .addStatus(CommandStatus.INSERTED_IDS, 1) + .addThrowable(new RuntimeException("test error")) + .build(); + + when(meteredCommandProcessor.processCommand(any(), any())) + .thenReturn(Uni.createFrom().item(commandResult)); + + // Act + ToolResponse response = + mcpResource + .processCommand(mockContextWithMcpEnabled(), mock(Command.class)) + .await() + .indefinitely(); + + // Assert: no content, should be an error in structuredContent, and status in meta_ + assertThat(response.content()).isEmpty(); + assertTrue(response.isError()); + assertThat(response.structuredContent()).isNotNull(); + @SuppressWarnings("unchecked") + var errorContent = (Map) response.structuredContent(); + assertThat(errorContent).containsKey("errors"); + assertNotNull(response._meta()); + assertNotNull(response._meta().get(MetaKey.of("status"))); + } + + @Test + void failWithMCPFeatureDisabled() { + // Arrange & Act: context with MCP feature disabled + ToolResponse response = + mcpResource + .processCommand(mockContextWithMcpDisabled(), mock(Command.class)) + .await() + .indefinitely(); + + // Assert: should return error ToolResponse without calling processCommand + assertThat(response.content()).isEmpty(); + assertThat(response._meta()).isEmpty(); + assertThat(response.isError()).isTrue(); + assertThat(response.structuredContent()).isNotNull(); + @SuppressWarnings("unchecked") + var errorContent = (Map) response.structuredContent(); + assertThat(errorContent).containsKey("errors"); + + // processCommand should never have been called + verify(meteredCommandProcessor, never()).processCommand(any(), any()); + } + + /** Create a mock CommandContext with MCP feature enabled. */ + @SuppressWarnings("unchecked") + private CommandContext mockContextWithMcpEnabled() { + CommandContext ctx = mock(CommandContext.class); + ApiFeatures features = mock(ApiFeatures.class); + when(features.isFeatureEnabled(ApiFeature.MCP)).thenReturn(true); + when(ctx.apiFeatures()).thenReturn(features); + when(ctx.requestTracing()).thenReturn(RequestTracing.NO_OP); + return ctx; + } + + /** Create a mock CommandContext with MCP feature disabled. */ + @SuppressWarnings("unchecked") + private CommandContext mockContextWithMcpDisabled() { + CommandContext ctx = mock(CommandContext.class); + ApiFeatures features = mock(ApiFeatures.class); + when(features.isFeatureEnabled(ApiFeature.MCP)).thenReturn(false); + when(ctx.apiFeatures()).thenReturn(features); + when(ctx.requestTracing()).thenReturn(RequestTracing.NO_OP); + return ctx; + } +} From 0c3773882d26997afc6a6c66ee6e29585ea341c3 Mon Sep 17 00:00:00 2001 From: Hazel Date: Thu, 5 Mar 2026 18:20:40 -0800 Subject: [PATCH 6/8] update comment --- .../io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java index c56f718373..e1730dd967 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpResourceTest.java @@ -118,7 +118,7 @@ void failWithErrorOnly() { @Test void failWithErrorAndStatus() { - // Arrange: a result that contains errors + // Arrange: a result that contains errors and status var commandResult = CommandResult.statusOnlyBuilder(RequestTracing.NO_OP) .addStatus(CommandStatus.INSERTED_IDS, 1) From db790cea70841e6bb43c77edd33abf38982e85fa Mon Sep 17 00:00:00 2001 From: Hazel Date: Fri, 6 Mar 2026 12:09:11 -0800 Subject: [PATCH 7/8] update and fix tests --- ...GeneralCommandToolsMcpIntegrationTest.java | 6 ++-- .../api/v1/mcp/McpIntegrationTestBase.java | 30 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java index 3d981cfc21..f57cf27524 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/GeneralCommandToolsMcpIntegrationTest.java @@ -1,5 +1,6 @@ package io.stargate.sgv2.jsonapi.api.v1.mcp; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import io.quarkiverse.mcp.server.MetaKey; @@ -35,6 +36,7 @@ void testCreateKeyspaceToolCall() { assertFalse(response.isError()); assertNotNull(response._meta()); assertNull(response.structuredContent()); + assertThat(response.content()).isEmpty(); }); } @@ -74,7 +76,7 @@ void testDropKeyspaceToolCall() { } @Test - @Order(2) + @Order(4) void testFindKeyspaceToolCallAfterDropKeyspace() { callToolAndAssert( CommandName.Names.FIND_KEYSPACES, @@ -85,7 +87,7 @@ void testFindKeyspaceToolCallAfterDropKeyspace() { assertNotNull(response._meta()); assertNull(response.structuredContent()); - // check the new keyspace is there + // check the new keyspace is dropped var status = (JsonObject) response._meta().get(MetaKey.of("status")); assertNotNull(status, "Status should not be null"); JsonArray keyspaces = status.getJsonArray("keyspaces"); diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java index e6eba16539..4f48879d2d 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java @@ -1,7 +1,7 @@ package io.stargate.sgv2.jsonapi.api.v1.mcp; import static io.stargate.sgv2.jsonapi.api.v1.util.IntegrationTestUtils.*; -import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; import io.quarkiverse.mcp.server.ToolResponse; import io.quarkiverse.mcp.server.test.McpAssured; @@ -80,19 +80,30 @@ protected MultiMap authHeaders() { } /** Create a keyspace via the MCP createKeyspace tool. */ - protected void createKeyspace(String name) { - callToolExpectSuccess("createKeyspace", Map.of("name", name)); + protected void createKeyspace(String keyspace) { + callToolAndAssert( + "createKeyspace", Map.of("name", keyspace), response -> assertFalse(response.isError())); } /** Drop a keyspace via the MCP dropKeyspace tool. */ - protected void dropKeyspace(String name) { - callToolExpectSuccess("dropKeyspace", Map.of("name", name)); + protected void dropKeyspace(String keyspace) { + callToolAndAssert( + "dropKeyspace", Map.of("name", keyspace), response -> assertFalse(response.isError())); } /** Create a collection via the MCP createCollection tool. */ protected void createCollection(String keyspace, String collection) { - callToolExpectSuccess( - "createCollection", Map.of("keyspace", keyspace, "collection", collection)); + callToolAndAssert( + "createCollection", + Map.of("keyspace", keyspace, "collection", collection), + response -> assertFalse(response.isError())); + } + + protected void dropCollection(String keyspace, String collection) { + callToolAndAssert( + "dropCollection", + Map.of("keyspace", keyspace, "collection", collection), + response -> assertFalse(response.isError())); } /** @@ -106,9 +117,4 @@ protected void callToolAndAssert( String toolName, Map args, Consumer assertFn) { mcpClient.when().toolsCall(toolName, args, assertFn).thenAssertResults(); } - - /** Execute an MCP tool call expecting success (no error) using the shared client. */ - protected void callToolExpectSuccess(String toolName, Map args) { - callToolAndAssert(toolName, args, response -> assertThat(response.isError()).isFalse()); - } } From e8f568c7d2ff5d900f59d4516c658c77e6643d3f Mon Sep 17 00:00:00 2001 From: Hazel Date: Fri, 6 Mar 2026 12:31:57 -0800 Subject: [PATCH 8/8] update comments --- .../jsonapi/api/v1/mcp/McpIntegrationTestBase.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java index 4f48879d2d..f293bb01b7 100644 --- a/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java +++ b/src/test/java/io/stargate/sgv2/jsonapi/api/v1/mcp/McpIntegrationTestBase.java @@ -41,6 +41,10 @@ public abstract class McpIntegrationTestBase { /** Shared MCP client instance, connected once per test class. */ protected McpStreamableTestClient mcpClient; + /** + * Initializes the shared MCP client before all tests in the class. Connects to the local test + * server using Streamable HTTP transport with authentication headers. + */ @BeforeAll void setUpMcpClient() { mcpClient = @@ -52,6 +56,7 @@ void setUpMcpClient() { .connect(); } + /** Disconnects and releases the shared MCP client after all tests in the class have run. */ @AfterAll void tearDownMcpClient() { if (mcpClient != null) { @@ -99,9 +104,10 @@ protected void createCollection(String keyspace, String collection) { response -> assertFalse(response.isError())); } - protected void dropCollection(String keyspace, String collection) { + /** Delete a collection via the MCP deleteCollection tool */ + protected void deleteCollection(String keyspace, String collection) { callToolAndAssert( - "dropCollection", + "deleteCollection", Map.of("keyspace", keyspace, "collection", collection), response -> assertFalse(response.isError())); }