|
|
|
|
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.*;
|
|
|
|
|
|
|
|
|
|
class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
@Mock OllamaClient ollamaClient;
|
|
|
|
|
@Mock NlpClient nlpClient;
|
|
|
|
|
@Mock PersonService personService;
|
|
|
|
|
@Mock DocumentService documentService;
|
|
|
|
|
@Mock TagService tagService;
|
|
|
|
|
@@ -45,7 +45,7 @@ class NlQueryParserServiceTest {
|
|
|
|
|
@BeforeEach
|
|
|
|
|
void setUp() {
|
|
|
|
|
MockitoAnnotations.openMocks(this);
|
|
|
|
|
service = new NlQueryParserService(ollamaClient, personService, documentService, tagService);
|
|
|
|
|
service = new NlQueryParserService(nlpClient, personService, documentService, tagService);
|
|
|
|
|
when(documentService.searchDocuments(any(), any(), any(), any()))
|
|
|
|
|
.thenReturn(DocumentSearchResult.of(List.of()));
|
|
|
|
|
when(documentService.searchDocumentsByPersonId(any(), any(), any(), any()))
|
|
|
|
|
@@ -55,10 +55,10 @@ class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
// --- Factory helpers ---
|
|
|
|
|
|
|
|
|
|
private OllamaExtraction extraction(List<String> names, String role, LocalDate from, LocalDate to,
|
|
|
|
|
List<String> keywords) {
|
|
|
|
|
private NlpExtraction extraction(List<String> names, String role, LocalDate from, LocalDate to,
|
|
|
|
|
List<String> keywords) {
|
|
|
|
|
String raw = names.isEmpty() ? "test query" : String.join(" ", names);
|
|
|
|
|
return new OllamaExtraction(names, role, from, to, keywords, raw);
|
|
|
|
|
return new NlpExtraction(names, role, from, to, keywords, raw);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Person person(UUID id, String firstName, String lastName) {
|
|
|
|
|
@@ -86,12 +86,13 @@ class NlQueryParserServiceTest {
|
|
|
|
|
@Test
|
|
|
|
|
void search_resolvesSingleName_asSender() {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter"), "sender", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Was hat Walter geschrieben?", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Was hat Walter geschrieben?", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(nlpClient).parse(eq("Was hat Walter geschrieben?"), eq("de"));
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), eq(DocumentSort.DATE), eq("desc"), eq(PAGE));
|
|
|
|
|
assertThat(cap.getValue().sender()).isEqualTo(P1);
|
|
|
|
|
@@ -107,11 +108,11 @@ class NlQueryParserServiceTest {
|
|
|
|
|
void search_multiMatchName_populatesAmbiguous_andSkipsSearch() {
|
|
|
|
|
Person a = person(UUID.randomUUID(), "Walter", "Braun");
|
|
|
|
|
Person b = person(UUID.randomUUID(), "Walter", "Schmidt");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter"), "sender", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(a, b)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
verify(documentService, never()).searchDocumentsByPersonId(any(), any(), any(), any());
|
|
|
|
|
@@ -125,11 +126,11 @@ class NlQueryParserServiceTest {
|
|
|
|
|
void search_multiMatchName_withPersonRoleAny_stillSkipsSearch() {
|
|
|
|
|
Person a = person(UUID.randomUUID(), "Emma", "Braun");
|
|
|
|
|
Person b = person(UUID.randomUUID(), "Emma", "Raddatz");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Emma"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Emma")).thenReturn(makeNameMatches(List.of(a, b)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe an Emma", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe an Emma", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
verify(documentService, never()).searchDocumentsByPersonId(any(), any(), any(), any());
|
|
|
|
|
@@ -140,11 +141,11 @@ class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_noMatchName_isFoldedIntoText() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Karl"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Karl")).thenReturn(makeNameMatches());
|
|
|
|
|
|
|
|
|
|
service.search("Briefe von Karl", PAGE);
|
|
|
|
|
service.search("Briefe von Karl", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -158,11 +159,11 @@ class NlQueryParserServiceTest {
|
|
|
|
|
@Test
|
|
|
|
|
void search_personRoleAny_singleMatch_callsSearchDocumentsByPersonId() {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(documentService).searchDocumentsByPersonId(eq(P1), isNull(), isNull(), eq(PAGE));
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
@@ -175,12 +176,12 @@ class NlQueryParserServiceTest {
|
|
|
|
|
void search_twoNamesResolve_assignsSenderAndReceiver() {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
Person emma = person(P2, "Emma", "Raddatz");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter", "Emma"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
when(personService.resolveByName("Emma")).thenReturn(makeNameMatches(List.of(emma)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter an Emma", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter an Emma", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), eq(DocumentSort.DATE), eq("desc"), eq(PAGE));
|
|
|
|
|
@@ -197,12 +198,12 @@ class NlQueryParserServiceTest {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
Person emma1 = person(P2, "Emma", "Braun");
|
|
|
|
|
Person emma2 = person(P3, "Emma", "Schmidt");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter", "Emma"), "sender", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
when(personService.resolveByName("Emma")).thenReturn(makeNameMatches(List.of(emma1, emma2)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter an Emma", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter an Emma", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
assertThat(resp.interpretation().ambiguousPersons()).hasSize(2);
|
|
|
|
|
@@ -213,12 +214,12 @@ class NlQueryParserServiceTest {
|
|
|
|
|
@Test
|
|
|
|
|
void search_twoNames_firstNoMatch_secondResolved_foldFirstIntoText() {
|
|
|
|
|
Person emma = person(P2, "Emma", "Raddatz");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Karl", "Emma"), "sender", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Karl")).thenReturn(makeNameMatches());
|
|
|
|
|
when(personService.resolveByName("Emma")).thenReturn(makeNameMatches(List.of(emma)));
|
|
|
|
|
|
|
|
|
|
service.search("Briefe von Karl an Emma", PAGE);
|
|
|
|
|
service.search("Briefe von Karl an Emma", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -233,13 +234,13 @@ class NlQueryParserServiceTest {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
Person emma = person(P2, "Emma", "Raddatz");
|
|
|
|
|
Person heinrich = person(P3, "Heinrich", "Braun");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter", "Emma", "Heinrich"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
when(personService.resolveByName("Emma")).thenReturn(makeNameMatches(List.of(emma)));
|
|
|
|
|
when(personService.resolveByName("Heinrich")).thenReturn(makeNameMatches(List.of(heinrich)));
|
|
|
|
|
|
|
|
|
|
service.search("Briefe von Walter an Emma über Heinrich", PAGE);
|
|
|
|
|
service.search("Briefe von Walter an Emma über Heinrich", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -252,10 +253,10 @@ class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_keywords_areJoinedIntoText() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Krieg", "Walter")));
|
|
|
|
|
|
|
|
|
|
service.search("Dokumente über den Krieg Walter", PAGE);
|
|
|
|
|
service.search("Dokumente über den Krieg Walter", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -268,10 +269,10 @@ class NlQueryParserServiceTest {
|
|
|
|
|
void search_dateRange_passedIntoSearchFilters() {
|
|
|
|
|
LocalDate from = LocalDate.of(1914, 1, 1);
|
|
|
|
|
LocalDate to = LocalDate.of(1914, 12, 31);
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", from, to, List.of()));
|
|
|
|
|
|
|
|
|
|
service.search("Briefe aus dem Jahr 1914", PAGE);
|
|
|
|
|
service.search("Briefe aus dem Jahr 1914", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -283,10 +284,10 @@ class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_nullDates_passedAsNullIntoFilters() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
|
|
|
|
|
service.search("Hochzeitsbriefe", PAGE);
|
|
|
|
|
service.search("Hochzeitsbriefe", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -294,104 +295,75 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(cap.getValue().to()).isNull();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 13. Query under 3 chars → VALIDATION_ERROR before Ollama call ---
|
|
|
|
|
// --- 13. NLP service returns empty names/keywords → raw query used as keyword fallback ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_queryTooShort_throwsValidationError() {
|
|
|
|
|
assertThatThrownBy(() -> service.search("ab", PAGE))
|
|
|
|
|
.isInstanceOf(DomainException.class)
|
|
|
|
|
.extracting(e -> ((DomainException) e).getCode())
|
|
|
|
|
.isEqualTo(ErrorCode.VALIDATION_ERROR);
|
|
|
|
|
|
|
|
|
|
verify(ollamaClient, never()).parse(anyString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 14. Query over 500 chars → VALIDATION_ERROR ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_queryTooLong_throwsValidationError() {
|
|
|
|
|
String longQuery = "a".repeat(501);
|
|
|
|
|
assertThatThrownBy(() -> service.search(longQuery, PAGE))
|
|
|
|
|
.isInstanceOf(DomainException.class)
|
|
|
|
|
.extracting(e -> ((DomainException) e).getCode())
|
|
|
|
|
.isEqualTo(ErrorCode.VALIDATION_ERROR);
|
|
|
|
|
|
|
|
|
|
verify(ollamaClient, never()).parse(anyString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 15. Ollama returns empty names/keywords → raw query used as keyword fallback ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_ollamaReturnsEmpty_usesRawQueryAsTextFallback() {
|
|
|
|
|
void search_nlpReturnsEmpty_usesRawQueryAsTextFallback() {
|
|
|
|
|
String raw = "Briefe aus dem Krieg";
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
.thenReturn(new OllamaExtraction(List.of(), "any", null, null, List.of(), raw));
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(new NlpExtraction(List.of(), "any", null, null, List.of(), raw));
|
|
|
|
|
|
|
|
|
|
service.search(raw, PAGE);
|
|
|
|
|
service.search(raw, "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
assertThat(cap.getValue().text()).isEqualTo(raw);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 16. Null personNames/keywords from Ollama → no NPE ---
|
|
|
|
|
// --- 14. Null personNames/keywords → no NPE ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_nullPersonNamesAndKeywords_handledWithoutNpe() {
|
|
|
|
|
OllamaExtraction ext = new OllamaExtraction(null, "any", null, null, null, "test query");
|
|
|
|
|
when(ollamaClient.parse(anyString())).thenReturn(ext);
|
|
|
|
|
NlpExtraction ext = new NlpExtraction(null, "any", null, null, null, "test query");
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString())).thenReturn(ext);
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("test query", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("test query", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp).isNotNull();
|
|
|
|
|
verify(documentService).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 17. Unrecognized personRole → defaults to any-like behavior (no crash) ---
|
|
|
|
|
// --- 15. Unrecognized personRole → defaults to any-like behavior (no crash) ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_unrecognizedPersonRole_treatedLikeAny_withSingleResolvedPerson() {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
// OllamaClient defensive parsing returns "any" for unknown roles,
|
|
|
|
|
// but NlQueryParserService must also be safe if something unexpected arrives.
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
.thenReturn(new OllamaExtraction(List.of("Walter"), "unknown_role", null, null, List.of(), "query"));
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(new NlpExtraction(List.of("Walter"), "unknown_role", null, null, List.of(), "query"));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
// Should not crash; "unknown_role" treated as fallback (neither sender nor receiver → any)
|
|
|
|
|
assertThat(resp).isNotNull();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 18. Ollama throws SMART_SEARCH_UNAVAILABLE → propagates to caller ---
|
|
|
|
|
// --- 16. NLP service throws SMART_SEARCH_UNAVAILABLE → propagates to caller ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_ollamaThrowsUnavailable_propagates() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
void search_nlpThrowsUnavailable_propagates() {
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenThrow(DomainException.tooManyRequests(ErrorCode.SMART_SEARCH_UNAVAILABLE, "offline"));
|
|
|
|
|
|
|
|
|
|
assertThatThrownBy(() -> service.search("Was hat Walter geschrieben?", PAGE))
|
|
|
|
|
assertThatThrownBy(() -> service.search("Was hat Walter geschrieben?", "de", PAGE))
|
|
|
|
|
.isInstanceOf(DomainException.class)
|
|
|
|
|
.extracting(e -> ((DomainException) e).getCode())
|
|
|
|
|
.isEqualTo(ErrorCode.SMART_SEARCH_UNAVAILABLE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 19. LLM-extracted name > 200 chars → skipped, PersonService never called ---
|
|
|
|
|
// --- 17. LLM-extracted name > 200 chars → skipped, PersonService never called ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_nameLongerThan200Chars_isSkippedBeforePersonServiceCall() {
|
|
|
|
|
String longName = "A".repeat(201);
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(longName), "sender", null, null, List.of()));
|
|
|
|
|
|
|
|
|
|
service.search("Briefe von sehr langem Namen", PAGE);
|
|
|
|
|
service.search("Briefe von sehr langem Namen", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(personService, never()).resolveByName(anyString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 20. Cap lives in resolveByName (after classification): a pre-capped 10-direct result
|
|
|
|
|
// maps straight to ambiguousPersons; the search layer adds no second cap. ---
|
|
|
|
|
// --- 18. Cap: 10 direct matches → all shown as ambiguous ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_tenDirectMatches_allShownAsAmbiguous() {
|
|
|
|
|
@@ -399,24 +371,24 @@ class NlQueryParserServiceTest {
|
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
|
ten.add(person(UUID.randomUUID(), "Walter", "Person" + i));
|
|
|
|
|
}
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter"), "sender", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(ten));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().ambiguousPersons()).hasSize(10);
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 21. SearchFilters defaults: tagOperator=AND, status=null, undated=false, tags=empty ---
|
|
|
|
|
// --- 19. SearchFilters defaults: tagOperator=AND, status=null, undated=false, tags=empty ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_searchFiltersDefaults_areCorrect() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Krieg")));
|
|
|
|
|
|
|
|
|
|
service.search("Dokumente über den Krieg", PAGE);
|
|
|
|
|
service.search("Dokumente über den Krieg", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), eq(DocumentSort.DATE), eq("desc"), eq(PAGE));
|
|
|
|
|
@@ -428,16 +400,16 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(f.tagQ()).isNull();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 22. personRole=receiver + 1 resolved → receiver UUID set ---
|
|
|
|
|
// --- 20. personRole=receiver + 1 resolved → receiver UUID set ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_personRoleReceiver_singleMatch_setsReceiver() {
|
|
|
|
|
Person emma = person(P2, "Emma", "Raddatz");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Emma"), "receiver", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Emma")).thenReturn(makeNameMatches(List.of(emma)));
|
|
|
|
|
|
|
|
|
|
service.search("Briefe an Emma", PAGE);
|
|
|
|
|
service.search("Briefe an Emma", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -445,59 +417,59 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(cap.getValue().sender()).isNull();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 23. keywordsApplied=true when text is non-blank ---
|
|
|
|
|
// --- 21. keywordsApplied=true when text is non-blank ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_keywordsApplied_trueWhenTextNonBlank() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Feldpost")));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Feldpost aus dem Krieg", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Feldpost aus dem Krieg", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().keywordsApplied()).isTrue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 23a. Partial-only, one candidate → ambiguous (1-item picker), search skipped ---
|
|
|
|
|
// --- 22. Partial-only, one candidate → ambiguous (1-item picker), search skipped ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_partialOnly_oneCandidate_populatesAmbiguous() {
|
|
|
|
|
Person cramer = person(P1, "Clara", "Cramer");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Clara Cram"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Clara Cram")).thenReturn(makeNameMatches(List.of(), List.of(cramer)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Clara Cram", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Clara Cram", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().ambiguousPersons()).hasSize(1);
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 23b. Partial-only, two candidates → ambiguous (multi-item picker) ---
|
|
|
|
|
// --- 23. Partial-only, two candidates → ambiguous (multi-item picker) ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_partialOnly_twoCandidates_populatesAmbiguous() {
|
|
|
|
|
Person cramer = person(P1, "Clara", "Cramer");
|
|
|
|
|
Person crammond = person(P2, "Clara", "Crammond");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Clara Cram"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Clara Cram"))
|
|
|
|
|
.thenReturn(makeNameMatches(List.of(), List.of(cramer, crammond)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Clara Cram", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Clara Cram", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().ambiguousPersons()).hasSize(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 23c. Exactly one direct match → search executes, no picker ---
|
|
|
|
|
// --- 24. Exactly one direct match → search executes, no picker ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_oneDirect_executesSearch() {
|
|
|
|
|
Person clara = person(P1, "Clara", "Cram");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Clara Cram"), "any", null, null, List.of()));
|
|
|
|
|
when(personService.resolveByName("Clara Cram")).thenReturn(makeNameMatches(List.of(clara)));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Clara Cram", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Clara Cram", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(documentService).searchDocumentsByPersonId(eq(P1), isNull(), isNull(), eq(PAGE));
|
|
|
|
|
assertThat(resp.interpretation().ambiguousPersons()).isEmpty();
|
|
|
|
|
@@ -519,16 +491,16 @@ class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
private static final UUID T1 = UUID.fromString("00000000-0000-0000-0001-000000000001");
|
|
|
|
|
|
|
|
|
|
// --- 24. Single keyword resolves to one tag → tag filter applied ---
|
|
|
|
|
// --- 25. Single keyword resolves to one tag → tag filter applied ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_singleKeywordResolvesToTag_appliesTagFilter() {
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe über Hochzeit", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe über Hochzeit", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -542,17 +514,17 @@ class NlQueryParserServiceTest {
|
|
|
|
|
|
|
|
|
|
private static final UUID T2 = UUID.fromString("00000000-0000-0000-0001-000000000002");
|
|
|
|
|
|
|
|
|
|
// --- 25. Keyword matches multiple tags → all in resolvedTags, OR-union ---
|
|
|
|
|
// --- 26. Keyword matches multiple tags → all in resolvedTags, OR-union ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_keywordMatchesMultipleTags_allIncluded() {
|
|
|
|
|
Tag hochzeit1 = tag(T1, "Hochzeit Raddatz");
|
|
|
|
|
Tag hochzeit2 = tag(T2, "Hochzeit Braun");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit1, hochzeit2));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe über Hochzeit", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe über Hochzeit", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -561,14 +533,14 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(resp.interpretation().resolvedTags()).hasSize(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 26. Keyword no tag match → stays as FTS text, resolvedTags empty ---
|
|
|
|
|
// --- 27. Keyword no tag match → stays as FTS text, resolvedTags empty ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_keywordNoTagMatch_staysAsFtsText() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Feldpost")));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Feldpost Briefe", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Feldpost Briefe", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -578,16 +550,16 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(resp.interpretation().tagsApplied()).isFalse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 27. Mixed: one keyword resolves, one doesn't → tag filter + FTS text ---
|
|
|
|
|
// --- 28. Mixed: one keyword resolves, one doesn't → tag filter + FTS text ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_mixedKeywords_oneResolves_oneStaysAsText() {
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit", "Feldpost")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit und Feldpost", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit und Feldpost", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -598,18 +570,18 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(resp.interpretation().tagsApplied()).isTrue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 28. personRole=any + 1 person + resolvable keyword → personId search, tagsApplied=false ---
|
|
|
|
|
// --- 29. personRole=any + 1 person + resolvable keyword → personId search, tagsApplied=false ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_personRoleAny_singlePerson_resolvableKeyword_tagsAppliedFalse() {
|
|
|
|
|
Person walter = person(P1, "Walter", "Raddatz");
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of("Walter"), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
when(personService.resolveByName("Walter")).thenReturn(makeNameMatches(List.of(walter)));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter über Hochzeit", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Briefe von Walter über Hochzeit", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(documentService).searchDocumentsByPersonId(eq(P1), isNull(), isNull(), eq(PAGE));
|
|
|
|
|
verify(documentService, never()).searchDocuments(any(), any(), any(), any());
|
|
|
|
|
@@ -618,7 +590,7 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(resp.interpretation().resolvedTags().get(0).name()).isEqualTo("Hochzeit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 29. Cap: keyword matches > 10 tags → capped at 10 ---
|
|
|
|
|
// --- 30. Cap: keyword matches > 10 tags → capped at 10 ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_keywordMatchesMoreThanMaxTags_cappedAtTen() {
|
|
|
|
|
@@ -626,11 +598,11 @@ class NlQueryParserServiceTest {
|
|
|
|
|
for (int i = 0; i < 11; i++) {
|
|
|
|
|
eleven.add(tag(UUID.randomUUID(), "Thema " + i));
|
|
|
|
|
}
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Thema")));
|
|
|
|
|
when(tagService.findByNameContaining("Thema")).thenReturn(eleven);
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Dokumente zum Thema", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Dokumente zum Thema", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().resolvedTags()).hasSize(10);
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
@@ -638,14 +610,14 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(cap.getValue().tags()).hasSize(10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 30. Short keyword (< 3 chars) → skipped, not passed to TagService ---
|
|
|
|
|
// --- 31. Short keyword (< 3 chars) → skipped, not passed to TagService ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_shortKeyword_skippedByTagResolution() {
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("ab", "Krieg")));
|
|
|
|
|
|
|
|
|
|
service.search("ab Krieg", PAGE);
|
|
|
|
|
service.search("ab Krieg", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
verify(tagService, never()).findByNameContaining("ab");
|
|
|
|
|
verify(tagService).findByNameContaining("Krieg");
|
|
|
|
|
@@ -654,17 +626,17 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(cap.getValue().text()).contains("ab");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 31. Dedup: same tag matched by two keywords → appears once ---
|
|
|
|
|
// --- 32. Dedup: same tag matched by two keywords → appears once ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_sameTagMatchedByTwoKeywords_deduplicatedToOne() {
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit", "hoch")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
when(tagService.findByNameContaining("hoch")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit hoch", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit hoch", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().resolvedTags()).hasSize(1);
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
@@ -672,16 +644,16 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(cap.getValue().tags()).hasSize(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 32. All keywords resolve → rawQuery fallback suppressed, text=null ---
|
|
|
|
|
// --- 33. All keywords resolve → rawQuery fallback suppressed, text=null ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_allKeywordsResolveToTags_rawQueryFallbackSuppressed() {
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
.thenReturn(new OllamaExtraction(List.of(), "any", null, null, List.of("Hochzeit"), "raw query text"));
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(new NlpExtraction(List.of(), "any", null, null, List.of("Hochzeit"), "raw query text"));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
ArgumentCaptor<SearchFilters> cap = ArgumentCaptor.forClass(SearchFilters.class);
|
|
|
|
|
verify(documentService).searchDocuments(cap.capture(), any(), any(), any());
|
|
|
|
|
@@ -689,22 +661,22 @@ class NlQueryParserServiceTest {
|
|
|
|
|
assertThat(cap.getValue().tags()).containsExactly("Hochzeit");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 33. Flag independence: keywordsApplied=false AND tagsApplied=true ---
|
|
|
|
|
// --- 34. Flag independence: keywordsApplied=false AND tagsApplied=true ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_allKeywordsResolveToTags_keywordsAppliedFalse_tagsAppliedTrue() {
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit Briefe", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit Briefe", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().keywordsApplied()).isFalse();
|
|
|
|
|
assertThat(resp.interpretation().tagsApplied()).isTrue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 34. Color carried through from resolveEffectiveColors ---
|
|
|
|
|
// --- 35. Color carried through from resolveEffectiveColors ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_tagHint_carriesColorSetByResolveEffectiveColors() {
|
|
|
|
|
@@ -714,25 +686,25 @@ class NlQueryParserServiceTest {
|
|
|
|
|
tags.forEach(t -> t.setColor("sage"));
|
|
|
|
|
return null;
|
|
|
|
|
}).when(tagService).resolveEffectiveColors(any());
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().resolvedTags().get(0).color()).isEqualTo("sage");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 35. Color stays null when resolveEffectiveColors leaves it unset ---
|
|
|
|
|
// --- 36. Color stays null when resolveEffectiveColors leaves it unset ---
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void search_tagHint_colorIsNull_whenNoColorResolved() {
|
|
|
|
|
Tag hochzeit = tag(T1, "Hochzeit");
|
|
|
|
|
when(ollamaClient.parse(anyString()))
|
|
|
|
|
when(nlpClient.parse(anyString(), anyString()))
|
|
|
|
|
.thenReturn(extraction(List.of(), "any", null, null, List.of("Hochzeit")));
|
|
|
|
|
when(tagService.findByNameContaining("Hochzeit")).thenReturn(List.of(hochzeit));
|
|
|
|
|
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit", PAGE);
|
|
|
|
|
NlSearchResponse resp = service.search("Hochzeit", "de", PAGE);
|
|
|
|
|
|
|
|
|
|
assertThat(resp.interpretation().resolvedTags().get(0).color()).isNull();
|
|
|
|
|
}
|
|
|
|
|
|