Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:

permissions:
contents: read
actions: write # required for actions/upload-artifact
actions: write # required for actions/upload-artifact

concurrency:
group: build-${{ github.event.pull_request.number || github.ref }}
Expand Down Expand Up @@ -107,6 +107,19 @@ jobs:
- name: Run tests
run: ./gradlew :core:test :demos:langchain4j-vcr:test :demos:spring-ai-vcr:test -S

- name: Verify coverage and generate reports
run: ./gradlew :core:jacocoTestCoverageVerification :core:jacocoTestReport :demos:langchain4j-vcr:jacocoTestCoverageVerification :demos:langchain4j-vcr:jacocoTestReport :demos:spring-ai-vcr:jacocoTestCoverageVerification :demos:spring-ai-vcr:jacocoTestReport -S

- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage-reports
path: |
core/build/reports/jacoco/test/
demos/langchain4j-vcr/build/reports/jacoco/test/
demos/spring-ai-vcr/build/reports/jacoco/test/
Comment thread
cursor[bot] marked this conversation as resolved.

- name: Upload test reports
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
Expand Down
35 changes: 34 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,51 @@ subprojects {
xml.required = true
html.required = true
}
// Exclude Lombok @Generated inner classes and pure exception types from coverage
classDirectories.setFrom(
files(classDirectories.files.map { dir ->
fileTree(dir) {
exclude(
// Exception types — no testable logic
"**/exceptions/**",
"**/*Exception.class",
// Constants classes — only static final fields
"**/extensions/Constants.class",
"**/extensions/ExtensionConstants.class",
// Pure enums — no branching logic
"**/schema/FieldType.class",
"**/schema/StorageType.class",
"**/query/ReducerFunction.class",
"**/extensions/router/DistanceAggregationMethod.class",
// Pure DTOs / config value objects — only Lombok-generated accessors
"**/extensions/router/RouteMatch.class",
"**/query/SortField.class",
"**/redis/RedisConnectionConfig.class",
"**/redis/RedisConnectionConfig\$*.class"
)
}
})
)
}

tasks.jacocoTestCoverageVerification {
dependsOn(tasks.jacocoTestReport)
classDirectories.setFrom(tasks.jacocoTestReport.get().classDirectories)
violationRules {
rule {
limit {
minimum = "0.80".toBigDecimal()
counter = "INSTRUCTION"
value = "COVEREDRATIO"
minimum = "0.42".toBigDecimal()
Comment thread
sagile marked this conversation as resolved.
}
}
}
}

tasks.check {
dependsOn(tasks.jacocoTestCoverageVerification)
}

