diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dto/DocumentSearchResult.java b/backend/src/main/java/org/raddatz/familienarchiv/dto/DocumentSearchResult.java index 764d9c12..9374e6c6 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/dto/DocumentSearchResult.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/dto/DocumentSearchResult.java @@ -1,6 +1,7 @@ package org.raddatz.familienarchiv.dto; import io.swagger.v3.oas.annotations.media.Schema; +import org.springframework.data.domain.Pageable; import java.util.List; @@ -8,9 +9,30 @@ public record DocumentSearchResult( @Schema(requiredMode = Schema.RequiredMode.REQUIRED) List items, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - long total + long totalElements, + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + int pageNumber, + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + int pageSize, + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + int totalPages ) { + /** + * Single-page convenience factory used by empty-result shortcuts and by tests that + * don't care about paging. Treats the whole list as page 0 of itself. + */ public static DocumentSearchResult of(List items) { - return new DocumentSearchResult(items, items.size()); + int size = items.size(); + return new DocumentSearchResult(items, size, 0, size, size == 0 ? 0 : 1); + } + + /** + * Paged factory used by the service when it has a real Pageable + full match count + * (e.g. from Spring's Page or from an in-memory sort-then-slice). + */ + public static DocumentSearchResult paged(List slice, Pageable pageable, long totalElements) { + int pageSize = pageable.getPageSize(); + int totalPages = pageSize == 0 ? 0 : (int) ((totalElements + pageSize - 1) / pageSize); + return new DocumentSearchResult(slice, totalElements, pageable.getPageNumber(), pageSize, totalPages); } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/dto/DocumentSearchResultTest.java b/backend/src/test/java/org/raddatz/familienarchiv/dto/DocumentSearchResultTest.java index 3673459d..c860f9b6 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/dto/DocumentSearchResultTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/dto/DocumentSearchResultTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import org.raddatz.familienarchiv.audit.ActivityActorDTO; import org.raddatz.familienarchiv.model.Document; import org.raddatz.familienarchiv.model.DocumentStatus; +import org.springframework.data.domain.PageRequest; import java.util.List; import java.util.UUID; @@ -24,10 +25,43 @@ class DocumentSearchResultTest { } @Test - void of_total_equals_list_size() { - DocumentSearchResult result = DocumentSearchResult.of(List.of(item(UUID.randomUUID()), item(UUID.randomUUID()))); + void of_totalElements_equals_list_size_for_unpaged_shortcut() { + DocumentSearchResult result = DocumentSearchResult.of( + List.of(item(UUID.randomUUID()), item(UUID.randomUUID()))); - assertThat(result.total()).isEqualTo(2L); + assertThat(result.totalElements()).isEqualTo(2L); + assertThat(result.pageNumber()).isZero(); + assertThat(result.pageSize()).isEqualTo(2); + assertThat(result.totalPages()).isEqualTo(1); + } + + @Test + void of_empty_shortcut_has_zero_totalPages() { + DocumentSearchResult result = DocumentSearchResult.of(List.of()); + + assertThat(result.totalElements()).isZero(); + assertThat(result.totalPages()).isZero(); + } + + @Test + void paged_factory_populates_paging_fields_from_pageable_and_total() { + List slice = List.of(item(UUID.randomUUID()), item(UUID.randomUUID())); + + DocumentSearchResult result = DocumentSearchResult.paged(slice, PageRequest.of(1, 50), 120L); + + assertThat(result.items()).hasSize(2); + assertThat(result.totalElements()).isEqualTo(120L); + assertThat(result.pageNumber()).isEqualTo(1); + assertThat(result.pageSize()).isEqualTo(50); + assertThat(result.totalPages()).isEqualTo(3); // ceil(120 / 50) + } + + @Test + void paged_factory_totalPages_rounds_up_on_remainder() { + DocumentSearchResult result = + DocumentSearchResult.paged(List.of(), PageRequest.of(0, 7), 30L); + + assertThat(result.totalPages()).isEqualTo(5); // ceil(30 / 7) } @Test @@ -53,9 +87,18 @@ class DocumentSearchResultTest { } @Test - void total_component_is_annotated_as_required_in_openapi_schema() throws NoSuchFieldException { - Schema schema = DocumentSearchResult.class.getDeclaredField("total").getAnnotation(Schema.class); + void totalElements_component_is_annotated_as_required_in_openapi_schema() throws NoSuchFieldException { + Schema schema = DocumentSearchResult.class.getDeclaredField("totalElements").getAnnotation(Schema.class); assertThat(schema).isNotNull(); assertThat(schema.requiredMode()).isEqualTo(Schema.RequiredMode.REQUIRED); } + + @Test + void paging_components_are_annotated_as_required_in_openapi_schema() throws NoSuchFieldException { + for (String name : List.of("pageNumber", "pageSize", "totalPages")) { + Schema schema = DocumentSearchResult.class.getDeclaredField(name).getAnnotation(Schema.class); + assertThat(schema).as(name + " must have @Schema").isNotNull(); + assertThat(schema.requiredMode()).isEqualTo(Schema.RequiredMode.REQUIRED); + } + } }