This is an automated email from the ASF dual-hosted git repository.
epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-mcp.git
The following commit(s) were added to refs/heads/main by this push:
new 6ccade5 test: add MCP client integration test and reorganize test
structure (#91)
6ccade5 is described below
commit 6ccade5e3a9354a3dae5d3f18c1804b08762fa87
Author: Aditya Parikh <[email protected]>
AuthorDate: Thu Apr 23 14:50:49 2026 -0400
test: add MCP client integration test and reorganize test structure (#91)
* docs: update Spring AI version reference in AGENTS.md
https://claude.ai/code/session_018sFVmRBfFQ3aaU8yyPDvCG
* test: add MCP client integration test
Add McpClientIntegrationTest that exercises the MCP server through a real
MCP client over HTTP. Boots the app in HTTP mode on a random port with
Solr via Testcontainers, connects an McpSyncClient, and tests the full
create-collection → index → search workflow via MCP tool calls.
Tests cover: ping, tool discovery, collection creation, listing,
JSON/CSV indexing, search (all docs, filter query, keyword, pagination,
facets), health check, collection stats, and schema introspection.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: adityamparikh <[email protected]>
* refactor(test): reorganize tests into unit and integration classes
Establish consistent test naming convention:
- *ServiceTest.java = unit tests (mocked dependencies, fast)
- *ServiceIntegrationTest.java = integration tests (Testcontainers, real
Solr)
Search tests:
- SearchServiceTest: unit tests with mocked SolrClient (replaces
SearchServiceDirectTest)
- SearchServiceIntegrationTest: real Solr via Testcontainers (new)
Indexing tests:
- IndexingServiceTest: unit tests with mocked SolrClient (replaces nested
UnitTests + DirectTest)
- IndexingServiceIntegrationTest: real Solr via Testcontainers (new)
Also:
- Delete redundant *DirectTest.java files
- Fix TestcontainersConfiguration default Solr image to "solr:9.9-slim"
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: adityamparikh <[email protected]>
---------
Signed-off-by: adityamparikh <[email protected]>
Co-authored-by: Claude <[email protected]>
---
AGENTS.md | 2 +-
.../solr/mcp/server/McpClientIntegrationTest.java | 325 +++++++++
.../mcp/server/TestcontainersConfiguration.java | 2 +-
.../server/indexing/IndexingServiceDirectTest.java | 212 ------
...st.java => IndexingServiceIntegrationTest.java} | 305 +--------
.../mcp/server/indexing/IndexingServiceTest.java | 732 +--------------------
.../mcp/server/search/SearchServiceDirectTest.java | 288 --------
...Test.java => SearchServiceIntegrationTest.java} | 292 +-------
.../solr/mcp/server/search/SearchServiceTest.java | 434 +-----------
9 files changed, 362 insertions(+), 2230 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 10f6aa5..b2236c7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -8,7 +8,7 @@ Solr MCP Server is a Spring AI Model Context Protocol (MCP)
server that enables
- **Status:** Apache incubating project (v0.0.2-SNAPSHOT)
- **Java:** 25+ (centralized in build.gradle.kts)
-- **Framework:** Spring Boot 3.5.8, Spring AI 1.1.2
+- **Framework:** Spring Boot 3.5.13, Spring AI 1.1.4
- **License:** Apache 2.0
## Common Commands
diff --git
a/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTest.java
b/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTest.java
new file mode 100644
index 0000000..3eed6f8
--- /dev/null
+++ b/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.solr.mcp.server;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.client.McpClient;
+import io.modelcontextprotocol.client.McpSyncClient;
+import
io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
+import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
+import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.server.LocalServerPort;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ActiveProfiles;
+import org.testcontainers.junit.jupiter.Testcontainers;
+
+/**
+ * Integration test that exercises the MCP server through a real MCP client.
+ *
+ * <p>
+ * Boots the application in HTTP mode, connects an MCP client, and tests the
+ * full create-collection → index → search workflow via MCP tool calls.
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
properties = {"http.security.enabled=false",
+ "spring.docker.compose.enabled=false"})
+@ActiveProfiles("http")
+@Import(TestcontainersConfiguration.class)
+@Testcontainers(disabledWithoutDocker = true)
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+class McpClientIntegrationTest {
+
+ private static final String COLLECTION = "mcp-client-test";
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ @LocalServerPort
+ private int port;
+
+ private McpSyncClient mcpClient;
+
+ @BeforeAll
+ void setupClient() {
+ var transport =
HttpClientStreamableHttpTransport.builder("http://localhost:" + port).build();
+ mcpClient = McpClient.sync(transport).build();
+ mcpClient.initialize();
+ }
+
+ @AfterAll
+ void tearDown() {
+ if (mcpClient != null) {
+ mcpClient.close();
+ }
+ }
+
+ @Test
+ @Order(1)
+ void pingServer() {
+ assertDoesNotThrow(() -> mcpClient.ping(), "MCP ping should
succeed");
+ }
+
+ @Test
+ @Order(2)
+ void listToolsReturnsExpectedTools() {
+ var toolsResult = mcpClient.listTools();
+ assertNotNull(toolsResult);
+ List<String> toolNames = toolsResult.tools().stream().map(t ->
t.name()).toList();
+
+ assertTrue(toolNames.contains("create-collection"), "Should
have create-collection tool");
+ assertTrue(toolNames.contains("index-json-documents"), "Should
have index-json-documents tool");
+ assertTrue(toolNames.contains("search"), "Should have search
tool");
+ assertTrue(toolNames.contains("list-collections"), "Should have
list-collections tool");
+ assertTrue(toolNames.contains("check-health"), "Should have
check-health tool");
+ assertTrue(toolNames.contains("get-collection-stats"), "Should
have get-collection-stats tool");
+ assertTrue(toolNames.contains("get-schema"), "Should have
get-schema tool");
+ }
+
+ @Test
+ @Order(3)
+ void createCollection() {
+ CallToolResult result = mcpClient
+ .callTool(new
CallToolRequest("create-collection", Map.of("name", COLLECTION)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+ assertTrue(text.contains("success") || text.contains("true"),
"Collection creation should succeed: " + text);
+ }
+
+ @Test
+ @Order(4)
+ void listCollectionsContainsCreatedCollection() {
+ CallToolResult result = mcpClient.callTool(new
CallToolRequest("list-collections", Map.of()));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+ assertTrue(text.contains(COLLECTION), "Created collection
should appear in list: " + text);
+ }
+
+ @Test
+ @Order(5)
+ void indexJsonDocuments() {
+ String json = """
+ [
+ {"id": "1", "title": "Introduction to Solr",
"author": "Alice", "category": "search"},
+ {"id": "2", "title": "MCP Protocol Guide",
"author": "Bob", "category": "protocol"},
+ {"id": "3", "title": "Spring Boot in Action",
"author": "Charlie", "category": "framework"},
+ {"id": "4", "title": "Advanced Solr
Techniques", "author": "Alice", "category": "search"},
+ {"id": "5", "title": "Building MCP Servers",
"author": "Diana", "category": "protocol"}
+ ]
+ """;
+
+ CallToolResult result = mcpClient
+ .callTool(new
CallToolRequest("index-json-documents", Map.of("collection", COLLECTION,
"json", json)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ }
+
+ @Test
+ @Order(6)
+ void checkHealthShowsIndexedDocuments() {
+ CallToolResult result = mcpClient
+ .callTool(new CallToolRequest("check-health",
Map.of("collection", COLLECTION)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+ assertTrue(text.contains("true") || text.contains("healthy"),
"Collection should be healthy: " + text);
+ assertTrue(text.contains("5"), "Should report 5 documents: " +
text);
+ }
+
+ @Test
+ @Order(7)
+ void searchAllDocuments() throws Exception {
+ CallToolResult result = mcpClient
+ .callTool(new CallToolRequest("search",
Map.of("collection", COLLECTION, "query", "*:*", "rows", 10)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+
+ Map<String, Object> response = OBJECT_MAPPER.readValue(text,
new TypeReference<>() {
+ });
+ assertEquals(5, getNumFound(response), "Should find all 5
documents");
+ }
+
+ @Test
+ @Order(8)
+ void searchWithFilterQuery() throws Exception {
+ CallToolResult result = mcpClient.callTool(new
CallToolRequest("search",
+ Map.of("collection", COLLECTION, "query",
"*:*", "filterQueries", List.of("category:search"))));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+
+ Map<String, Object> response = OBJECT_MAPPER.readValue(text,
new TypeReference<>() {
+ });
+ assertEquals(2, getNumFound(response), "Should find 2
search-category documents");
+ }
+
+ @Test
+ @Order(9)
+ void searchWithKeyword() throws Exception {
+ CallToolResult result = mcpClient
+ .callTool(new CallToolRequest("search",
Map.of("collection", COLLECTION, "query", "title:Solr")));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+
+ Map<String, Object> response = OBJECT_MAPPER.readValue(text,
new TypeReference<>() {
+ });
+ int numFound = getNumFound(response);
+ assertTrue(numFound >= 1, "Should find at least 1 document with
'Solr' in title: " + numFound);
+ }
+
+ @Test
+ @Order(10)
+ void searchWithPagination() throws Exception {
+ CallToolResult page1 = mcpClient.callTool(
+ new CallToolRequest("search",
Map.of("collection", COLLECTION, "query", "*:*", "start", 0, "rows", 2)));
+ CallToolResult page2 = mcpClient.callTool(
+ new CallToolRequest("search",
Map.of("collection", COLLECTION, "query", "*:*", "start", 2, "rows", 2)));
+
+ Map<String, Object> response1 =
OBJECT_MAPPER.readValue(extractText(page1), new TypeReference<>() {
+ });
+ Map<String, Object> response2 =
OBJECT_MAPPER.readValue(extractText(page2), new TypeReference<>() {
+ });
+
+ List<Map<String, Object>> docs1 = getDocuments(response1);
+ List<Map<String, Object>> docs2 = getDocuments(response2);
+
+ assertEquals(2, docs1.size(), "Page 1 should have 2 documents");
+ assertEquals(2, docs2.size(), "Page 2 should have 2 documents");
+ assertNotEquals(docs1.get(0).get("id"), docs2.get(0).get("id"),
"Pages should return different documents");
+ }
+
+ @Test
+ @Order(11)
+ void getCollectionStats() {
+ CallToolResult result = mcpClient
+ .callTool(new
CallToolRequest("get-collection-stats", Map.of("collection", COLLECTION)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+ assertTrue(text.contains("5") || text.contains("numDocs"),
"Stats should reference indexed documents: " + text);
+ }
+
+ @Test
+ @Order(12)
+ void getSchema() {
+ CallToolResult result = mcpClient.callTool(new
CallToolRequest("get-schema", Map.of("collection", COLLECTION)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+ assertFalse(text.isEmpty(), "Schema response should not be
empty");
+ }
+
+ @Test
+ @Order(13)
+ void searchWithFacets() throws Exception {
+ CallToolResult result = mcpClient.callTool(new
CallToolRequest("search",
+ Map.of("collection", COLLECTION, "query",
"*:*", "facetFields", List.of("id"), "rows", 0)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ String text = extractText(result);
+
+ Map<String, Object> response = OBJECT_MAPPER.readValue(text,
new TypeReference<>() {
+ });
+ @SuppressWarnings("unchecked")
+ Map<String, Object> facets = (Map<String, Object>)
response.get("facets");
+ assertNotNull(facets, "Should have facets in response");
+ assertTrue(facets.containsKey("id"), "Should have id facet");
+ }
+
+ @Test
+ @Order(14)
+ void indexCsvDocuments() {
+ String csv = """
+ id,title,author,category
+ 6,CSV Document One,Eve,csv-test
+ 7,CSV Document Two,Frank,csv-test
+ """;
+
+ CallToolResult result = mcpClient
+ .callTool(new
CallToolRequest("index-csv-documents", Map.of("collection", COLLECTION, "csv",
csv)));
+
+ assertNotNull(result);
+ assertNotError(result);
+ }
+
+ @Test
+ @Order(15)
+ void searchFindsAllDocumentsAfterCsvIndexing() throws Exception {
+ CallToolResult result = mcpClient
+ .callTool(new CallToolRequest("search",
Map.of("collection", COLLECTION, "query", "*:*", "rows", 0)));
+
+ Map<String, Object> response =
OBJECT_MAPPER.readValue(extractText(result), new TypeReference<>() {
+ });
+ assertEquals(7, getNumFound(response), "Should find 7 documents
(5 JSON + 2 CSV)");
+ }
+
+ private static String extractText(CallToolResult result) {
+ assertNotNull(result.content(), "Result content should not be
null");
+ assertFalse(result.content().isEmpty(), "Result content should
not be empty");
+ assertInstanceOf(TextContent.class, result.content().get(0),
"Content should be TextContent");
+ return ((TextContent) result.content().get(0)).text();
+ }
+
+ private static void assertNotError(CallToolResult result) {
+ if (Boolean.TRUE.equals(result.isError())) {
+ String errorText = result.content().isEmpty()
+ ? "unknown error"
+ : ((TextContent)
result.content().get(0)).text();
+ fail("MCP tool call returned error: " + errorText);
+ }
+ }
+
+ private static int getNumFound(Map<String, Object> response) {
+ Object value = response.get("numFound");
+ assertNotNull(value, "numFound should be present in response");
+ return ((Number) value).intValue();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static List<Map<String, Object>> getDocuments(Map<String,
Object> response) {
+ Object value = response.get("documents");
+ assertNotNull(value, "documents should be present in response");
+ return (List<Map<String, Object>>) value;
+ }
+
+}
diff --git
a/src/test/java/org/apache/solr/mcp/server/TestcontainersConfiguration.java
b/src/test/java/org/apache/solr/mcp/server/TestcontainersConfiguration.java
index 0a0f91d..5f6267a 100644
--- a/src/test/java/org/apache/solr/mcp/server/TestcontainersConfiguration.java
+++ b/src/test/java/org/apache/solr/mcp/server/TestcontainersConfiguration.java
@@ -29,7 +29,7 @@ public class TestcontainersConfiguration {
@Bean
SolrContainer solr() {
- String solrImage = System.getProperty("solr.test.image");
+ String solrImage = System.getProperty("solr.test.image",
"solr:9.9-slim");
return new SolrContainer(DockerImageName.parse(solrImage));
}
diff --git
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceDirectTest.java
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceDirectTest.java
deleted file mode 100644
index 4d57b34..0000000
---
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceDirectTest.java
+++ /dev/null
@@ -1,212 +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.solr.mcp.server.indexing;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.response.UpdateResponse;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.mcp.server.indexing.documentcreator.*;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-@ExtendWith(MockitoExtension.class)
-class IndexingServiceDirectTest {
-
- @Mock
- private SolrClient solrClient;
-
- @Mock
- private UpdateResponse updateResponse;
-
- private IndexingService indexingService;
- private IndexingDocumentCreator indexingDocumentCreator;
-
- @BeforeEach
- void setUp() {
- indexingDocumentCreator = new IndexingDocumentCreator(new
XmlDocumentCreator(), new CsvDocumentCreator(),
- new JsonDocumentCreator());
- indexingService = new IndexingService(solrClient,
indexingDocumentCreator);
- }
-
- @Test
- void testBatchIndexingErrorHandling() throws Exception {
- // Create a list of test documents
- List<SolrInputDocument> documents = new ArrayList<>();
- for (int i = 0; i < 10; i++) {
- SolrInputDocument doc = new SolrInputDocument();
- doc.addField("id", "test" + i);
- doc.addField("title", "Test Document " + i);
- documents.add(doc);
- }
-
- // Mock behavior: Batch add fails, but individual adds succeed
- when(solrClient.add(anyString(), anyList())).thenThrow(new
RuntimeException("Batch indexing failed"));
-
- // Individual document adds should succeed
- when(solrClient.add(anyString(),
any(SolrInputDocument.class))).thenReturn(updateResponse);
-
- // Call the method under test
- int successCount =
indexingService.indexDocuments("test_collection", documents);
-
- // Verify the results
- assertEquals(10, successCount, "All documents should be
successfully indexed individually");
-
- // Verify that batch add was attempted once
- verify(solrClient, times(1)).add(eq("test_collection"),
anyList());
-
- // Verify that individual adds were attempted for each document
- verify(solrClient, times(10)).add(eq("test_collection"),
any(SolrInputDocument.class));
-
- // Verify that commit was called
- verify(solrClient, times(1)).commit("test_collection");
- }
-
- @Test
- void testBatchIndexingPartialFailure() throws Exception {
- // Create a list of test documents
- List<SolrInputDocument> documents = new ArrayList<>();
- for (int i = 0; i < 10; i++) {
- SolrInputDocument doc = new SolrInputDocument();
- doc.addField("id", "test" + i);
- doc.addField("title", "Test Document " + i);
- documents.add(doc);
- }
-
- // Mock behavior: Batch add fails
- when(solrClient.add(anyString(), anyList())).thenThrow(new
RuntimeException("Batch indexing failed"));
-
- // Even-numbered documents succeed, odd-numbered documents fail
- for (int i = 0; i < 10; i++) {
- if (i % 2 == 0) {
- when(solrClient.add("test_collection",
documents.get(i))).thenReturn(updateResponse);
- } else {
- when(solrClient.add("test_collection",
documents.get(i)))
- .thenThrow(new
RuntimeException("Document " + i + " indexing failed"));
- }
- }
-
- // Call the method under test
- int successCount =
indexingService.indexDocuments("test_collection", documents);
-
- // Verify the results - only even-numbered documents should
succeed
- assertEquals(5, successCount, "Only half of the documents
should be successfully indexed");
-
- // Verify that batch add was attempted once
- verify(solrClient, times(1)).add(eq("test_collection"),
anyList());
-
- // Verify that individual adds were attempted for each document
- verify(solrClient, times(10)).add(eq("test_collection"),
any(SolrInputDocument.class));
-
- // Verify that commit was called
- verify(solrClient, times(1)).commit("test_collection");
- }
-
- @Test
- void testIndexJsonDocumentsWithJsonString() throws Exception {
- // Test JSON string with multiple documents
- String json = """
- [
- {
- "id": "test001",
- "title": "Test Document 1",
- "content": "This is test content 1"
- },
- {
- "id": "test002",
- "title": "Test Document 2",
- "content": "This is test content 2"
- }
- ]
- """;
-
- // Create a spy on the indexingDocumentCreator and inject it
into a new
- // IndexingService
- IndexingDocumentCreator indexingDocumentCreatorSpy =
spy(indexingDocumentCreator);
- IndexingService indexingServiceWithSpy = new
IndexingService(solrClient, indexingDocumentCreatorSpy);
- IndexingService indexingServiceSpy =
spy(indexingServiceWithSpy);
-
- // Create mock documents that would be returned by
createSchemalessDocuments
- List<SolrInputDocument> mockDocuments = new ArrayList<>();
- SolrInputDocument doc1 = new SolrInputDocument();
- doc1.addField("id", "test001");
- doc1.addField("title", "Test Document 1");
- doc1.addField("content", "This is test content 1");
-
- SolrInputDocument doc2 = new SolrInputDocument();
- doc2.addField("id", "test002");
- doc2.addField("title", "Test Document 2");
- doc2.addField("content", "This is test content 2");
-
- mockDocuments.add(doc1);
- mockDocuments.add(doc2);
-
- // Mock the createSchemalessDocuments method to return our mock
documents
-
doReturn(mockDocuments).when(indexingDocumentCreatorSpy).createSchemalessDocumentsFromJson(json);
-
- // Mock the indexDocuments method that takes a collection and
list of documents
-
doReturn(2).when(indexingServiceSpy).indexDocuments(anyString(), anyList());
-
- // Call the method under test
- indexingServiceSpy.indexJsonDocuments("test_collection", json);
-
- // Verify that createSchemalessDocuments was called with the
JSON string
- verify(indexingDocumentCreatorSpy,
times(1)).createSchemalessDocumentsFromJson(json);
-
- // Verify that indexDocuments was called with the collection
name and the
- // documents
- verify(indexingServiceSpy,
times(1)).indexDocuments("test_collection", mockDocuments);
- }
-
- @Test
- void testIndexJsonDocumentsWithJsonStringErrorHandling() throws
Exception {
- // Test JSON string with invalid format
- String invalidJson = "{ This is not valid JSON }";
-
- // Create a spy on the indexingDocumentCreator and inject it
into a new
- // IndexingService
- IndexingDocumentCreator indexingDocumentCreatorSpy =
spy(indexingDocumentCreator);
- IndexingService indexingServiceWithSpy = new
IndexingService(solrClient, indexingDocumentCreatorSpy);
- IndexingService indexingServiceSpy =
spy(indexingServiceWithSpy);
-
- // Mock the createSchemalessDocuments method to throw an
exception
- doThrow(new DocumentProcessingException("Invalid
JSON")).when(indexingDocumentCreatorSpy)
- .createSchemalessDocumentsFromJson(invalidJson);
-
- // Call the method under test and verify it throws an exception
- DocumentProcessingException exception =
assertThrows(DocumentProcessingException.class, () -> {
-
indexingServiceSpy.indexJsonDocuments("test_collection", invalidJson);
- });
-
- // Verify the exception message
- assertTrue(exception.getMessage().contains("Invalid JSON"));
-
- // Verify that createSchemalessDocuments was called
- verify(indexingDocumentCreatorSpy,
times(1)).createSchemalessDocumentsFromJson(invalidJson);
-
- // Verify that indexDocuments with documents was not called
- verify(indexingServiceSpy, never()).indexDocuments(anyString(),
anyList());
- }
-}
diff --git
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
similarity index 66%
copy from
src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
copy to
src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
index c0bec69..bb7e586 100644
--- a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
+++
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
@@ -17,16 +17,10 @@
package org.apache.solr.mcp.server.indexing;
import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.*;
-import static org.mockito.Mockito.*;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.mcp.server.TestcontainersConfiguration;
@@ -37,22 +31,23 @@ import
org.apache.solr.mcp.server.indexing.documentcreator.XmlDocumentCreator;
import org.apache.solr.mcp.server.search.SearchResponse;
import org.apache.solr.mcp.server.search.SearchService;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
+import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.testcontainers.containers.SolrContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
+/**
+ * Integration tests for IndexingService using a real Solr instance via
+ * Testcontainers.
+ */
@SpringBootTest
@Import(TestcontainersConfiguration.class)
@Testcontainers(disabledWithoutDocker = true)
-class IndexingServiceTest {
+@DisabledInNativeImage
+class IndexingServiceIntegrationTest {
private static boolean initialized = false;
@@ -758,289 +753,3 @@ class IndexingServiceTest {
assertEquals("Value 7",
doc.getFieldValue("multiple_underscores"));
}
}
-
-@Nested
-@ExtendWith(MockitoExtension.class)
-class UnitTests {
-
- @Mock
- private SolrClient solrClient;
-
- @Mock
- private IndexingDocumentCreator indexingDocumentCreator;
-
- private IndexingService indexingService;
-
- @BeforeEach
- void setUp() {
- indexingService = new IndexingService(solrClient,
indexingDocumentCreator);
- }
-
- @Test
- void constructor_ShouldInitializeWithDependencies() {
- assertNotNull(indexingService);
- }
-
- @Test
- void indexJsonDocuments_WithValidJson_ShouldIndexDocuments() throws
Exception {
- String json = "[{\"id\":\"1\",\"title\":\"Test\"}]";
- List<SolrInputDocument> mockDocs = createMockDocuments(1);
-
when(indexingDocumentCreator.createSchemalessDocumentsFromJson(json)).thenReturn(mockDocs);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- indexingService.indexJsonDocuments("test_collection", json);
-
-
verify(indexingDocumentCreator).createSchemalessDocumentsFromJson(json);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void
indexJsonDocuments_WhenDocumentCreatorThrowsException_ShouldPropagateException()
throws Exception {
- String invalidJson = "not valid json";
-
when(indexingDocumentCreator.createSchemalessDocumentsFromJson(invalidJson)).thenThrow(
- new
org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException("Invalid
JSON"));
-
-
assertThrows(org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException.class,
() -> {
- indexingService.indexJsonDocuments("test_collection",
invalidJson);
- });
- verify(solrClient, never()).add(anyString(),
any(Collection.class));
- verify(solrClient, never()).commit(anyString());
- }
-
- @Test
- void indexCsvDocuments_WithValidCsv_ShouldIndexDocuments() throws
Exception {
- String csv = "id,title\n1,Test\n2,Test2";
- List<SolrInputDocument> mockDocs = createMockDocuments(2);
-
when(indexingDocumentCreator.createSchemalessDocumentsFromCsv(csv)).thenReturn(mockDocs);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- indexingService.indexCsvDocuments("test_collection", csv);
-
-
verify(indexingDocumentCreator).createSchemalessDocumentsFromCsv(csv);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void
indexCsvDocuments_WhenDocumentCreatorThrowsException_ShouldPropagateException()
throws Exception {
- String invalidCsv = "malformed csv data";
-
when(indexingDocumentCreator.createSchemalessDocumentsFromCsv(invalidCsv)).thenThrow(
- new
org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException("Invalid
CSV"));
-
-
assertThrows(org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException.class,
() -> {
- indexingService.indexCsvDocuments("test_collection",
invalidCsv);
- });
- verify(solrClient, never()).add(anyString(),
any(Collection.class));
- verify(solrClient, never()).commit(anyString());
- }
-
- @Test
- void indexXmlDocuments_WithValidXml_ShouldIndexDocuments() throws
Exception {
- String xml =
"<documents><doc><id>1</id><title>Test</title></doc></documents>";
- List<SolrInputDocument> mockDocs = createMockDocuments(1);
-
when(indexingDocumentCreator.createSchemalessDocumentsFromXml(xml)).thenReturn(mockDocs);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- indexingService.indexXmlDocuments("test_collection", xml);
-
-
verify(indexingDocumentCreator).createSchemalessDocumentsFromXml(xml);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void
indexXmlDocuments_WhenParserConfigurationFails_ShouldPropagateException()
throws Exception {
- String xml = "<invalid>xml</invalid>";
-
when(indexingDocumentCreator.createSchemalessDocumentsFromXml(xml)).thenThrow(
- new
org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException("Parser
error"));
-
-
assertThrows(org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException.class,
() -> {
- indexingService.indexXmlDocuments("test_collection",
xml);
- });
- verify(solrClient, never()).add(anyString(),
any(Collection.class));
- verify(solrClient, never()).commit(anyString());
- }
-
- @Test
- void
indexXmlDocuments_WhenSaxExceptionOccurs_ShouldPropagateException() throws
Exception {
- String xml = "<malformed><unclosed>";
-
when(indexingDocumentCreator.createSchemalessDocumentsFromXml(xml))
- .thenThrow(new
org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException(
- "SAX parsing error"));
-
-
assertThrows(org.apache.solr.mcp.server.indexing.documentcreator.DocumentProcessingException.class,
() -> {
- indexingService.indexXmlDocuments("test_collection",
xml);
- });
- verify(solrClient, never()).add(anyString(),
any(Collection.class));
- verify(solrClient, never()).commit(anyString());
- }
-
- @Test
- void indexDocuments_WithSmallBatch_ShouldIndexSuccessfully() throws
Exception {
- List<SolrInputDocument> docs = createMockDocuments(5);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
docs);
-
- assertEquals(5, result);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void indexDocuments_WithLargeBatch_ShouldProcessInBatches() throws
Exception {
- List<SolrInputDocument> docs = createMockDocuments(2500);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit(eq("test_collection"))).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
docs);
-
- assertEquals(2500, result);
- verify(solrClient, times(3)).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void indexDocuments_WhenBatchFails_ShouldRetryIndividually() throws
Exception {
- List<SolrInputDocument> docs = createMockDocuments(3);
-
- when(solrClient.add(eq("test_collection"),
any(List.class))).thenThrow(new SolrServerException("Batch error"));
-
- when(solrClient.add(eq("test_collection"),
any(SolrInputDocument.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
docs);
-
- assertEquals(3, result);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient, times(3)).add(eq("test_collection"),
any(SolrInputDocument.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void
indexDocuments_WhenSomeIndividualDocumentsFail_ShouldIndexSuccessfulOnes()
throws Exception {
- List<SolrInputDocument> docs = createMockDocuments(3);
-
- when(solrClient.add(eq("test_collection"),
any(List.class))).thenThrow(new SolrServerException("Batch error"));
-
- when(solrClient.add(eq("test_collection"),
any(SolrInputDocument.class))).thenReturn(null)
- .thenThrow(new SolrServerException("Document
error")).thenReturn(null);
-
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
docs);
-
- assertEquals(2, result);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient, times(3)).add(eq("test_collection"),
any(SolrInputDocument.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void indexDocuments_WithEmptyList_ShouldStillCommit() throws Exception {
- List<SolrInputDocument> emptyDocs = new ArrayList<>();
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
emptyDocs);
-
- assertEquals(0, result);
- verify(solrClient, never()).add(anyString(), any(List.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void indexDocuments_WhenCommitFails_ShouldPropagateException() throws
Exception {
- List<SolrInputDocument> docs = createMockDocuments(2);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenThrow(new
IOException("Commit failed"));
-
- assertThrows(IOException.class, () -> {
- indexingService.indexDocuments("test_collection", docs);
- });
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void indexDocuments_ShouldBatchCorrectly() throws Exception {
- List<SolrInputDocument> docs = createMockDocuments(1000);
- when(solrClient.add(eq("test_collection"),
any(Collection.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
docs);
-
- assertEquals(1000, result);
-
- ArgumentCaptor<Collection<SolrInputDocument>> captor =
ArgumentCaptor.forClass(Collection.class);
- verify(solrClient).add(eq("test_collection"), captor.capture());
- assertEquals(1000, captor.getValue().size());
- verify(solrClient).commit("test_collection");
- }
-
- @Test
- void
indexJsonDocuments_WhenSolrClientThrowsException_ShouldPropagateException()
throws Exception {
- String json = "[{\"id\":\"1\"}]";
- List<SolrInputDocument> mockDocs = createMockDocuments(1);
-
when(indexingDocumentCreator.createSchemalessDocumentsFromJson(json)).thenReturn(mockDocs);
- when(solrClient.add(eq("test_collection"), any(List.class)))
- .thenThrow(new SolrServerException("Solr
connection error"));
- when(solrClient.add(eq("test_collection"),
any(SolrInputDocument.class)))
- .thenThrow(new SolrServerException("Solr
connection error"));
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- indexingService.indexJsonDocuments("test_collection", json);
-
- verify(solrClient).add(eq("test_collection"), any(List.class));
- verify(solrClient).add(eq("test_collection"),
any(SolrInputDocument.class));
- }
-
- @Test
- void
indexCsvDocuments_WhenSolrClientThrowsIOException_ShouldPropagateException()
throws Exception {
- String csv = "id,title\n1,Test";
- List<SolrInputDocument> mockDocs = createMockDocuments(1);
-
when(indexingDocumentCreator.createSchemalessDocumentsFromCsv(csv)).thenReturn(mockDocs);
- when(solrClient.add(eq("test_collection"),
any(List.class))).thenThrow(new IOException("Network error"));
- when(solrClient.add(eq("test_collection"),
any(SolrInputDocument.class)))
- .thenThrow(new IOException("Network error"));
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- indexingService.indexCsvDocuments("test_collection", csv);
-
- verify(solrClient).add(eq("test_collection"), any(List.class));
- verify(solrClient).add(eq("test_collection"),
any(SolrInputDocument.class));
- }
-
- @Test
- void indexDocuments_WithRuntimeException_ShouldRetryIndividually()
throws Exception {
- List<SolrInputDocument> docs = createMockDocuments(2);
-
- when(solrClient.add(eq("test_collection"), any(List.class)))
- .thenThrow(new RuntimeException("Unexpected
error"));
-
- when(solrClient.add(eq("test_collection"),
any(SolrInputDocument.class))).thenReturn(null);
- when(solrClient.commit("test_collection")).thenReturn(null);
-
- int result = indexingService.indexDocuments("test_collection",
docs);
-
- assertEquals(2, result);
- verify(solrClient).add(eq("test_collection"),
any(Collection.class));
- verify(solrClient, times(2)).add(eq("test_collection"),
any(SolrInputDocument.class));
- verify(solrClient).commit("test_collection");
- }
-
- private List<SolrInputDocument> createMockDocuments(int count) {
- List<SolrInputDocument> docs = new ArrayList<>();
- for (int i = 0; i < count; i++) {
- SolrInputDocument doc = new SolrInputDocument();
- doc.addField("id", "doc" + i);
- doc.addField("title", "Document " + i);
- docs.add(doc);
- }
- return docs;
- }
-}
diff --git
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
index c0bec69..186b543 100644
--- a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
@@ -24,744 +24,24 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.Map;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.mcp.server.TestcontainersConfiguration;
-import org.apache.solr.mcp.server.indexing.documentcreator.CsvDocumentCreator;
import
org.apache.solr.mcp.server.indexing.documentcreator.IndexingDocumentCreator;
-import org.apache.solr.mcp.server.indexing.documentcreator.JsonDocumentCreator;
-import org.apache.solr.mcp.server.indexing.documentcreator.XmlDocumentCreator;
-import org.apache.solr.mcp.server.search.SearchResponse;
-import org.apache.solr.mcp.server.search.SearchService;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.context.annotation.Import;
-import org.testcontainers.containers.SolrContainer;
-import org.testcontainers.junit.jupiter.Testcontainers;
-@SpringBootTest
-@Import(TestcontainersConfiguration.class)
-@Testcontainers(disabledWithoutDocker = true)
-class IndexingServiceTest {
-
- private static boolean initialized = false;
-
- private static final String COLLECTION_NAME = "indexing_test_" +
System.currentTimeMillis();
- @Autowired
- private SolrContainer solrContainer;
- @Autowired
- private IndexingDocumentCreator indexingDocumentCreator;
- @Autowired
- private IndexingService indexingService;
- @Autowired
- private SearchService searchService;
- @Autowired
- private SolrClient solrClient;
-
- @BeforeEach
- void setUp() throws Exception {
-
- // Create processor instances and wire them manually since this
is not a Spring
- // Boot test
- XmlDocumentCreator xmlDocumentCreator = new
XmlDocumentCreator();
- CsvDocumentCreator csvDocumentCreator = new
CsvDocumentCreator();
- JsonDocumentCreator jsonDocumentCreator = new
JsonDocumentCreator();
-
- indexingDocumentCreator = new
IndexingDocumentCreator(xmlDocumentCreator, csvDocumentCreator,
- jsonDocumentCreator);
-
- indexingService = new IndexingService(solrClient,
indexingDocumentCreator);
- searchService = new SearchService(solrClient);
-
- if (!initialized) {
- // Create collection
- CollectionAdminRequest.Create createRequest =
CollectionAdminRequest.createCollection(COLLECTION_NAME,
- "_default", 1, 1);
- createRequest.process(solrClient);
- initialized = true;
- }
- }
-
- @Test
- void testCreateSchemalessDocumentsFromJson() throws Exception {
- // Test JSON string
- String json = """
- [
- {
- "id": "test001",
- "cat": ["book"],
- "name": ["Test Book 1"],
- "price": [9.99],
- "inStock": [true],
- "author": ["Test Author"],
- "series_t": "Test Series",
- "sequence_i": 1,
- "genre_s": "test"
- }
- ]
- """;
-
- // Create documents
- List<SolrInputDocument> documents =
indexingDocumentCreator.createSchemalessDocumentsFromJson(json);
-
- // Verify documents were created correctly
- assertNotNull(documents);
- assertEquals(1, documents.size());
-
- SolrInputDocument doc = documents.getFirst();
- assertEquals("test001", doc.getFieldValue("id"));
-
- // Check field values - they might be stored directly or as
collections
- Object nameValue = doc.getFieldValue("name");
- if (nameValue instanceof List) {
- assertEquals("Test Book 1", ((List<?>)
nameValue).getFirst());
- } else {
- assertEquals("Test Book 1", nameValue);
- }
-
- Object priceValue = doc.getFieldValue("price");
- if (priceValue instanceof List) {
- assertEquals(9.99, ((List<?>) priceValue).getFirst());
- } else {
- assertEquals(9.99, priceValue);
- }
-
- Object inStockValue = doc.getFieldValue("inStock");
- // Check if inStock field exists
- if (inStockValue != null) {
- if (inStockValue instanceof List) {
- assertEquals(true, ((List<?>)
inStockValue).getFirst());
- } else {
- assertEquals(true, inStockValue);
- }
- } else {
- // If inStock is not present in the document, we'll
skip this assertion
- // Removed debug print statement
- }
-
- Object authorValue = doc.getFieldValue("author");
- if (authorValue instanceof List) {
- assertEquals("Test Author", ((List<?>)
authorValue).getFirst());
- } else {
- assertEquals("Test Author", authorValue);
- }
-
- assertEquals("Test Series", doc.getFieldValue("series_t"));
- assertEquals(1, doc.getFieldValue("sequence_i"));
- assertEquals("test", doc.getFieldValue("genre_s"));
- }
-
- @Test
- void testIndexJsonDocuments() throws Exception {
-
- // Test JSON string with multiple documents
- String json = """
- [
- {
- "id": "test002",
- "cat": ["book"],
- "name": ["Test Book 2"],
- "price": [19.99],
- "inStock": [true],
- "author": ["Test Author 2"],
- "genre_s": "scifi"
- },
- {
- "id": "test003",
- "cat": ["book"],
- "name": ["Test Book 3"],
- "price": [29.99],
- "inStock": [false],
- "author": ["Test Author 3"],
- "genre_s": "fantasy"
- }
- ]
- """;
-
- // Index documents
- indexingService.indexJsonDocuments(COLLECTION_NAME, json);
-
- // Verify documents were indexed by searching for them
- SearchResponse result = searchService.search(COLLECTION_NAME,
"id:test002 OR id:test003", null, null, null,
- null, null);
-
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(2, documents.size());
-
- // Verify specific document fields
- boolean foundBook2 = false;
- boolean foundBook3 = false;
-
- for (Map<String, Object> book : documents) {
- // Get ID and handle both String and List cases
- Object idValue = book.get("id");
- String id;
- if (idValue instanceof List) {
- id = (String) ((List<?>) idValue).getFirst();
- } else {
- id = (String) idValue;
- }
-
- if (id.equals("test002")) {
- foundBook2 = true;
-
- // Handle name field
- Object nameValue = book.get("name");
- if (nameValue instanceof List) {
- assertEquals("Test Book 2", ((List<?>)
nameValue).getFirst());
- } else {
- assertEquals("Test Book 2", nameValue);
- }
-
- // Handle author field
- Object authorValue = book.get("author");
- if (authorValue instanceof List) {
- assertEquals("Test Author 2",
((List<?>) authorValue).getFirst());
- } else {
- assertEquals("Test Author 2",
authorValue);
- }
-
- // Handle genre field
- Object genreValue = book.get("genre_s");
- if (genreValue instanceof List) {
- assertEquals("scifi", ((List<?>)
genreValue).getFirst());
- } else {
- assertEquals("scifi", genreValue);
- }
- } else if (id.equals("test003")) {
- foundBook3 = true;
-
- // Handle name field
- Object nameValue = book.get("name");
- if (nameValue instanceof List) {
- assertEquals("Test Book 3", ((List<?>)
nameValue).getFirst());
- } else {
- assertEquals("Test Book 3", nameValue);
- }
-
- // Handle author field
- Object authorValue = book.get("author");
- if (authorValue instanceof List) {
- assertEquals("Test Author 3",
((List<?>) authorValue).getFirst());
- } else {
- assertEquals("Test Author 3",
authorValue);
- }
-
- // Handle genre field
- Object genreValue = book.get("genre_s");
- if (genreValue instanceof List) {
- assertEquals("fantasy", ((List<?>)
genreValue).getFirst());
- } else {
- assertEquals("fantasy", genreValue);
- }
- }
- }
-
- assertTrue(foundBook2, "Book 2 should be found in search
results");
- assertTrue(foundBook3, "Book 3 should be found in search
results");
- }
-
- @Test
- void testIndexJsonDocumentsWithNestedObjects() throws Exception {
-
- // Test JSON string with nested objects
- String json = """
- [
- {
- "id": "test004",
- "cat": ["book"],
- "name": ["Test Book 4"],
- "price": [39.99],
- "details": {
- "publisher": "Test Publisher",
- "year": 2023,
- "edition": 1
- },
- "author": ["Test Author 4"]
- }
- ]
- """;
-
- // Index documents
- indexingService.indexJsonDocuments(COLLECTION_NAME, json);
-
- // Verify documents were indexed by searching for them
- SearchResponse result = searchService.search(COLLECTION_NAME,
"id:test004", null, null, null, null, null);
-
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(1, documents.size());
-
- Map<String, Object> book = documents.getFirst();
-
- // Handle ID field
- Object idValue = book.get("id");
- if (idValue instanceof List) {
- assertEquals("test004", ((List<?>) idValue).getFirst());
- } else {
- assertEquals("test004", idValue);
- }
-
- // Handle name field
- Object nameValue = book.get("name");
- if (nameValue instanceof List) {
- assertEquals("Test Book 4", ((List<?>)
nameValue).getFirst());
- } else {
- assertEquals("Test Book 4", nameValue);
- }
-
- // Check that nested fields were flattened with underscore
prefix
- assertNotNull(book.get("details_publisher"));
- Object publisherValue = book.get("details_publisher");
- if (publisherValue instanceof List) {
- assertEquals("Test Publisher", ((List<?>)
publisherValue).getFirst());
- } else {
- assertEquals("Test Publisher", publisherValue);
- }
-
- assertNotNull(book.get("details_year"));
- Object yearValue = book.get("details_year");
- if (yearValue instanceof List) {
- assertEquals(2023, ((Number) ((List<?>)
yearValue).getFirst()).intValue());
- } else if (yearValue instanceof Number) {
- assertEquals(2023, ((Number) yearValue).intValue());
- } else {
- assertEquals("2023", yearValue.toString());
- }
- }
-
- @Test
- void testSanitizeFieldName() throws Exception {
-
- // Test JSON string with field names that need sanitizing
- String json = """
- [
- {
- "id": "test005",
- "invalid-field": "Value with hyphen",
- "another.invalid": "Value with dot",
- "UPPERCASE": "Value with uppercase",
- "multiple__underscores": "Value with
multiple underscores"
- }
- ]
- """;
-
- // Index documents
- indexingService.indexJsonDocuments(COLLECTION_NAME, json);
-
- // Verify documents were indexed with sanitized field names
- SearchResponse result = searchService.search(COLLECTION_NAME,
"id:test005", null, null, null, null, null);
-
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(1, documents.size());
-
- Map<String, Object> doc = documents.getFirst();
-
- // Check that field names were sanitized
- assertNotNull(doc.get("invalid_field"));
- Object invalidFieldValue = doc.get("invalid_field");
- if (invalidFieldValue instanceof List) {
- assertEquals("Value with hyphen", ((List<?>)
invalidFieldValue).getFirst());
- } else {
- assertEquals("Value with hyphen", invalidFieldValue);
- }
-
- assertNotNull(doc.get("another_invalid"));
- Object anotherInvalidValue = doc.get("another_invalid");
- if (anotherInvalidValue instanceof List) {
- assertEquals("Value with dot", ((List<?>)
anotherInvalidValue).getFirst());
- } else {
- assertEquals("Value with dot", anotherInvalidValue);
- }
-
- // Should be lowercase
- assertNotNull(doc.get("uppercase"));
- Object uppercaseValue = doc.get("uppercase");
- if (uppercaseValue instanceof List) {
- assertEquals("Value with uppercase", ((List<?>)
uppercaseValue).getFirst());
- } else {
- assertEquals("Value with uppercase", uppercaseValue);
- }
-
- // Multiple underscores should be collapsed
- assertNotNull(doc.get("multiple_underscores"));
- Object multipleUnderscoresValue =
doc.get("multiple_underscores");
- if (multipleUnderscoresValue instanceof List) {
- assertEquals("Value with multiple underscores",
((List<?>) multipleUnderscoresValue).getFirst());
- } else {
- assertEquals("Value with multiple underscores",
multipleUnderscoresValue);
- }
- }
-
- @Test
- void testDeeplyNestedJsonStructures() throws Exception {
-
- // Test JSON string with deeply nested objects (3+ levels)
- String json = """
- [
- {
- "id": "nested001",
- "title": "Deeply nested document",
- "metadata": {
- "publication": {
- "publisher": {
- "name": "Deep Nest Publishing",
- "location": {
- "city": "Nestville",
- "country": "Nestland",
- "coordinates": {
- "latitude": 42.123,
- "longitude": -71.456
- }
- }
- },
- "year": 2023,
- "edition": {
- "number": 1,
- "type": "First Edition",
- "notes": {
- "condition": "New",
- "availability": "Limited"
- }
- }
- },
- "classification": {
- "primary": "Test",
- "secondary": {
- "category": "Nested",
- "subcategory": "Deep"
- }
- }
- }
- }
- ]
- """;
-
- // Index documents
- indexingService.indexJsonDocuments(COLLECTION_NAME, json);
-
- // Verify documents were indexed by searching for them
- SearchResponse result = searchService.search(COLLECTION_NAME,
"id:nested001", null, null, null, null, null);
-
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(1, documents.size());
-
- Map<String, Object> doc = documents.getFirst();
-
- // Check that deeply nested fields were flattened with
underscore prefix
- // Level 1
- assertNotNull(doc.get("metadata_publication_publisher_name"));
- assertEquals("Deep Nest Publishing", getFieldValue(doc,
"metadata_publication_publisher_name"));
-
- // Level 2
-
assertNotNull(doc.get("metadata_publication_publisher_location_city"));
- assertEquals("Nestville", getFieldValue(doc,
"metadata_publication_publisher_location_city"));
-
- // Level 3
-
assertNotNull(doc.get("metadata_publication_publisher_location_coordinates_latitude"));
- assertEquals(42.123,
- ((Number) getFieldValue(doc,
"metadata_publication_publisher_location_coordinates_latitude"))
- .doubleValue(),
- 0.001);
-
- // Check other branches of the nested structure
-
assertNotNull(doc.get("metadata_publication_edition_notes_condition"));
- assertEquals("New", getFieldValue(doc,
"metadata_publication_edition_notes_condition"));
-
-
assertNotNull(doc.get("metadata_classification_secondary_subcategory"));
- assertEquals("Deep", getFieldValue(doc,
"metadata_classification_secondary_subcategory"));
- }
-
- private Object getFieldValue(Map<String, Object> doc, String fieldName)
{
- Object value = doc.get(fieldName);
- if (value instanceof List) {
- return ((List<?>) value).getFirst();
- }
- return value;
- }
-
- @Test
- void testSpecialCharactersInFieldNames() throws Exception {
-
- // Test JSON string with field names containing various special
characters
- String json = """
- [
- {
- "id": "special_fields_001",
- "field@with@at": "Value with @ symbols",
- "field#with#hash": "Value with # symbols",
- "field$with$dollar": "Value with $ symbols",
- "field%with%percent": "Value with %
symbols",
- "field^with^caret": "Value with ^ symbols",
- "field&with&ersand": "Value with &
symbols",
- "field*with*asterisk": "Value with *
symbols",
- "field(with)parentheses": "Value with
parentheses",
- "field[with]brackets": "Value with
brackets",
- "field{with}braces": "Value with braces",
- "field+with+plus": "Value with + symbols",
- "field=with=equals": "Value with = symbols",
- "field:with:colon": "Value with : symbols",
- "field;with;semicolon": "Value with ;
symbols",
- "field'with'quotes": "Value with ' symbols",
- "field\\"with\\"doublequotes": "Value with
\\" symbols",
- "field<with>anglebrackets": "Value with
angle brackets",
- "field,with,commas": "Value with , symbols",
- "field?with?question": "Value with ?
symbols",
- "field/with/slashes": "Value with /
symbols",
- "field\\\\with\\\\backslashes": "Value with
\\\\ symbols",
- "field|with|pipes": "Value with | symbols",
- "field`with`backticks": "Value with `
symbols",
- "field~with~tildes": "Value with ~ symbols"
- }
- ]
- """;
-
- // Index documents
- indexingService.indexJsonDocuments(COLLECTION_NAME, json);
-
- // Verify documents were indexed by searching for them
- SearchResponse result = searchService.search(COLLECTION_NAME,
"id:special_fields_001", null, null, null, null,
- null);
-
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(1, documents.size());
-
- Map<String, Object> doc = documents.getFirst();
-
- // Check that field names with special characters were sanitized
- // All special characters should be replaced with underscores
- assertNotNull(doc.get("field_with_at"));
- assertEquals("Value with @ symbols", getFieldValue(doc,
"field_with_at"));
-
- assertNotNull(doc.get("field_with_hash"));
- assertEquals("Value with # symbols", getFieldValue(doc,
"field_with_hash"));
-
- assertNotNull(doc.get("field_with_dollar"));
- assertEquals("Value with $ symbols", getFieldValue(doc,
"field_with_dollar"));
-
- assertNotNull(doc.get("field_with_percent"));
- assertEquals("Value with % symbols", getFieldValue(doc,
"field_with_percent"));
-
- assertNotNull(doc.get("field_with_caret"));
- assertEquals("Value with ^ symbols", getFieldValue(doc,
"field_with_caret"));
-
- assertNotNull(doc.get("field_with_ampersand"));
- assertEquals("Value with & symbols", getFieldValue(doc,
"field_with_ampersand"));
-
- assertNotNull(doc.get("field_with_asterisk"));
- assertEquals("Value with * symbols", getFieldValue(doc,
"field_with_asterisk"));
-
- assertNotNull(doc.get("field_with_parentheses"));
- assertEquals("Value with parentheses", getFieldValue(doc,
"field_with_parentheses"));
-
- assertNotNull(doc.get("field_with_brackets"));
- assertEquals("Value with brackets", getFieldValue(doc,
"field_with_brackets"));
-
- assertNotNull(doc.get("field_with_braces"));
- assertEquals("Value with braces", getFieldValue(doc,
"field_with_braces"));
- }
-
- @Test
- void testArraysOfObjects() throws Exception {
-
- // Test JSON string with arrays of objects
- String json = """
- [
- {
- "id": "array_objects_001",
- "title": "Document with arrays of objects",
- "authors": [
- {
- "name": "Author One",
- "email": "[email protected]",
- "affiliation": "University A"
- },
- {
- "name": "Author Two",
- "email": "[email protected]",
- "affiliation": "University B"
- }
- ],
- "reviews": [
- {
- "reviewer": "Reviewer A",
- "rating": 4,
- "comments": "Good document"
- },
- {
- "reviewer": "Reviewer B",
- "rating": 5,
- "comments": "Excellent document"
- },
- {
- "reviewer": "Reviewer C",
- "rating": 3,
- "comments": "Average document"
- }
- ],
- "keywords": ["arrays", "objects", "testing"]
- }
- ]
- """;
-
- // Index documents
- indexingService.indexJsonDocuments(COLLECTION_NAME, json);
-
- // Verify documents were indexed by searching for them
- SearchResponse result = searchService.search(COLLECTION_NAME,
"id:array_objects_001", null, null, null, null,
- null);
-
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(1, documents.size());
-
- Map<String, Object> doc = documents.getFirst();
-
- // Check that the document was indexed correctly
- assertEquals("array_objects_001", getFieldValue(doc, "id"));
- assertEquals("Document with arrays of objects",
getFieldValue(doc, "title"));
-
- // Check that the arrays of primitive values were indexed
correctly
- Object keywordsObj = doc.get("keywords");
- if (keywordsObj instanceof List) {
- List<?> keywords = (List<?>) keywordsObj;
- assertEquals(3, keywords.size());
- assertTrue(keywords.contains("arrays"));
- assertTrue(keywords.contains("objects"));
- assertTrue(keywords.contains("testing"));
- }
-
- // For arrays of objects, the IndexingService should flatten
them with field
- // names
- // that include the array name and the object field name
- // We can't directly access the array elements, but we can
check if the
- // flattened fields
- // exist
-
- // Check for flattened author fields
- // Note: The current implementation in IndexingService.java
doesn't handle
- // arrays of objects
- // in a way that preserves the array structure. It skips object
items in arrays
- // (line
- // 68-70).
- // This test is checking the current behavior, which may need
improvement in the
- // future.
-
- // Check for flattened review fields
- // Same note as above applies here
- }
-
- @Test
- void testNonArrayJsonInput() throws Exception {
- // Test JSON string that is not an array but a single object
- String json = """
- {
- "id": "single_object_001",
- "title": "Single Object Document",
- "author": "Test Author",
- "year": 2023
- }
- """;
-
- // Create documents
- List<SolrInputDocument> documents =
indexingDocumentCreator.createSchemalessDocumentsFromJson(json);
-
- // Verify no documents were created since input is not an array
- assertNotNull(documents);
- assertEquals(0, documents.size());
- }
-
- @Test
- void testConvertJsonValueTypes() throws Exception {
- // Test JSON with different value types
- String json = """
- [
- {
- "id": "value_types_001",
- "boolean_value": true,
- "int_value": 42,
- "double_value": 3.14159,
- "long_value": 9223372036854775807,
- "text_value": "This is a text value"
- }
- ]
- """;
-
- // Create documents
- List<SolrInputDocument> documents =
indexingDocumentCreator.createSchemalessDocumentsFromJson(json);
-
- // Verify documents were created correctly
- assertNotNull(documents);
- assertEquals(1, documents.size());
-
- SolrInputDocument doc = documents.getFirst();
- assertEquals("value_types_001", doc.getFieldValue("id"));
-
- // Verify each value type was converted correctly
- assertEquals(true, doc.getFieldValue("boolean_value"));
- assertEquals(42, doc.getFieldValue("int_value"));
- assertEquals(3.14159, doc.getFieldValue("double_value"));
- assertEquals(9223372036854775807L,
doc.getFieldValue("long_value"));
- assertEquals("This is a text value",
doc.getFieldValue("text_value"));
- }
-
- @Test
- void testDirectSanitizeFieldName() throws Exception {
- // Test sanitizing field names directly
- // Create a document with field names that need sanitizing
- String json = """
- [
- {
- "id": "field_names_001",
- "field-with-hyphens": "Value 1",
- "field.with.dots": "Value 2",
- "field with spaces": "Value 3",
- "UPPERCASE_FIELD": "Value 4",
- "__leading_underscores__": "Value 5",
- "trailing_underscores___": "Value 6",
- "multiple___underscores": "Value 7"
- }
- ]
- """;
-
- // Create documents
- List<SolrInputDocument> documents =
indexingDocumentCreator.createSchemalessDocumentsFromJson(json);
-
- // Verify documents were created correctly
- assertNotNull(documents);
- assertEquals(1, documents.size());
-
- SolrInputDocument doc = documents.getFirst();
-
- // Verify field names were sanitized correctly
- assertEquals("field_names_001", doc.getFieldValue("id"));
- assertEquals("Value 1",
doc.getFieldValue("field_with_hyphens"));
- assertEquals("Value 2", doc.getFieldValue("field_with_dots"));
- assertEquals("Value 3", doc.getFieldValue("field_with_spaces"));
- assertEquals("Value 4", doc.getFieldValue("uppercase_field"));
- assertEquals("Value 5",
doc.getFieldValue("leading_underscores"));
- assertEquals("Value 6",
doc.getFieldValue("trailing_underscores"));
- assertEquals("Value 7",
doc.getFieldValue("multiple_underscores"));
- }
-}
-
-@Nested
+/**
+ * Unit tests for IndexingService with mocked SolrClient.
+ */
@ExtendWith(MockitoExtension.class)
-class UnitTests {
+@DisabledInNativeImage
+class IndexingServiceTest {
@Mock
private SolrClient solrClient;
diff --git
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceDirectTest.java
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceDirectTest.java
deleted file mode 100644
index ae38775..0000000
---
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceDirectTest.java
+++ /dev/null
@@ -1,288 +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.solr.mcp.server.search;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import org.apache.solr.client.solrj.SolrClient;
-import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.request.SolrQuery;
-import org.apache.solr.client.solrj.response.FacetField;
-import org.apache.solr.client.solrj.response.QueryResponse;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.Mock;
-import org.mockito.junit.jupiter.MockitoExtension;
-
-@ExtendWith(MockitoExtension.class)
-class SearchServiceDirectTest {
-
- @Mock
- private SolrClient solrClient;
-
- @Mock
- private QueryResponse queryResponse;
-
- private SearchService searchService;
-
- @BeforeEach
- void setUp() {
- searchService = new SearchService(solrClient);
- }
-
- @Test
- void testBasicSearch() throws SolrServerException, IOException {
- // Setup mock response
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(2);
- documents.setStart(0);
- documents.setMaxScore(1.0f);
-
- SolrDocument doc1 = new SolrDocument();
- doc1.addField("id", "1");
- doc1.addField("name", List.of("Book 1"));
- doc1.addField("author", List.of("Author 1"));
-
- SolrDocument doc2 = new SolrDocument();
- doc2.addField("id", "2");
- doc2.addField("name", List.of("Book 2"));
- doc2.addField("author", List.of("Author 2"));
-
- documents.add(doc1);
- documents.add(doc2);
-
- when(queryResponse.getResults()).thenReturn(documents);
- when(solrClient.query(eq("books"),
any(SolrQuery.class))).thenReturn(queryResponse);
-
- // Test
- SearchResponse result = searchService.search("books", null,
null, null, null, null, null);
-
- // Verify
- assertNotNull(result);
- assertEquals(2L, result.numFound());
-
- List<Map<String, Object>> resultDocs = result.documents();
- assertEquals(2, resultDocs.size());
- assertEquals("1", resultDocs.getFirst().get("id"));
- assertEquals("2", resultDocs.get(1).get("id"));
- }
-
- @Test
- void testSearchWithFacets() throws SolrServerException, IOException {
- // Setup mock response
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(2);
- documents.setStart(0);
- documents.setMaxScore(1.0f);
-
- SolrDocument doc1 = new SolrDocument();
- doc1.addField("id", "1");
- doc1.addField("genre_s", "fantasy");
-
- SolrDocument doc2 = new SolrDocument();
- doc2.addField("id", "2");
- doc2.addField("genre_s", "scifi");
-
- documents.add(doc1);
- documents.add(doc2);
-
- // Create facet fields
- List<FacetField> facetFields = new ArrayList<>();
- FacetField genreFacet = new FacetField("genre_s");
- genreFacet.add("fantasy", 1);
- genreFacet.add("scifi", 1);
- facetFields.add(genreFacet);
-
- when(queryResponse.getResults()).thenReturn(documents);
- when(queryResponse.getFacetFields()).thenReturn(facetFields);
- when(solrClient.query(eq("books"),
any(SolrQuery.class))).thenReturn(queryResponse);
-
- // Test
- SearchResponse result = searchService.search("books", null,
null, null, null, null, null);
-
- // Verify
- assertNotNull(result);
- assertTrue(result.facets().containsKey("genre_s"));
-
- Map<String, Long> genreFacets = result.facets().get("genre_s");
- assertEquals(2, genreFacets.size());
- assertEquals(1L, genreFacets.get("fantasy"));
- assertEquals(1L, genreFacets.get("scifi"));
- }
-
- @Test
- void testSearchWithEmptyResults() throws SolrServerException,
IOException {
- // Setup mock response with empty results
- SolrDocumentList emptyDocuments = new SolrDocumentList();
- emptyDocuments.setNumFound(0);
- emptyDocuments.setStart(0);
- emptyDocuments.setMaxScore(0.0f);
-
- when(queryResponse.getResults()).thenReturn(emptyDocuments);
- when(solrClient.query(eq("books"),
any(SolrQuery.class))).thenReturn(queryResponse);
-
- // Test
- SearchResponse result = searchService.search("books",
"nonexistent_query", null, null, null, null, null);
-
- // Verify
- assertNotNull(result);
- assertEquals(0L, result.numFound());
- assertEquals(0, result.documents().size());
- assertTrue(result.facets().isEmpty());
- }
-
- @Test
- void testSearchWithEmptyFacets() throws SolrServerException,
IOException {
- // Setup mock response with documents but no facets
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(2);
- documents.setStart(0);
- documents.setMaxScore(1.0f);
-
- SolrDocument doc1 = new SolrDocument();
- doc1.addField("id", "1");
- doc1.addField("name", "Book 1");
-
- SolrDocument doc2 = new SolrDocument();
- doc2.addField("id", "2");
- doc2.addField("name", "Book 2");
-
- documents.add(doc1);
- documents.add(doc2);
-
- when(queryResponse.getResults()).thenReturn(documents);
- when(queryResponse.getFacetFields()).thenReturn(null); // No
facet fields
- when(solrClient.query(eq("books"),
any(SolrQuery.class))).thenReturn(queryResponse);
-
- // Test with facet fields requested but none returned
- SearchResponse result = searchService.search("books", null,
null, List.of("genre_s"), null, null, null);
-
- // Verify
- assertNotNull(result);
- assertEquals(2L, result.numFound());
- assertEquals(2, result.documents().size());
- assertTrue(result.facets().isEmpty());
- }
-
- @Test
- void testSearchWithEmptyFacetValues() throws SolrServerException,
IOException {
- // Setup mock response with facet fields but no values
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(2);
- documents.setStart(0);
- documents.setMaxScore(1.0f);
-
- SolrDocument doc1 = new SolrDocument();
- doc1.addField("id", "1");
- SolrDocument doc2 = new SolrDocument();
- doc2.addField("id", "2");
-
- documents.add(doc1);
- documents.add(doc2);
-
- // Create facet field with no values
- List<FacetField> facetFields = new ArrayList<>();
- FacetField emptyFacet = new FacetField("genre_s");
- facetFields.add(emptyFacet);
-
- when(queryResponse.getResults()).thenReturn(documents);
- when(queryResponse.getFacetFields()).thenReturn(facetFields);
- when(solrClient.query(eq("books"),
any(SolrQuery.class))).thenReturn(queryResponse);
-
- // Test
- SearchResponse result = searchService.search("books", null,
null, List.of("genre_s"), null, null, null);
-
- // Verify
- assertNotNull(result);
- assertEquals(2L, result.numFound());
- assertTrue(result.facets().containsKey("genre_s"));
- assertTrue(result.facets().get("genre_s").isEmpty());
- }
-
- @Test
- void testSearchWithSolrError() {
- // Setup mock to throw exception
- try {
- when(solrClient.query(eq("books"),
any(SolrQuery.class)))
- .thenThrow(new
SolrServerException("Simulated Solr server error"));
-
- // Test
- assertThrows(SolrServerException.class, () -> {
- searchService.search("books", null, null, null,
null, null, null);
- });
- } catch (Exception e) {
- fail("Test setup failed: " + e.getMessage());
- }
- }
-
- @Test
- void testSearchWithAllParameters() throws SolrServerException,
IOException {
- // Setup mock response
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(1);
- documents.setStart(5);
- documents.setMaxScore(0.75f);
-
- SolrDocument doc = new SolrDocument();
- doc.addField("id", "5");
- doc.addField("name", "Book 5");
- doc.addField("author", "Author 5");
- doc.addField("genre_s", "mystery");
- doc.addField("price", 12.99);
-
- documents.add(doc);
-
- // Create facet fields
- List<FacetField> facetFields = new ArrayList<>();
- FacetField genreFacet = new FacetField("genre_s");
- genreFacet.add("mystery", 1);
- facetFields.add(genreFacet);
-
- when(queryResponse.getResults()).thenReturn(documents);
- when(queryResponse.getFacetFields()).thenReturn(facetFields);
- when(solrClient.query(eq("books"),
any(SolrQuery.class))).thenReturn(queryResponse);
-
- // Test with all parameters
- List<String> filterQueries = List.of("price:[10 TO 15]");
- List<String> facetFields2 = List.of("genre_s", "author");
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "desc"));
-
- SearchResponse result = searchService.search("books",
"mystery", filterQueries, facetFields2, sortClauses, 5,
- 10);
-
- // Verify
- assertNotNull(result);
- assertEquals(1L, result.numFound());
- assertEquals(5, result.start());
- assertEquals(0.75f, result.maxScore());
- assertEquals(1, result.documents().size());
- assertEquals("5", result.documents().getFirst().get("id"));
- assertEquals("Book 5",
result.documents().getFirst().get("name"));
- assertTrue(result.facets().containsKey("genre_s"));
- assertEquals(1L, result.facets().get("genre_s").get("mystery"));
- }
-}
diff --git
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
similarity index 54%
copy from src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
copy to
src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
index 1e8f8be..59f712a 100644
--- a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
+++
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
@@ -17,10 +17,6 @@
package org.apache.solr.mcp.server.search;
import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.ArrayList;
@@ -30,30 +26,26 @@ import java.util.OptionalDouble;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.client.solrj.request.SolrQuery;
-import org.apache.solr.client.solrj.response.FacetField;
-import org.apache.solr.client.solrj.response.QueryResponse;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.mcp.server.TestcontainersConfiguration;
import org.apache.solr.mcp.server.indexing.IndexingService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.testcontainers.junit.jupiter.Testcontainers;
/**
- * Combined tests for SearchService: integration + unit (mocked SolrClient) in
- * one class.
+ * Integration tests for SearchService using a real Solr instance via
+ * Testcontainers.
*/
@SpringBootTest
@Import(TestcontainersConfiguration.class)
@Testcontainers(disabledWithoutDocker = true)
-class SearchServiceTest {
+@DisabledInNativeImage
+class SearchServiceIntegrationTest {
- // ===== Integration test context =====
private static final String COLLECTION_NAME = "search_test_" +
System.currentTimeMillis();
@Autowired
@@ -183,8 +175,6 @@ class SearchServiceTest {
}
}
- // ===== Integration tests (from original SearchServiceTest) =====
-
@Test
void testBasicSearch() throws SolrServerException, IOException {
SearchResponse result = searchService.search(COLLECTION_NAME,
null, null, null, null, null, null);
@@ -406,239 +396,6 @@ class SearchServiceTest {
assertTrue(result.numFound() > 0, "Should find at least one
document");
}
- // ===== Unit-style tests with mocked SolrClient (from
SearchServiceUnitTest)
- // =====
-
- @Test
- void unit_constructor_ShouldInitializeWithSolrClient() {
- SearchService localService = new
SearchService(mock(SolrClient.class));
- assertNotNull(localService);
- }
-
- @Test
- void unit_search_WithNullQuery_ShouldDefaultToMatchAll() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery q = invocation.getArgument(1);
- assertEquals("*:*", q.getQuery());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, null, null, null, null);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WithCustomQuery_ShouldUseProvidedQuery() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- String customQuery = "name:\"Spring Boot\"";
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery q = invocation.getArgument(1);
- assertEquals(customQuery, q.getQuery());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
customQuery, null, null, null, null, null);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WithFilterQueries_ShouldApplyFilters() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- List<String> filterQueries = List.of("genre_s:fantasy",
"price:[0 TO 10]");
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery q = invocation.getArgument(1);
- assertArrayEquals(filterQueries.toArray(),
q.getFilterQueries());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, filterQueries, null, null, null, null);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WithFacetFields_ShouldEnableFaceting() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- List<String> facetFields = List.of("genre_s", "author_ss");
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
-
when(mockResponse.getFacetFields()).thenReturn(createMockFacetFields());
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> mockResponse);
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, facetFields, null, null, null);
- assertNotNull(result);
- assertNotNull(result.facets());
- }
-
- @Test
- void unit_search_WithSortClauses_ShouldApplySorting() throws Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "asc"),
- Map.of("item", "name", "order", "desc"));
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> mockResponse);
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, null, sortClauses, null, null);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WithPagination_ShouldApplyStartAndRows() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- Integer start = 10;
- Integer rows = 20;
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery q = invocation.getArgument(1);
- assertEquals(start, q.getStart());
- assertEquals(rows, q.getRows());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, null, null, start, rows);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WithAllParameters_ShouldCombineAllOptions() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- String query = "title:Java";
- List<String> filterQueries = List.of("inStock:true");
- List<String> facetFields = List.of("category");
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "asc"));
- Integer start = 0;
- Integer rows = 10;
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
-
when(mockResponse.getFacetFields()).thenReturn(createMockFacetFields());
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery captured = invocation.getArgument(1);
- assertEquals(query, captured.getQuery());
- assertArrayEquals(filterQueries.toArray(),
captured.getFilterQueries());
- assertNotNull(captured.getFacetFields());
- assertEquals(start, captured.getStart());
- assertEquals(rows, captured.getRows());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
query, filterQueries, facetFields, sortClauses,
- start, rows);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WhenSolrThrowsException_ShouldPropagateException()
throws Exception {
- SolrClient mockClient = mock(SolrClient.class);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class)))
- .thenThrow(new SolrServerException("Connection
error"));
- SearchService localService = new SearchService(mockClient);
- assertThrows(SolrServerException.class,
- () -> localService.search("test_collection",
null, null, null, null, null, null));
- }
-
- @Test
- void unit_search_WhenIOException_ShouldPropagateException() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenThrow(new IOException("Network error"));
- SearchService localService = new SearchService(mockClient);
- assertThrows(IOException.class,
- () -> localService.search("test_collection",
null, null, null, null, null, null));
- }
-
- @Test
- void unit_search_WithEmptyResults_ShouldReturnEmptyDocumentList()
throws Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- SolrDocumentList emptyDocuments = new SolrDocumentList();
- emptyDocuments.setNumFound(0);
- emptyDocuments.setStart(0);
- when(mockResponse.getResults()).thenReturn(emptyDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenReturn(mockResponse);
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
"nonexistent:value", null, null, null, null,
- null);
- assertNotNull(result);
- assertEquals(0, result.numFound());
- assertTrue(result.documents().isEmpty());
- }
-
- @Test
- void unit_search_WithNullFilterQueries_ShouldNotApplyFilters() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery q = invocation.getArgument(1);
- assertNull(q.getFilterQueries());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, null, null, null, null);
- assertNotNull(result);
- }
-
- @Test
- void unit_search_WithEmptyFacetFields_ShouldNotEnableFaceting() throws
Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- SolrDocumentList mockDocuments = createMockDocumentList();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
- when(mockResponse.getFacetFields()).thenReturn(null);
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenAnswer(invocation -> {
- SolrQuery q = invocation.getArgument(1);
- assertNull(q.getFacetFields());
- return mockResponse;
- });
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, List.of(), null, null, null);
- assertNotNull(result);
- }
-
- @Test
- void unit_searchResponse_ShouldContainAllFields() throws Exception {
- SolrClient mockClient = mock(SolrClient.class);
- QueryResponse mockResponse = mock(QueryResponse.class);
- SolrDocumentList mockDocuments =
createMockDocumentListWithData();
- when(mockResponse.getResults()).thenReturn(mockDocuments);
-
when(mockResponse.getFacetFields()).thenReturn(createMockFacetFields());
- when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenReturn(mockResponse);
- SearchService localService = new SearchService(mockClient);
- SearchResponse result = localService.search("test_collection",
null, null, List.of("genre_s"), null, null,
- null);
- assertNotNull(result);
- assertEquals(2, result.numFound());
- assertEquals(0, result.start());
- assertNotNull(result.documents());
- assertEquals(2, result.documents().size());
- assertNotNull(result.facets());
- assertFalse(result.facets().isEmpty());
- }
-
- // ===== Helpers (from unit tests and integration tests) =====
-
private OptionalDouble extractPrice(Map<String, Object> document) {
Object priceObj = document.get("price");
if (priceObj == null) {
@@ -668,43 +425,4 @@ class SearchServiceTest {
}
return ids;
}
-
- private SolrDocumentList createMockDocumentList() {
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(0);
- documents.setStart(0);
- return documents;
- }
-
- private SolrDocumentList createMockDocumentListWithData() {
- SolrDocumentList documents = new SolrDocumentList();
- documents.setNumFound(2);
- documents.setStart(0);
- documents.setMaxScore(1.0f);
- SolrDocument doc1 = new SolrDocument();
- doc1.setField("id", "book001");
- doc1.setField("name", "Spring Boot in Action");
- doc1.setField("author_ss", List.of("Craig Walls"));
- doc1.setField("price", 39.99);
- doc1.setField("genre_s", "technology");
- documents.add(doc1);
- SolrDocument doc2 = new SolrDocument();
- doc2.setField("id", "book002");
- doc2.setField("name", "Effective Java");
- doc2.setField("author_ss", List.of("Joshua Bloch"));
- doc2.setField("price", 44.99);
- doc2.setField("genre_s", "technology");
- documents.add(doc2);
- return documents;
- }
-
- private List<FacetField> createMockFacetFields() {
- FacetField genreFacet = new FacetField("genre_s");
- genreFacet.add("technology", 5);
- genreFacet.add("fiction", 3);
- FacetField authorFacet = new FacetField("author_ss");
- authorFacet.add("Craig Walls", 2);
- authorFacet.add("Joshua Bloch", 1);
- return List.of(genreFacet, authorFacet);
- }
}
diff --git
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
index 1e8f8be..e12d9d1 100644
--- a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
@@ -23,400 +23,32 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.OptionalDouble;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.SolrQuery;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.mcp.server.TestcontainersConfiguration;
-import org.apache.solr.mcp.server.indexing.IndexingService;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.context.annotation.Import;
-import org.testcontainers.junit.jupiter.Testcontainers;
+import org.junit.jupiter.api.condition.DisabledInNativeImage;
/**
- * Combined tests for SearchService: integration + unit (mocked SolrClient) in
- * one class.
+ * Unit tests for SearchService with mocked SolrClient.
*/
-@SpringBootTest
-@Import(TestcontainersConfiguration.class)
-@Testcontainers(disabledWithoutDocker = true)
+@DisabledInNativeImage
class SearchServiceTest {
- // ===== Integration test context =====
- private static final String COLLECTION_NAME = "search_test_" +
System.currentTimeMillis();
-
- @Autowired
- private SearchService searchService;
- @Autowired
- private IndexingService indexingService;
- @Autowired
- private SolrClient solrClient;
-
- private static boolean initialized = false;
-
- @BeforeEach
- void setUp() throws Exception {
- if (!initialized) {
- CollectionAdminRequest.Create createRequest =
CollectionAdminRequest.createCollection(COLLECTION_NAME,
- "_default", 1, 1);
- createRequest.process(solrClient);
-
- String sampleData = """
- [
- {
- "id": "book001",
- "name": ["A Game of Thrones"],
- "author_ss": ["George R.R. Martin"],
- "price": [7.99],
- "genre_s": "fantasy",
- "series_s": "A Song of Ice and
Fire",
- "sequence_i": 1,
- "cat_ss": ["book"]
- },
- {
- "id": "book002",
- "name": ["A Clash of Kings"],
- "author_ss": ["George R.R. Martin"],
- "price": [8.99],
- "genre_s": "fantasy",
- "series_s": "A Song of Ice and
Fire",
- "sequence_i": 2,
- "cat_ss": ["book"]
- },
- {
- "id": "book003",
- "name": ["A Storm of Swords"],
- "author_ss": ["George R.R. Martin"],
- "price": [9.99],
- "genre_s": "fantasy",
- "series_s": "A Song of Ice and
Fire",
- "sequence_i": 3,
- "cat_ss": ["book"]
- },
- {
- "id": "book004",
- "name": ["The Hobbit"],
- "author_ss": ["J.R.R. Tolkien"],
- "price": [6.99],
- "genre_s": "fantasy",
- "series_s": "Middle Earth",
- "sequence_i": 1,
- "cat_ss": ["book"]
- },
- {
- "id": "book005",
- "name": ["Dune"],
- "author_ss": ["Frank Herbert"],
- "price": [10.99],
- "genre_s": "scifi",
- "series_s": "Dune",
- "sequence_i": 1,
- "cat_ss": ["book"]
- },
- {
- "id": "book006",
- "name": ["Foundation"],
- "author_ss": ["Isaac Asimov"],
- "price": [5.99],
- "genre_s": "scifi",
- "series_s": "Foundation",
- "sequence_i": 1,
- "cat_ss": ["book"]
- },
- {
- "id": "book007",
- "name": ["The Fellowship of the
Ring"],
- "author_ss": ["J.R.R. Tolkien"],
- "price": [8.99],
- "genre_s": "fantasy",
- "series_s": "The Lord of the Rings",
- "sequence_i": 1,
- "cat_ss": ["book"]
- },
- {
- "id": "book008",
- "name": ["The Two Towers"],
- "author_ss": ["J.R.R. Tolkien"],
- "price": [8.99],
- "genre_s": "fantasy",
- "series_s": "The Lord of the Rings",
- "sequence_i": 2,
- "cat_ss": ["book"]
- },
- {
- "id": "book009",
- "name": ["The Return of the King"],
- "author_ss": ["J.R.R. Tolkien"],
- "price": [8.99],
- "genre_s": "fantasy",
- "series_s": "The Lord of the Rings",
- "sequence_i": 3,
- "cat_ss": ["book"]
- },
- {
- "id": "book010",
- "name": ["Neuromancer"],
- "author_ss": ["William Gibson"],
- "price": [7.99],
- "genre_s": "scifi",
- "series_s": "Sprawl",
- "sequence_i": 1,
- "cat_ss": ["book"]
- }
- ]
- """;
-
- indexingService.indexJsonDocuments(COLLECTION_NAME,
sampleData);
- solrClient.commit(COLLECTION_NAME);
- initialized = true;
- }
- }
-
- // ===== Integration tests (from original SearchServiceTest) =====
-
- @Test
- void testBasicSearch() throws SolrServerException, IOException {
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, null, null, null, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- assertEquals(10, documents.size());
- }
-
- @Test
- void testSearchWithQuery() throws SolrServerException, IOException {
- SearchResponse result = searchService.search(COLLECTION_NAME,
"name:\"Game of Thrones\"", null, null, null,
- null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(1, documents.size());
- Map<String, Object> book = documents.getFirst();
- assertEquals("A Game of Thrones", ((List<?>)
book.get("name")).getFirst());
- }
-
- @Test
- void testSearchReturnsAuthor() throws Exception {
- SearchResponse result = searchService.search(COLLECTION_NAME,
"author_ss:\"George R.R. Martin\"", null, null,
- null, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertEquals(3, documents.size());
- Map<String, Object> book = documents.getFirst();
- assertEquals("George R.R. Martin", ((List<?>)
book.get("author_ss")).getFirst());
- }
-
- @Test
- void testSearchWithFacets() throws Exception {
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, null, List.of("genre_s"), null, null, null);
- assertNotNull(result);
- Map<String, Map<String, Long>> facets = result.facets();
- assertNotNull(facets);
- assertTrue(facets.containsKey("genre_s"));
- }
-
- @Test
- void testSearchWithPrice() throws Exception {
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, null, null, null, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- Map<String, Object> book = documents.getFirst();
- double currentPrice = ((List<?>) book.get("price")).isEmpty()
- ? 0.0
- : ((Number) ((List<?>)
book.get("price")).getFirst()).doubleValue();
- assertTrue(currentPrice > 0);
- }
-
- @Test
- void testSortByPriceAscending() throws Exception {
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "asc"));
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, null, null, sortClauses, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- double previousPrice = 0.0;
- for (Map<String, Object> book : documents) {
- OptionalDouble priceOpt = extractPrice(book);
- if (priceOpt.isEmpty())
- continue;
- double currentPrice = priceOpt.getAsDouble();
- assertTrue(currentPrice >= previousPrice, "Books should
be sorted by price in ascending order");
- previousPrice = currentPrice;
- }
- }
-
- @Test
- void testSortByPriceDescending() throws Exception {
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "desc"));
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, null, null, sortClauses, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- double previousPrice = Double.MAX_VALUE;
- for (Map<String, Object> book : documents) {
- OptionalDouble priceOpt = extractPrice(book);
- if (priceOpt.isEmpty())
- continue;
- double currentPrice = priceOpt.getAsDouble();
- assertTrue(currentPrice <= previousPrice, "Books should
be sorted by price in descending order");
- previousPrice = currentPrice;
- }
- }
-
- @Test
- void testSortBySequence() throws Exception {
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"sequence_i", "order", "asc"));
- List<String> filterQueries = List.of("series_s:\"A Song of Ice
and Fire\"");
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, filterQueries, null, sortClauses, null,
- null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- int previousSequence = 0;
- for (Map<String, Object> book : documents) {
- int currentSequence = ((Number)
book.get("sequence_i")).intValue();
- assertTrue(currentSequence >= previousSequence, "Books
should be sorted by sequence_i in ascending order");
- previousSequence = currentSequence;
- }
- }
-
- @Test
- void testFilterByGenre() throws Exception {
- List<String> filterQueries = List.of("genre_s:fantasy");
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, filterQueries, null, null, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- for (Map<String, Object> book : documents) {
- String genre = (String) book.get("genre_s");
- assertEquals("fantasy", genre, "All books should have
genre_s = fantasy");
- }
- }
-
@Test
- void testFilterByPriceRange() throws Exception {
- List<String> filterQueries = List.of("price:[6.0 TO 7.0]");
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, filterQueries, null, null, null, null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- for (Map<String, Object> book : documents) {
- if (book.get("price") == null)
- continue;
- OptionalDouble priceOpt = extractPrice(book);
- if (priceOpt.isEmpty())
- continue;
- double price = priceOpt.getAsDouble();
- assertTrue(price >= 6.0 && price <= 7.0, "All books
should have price between 6.0 and 7.0");
- }
- }
-
- @Test
- void testCombinedSortingAndFiltering() throws Exception {
- List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "desc"));
- List<String> filterQueries = List.of("genre_s:fantasy");
- SearchResponse result = searchService.search(COLLECTION_NAME,
null, filterQueries, null, sortClauses, null,
- null);
- assertNotNull(result);
- List<Map<String, Object>> documents = result.documents();
- assertFalse(documents.isEmpty());
- for (Map<String, Object> book : documents) {
- String genre = (String) book.get("genre_s");
- assertEquals("fantasy", genre, "All books should have
genre_s = fantasy");
- }
- double previousPrice = Double.MAX_VALUE;
- for (Map<String, Object> book : documents) {
- Object priceObj = book.get("price");
- double currentPrice;
- if (priceObj instanceof List) {
- List<?> priceList = (List<?>) priceObj;
- if (priceList.isEmpty()) {
- continue;
- }
- currentPrice = ((Number)
priceList.getFirst()).doubleValue();
- } else if (priceObj instanceof Number) {
- currentPrice = ((Number)
priceObj).doubleValue();
- } else {
- continue;
- }
- assertTrue(currentPrice <= previousPrice, "Books should
be sorted by price in descending order");
- previousPrice = currentPrice;
- }
- }
-
- @Test
- void testPagination() throws Exception {
- SearchResponse allResults =
searchService.search(COLLECTION_NAME, null, null, null, null, null, null);
- assertNotNull(allResults);
- long totalDocuments = allResults.numFound();
- assertTrue(totalDocuments > 0, "Should have at least some
documents");
- SearchResponse firstPage =
searchService.search(COLLECTION_NAME, null, null, null, null, 0, 2);
- assertNotNull(firstPage);
- assertEquals(0, firstPage.start(), "Start offset should be 0");
- assertEquals(totalDocuments, firstPage.numFound(), "Total count
should match");
- assertEquals(2, firstPage.documents().size(), "Should return
exactly 2 documents");
- SearchResponse secondPage =
searchService.search(COLLECTION_NAME, null, null, null, null, 2, 2);
- assertNotNull(secondPage);
- assertEquals(2, secondPage.start(), "Start offset should be 2");
- assertEquals(totalDocuments, secondPage.numFound(), "Total
count should match");
- assertEquals(2, secondPage.documents().size(), "Should return
exactly 2 documents");
- List<String> firstPageIds =
getDocumentIds(firstPage.documents());
- List<String> secondPageIds =
getDocumentIds(secondPage.documents());
- for (String id : firstPageIds) {
- assertFalse(secondPageIds.contains(id), "Second page
should not contain documents from first page");
- }
- }
-
- @Test
- void testSpecialCharactersInQuery() throws Exception {
- String specialJson = """
- [
- {
- "id": "special001",
- "title": "Book with special characters: & +
- ! ( ) { } [ ] ^ \\" ~ * ? : \\\\ /",
- "author_ss": ["Special Author (with
parentheses)"],
- "description": "This is a test document
with special characters: & + - ! ( ) { } [ ] ^ \\" ~ * ? : \\\\ /"
- }
- ]
- """;
- indexingService.indexJsonDocuments(COLLECTION_NAME,
specialJson);
- solrClient.commit(COLLECTION_NAME);
- String query = "id:special001";
- SearchResponse result = searchService.search(COLLECTION_NAME,
query, null, null, null, null, null);
- assertNotNull(result);
- assertEquals(1, result.numFound(), "Should find exactly one
document");
- query = "author_ss:\"Special Author \\(" + "with
parentheses\\)\""; // escape parentheses
- result = searchService.search(COLLECTION_NAME, query, null,
null, null, null, null);
- assertNotNull(result);
- assertEquals(1, result.numFound(), "Should find exactly one
document");
- query = "title:special*";
- result = searchService.search(COLLECTION_NAME, query, null,
null, null, null, null);
- assertNotNull(result);
- assertTrue(result.numFound() > 0, "Should find at least one
document");
- }
-
- // ===== Unit-style tests with mocked SolrClient (from
SearchServiceUnitTest)
- // =====
-
- @Test
- void unit_constructor_ShouldInitializeWithSolrClient() {
+ void constructor_ShouldInitializeWithSolrClient() {
SearchService localService = new
SearchService(mock(SolrClient.class));
assertNotNull(localService);
}
@Test
- void unit_search_WithNullQuery_ShouldDefaultToMatchAll() throws
Exception {
+ void search_WithNullQuery_ShouldDefaultToMatchAll() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
SolrDocumentList mockDocuments = createMockDocumentList();
@@ -433,7 +65,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithCustomQuery_ShouldUseProvidedQuery() throws
Exception {
+ void search_WithCustomQuery_ShouldUseProvidedQuery() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
String customQuery = "name:\"Spring Boot\"";
@@ -451,7 +83,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithFilterQueries_ShouldApplyFilters() throws
Exception {
+ void search_WithFilterQueries_ShouldApplyFilters() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
List<String> filterQueries = List.of("genre_s:fantasy",
"price:[0 TO 10]");
@@ -469,7 +101,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithFacetFields_ShouldEnableFaceting() throws
Exception {
+ void search_WithFacetFields_ShouldEnableFaceting() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
List<String> facetFields = List.of("genre_s", "author_ss");
@@ -484,7 +116,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithSortClauses_ShouldApplySorting() throws Exception {
+ void search_WithSortClauses_ShouldApplySorting() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
List<Map<String, String>> sortClauses = List.of(Map.of("item",
"price", "order", "asc"),
@@ -499,7 +131,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithPagination_ShouldApplyStartAndRows() throws
Exception {
+ void search_WithPagination_ShouldApplyStartAndRows() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
Integer start = 10;
@@ -519,7 +151,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithAllParameters_ShouldCombineAllOptions() throws
Exception {
+ void search_WithAllParameters_ShouldCombineAllOptions() throws
Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
String query = "title:Java";
@@ -547,7 +179,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WhenSolrThrowsException_ShouldPropagateException()
throws Exception {
+ void search_WhenSolrThrowsException_ShouldPropagateException() throws
Exception {
SolrClient mockClient = mock(SolrClient.class);
when(mockClient.query(eq("test_collection"),
any(SolrQuery.class)))
.thenThrow(new SolrServerException("Connection
error"));
@@ -557,7 +189,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WhenIOException_ShouldPropagateException() throws
Exception {
+ void search_WhenIOException_ShouldPropagateException() throws Exception
{
SolrClient mockClient = mock(SolrClient.class);
when(mockClient.query(eq("test_collection"),
any(SolrQuery.class))).thenThrow(new IOException("Network error"));
SearchService localService = new SearchService(mockClient);
@@ -566,7 +198,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithEmptyResults_ShouldReturnEmptyDocumentList()
throws Exception {
+ void search_WithEmptyResults_ShouldReturnEmptyDocumentList() throws
Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
SolrDocumentList emptyDocuments = new SolrDocumentList();
@@ -584,7 +216,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithNullFilterQueries_ShouldNotApplyFilters() throws
Exception {
+ void search_WithNullFilterQueries_ShouldNotApplyFilters() throws
Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
SolrDocumentList mockDocuments = createMockDocumentList();
@@ -601,7 +233,7 @@ class SearchServiceTest {
}
@Test
- void unit_search_WithEmptyFacetFields_ShouldNotEnableFaceting() throws
Exception {
+ void search_WithEmptyFacetFields_ShouldNotEnableFaceting() throws
Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
SolrDocumentList mockDocuments = createMockDocumentList();
@@ -618,7 +250,7 @@ class SearchServiceTest {
}
@Test
- void unit_searchResponse_ShouldContainAllFields() throws Exception {
+ void searchResponse_ShouldContainAllFields() throws Exception {
SolrClient mockClient = mock(SolrClient.class);
QueryResponse mockResponse = mock(QueryResponse.class);
SolrDocumentList mockDocuments =
createMockDocumentListWithData();
@@ -637,38 +269,6 @@ class SearchServiceTest {
assertFalse(result.facets().isEmpty());
}
- // ===== Helpers (from unit tests and integration tests) =====
-
- private OptionalDouble extractPrice(Map<String, Object> document) {
- Object priceObj = document.get("price");
- if (priceObj == null) {
- return OptionalDouble.empty();
- }
- if (priceObj instanceof List) {
- List<?> priceList = (List<?>) priceObj;
- if (priceList.isEmpty()) {
- return OptionalDouble.empty();
- }
- return OptionalDouble.of(((Number)
priceList.getFirst()).doubleValue());
- } else if (priceObj instanceof Number) {
- return OptionalDouble.of(((Number)
priceObj).doubleValue());
- }
- return OptionalDouble.empty();
- }
-
- private List<String> getDocumentIds(List<Map<String, Object>>
documents) {
- List<String> ids = new ArrayList<>();
- for (Map<String, Object> doc : documents) {
- Object idObj = doc.get("id");
- if (idObj instanceof List) {
- ids.add(((List<?>)
idObj).getFirst().toString());
- } else if (idObj != null) {
- ids.add(idObj.toString());
- }
- }
- return ids;
- }
-
private SolrDocumentList createMockDocumentList() {
SolrDocumentList documents = new SolrDocumentList();
documents.setNumFound(0);