FIX BUILD: Fix code coverage and some improvements (#2010)

## Issue
Build was failing due to low test coverage after [this
change](https://github.com/langchain4j/langchain4j/pull/1987/files#diff-9a5519a26a4b6d2fd412c877de4459c2a119e793fece9385f558a93a7e0aee5aR273-R275).
The hotfix is to enable tracing for logger with mockStatic for in the
affected DefaultRetrievalAugmentorTest.

Other changes:

* **Refine integration test run conditions to exclude experimental
builds and require the presence of the `OPENAI_API_KEY` secret.**
* [Upgrade mockito
instrumentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#0.3)
(javaagent) setup to make it compatible with new JDKs
* Updated `pom.xml` to upgrade `jacoco-maven-plugin` version. 
* Enhanced `README.md` with additional badges for nightly build and
Codacy dashboard.
(`[README.mdL3-R6](diffhunk://#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L3-R6)`)
* Introduced new tests for comparison filters
* Added tests for logical filters
* Added configuration for external dependencies in
`.idea/externalDependencies.xml` to include SonarLint and SpotBugs
plugins.

## General checklist
- [x] There are no breaking changes
- [x] I have added unit and integration tests for my change
- [ ] I have manually run all the unit and integration tests in the
module I have added/changed, and they are all green
- [ ] I have manually run all the unit and integration tests in the
[core](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-core)
and
[main](https://github.com/langchain4j/langchain4j/tree/main/langchain4j)
modules, and they are all green
- [ ] I have added/updated the
[documentation](https://github.com/langchain4j/langchain4j/tree/main/docs/docs)
- [ ] I have added an example in the [examples
repo](https://github.com/langchain4j/langchain4j-examples) (only for
"big" features)
- [ ] I have added/updated [Spring Boot
starter(s)](https://github.com/langchain4j/langchain4j-spring) (if
applicable)
This commit is contained in:
Konstantin Pavlov 2024-10-31 17:34:05 +02:00 committed by GitHub
parent aa0e488166
commit 9178367109
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 650 additions and 80 deletions

View File

@ -46,6 +46,8 @@ jobs:
- java_version: 23
mvn_opts: ''
experimental: true
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
steps:
- uses: actions/checkout@v4
with:
@ -69,11 +71,10 @@ jobs:
${{ matrix.mvn_opts }}
- name: Integration test with JDK ${{ matrix.java_version }}
## allow builds to run only:
## - on the main branch when it is not a pull request,
## - for pull requests from the same repository
## - when triggered manually
if: ${{ !matrix.experimental }} && (github.event_name == 'pull_request' && github.head_repo.full_name == github.repository) || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch'
## The step or job will only run if the `experimental` variable
## in the matrix is false (not set to true)
## and the OPENAI_API_KEY secret is available and not empty.
if: ${{ !matrix.experimental && env.OPENAI_API_KEY != '' }}
run: |
mvn -B -U verify \
-Dgib.disable=false -Dgib.referenceBranch=__branch_before \

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalDependencies">
<plugin id="org.sonarlint.idea" />
<plugin id="org.jetbrains.plugins.spotbugs" />
</component>
</project>

View File

@ -1,6 +1,9 @@
# LangChain for Java: Supercharge your Java application with the power of LLMs
[![Build Status](https://img.shields.io/github/actions/workflow/status/langchain4j/langchain4j/main.yaml?branch=main&style=for-the-badge&label=GITHUB%20ACTIONS&logo=github)](https://github.com/langchain4j/langchain4j/actions/workflows/main.yaml)
[![Build Status](https://img.shields.io/github/actions/workflow/status/langchain4j/langchain4j/main.yaml?branch=main&style=for-the-badge&label=CI%20BUILD&logo=github)](https://github.com/langchain4j/langchain4j/actions/workflows/main.yaml)
[![Nightly Build](https://img.shields.io/github/actions/workflow/status/langchain4j/langchain4j/nightly.yaml?branch=main&style=for-the-badge&label=NIGHTLY%20BUILD&logo=github)](https://github.com/langchain4j/langchain4j/actions/workflows/nightly.yaml)
[![CODACY](https://img.shields.io/badge/Codacy-Dashboard-blue?style=for-the-badge&logo=codacy)](https://app.codacy.com/gh/langchain4j/langchain4j/dashboard)
[![Discord](https://dcbadge.vercel.app/api/server/JzTFvyjG6R?style=for-the-badge)](https://discord.gg/JzTFvyjG6R)
[![X](https://img.shields.io/badge/@langchain4j-follow-blue?logo=x&style=for-the-badge)](https://x.com/langchain4j)
[![Maven Version](https://img.shields.io/maven-central/v/dev.langchain4j/langchain4j?logo=apachemaven&style=for-the-badge)](https://search.maven.org/#search|gav|1|g:"dev.langchain4j"%20AND%20a:"langchain4j")

View File

@ -22,11 +22,11 @@ public class GraalVmJavaScriptExecutionEngine implements CodeExecutionEngine {
public String execute(String code) {
OutputStream outputStream = new ByteArrayOutputStream();
try (Context context = Context.newBuilder("js")
.sandbox(CONSTRAINED)
.allowHostAccess(UNTRUSTED)
.out(outputStream)
.err(outputStream)
.build()) {
.sandbox(CONSTRAINED)
.allowHostAccess(UNTRUSTED)
.out(outputStream)
.err(outputStream)
.build()) {
Object result = context.eval("js", code).as(Object.class);
return String.valueOf(result);
}

View File

@ -22,11 +22,11 @@ public class GraalVmPythonExecutionEngine implements CodeExecutionEngine {
public String execute(String code) {
OutputStream outputStream = new ByteArrayOutputStream();
try (Context context = Context.newBuilder("python")
.sandbox(TRUSTED)
.allowHostAccess(UNTRUSTED)
.out(outputStream)
.err(outputStream)
.build()) {
.sandbox(TRUSTED)
.allowHostAccess(UNTRUSTED)
.out(outputStream)
.err(outputStream)
.build()) {
Object result = context.eval("python", code).as(Object.class);
return String.valueOf(result);
}

View File

@ -25,11 +25,11 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
this.apiKey = apiKey;
this.languageId = languageId;
this.client = new OkHttpClient.Builder()
.connectTimeout(timeout)
.readTimeout(timeout)
.writeTimeout(timeout)
.callTimeout(timeout)
.build();
.connectTimeout(timeout)
.readTimeout(timeout)
.writeTimeout(timeout)
.callTimeout(timeout)
.build();
}
@Override
@ -42,10 +42,10 @@ class Judge0JavaScriptEngine implements CodeExecutionEngine {
RequestBody requestBody = RequestBody.create(Json.toJson(submission), MEDIA_TYPE);
Request request = new Request.Builder()
.url("https://judge0-ce.p.rapidapi.com/submissions?base64_encoded=true&wait=true&fields=*")
.addHeader("X-RapidAPI-Key", apiKey)
.post(requestBody)
.build();
.url("https://judge0-ce.p.rapidapi.com/submissions?base64_encoded=true&wait=true&fields=*")
.addHeader("X-RapidAPI-Key", apiKey)
.post(requestBody)
.build();
try {
Response response = client.newCall(request).execute();

View File

@ -34,6 +34,7 @@
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
@ -85,7 +86,6 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@ -101,7 +101,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.11</version>
<version>0.8.12</version>
<executions>
<execution>
<id>prepare-agent</id>
@ -209,4 +209,21 @@
</license>
</licenses>
<reporting>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<reportSets>
<reportSet>
<reports>
<!-- select non-aggregate reports -->
<report>report</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
</project>

View File

@ -12,9 +12,14 @@ import dev.langchain4j.rag.query.router.DefaultQueryRouter;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.rag.query.transformer.DefaultQueryTransformer;
import dev.langchain4j.rag.query.transformer.QueryTransformer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.MockedStatic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.HashMap;
@ -32,14 +37,31 @@ import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
class DefaultRetrievalAugmentorTest {
private static MockedStatic<LoggerFactory> loggerFactoryMock;
@BeforeAll
static void mockLogger() {
loggerFactoryMock = mockStatic(LoggerFactory.class);
Logger logger = mock(Logger.class);
when(LoggerFactory.getLogger(DefaultRetrievalAugmentor.class)).thenReturn(logger);
when(logger.isTraceEnabled()).thenReturn(true);
}
@AfterAll
static void releaseLogger() {
loggerFactoryMock.close();
}
@ParameterizedTest
@MethodSource("executors")
void should_augment_user_message__multiple_queries_multiple_retrievers(Executor executor) {
@ -64,12 +86,12 @@ class DefaultRetrievalAugmentorTest {
ContentInjector contentInjector = spy(new TestContentInjector());
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(executor)
.build();
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(executor)
.build();
UserMessage userMessage = UserMessage.from("query");
@ -80,7 +102,7 @@ class DefaultRetrievalAugmentorTest {
// then
assertThat(augmented.singleText()).isEqualTo(
"""
"""
query
content 1
content 2
@ -109,25 +131,25 @@ class DefaultRetrievalAugmentorTest {
Map<Query, Collection<List<Content>>> queryToContents = new HashMap<>();
queryToContents.put(query1, asList(
asList(content1, content2),
asList(content3, content4)
asList(content1, content2),
asList(content3, content4)
));
queryToContents.put(query2, asList(
asList(content1, content2),
asList(content3, content4)
asList(content1, content2),
asList(content3, content4)
));
verify(contentAggregator).aggregate(queryToContents);
verifyNoMoreInteractions(contentAggregator);
verify(contentInjector).inject(asList(
content1, content2, content3, content4,
content1, content2, content3, content4
content1, content2, content3, content4,
content1, content2, content3, content4
), userMessage);
verify(contentInjector).inject(asList(
content1, content2, content3, content4,
content1, content2, content3, content4
content1, content2, content3, content4,
content1, content2, content3, content4
), (ChatMessage) userMessage);
verifyNoMoreInteractions(contentInjector);
}
@ -155,12 +177,12 @@ class DefaultRetrievalAugmentorTest {
Executor executor = spy(new TestExecutor());
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(executor)
.build();
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(executor)
.build();
UserMessage userMessage = UserMessage.from("query");
@ -171,7 +193,7 @@ class DefaultRetrievalAugmentorTest {
// then
assertThat(augmented.singleText()).isEqualTo(
"""
"""
query
content 1
content 2
@ -194,8 +216,8 @@ class DefaultRetrievalAugmentorTest {
Map<Query, Collection<List<Content>>> queryToContents = new HashMap<>();
queryToContents.put(query, asList(
asList(content1, content2),
asList(content3, content4)
asList(content1, content2),
asList(content3, content4)
));
verify(contentAggregator).aggregate(queryToContents);
@ -236,12 +258,12 @@ class DefaultRetrievalAugmentorTest {
Executor executor = mock(Executor.class);
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(executor)
.build();
.queryTransformer(queryTransformer)
.queryRouter(queryRouter)
.contentAggregator(contentAggregator)
.contentInjector(contentInjector)
.executor(executor)
.build();
UserMessage userMessage = UserMessage.from("query");
@ -252,7 +274,7 @@ class DefaultRetrievalAugmentorTest {
// then
assertThat(augmented.singleText()).isEqualTo(
"""
"""
query
content 1
content 2"""
@ -289,9 +311,9 @@ class DefaultRetrievalAugmentorTest {
QueryRouter queryRouter = spy(new TestQueryRouter(retrievers));
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.queryRouter(queryRouter)
.executor(executor)
.build();
.queryRouter(queryRouter)
.executor(executor)
.build();
UserMessage userMessage = UserMessage.from("query");
@ -309,14 +331,14 @@ class DefaultRetrievalAugmentorTest {
static Stream<Executor> executors() {
return Stream.<Executor>builder()
.add(Executors.newCachedThreadPool())
.add(Executors.newFixedThreadPool(1))
.add(Executors.newFixedThreadPool(2))
.add(Executors.newFixedThreadPool(3))
.add(Executors.newFixedThreadPool(4))
.add(Runnable::run) // same thread executor
.add(null) // to use default Executor in DefaultRetrievalAugmentor
.build();
.add(Executors.newCachedThreadPool())
.add(Executors.newFixedThreadPool(1))
.add(Executors.newFixedThreadPool(2))
.add(Executors.newFixedThreadPool(3))
.add(Executors.newFixedThreadPool(4))
.add(Runnable::run) // same thread executor
.add(null) // to use default Executor in DefaultRetrievalAugmentor
.build();
}
static class TestQueryTransformer implements QueryTransformer {
@ -366,10 +388,10 @@ class DefaultRetrievalAugmentorTest {
@Override
public List<Content> aggregate(Map<Query, Collection<List<Content>>> queryToContents) {
return queryToContents.values()
.stream()
.flatMap(Collection::stream)
.flatMap(List::stream)
.collect(toList());
.stream()
.flatMap(Collection::stream)
.flatMap(List::stream)
.collect(toList());
}
}
@ -378,8 +400,8 @@ class DefaultRetrievalAugmentorTest {
@Override
public UserMessage inject(List<Content> contents, UserMessage userMessage) {
String joinedContents = contents.stream()
.map(it -> it.textSegment().text())
.collect(joining("\n"));
.map(it -> it.textSegment().text())
.collect(joining("\n"));
return UserMessage.from(userMessage.text() + "\n" + joinedContents);
}
}

View File

@ -159,9 +159,10 @@ class LanguageModelQueryRouterTest {
ChatModelMock model = ChatModelMock.thatAlwaysResponds("Sorry, I don't know");
Map<ContentRetriever, String> retrieverToDescription = new LinkedHashMap<>();
retrieverToDescription.put(catArticlesRetriever, "articles about cats");
retrieverToDescription.put(dogArticlesRetriever, "articles about dogs");
final var retrieverToDescription = Map.of(
catArticlesRetriever, "articles about cats",
dogArticlesRetriever, "articles about dogs"
);
QueryRouter router = new LanguageModelQueryRouter(model, retrieverToDescription);
@ -290,4 +291,4 @@ class LanguageModelQueryRouterTest {
.isExactlyInstanceOf(RuntimeException.class)
.hasMessageContaining("Something went wrong");
}
}
}

View File

@ -0,0 +1,27 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import dev.langchain4j.store.embedding.filter.Filter;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
abstract class AbstractComparisonTest<T extends Filter> {
protected T subject;
@Test
void shouldReturnsFalseWhenObjectIsNotMetadata() {
assertThat(subject.test(new Object())).isFalse();
}
@Test
void shouldReturnsFalseWhenMetadataDoesNotContainKey() {
Metadata metadata = mock(Metadata.class);
when(metadata.containsKey("key")).thenReturn(false);
assertThat(subject.test(metadata)).isFalse();
}
}

View File

@ -0,0 +1,52 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class IsEqualToTest {
@Test
void testShouldReturnFalseWhenNotMetadata() {
IsEqualTo isEqualTo = new IsEqualTo("key", "value");
assertThat(isEqualTo.test("notMetadata")).isFalse();
}
@Test
void testShouldReturnFalseWhenKeyNotFound() {
IsEqualTo isEqualTo = new IsEqualTo("key", "value");
Metadata metadata = new Metadata(Map.of());
assertThat(isEqualTo.test(metadata)).isFalse();
}
@Test
void testShouldReturnTrueWhenValuesAreNumbers() {
IsEqualTo isEqualTo = new IsEqualTo("key", 2);
Metadata metadata = new Metadata(Map.of("key", 2));
assertThat(isEqualTo.test(metadata)).isTrue();
}
@Test
void testShouldReturnTrueWhenValuesAreStrings() {
IsEqualTo isEqualTo = new IsEqualTo("key", "value");
Metadata metadata = new Metadata(new HashMap<>() {{
put("key", "value");
}});
assertThat(isEqualTo.test(metadata)).isTrue();
}
@Test
void testShouldReturnTrueWhenActualValueIsUUIDAsString() {
UUID uuid = UUID.randomUUID();
IsEqualTo isEqualTo = new IsEqualTo("key", uuid);
Metadata metadata = new Metadata(new HashMap<>() {{
put("key", uuid.toString());
}});
assertThat(isEqualTo.test(metadata)).isTrue();
}
}

View File

@ -0,0 +1,30 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class IsGreaterThanOrEqualToTest extends AbstractComparisonTest<IsGreaterThanOrEqualTo> {
@BeforeEach
void setUp() {
subject = new IsGreaterThanOrEqualTo("key", 5);
}
@ParameterizedTest
@CsvSource({
"4, false",
"5, true",
"6, true"
})
void testComparisonValue(Integer value, boolean expectedResult) {
Metadata metadata = Metadata.from(Map.of("key", value));
assertThat(subject.test(metadata)).isEqualTo(expectedResult);
}
}

View File

@ -0,0 +1,34 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class IsGreaterThanTest extends AbstractComparisonTest<IsGreaterThan>{
@BeforeEach
void beforeEach() {
subject = new IsGreaterThan("key", 5);
}
@ParameterizedTest
@CsvSource({
"0, false",
"4, false",
"5, false",
"6, true"
})
void testComparisonValue(Integer value, boolean expectedResult) {
Metadata metadata = Metadata.from(Map.of("key", value));
assertThat(subject.test(metadata)).isEqualTo(expectedResult);
}
}

View File

@ -0,0 +1,31 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class IsLessThanOrEqualToTest extends AbstractComparisonTest<IsLessThanOrEqualTo> {
@BeforeEach
void setUp() {
subject = new IsLessThanOrEqualTo("key", 5);
}
@ParameterizedTest
@CsvSource({
"0, true",
"4, true",
"5, true",
"6, false"
})
void testComparisonValue(Integer value, boolean expectedResult) {
Metadata metadata = Metadata.from(Map.of("key", value));
assertThat(subject.test(metadata)).isEqualTo(expectedResult);
}
}

View File

@ -0,0 +1,31 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class IsLessThanTest extends AbstractComparisonTest<IsLessThan> {
@BeforeEach
void setUp() {
subject = new IsLessThan("key", 5);
}
@ParameterizedTest
@CsvSource({
"0, true",
"4, true",
"5, false",
"6, false"
})
void testComparisonValue(Integer value, boolean expectedResult) {
Metadata metadata = Metadata.from(Map.of("key", value));
assertThat(subject.test(metadata)).isEqualTo(expectedResult);
}
}

View File

@ -0,0 +1,71 @@
package dev.langchain4j.store.embedding.filter.comparison;
import dev.langchain4j.data.document.Metadata;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class IsNotEqualToTest {
@Test
void testIsNotEqualToFilter() {
String key = "testKey";
String unequalValue = "notEqual";
IsNotEqualTo subject = new IsNotEqualTo(key, "testValue");
assertIsNotEqualToObject(subject);
assertMetadataDoesNotContainKey(subject);
assertValuesAreNumbersNotEqual(key);
assertUuidAndStringComparison(key);
assertUnequalStringValues(key, unequalValue);
assertEqualStringValues(key);
}
private void assertIsNotEqualToObject(IsNotEqualTo subject) {
// Testing when object is not an instance of Metadata
assertFalse(subject.test(new Object()));
}
private void assertMetadataDoesNotContainKey(IsNotEqualTo subject) {
// Testing when Metadata does not contain key
assertTrue(subject.test(new Metadata(new HashMap<>())));
}
private void assertValuesAreNumbersNotEqual(String key) {
// Testing when actual value is Number
Metadata metadata = new Metadata(new HashMap<>());
metadata.put(key, 123);
IsNotEqualTo subject = new IsNotEqualTo(key, 1234);
assertTrue(subject.test(metadata));
}
private void assertUuidAndStringComparison(String key) {
// Testing when comparisonValue is instance of UUID and actualValue is instance of String
UUID uuid = UUID.randomUUID();
IsNotEqualTo subject = new IsNotEqualTo(key, uuid);
Metadata metadata = new Metadata(new HashMap<>());
metadata.put(key, uuid.toString() + "extra");
assertTrue(subject.test(metadata));
}
private void assertUnequalStringValues(String key, String unequalValue) {
// Testing when values are not equal
IsNotEqualTo subject = new IsNotEqualTo(key, unequalValue);
Metadata metadata = new Metadata(new HashMap<>());
metadata.put(key, "testValue");
assertTrue(subject.test(metadata));
}
private void assertEqualStringValues(String key) {
// Testing when values are equal
IsNotEqualTo subject = new IsNotEqualTo(key, "testValue");
Metadata metadata = new Metadata(new HashMap<>());
metadata.put(key, "testValue");
assertFalse(subject.test(metadata));
}
}

View File

@ -0,0 +1,75 @@
package dev.langchain4j.store.embedding.filter.logical;
import dev.langchain4j.store.embedding.filter.Filter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class AndTest {
@Mock
private Filter mockFilterPasses;
@Mock
private Filter mockFilterFails;
@Test
void testBothFiltersPass() {
when(mockFilterPasses.test(any())).thenReturn(true);
And andFilter = new And(mockFilterPasses, mockFilterPasses);
assertThat(andFilter.test(new Object())).isTrue();
}
@Test
void testLeftFilterFails() {
when(mockFilterFails.test(any())).thenReturn(false);
And andFilter = new And(mockFilterFails, mockFilterPasses);
assertThat(andFilter.test(new Object())).isFalse();
verifyNoInteractions(mockFilterPasses);
}
@Test
void testRightFilterFails() {
when(mockFilterPasses.test(any())).thenReturn(true);
when(mockFilterFails.test(any())).thenReturn(false);
And andFilter = new And(mockFilterPasses, mockFilterFails);
assertThat(andFilter.test(new Object())).isFalse();
}
@Test
void testBothFiltersFail() {
when(mockFilterFails.test(any())).thenReturn(false);
And andFilter = new And(mockFilterFails, mockFilterFails);
assertThat(andFilter.test(new Object())).isFalse();
}
@Test
void testEqualsAndHashCode() {
And andFilter = new And(mockFilterPasses, mockFilterPasses);
And sameAndFilter = new And(mockFilterPasses, mockFilterPasses);
assertThat(andFilter)
.isEqualTo(sameAndFilter)
.hasSameHashCodeAs(sameAndFilter);
}
@Test
void checkToStringImplemented() {
And andFilter = new And(mockFilterPasses, mockFilterPasses);
assertThat(andFilter).hasToString("And(left=mockFilterPasses, right=mockFilterPasses)");
}
}

View File

@ -0,0 +1,78 @@
package dev.langchain4j.store.embedding.filter.logical;
import dev.langchain4j.store.embedding.filter.Filter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@ExtendWith(MockitoExtension.class)
class NotTest {
@Mock
private Filter filter;
private Not subject;
@BeforeEach
void beforeEach() {
subject = new Not(filter);
}
@Test
void shouldReturnFalseWhenFilterReturnsTrue() {
Mockito.when(filter.test(any())).thenReturn(true);
boolean result = subject.test(new Object());
assertThat(result).isFalse();
}
@Test
void shouldReturnTrueWhenFilterReturnsFalse() {
Mockito.when(filter.test(any())).thenReturn(false);
boolean result = subject.test(new Object());
assertThat(result).isTrue();
}
@Test
void shouldReturnCorrectExpression() {
Filter result = subject.expression();
assertThat(result).isEqualTo(filter);
}
@Test
void shouldHaveCorrectToStringImplementation() {
assertThat(subject).hasToString("Not(expression=" + filter + ")");
}
@Test
void shouldReturnTrueForEqualsWithSameExpression() {
Not anotherWithSameExp = new Not(filter);
assertThat(subject)
.isEqualTo(anotherWithSameExp)
.hasSameHashCodeAs(anotherWithSameExp);
}
@Test
void shouldReturnFalseForEqualsWithDifferentExpression() {
Not anotherWithDiffExp = new Not(Mockito.mock(Filter.class));
assertThat(subject).isNotEqualTo(anotherWithDiffExp);
}
@Test
void shouldHaveConsistentHashCode() {
int initialHashCode = subject.hashCode();
assertThat(subject.hashCode()).isEqualTo(initialHashCode);
}
}

View File

@ -0,0 +1,74 @@
package dev.langchain4j.store.embedding.filter.logical;
import dev.langchain4j.store.embedding.filter.Filter;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.verifyNoInteractions;
@ExtendWith(MockitoExtension.class)
class OrTest {
@Mock
Filter mockFilterTrue;
@Mock
Filter mockFilterFalse;
@Test
void testShouldReturnTrueWhenLeftIsTrue() {
Mockito.when(mockFilterTrue.test(Mockito.any())).thenReturn(true);
Or or = new Or(mockFilterTrue, mockFilterFalse);
assertThat(or.test(new Object())).isTrue();
verifyNoInteractions(mockFilterFalse);
}
@Test
void testShouldReturnTrueWhenRightIsTrue() {
Mockito.when(mockFilterTrue.test(Mockito.any())).thenReturn(true);
Mockito.when(mockFilterFalse.test(Mockito.any())).thenReturn(false);
Or or = new Or(mockFilterFalse, mockFilterTrue);
assertThat(or.test(new Object())).isTrue();
}
@Test
void testShouldReturnFalseWhenBothAreFalse() {
Mockito.when(mockFilterFalse.test(Mockito.any())).thenReturn(false);
Or or = new Or(mockFilterFalse, mockFilterFalse);
assertThat(or.test(new Object())).isFalse();
}
@Test
void testEqualsMethodShouldReturnTrueForNonDistinctObjects() {
Or or1 = new Or(mockFilterTrue, mockFilterFalse);
Or or2 = new Or(mockFilterTrue, mockFilterFalse);
assertThat(or1).isEqualTo(or2);
}
@Test
void testEqualsAndHashCode() {
Or orFilter = new Or(mockFilterTrue, mockFilterFalse);
Or sameOrFilter = new Or(mockFilterTrue, mockFilterFalse);
assertThat(orFilter)
.isEqualTo(sameOrFilter)
.hasSameHashCodeAs(sameOrFilter);
}
@Test
void testToStringMethodShouldReturnExpectedValue() {
Or or = new Or(mockFilterTrue, mockFilterFalse);
assertThat(or).hasToString("Or(left=" + mockFilterTrue + ", right=" + mockFilterFalse + ")");
}
}

View File

@ -18,6 +18,7 @@
<maven.compiler.release>${java.version}</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>1714382357</project.build.outputTimestamp>
<argLine></argLine>
<!-- Dependency Versions -->
<openai4j.version>0.23.0</openai4j.version>
<azure-ai-openai.version>1.0.0-beta.11</azure-ai-openai.version>
@ -33,7 +34,7 @@
<gson.version>2.10.1</gson.version>
<junit.version>5.11.2</junit.version>
<testcontainers.version>1.20.2</testcontainers.version>
<bytebuddy.version>1.14.10</bytebuddy.version>
<bytebuddy.version>1.15.7</bytebuddy.version>
<mockito.version>5.14.1</mockito.version>
<assertj.version>3.24.2</assertj.version>
<tinylog.version>2.6.2</tinylog.version>
@ -436,6 +437,14 @@
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
@ -493,6 +502,12 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>properties</goal>
</goals>
</execution>
<execution>
<id>detect-unused-dependencies</id>
<goals>
@ -517,6 +532,7 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.3.1</version>
<configuration combine.children="append">
<argLine>@{argLine} -javaagent:${org.mockito:mockito-core:jar}</argLine>
<systemPropertyVariables>
<tinylog.level>info</tinylog.level>
</systemPropertyVariables>