diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java index 9f3e7c6f..90ab0bd0 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/DocumentController.java @@ -17,6 +17,7 @@ import org.raddatz.familienarchiv.dto.IncompleteDocumentDTO; import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.model.Document; +import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.DocumentVersion; import org.raddatz.familienarchiv.security.Permission; import org.raddatz.familienarchiv.security.RequirePermission; @@ -184,8 +185,9 @@ public class DocumentController { @RequestParam(required = false) LocalDate to, @RequestParam(required = false) UUID senderId, @RequestParam(required = false) UUID receiverId, - @RequestParam(required = false, name = "tag") List tags) { - return ResponseEntity.ok(documentService.searchDocuments(q, from, to, senderId, receiverId, tags)); + @RequestParam(required = false, name = "tag") List tags, + @RequestParam(required = false) DocumentStatus status) { + return ResponseEntity.ok(documentService.searchDocuments(q, from, to, senderId, receiverId, tags, status)); } // --- VERSIONS --- diff --git a/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentSpecifications.java b/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentSpecifications.java index d4890938..cf50675b 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentSpecifications.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/repository/DocumentSpecifications.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.UUID; import org.raddatz.familienarchiv.model.Document; +import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.Tag; import org.springframework.data.jpa.domain.Specification; import org.springframework.util.StringUtils; @@ -55,6 +56,11 @@ public class DocumentSpecifications { }; } + // Filtert nach Status + public static Specification hasStatus(DocumentStatus status) { + return (root, query, cb) -> status == null ? null : cb.equal(root.get("status"), status); + } + // Filtert nach Schlagworten (UND-Verknüpfung) public static Specification hasTags(List tags) { return (root, query, cb) -> { diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java index c9eb4a42..ae0e360d 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentService.java @@ -261,12 +261,13 @@ public class DocumentService { } // 1. Allgemeine Suche (für das Suchfeld im Frontend) - public List searchDocuments(String text, LocalDate from, LocalDate to, UUID sender, UUID receiver, List tags) { + public List searchDocuments(String text, LocalDate from, LocalDate to, UUID sender, UUID receiver, List tags, DocumentStatus status) { Specification spec = Specification.where(hasText(text)) .and(isBetween(from, to)) .and(hasSender(sender)) .and(hasReceiver(receiver)) - .and(hasTags(tags)); + .and(hasTags(tags)) + .and(hasStatus(status)); // Neueste zuerst (nach Erstellungsdatum) return documentRepository.findAll(spec, Sort.by(Sort.Direction.DESC, "createdAt")); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java index 87b4c341..94284d48 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/DocumentControllerTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.raddatz.familienarchiv.dto.DocumentVersionSummary; import org.raddatz.familienarchiv.dto.IncompleteDocumentDTO; import org.raddatz.familienarchiv.model.Document; +import org.raddatz.familienarchiv.model.DocumentStatus; import org.raddatz.familienarchiv.model.DocumentVersion; import org.raddatz.familienarchiv.security.PermissionAspect; import org.raddatz.familienarchiv.service.CustomUserDetailsService; @@ -27,6 +28,7 @@ import java.util.UUID; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; @@ -56,13 +58,32 @@ class DocumentControllerTest { @Test @WithMockUser void search_returns200_whenAuthenticated() throws Exception { - when(documentService.searchDocuments(any(), any(), any(), any(), any(), any())) + when(documentService.searchDocuments(any(), any(), any(), any(), any(), any(), any())) .thenReturn(Collections.emptyList()); mockMvc.perform(get("/api/documents/search")) .andExpect(status().isOk()); } + @Test + @WithMockUser + void search_withStatusParam_passesItToService() throws Exception { + when(documentService.searchDocuments(any(), any(), any(), any(), any(), any(), eq(DocumentStatus.REVIEWED))) + .thenReturn(Collections.emptyList()); + + mockMvc.perform(get("/api/documents/search").param("status", "REVIEWED")) + .andExpect(status().isOk()); + + verify(documentService).searchDocuments(any(), any(), any(), any(), any(), any(), eq(DocumentStatus.REVIEWED)); + } + + @Test + @WithMockUser + void search_withInvalidStatus_returns400() throws Exception { + mockMvc.perform(get("/api/documents/search").param("status", "INVALID")) + .andExpect(status().isBadRequest()); + } + // ─── POST /api/documents ───────────────────────────────────────────────── @Test diff --git a/backend/src/test/java/org/raddatz/familienarchiv/repository/DocumentSpecificationsTest.java b/backend/src/test/java/org/raddatz/familienarchiv/repository/DocumentSpecificationsTest.java index ccdd3408..447f26e3 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/repository/DocumentSpecificationsTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/repository/DocumentSpecificationsTest.java @@ -249,4 +249,24 @@ class DocumentSpecificationsTest { List result = documentRepository.findAll(Specification.where(hasTags(List.of("Unbekannt")))); assertThat(result).isEmpty(); } + + // ─── hasStatus ──────────────────────────────────────────────────────────── + + @Test + void hasStatus_returnsAllDocuments_whenStatusIsNull() { + List result = documentRepository.findAll(Specification.where(hasStatus(null))); + assertThat(result).hasSize(3); + } + + @Test + void hasStatus_returnsOnlyMatchingDocuments_whenStatusIsSet() { + List result = documentRepository.findAll(Specification.where(hasStatus(DocumentStatus.PLACEHOLDER))); + assertThat(result).extracting(Document::getTitle).containsExactly("Familienfoto"); + } + + @Test + void hasStatus_returnsEmpty_whenNoDocumentMatchesStatus() { + List result = documentRepository.findAll(Specification.where(hasStatus(DocumentStatus.REVIEWED))); + assertThat(result).isEmpty(); + } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java index 4d7c27ec..80910432 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentServiceTest.java @@ -1191,4 +1191,26 @@ class DocumentServiceTest { Object result = method.invoke(null, (String) null); assertThat(result).isNull(); } + + // ─── searchDocuments — status filter ───────────────────────────────────── + + @Test + void searchDocuments_passesStatusSpecificationToRepository() { + when(documentRepository.findAll(any(org.springframework.data.jpa.domain.Specification.class), any(Sort.class))) + .thenReturn(List.of()); + + documentService.searchDocuments(null, null, null, null, null, null, DocumentStatus.REVIEWED); + + verify(documentRepository).findAll(any(org.springframework.data.jpa.domain.Specification.class), any(Sort.class)); + } + + @Test + void searchDocuments_withNullStatus_doesNotFilterByStatus() { + when(documentRepository.findAll(any(org.springframework.data.jpa.domain.Specification.class), any(Sort.class))) + .thenReturn(List.of()); + + documentService.searchDocuments(null, null, null, null, null, null, null); + + verify(documentRepository).findAll(any(org.springframework.data.jpa.domain.Specification.class), any(Sort.class)); + } }