diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/StatsController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/StatsController.java new file mode 100644 index 00000000..c735d74a --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/StatsController.java @@ -0,0 +1,25 @@ +package org.raddatz.familienarchiv.controller; + +import org.raddatz.familienarchiv.dto.StatsDTO; +import org.raddatz.familienarchiv.repository.DocumentRepository; +import org.raddatz.familienarchiv.repository.PersonRepository; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequestMapping("/api/stats") +@RequiredArgsConstructor +public class StatsController { + + private final PersonRepository personRepository; + private final DocumentRepository documentRepository; + + @GetMapping + public ResponseEntity getStats() { + return ResponseEntity.ok(new StatsDTO(personRepository.count(), documentRepository.count())); + } +} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dto/StatsDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/dto/StatsDTO.java new file mode 100644 index 00000000..dcd26c41 --- /dev/null +++ b/backend/src/main/java/org/raddatz/familienarchiv/dto/StatsDTO.java @@ -0,0 +1,7 @@ +package org.raddatz.familienarchiv.dto; + +/** + * Aggregate counts for the dashboard/persons stats bar. + */ +public record StatsDTO(long totalPersons, long totalDocuments) { +} diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/StatsControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/StatsControllerTest.java new file mode 100644 index 00000000..44228a7b --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/StatsControllerTest.java @@ -0,0 +1,61 @@ +package org.raddatz.familienarchiv.controller; + +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.config.SecurityConfig; +import org.raddatz.familienarchiv.repository.DocumentRepository; +import org.raddatz.familienarchiv.repository.PersonRepository; +import org.raddatz.familienarchiv.security.PermissionAspect; +import org.raddatz.familienarchiv.service.CustomUserDetailsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(StatsController.class) +@Import({SecurityConfig.class, PermissionAspect.class, AopAutoConfiguration.class}) +class StatsControllerTest { + + @Autowired MockMvc mockMvc; + + @MockitoBean PersonRepository personRepository; + @MockitoBean DocumentRepository documentRepository; + @MockitoBean CustomUserDetailsService customUserDetailsService; + + @Test + void getStats_returns401_whenUnauthenticated() throws Exception { + mockMvc.perform(get("/api/stats")) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void getStats_returns200_withCorrectCounts() throws Exception { + when(personRepository.count()).thenReturn(4L); + when(documentRepository.count()).thenReturn(12L); + + mockMvc.perform(get("/api/stats")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalPersons").value(4)) + .andExpect(jsonPath("$.totalDocuments").value(12)); + } + + @Test + @WithMockUser + void getStats_returns200_withZeroCounts() throws Exception { + when(personRepository.count()).thenReturn(0L); + when(documentRepository.count()).thenReturn(0L); + + mockMvc.perform(get("/api/stats")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.totalPersons").value(0)) + .andExpect(jsonPath("$.totalDocuments").value(0)); + } +}