feat(search): add DocumentSearchResult.withMatchData() factory with match overlay map

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-15 15:34:00 +02:00
committed by marcel
parent 8a5ca6868f
commit 741eebc276
2 changed files with 73 additions and 3 deletions

View File

@@ -1,16 +1,33 @@
package org.raddatz.familienarchiv.dto; package org.raddatz.familienarchiv.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.Document;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
public record DocumentSearchResult(List<Document> documents, long total) { public record DocumentSearchResult(
List<Document> documents,
long total,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
Map<UUID, SearchMatchData> matchData
) {
/** /**
* Creates a result where total equals the list size. * Creates a fully-enriched result from documents and their match overlay data.
* Absent map entries (e.g. document deleted between FTS and enrichment) are safe —
* the frontend treats a missing entry as "no match data".
*/
public static DocumentSearchResult withMatchData(List<Document> documents, Map<UUID, SearchMatchData> matchData) {
return new DocumentSearchResult(documents, documents.size(), matchData);
}
/**
* Creates a result without match data — used for filter-only searches (no text query).
* No pagination yet — the full matched set is always returned. * No pagination yet — the full matched set is always returned.
* When pagination is added, total must come from a DB COUNT query, not list.size(). * When pagination is added, total must come from a DB COUNT query, not list.size().
*/ */
public static DocumentSearchResult of(List<Document> documents) { public static DocumentSearchResult of(List<Document> documents) {
return new DocumentSearchResult(documents, documents.size()); return withMatchData(documents, Map.of());
} }
} }

View File

@@ -0,0 +1,53 @@
package org.raddatz.familienarchiv.dto;
import org.junit.jupiter.api.Test;
import org.raddatz.familienarchiv.model.Document;
import org.raddatz.familienarchiv.model.DocumentStatus;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
class DocumentSearchResultTest {
private Document doc(UUID id) {
return Document.builder()
.id(id)
.title("Test")
.originalFilename("test.pdf")
.status(DocumentStatus.UPLOADED)
.build();
}
@Test
void withMatchData_total_equals_list_size() {
UUID id = UUID.randomUUID();
List<Document> docs = List.of(doc(id));
Map<UUID, SearchMatchData> matchData = Map.of(id, SearchMatchData.empty());
DocumentSearchResult result = DocumentSearchResult.withMatchData(docs, matchData);
assertThat(result.total()).isEqualTo(1L);
}
@Test
void withMatchData_exposes_match_data_map() {
UUID id = UUID.randomUUID();
SearchMatchData data = new SearchMatchData("snippet", List.of(), false, List.of(), List.of());
DocumentSearchResult result = DocumentSearchResult.withMatchData(List.of(doc(id)), Map.of(id, data));
assertThat(result.matchData()).containsKey(id);
assertThat(result.matchData().get(id).transcriptionSnippet()).isEqualTo("snippet");
}
@Test
void of_factory_returns_empty_match_data() {
UUID id = UUID.randomUUID();
DocumentSearchResult result = DocumentSearchResult.of(List.of(doc(id)));
assertThat(result.matchData()).isEmpty();
assertThat(result.total()).isEqualTo(1L);
}
}