diff --git a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java index d075ddd..13b6606 100644 --- a/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java +++ b/src/main/java/de/zendric/app/xpensely_server/controller/ExpenseListController.java @@ -15,6 +15,7 @@ import org.springframework.web.server.ResponseStatusException; import de.zendric.app.xpensely_server.model.*; import de.zendric.app.xpensely_server.model.DTO.HistoryPageDto; +import de.zendric.app.xpensely_server.model.DTO.RecentHistoryEntryDto; import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver; import de.zendric.app.xpensely_server.services.CategoryService; import de.zendric.app.xpensely_server.services.ExpenseListService; @@ -194,6 +195,14 @@ public class ExpenseListController { return ResponseEntity.ok(historyService.getHistory(id, page, size)); } + @GetMapping("/history/recent") + public ResponseEntity> getRecentHistory( + @RequestParam(defaultValue = "4") int limit, + Authentication authentication) { + AppUser user = authenticatedUserResolver.resolveCurrentUser(authentication); + return ResponseEntity.ok(historyService.getRecentForUser(user, limit)); + } + private void assertOwner(AppUser authenticated, ExpenseList list) { if (!list.getOwner().getId().equals(authenticated.getId())) throw new ResponseStatusException(HttpStatus.FORBIDDEN); diff --git a/src/test/java/de/zendric/app/xpensely_Server/controller/ExpenseListControllerTest.java b/src/test/java/de/zendric/app/xpensely_Server/controller/ExpenseListControllerTest.java index aba6b13..a4e0210 100644 --- a/src/test/java/de/zendric/app/xpensely_Server/controller/ExpenseListControllerTest.java +++ b/src/test/java/de/zendric/app/xpensely_Server/controller/ExpenseListControllerTest.java @@ -3,6 +3,7 @@ package de.zendric.app.xpensely_Server.controller; import de.zendric.app.xpensely_server.controller.ExpenseListController; import de.zendric.app.xpensely_server.model.AppUser; import de.zendric.app.xpensely_server.model.DTO.HistoryPageDto; +import de.zendric.app.xpensely_server.model.DTO.RecentHistoryEntryDto; import de.zendric.app.xpensely_server.model.Expense; import de.zendric.app.xpensely_server.model.ExpenseList; import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver; @@ -370,4 +371,49 @@ class ExpenseListControllerTest { mockMvc.perform(get("/api/expenselist/99/history")) .andExpect(status().isNotFound()); } + + // --- Recent history endpoint --- + + @Test + void getRecentHistory_returnsArrayWithListInfo() throws Exception { + AppUser user = new AppUser(); user.setId(1L); user.setUsername("alice"); + when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(user); + when(historyService.getRecentForUser(user, 4)).thenReturn(List.of( + new RecentHistoryEntryDto("UPDATED", "alice", "Groceries", + LocalDateTime.of(2026, 7, 6, 12, 0), 10L, "Trip to Rome"))); + + mockMvc.perform(get("/api/expenselist/history/recent")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].type").value("UPDATED")) + .andExpect(jsonPath("$[0].actorUsername").value("alice")) + .andExpect(jsonPath("$[0].expenseTitle").value("Groceries")) + .andExpect(jsonPath("$[0].listId").value(10)) + .andExpect(jsonPath("$[0].listName").value("Trip to Rome")); + } + + @Test + void getRecentHistory_passesLimitParam() throws Exception { + AppUser user = new AppUser(); user.setId(1L); + when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(user); + when(historyService.getRecentForUser(user, 10)).thenReturn(List.of()); + + mockMvc.perform(get("/api/expenselist/history/recent").param("limit", "10")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void getRecentHistory_doesNotClashWithPerListHistoryRoute() throws Exception { + // /api/expenselist/1/history must still hit the per-list handler. + AppUser owner = new AppUser(); owner.setId(1L); owner.setUsername("alice"); + ExpenseList list = new ExpenseList(); list.setId(1L); list.setOwner(owner); + when(expenseListService.findById(1L)).thenReturn(Optional.of(list)); + when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(owner); + when(historyService.getHistory(1L, 0, 30)) + .thenReturn(new HistoryPageDto(List.of(), false)); + + mockMvc.perform(get("/api/expenselist/1/history")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.entries").isArray()); + } }