dependencies {
// Logging
implementation("org.slf4j:slf4j-api:2.0.16")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.redis.vl.extensions.summarization;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

class EmbeddedSentenceTest {

@Test
void indexIsPreserved() {
EmbeddedSentence s = new EmbeddedSentence(3, new float[] {1.0f, 0.0f});
assertThat(s.index()).isEqualTo(3);
}

@Test
void getPointReturnsSameDimensionality() {
float[] embedding = {1.0f, 2.0f, 3.0f};
EmbeddedSentence s = new EmbeddedSentence(0, embedding);
assertThat(s.getPoint()).hasSize(3);
}

@Test
void cosineSimilarityOfIdenticalVectorsIsOne() {
float[] v = {1.0f, 0.0f, 0.0f};
EmbeddedSentence a = new EmbeddedSentence(0, v);
EmbeddedSentence b = new EmbeddedSentence(1, v);
assertThat(a.cosineSimilarity(b)).isCloseTo(1.0, org.assertj.core.data.Offset.offset(0.001));
}

@Test
void cosineSimilarityOfOrthogonalVectorsIsZero() {
EmbeddedSentence a = new EmbeddedSentence(0, new float[] {1.0f, 0.0f});
EmbeddedSentence b = new EmbeddedSentence(1, new float[] {0.0f, 1.0f});
assertThat(a.cosineSimilarity(b)).isCloseTo(0.0, org.assertj.core.data.Offset.offset(0.001));
}

@Test
void cosineSimilarityOfOppositeVectorsIsMinusOne() {
EmbeddedSentence a = new EmbeddedSentence(0, new float[] {1.0f, 0.0f});
EmbeddedSentence b = new EmbeddedSentence(1, new float[] {-1.0f, 0.0f});
assertThat(a.cosineSimilarity(b)).isCloseTo(-1.0, org.assertj.core.data.Offset.offset(0.001));
}

@Test
void cosineSimilarityOfZeroVectorIsZero() {
EmbeddedSentence a = new EmbeddedSentence(0, new float[] {1.0f, 0.0f});
EmbeddedSentence zero = new EmbeddedSentence(1, new float[] {0.0f, 0.0f});
assertThat(a.cosineSimilarity(zero)).isEqualTo(0.0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.redis.vl.extensions.summarization;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class SentenceSplitterTest {

private SentenceSplitter splitter;

@BeforeEach
void setUp() {
splitter = new SentenceSplitter();
}

@Test
void splitSimpleSentences() {
List<String> result = splitter.split("Hello world. How are you? I am fine.");
assertThat(result).hasSize(3);
}

@Test
void splitReturnsEmptyListForNull() {
assertThat(splitter.split(null)).isEmpty();
}

@Test
void splitReturnsEmptyListForBlank() {
assertThat(splitter.split(" ")).isEmpty();
}

@Test
void splitSingleSentenceReturnsSingleElement() {
List<String> result = splitter.split("Just one sentence here.");
assertThat(result).hasSize(1);
assertThat(result.get(0)).isEqualTo("Just one sentence here.");
}

@Test
void splitWithSpansReturnsEmptyForNull() {
assertThat(splitter.splitWithSpans(null)).isEmpty();
}

@Test
void splitWithSpansReturnsEmptyForBlank() {
assertThat(splitter.splitWithSpans("")).isEmpty();
}

@Test
void splitWithSpansReturnsCorrectCount() {
var spans = splitter.splitWithSpans("First sentence. Second sentence.");
assertThat(spans).hasSize(2);
}
}
59 changes: 59 additions & 0 deletions core/src/test/java/com/redis/vl/utils/ArrayUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.redis.vl.utils;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

class ArrayUtilsTest {

@Test
void floatArrayToBytesAndBackRoundTrips() {
float[] original = {1.0f, 2.5f, -3.14f, 0.0f};
byte[] bytes = ArrayUtils.floatArrayToBytes(original);
float[] result = ArrayUtils.bytesToFloatArray(bytes);
assertThat(result).hasSize(original.length);
for (int i = 0; i < original.length; i++) {
assertThat(result[i]).isCloseTo(original[i], org.assertj.core.data.Offset.offset(0.001f));
}
}

@Test
void floatArrayToBytesNullReturnsNull() {
assertThat(ArrayUtils.floatArrayToBytes(null)).isNull();
}

@Test
void bytesToFloatArrayNullReturnsNull() {
assertThat(ArrayUtils.bytesToFloatArray(null)).isNull();
}

@Test
void floatArrayToBytesProducesCorrectLength() {
float[] floats = {1.0f, 2.0f, 3.0f};
byte[] bytes = ArrayUtils.floatArrayToBytes(floats);
assertThat(bytes).hasSize(floats.length * Float.BYTES);
}

@Test
void doubleArrayToFloatsConvertsCorrectly() {
double[] doubles = {1.0, 2.5, -3.0};
float[] floats = ArrayUtils.doubleArrayToFloats(doubles);
assertThat(floats).hasSize(3);
assertThat(floats[0]).isCloseTo(1.0f, org.assertj.core.data.Offset.offset(0.001f));
assertThat(floats[1]).isCloseTo(2.5f, org.assertj.core.data.Offset.offset(0.001f));
assertThat(floats[2]).isCloseTo(-3.0f, org.assertj.core.data.Offset.offset(0.001f));
}

@Test
void doubleArrayToFloatsNullReturnsNull() {
assertThat(ArrayUtils.doubleArrayToFloats(null)).isNull();
}

@Test
void emptyFloatArrayRoundTrips() {
float[] empty = {};
byte[] bytes = ArrayUtils.floatArrayToBytes(empty);
float[] result = ArrayUtils.bytesToFloatArray(bytes);
assertThat(result).isEmpty();
}
}
67 changes: 67 additions & 0 deletions core/src/test/java/com/redis/vl/utils/FullTextQueryHelperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.redis.vl.utils;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.Set;
import org.junit.jupiter.api.Test;

class FullTextQueryHelperTest {

@Test
void loadDefaultStopwordsReturnsNonEmptySetForEnglish() {
Set<String> stopwords = FullTextQueryHelper.loadDefaultStopwords("english");
assertThat(stopwords).isNotEmpty().contains("the", "and", "is");
}

@Test
void loadDefaultStopwordsReturnsEmptySetForUnknownLanguage() {
Set<String> stopwords = FullTextQueryHelper.loadDefaultStopwords("klingon");
assertThat(stopwords).isEmpty();
}

@Test
void loadDefaultStopwordsReturnsEmptySetForNullLanguage() {
assertThat(FullTextQueryHelper.loadDefaultStopwords(null)).isEmpty();
}

@Test
void loadDefaultStopwordsReturnsEmptySetForEmptyLanguage() {
assertThat(FullTextQueryHelper.loadDefaultStopwords("")).isEmpty();
}

@Test
void tokenizeAndEscapeQueryFiltersStopwords() {
Set<String> stopwords = Set.of("the", "is", "a");
String result = FullTextQueryHelper.tokenizeAndEscapeQuery("the cat is a dog", stopwords);
assertThat(result).doesNotContain("the").doesNotContain("is").doesNotContain(" a ");
assertThat(result).contains("cat").contains("dog");
}

@Test
void tokenizeAndEscapeQueryJoinsWithPipe() {
Set<String> stopwords = Set.of();
String result = FullTextQueryHelper.tokenizeAndEscapeQuery("foo bar", stopwords);
assertThat(result).isEqualTo("foo | bar");
}

@Test
void tokenizeAndEscapeQueryEscapesSpecialChars() {
Set<String> stopwords = Set.of();
String result = FullTextQueryHelper.tokenizeAndEscapeQuery("hello-world", stopwords);
assertThat(result).contains("\\-");
}

@Test
void tokenizeAndEscapeQueryLowercasesTokens() {
Set<String> stopwords = Set.of();
String result = FullTextQueryHelper.tokenizeAndEscapeQuery("Hello World", stopwords);
assertThat(result).isEqualTo("hello | world");
}

@Test
void tokenizeAndEscapeQueryHandlesExtraWhitespace() {
Set<String> stopwords = Set.of();
String result = FullTextQueryHelper.tokenizeAndEscapeQuery("foo bar", stopwords);
assertThat(result).isEqualTo("foo | bar");
}
}
64 changes: 64 additions & 0 deletions core/src/test/java/com/redis/vl/utils/TokenEscaperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.redis.vl.utils;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class TokenEscaperTest {

private TokenEscaper escaper;

@BeforeEach
void setUp() {
escaper = new TokenEscaper();
}

@Test
void escapeReturnsUnchangedStringWithNoSpecialChars() {
assertThat(escaper.escape("hello")).isEqualTo("hello");
}

@Test
void escapesSpaces() {
assertThat(escaper.escape("hello world")).isEqualTo("hello\\ world");
}

@Test
void escapesComma() {
assertThat(escaper.escape("a,b")).isEqualTo("a\\,b");
}

@Test
void escapesDot() {
assertThat(escaper.escape("file.txt")).isEqualTo("file\\.txt");
}

@Test
void escapesHyphen() {
assertThat(escaper.escape("well-known")).isEqualTo("well\\-known");
}

@Test
void escapesAtSign() {
assertThat(escaper.escape("user@example.com")).isEqualTo("user\\@example\\.com");
}

@Test
void escapesMultipleSpecialChars() {
assertThat(escaper.escape("(test)")).isEqualTo("\\(test\\)");
}

@Test
void escapeEmptyStringReturnsEmpty() {
assertThat(escaper.escape("")).isEqualTo("");
}

@Test
void escapeNullThrowsIllegalArgumentException() {
assertThatThrownBy(() -> escaper.escape(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("null");
}
}
Loading
Loading