-
Notifications
You must be signed in to change notification settings - Fork 417
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* #1199 - add UT * #1199 - sonar fix --------- Co-authored-by: Duy Le Van <[email protected]>
- Loading branch information
Showing
14 changed files
with
2,451 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 33 additions & 0 deletions
33
...dation/src/test/java/com/yas/recommendation/config/KafkaIntegrationTestConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.yas.recommendation.config; | ||
|
||
import common.container.ContainerFactory; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.test.context.TestConfiguration; | ||
import org.springframework.boot.testcontainers.service.connection.ServiceConnection; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.test.context.DynamicPropertyRegistry; | ||
import org.testcontainers.containers.KafkaContainer; | ||
import org.testcontainers.containers.PostgreSQLContainer; | ||
import org.testcontainers.utility.DockerImageName; | ||
|
||
@TestConfiguration | ||
public class KafkaIntegrationTestConfiguration { | ||
|
||
@Value("${kafka.version}") | ||
private String kafkaVersion; | ||
|
||
@Value("${pgvector.version}") | ||
private String pgVectorVersion; | ||
|
||
@Bean | ||
@ServiceConnection | ||
public KafkaContainer kafkaContainer(DynamicPropertyRegistry registry) { | ||
return ContainerFactory.kafkaContainer(registry, kafkaVersion); | ||
} | ||
|
||
@Bean | ||
@ServiceConnection | ||
public PostgreSQLContainer pgvectorContainer(DynamicPropertyRegistry registry) { | ||
return ContainerFactory.pgvector(registry, pgVectorVersion); | ||
} | ||
} |
125 changes: 125 additions & 0 deletions
125
recommendation/src/test/java/com/yas/recommendation/query/VectorQueryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package com.yas.recommendation.query; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.when; | ||
|
||
import com.yas.recommendation.config.KafkaIntegrationTestConfiguration; | ||
import com.yas.recommendation.configuration.EmbeddingSearchConfiguration; | ||
import com.yas.recommendation.service.ProductService; | ||
import com.yas.recommendation.vector.product.query.RelatedProductQuery; | ||
import com.yas.recommendation.vector.product.store.ProductVectorRepository; | ||
import com.yas.recommendation.viewmodel.ProductDetailVm; | ||
import com.yas.recommendation.viewmodel.RelatedProductVm; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Random; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.ai.document.Document; | ||
import org.springframework.ai.embedding.EmbeddingModel; | ||
import org.springframework.ai.vectorstore.VectorStore; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.context.SpringBootTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.context.annotation.Import; | ||
import org.springframework.jdbc.core.JdbcTemplate; | ||
import org.springframework.test.context.TestPropertySource; | ||
import org.testcontainers.junit.jupiter.Testcontainers; | ||
|
||
@Testcontainers | ||
@SpringBootTest | ||
@Import(KafkaIntegrationTestConfiguration.class) | ||
@TestPropertySource("classpath:application-test.properties") | ||
public class VectorQueryTest { | ||
|
||
@Autowired | ||
private JdbcTemplate jdbcClient; | ||
|
||
@Autowired | ||
private VectorStore vectorStore; | ||
|
||
@Autowired | ||
private RelatedProductQuery relatedProductQuery; | ||
|
||
@Autowired | ||
private ProductVectorRepository productVectorRepository; | ||
|
||
@MockBean | ||
private EmbeddingModel embeddingModel; | ||
|
||
@MockBean | ||
private ProductService productService; | ||
|
||
@MockBean | ||
private EmbeddingSearchConfiguration embeddingSearchConfiguration; | ||
|
||
@AfterEach | ||
public void tearDown() { | ||
jdbcClient.execute("DELETE FROM vector_store;"); | ||
} | ||
|
||
@Test | ||
public void testSimilaritySearch() { | ||
// Given | ||
var productId = -1L; | ||
var similarProductId = -2L; | ||
ProductDetailVm searchedProduct = getProductDetailVm(productId); | ||
ProductDetailVm similarProduct = getProductDetailVm(similarProductId); | ||
|
||
// When | ||
when(embeddingSearchConfiguration.topK()).thenReturn(10); | ||
when(embeddingSearchConfiguration.similarityThreshold()).thenReturn(-1D); // force to query all data, not depend on vector compare operation | ||
when(productService.getProductDetail(productId)).thenReturn(searchedProduct); | ||
when(productService.getProductDetail(similarProductId)).thenReturn(similarProduct); | ||
when(embeddingModel.embed(any(Document.class))).thenReturn(randomEmbed()); | ||
productVectorRepository.add(productId); | ||
productVectorRepository.add(similarProductId); | ||
List<RelatedProductVm> relatedProductVms = relatedProductQuery.similaritySearch(-2L); | ||
|
||
// Then | ||
assertFalse(relatedProductVms.isEmpty()); | ||
} | ||
|
||
private static float @NotNull [] randomEmbed() { | ||
int size = 1536; | ||
float[] floatArray = new float[size]; | ||
Random random = new Random(); | ||
for (int i = 0; i < size; i++) { | ||
floatArray[i] = random.nextFloat(); | ||
} | ||
return floatArray; | ||
} | ||
|
||
private static @NotNull ProductDetailVm getProductDetailVm(long productId) { | ||
return new ProductDetailVm( | ||
productId, | ||
"IPhone 14 Pro", | ||
"Latest iPhone model", | ||
"The iPhone 14 Pro comes with the latest technology...", | ||
"6.1-inch display, A16 Bionic chip, 128GB Storage", | ||
"IPH14PRO", | ||
"0123456789012", | ||
"iphone-14-pro", | ||
true, | ||
true, | ||
true, | ||
true, | ||
true, | ||
999.99, | ||
101L, | ||
Collections.emptyList(), | ||
"iPhone 14 Pro", | ||
"iPhone, Apple, Smartphone", | ||
"Buy the latest iPhone 14 Pro...", | ||
1L, | ||
"Apple", | ||
Collections.emptyList(), | ||
null, | ||
null, | ||
null | ||
); | ||
} | ||
|
||
} |
96 changes: 96 additions & 0 deletions
96
recommendation/src/test/java/com/yas/recommendation/store/BaseVectorRepositoryTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package com.yas.recommendation.store; | ||
|
||
import static com.yas.recommendation.vector.common.store.SimpleVectorRepository.TYPE_METADATA; | ||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.yas.recommendation.configuration.EmbeddingSearchConfiguration; | ||
import com.yas.recommendation.vector.common.document.BaseDocument; | ||
import com.yas.recommendation.vector.common.document.DocumentMetadata; | ||
import com.yas.recommendation.vector.common.formatter.DocumentFormatter; | ||
import java.util.Map; | ||
import lombok.Getter; | ||
import lombok.SneakyThrows; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.springframework.ai.document.Document; | ||
import org.springframework.ai.vectorstore.SearchRequest; | ||
import org.springframework.ai.vectorstore.filter.Filter; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
|
||
public class BaseVectorRepositoryTest<D extends BaseDocument, E> { | ||
|
||
private final Class<D> docClass; | ||
private final DocumentMetadata documentMetadata; | ||
|
||
@Getter | ||
private final DocumentFormatter documentFormatter; | ||
|
||
@Getter | ||
@Autowired | ||
private ObjectMapper objectMapper; | ||
|
||
@Autowired | ||
private EmbeddingSearchConfiguration embeddingSearchConf; | ||
|
||
@SneakyThrows | ||
public BaseVectorRepositoryTest(Class<D> docClass) { | ||
Assertions.assertNotNull(docClass, "Document must not be 'null'"); | ||
this.docClass = docClass; | ||
this.documentMetadata = getDocumentMetadata(); | ||
this.documentFormatter = documentMetadata | ||
.documentFormatter() | ||
.getDeclaredConstructor() | ||
.newInstance(); | ||
} | ||
|
||
public DocumentMetadata getDocumentMetadata() { | ||
assertTrue( | ||
docClass.isAnnotationPresent(DocumentMetadata.class), | ||
"Document must be annotated by 'DocumentMetadata'" | ||
); | ||
return docClass.getAnnotation(DocumentMetadata.class); | ||
} | ||
|
||
public void assertDocumentData(Document createdDoc, E entity) { | ||
var expectedContent = getFormatEntity(entity); | ||
assertEquals(expectedContent, createdDoc.getContent(), "Document format must be formated at declared metadata"); | ||
assertNotNull(createdDoc.getMetadata(), "Document's metadata must not be null"); | ||
assertFalse(createdDoc.getMetadata().isEmpty(), "Document's metadata must not be empty"); | ||
|
||
var expectedMetadata = objectMapper.convertValue(entity, Map.class); | ||
expectedMetadata.put(TYPE_METADATA, documentMetadata.docIdPrefix()); | ||
assertEquals(expectedMetadata.keySet(), createdDoc.getMetadata().keySet()); | ||
} | ||
|
||
public void assertSearchRequest(SearchRequest searchRequest, E entity) { | ||
assertNotNull(searchRequest.query, "Search query must be created"); | ||
assertEquals(getFormatEntity(entity), searchRequest.query, "Search's Query must be formatted correctly"); | ||
assertEquals(searchRequest.getTopK(), embeddingSearchConf.topK(), "Search's top K must be configured"); | ||
assertEquals( | ||
searchRequest.getSimilarityThreshold(), | ||
embeddingSearchConf.similarityThreshold(), | ||
"Search's top K must be configured" | ||
); | ||
assertNotNull(searchRequest.getFilterExpression(), "Search filter default must be specified"); | ||
assertEquals( | ||
searchRequest.getFilterExpression().type(), | ||
Filter.ExpressionType.NE, | ||
"Search filter default must be correctly" | ||
); | ||
|
||
Filter.Key key = (Filter.Key) searchRequest.getFilterExpression().left(); | ||
assertEquals(key.key(), "id", "Search filter default must be correctly"); | ||
} | ||
|
||
private String getFormatEntity(E entity) { | ||
return documentFormatter.format( | ||
objectMapper.convertValue(entity, Map.class), | ||
documentMetadata.contentFormat(), | ||
objectMapper | ||
); | ||
} | ||
|
||
} |
Oops, something went wrong.