feat: add ExpenseListController validation and authorization tests

This commit is contained in:
2026-05-04 22:46:29 +02:00
parent a948bca2fc
commit bb2a4d70b2
2 changed files with 136 additions and 1 deletions
@@ -19,7 +19,7 @@ import de.zendric.app.xpensely_server.services.UserService;
@RestController @RestController
@RequestMapping("/api/expenselist") @RequestMapping("/api/expenselist")
class ExpenseListController { public class ExpenseListController {
private final ExpenseListService expenseListService; private final ExpenseListService expenseListService;
private final UserService userService; private final UserService userService;
@@ -0,0 +1,135 @@
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.ExpenseList;
import de.zendric.app.xpensely_server.security.AuthenticatedUserResolver;
import de.zendric.app.xpensely_server.services.CategoryService;
import de.zendric.app.xpensely_server.services.ExpenseListService;
import de.zendric.app.xpensely_server.services.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;
import java.util.List;
import java.util.Optional;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(ExpenseListController.class)
@AutoConfigureMockMvc(addFilters = false)
@ActiveProfiles("test")
class ExpenseListControllerTest {
@Autowired MockMvc mockMvc;
@MockitoBean ExpenseListService expenseListService;
@MockitoBean UserService userService;
@MockitoBean CategoryService categoryService;
@MockitoBean AuthenticatedUserResolver authenticatedUserResolver;
// --- Validation tests ---
@Test
void addExpense_blankTitle_returns400() throws Exception {
mockMvc.perform(post("/api/expenselist/1/add")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"\",\"owner\":\"alice\",\"amount\":10.0,\"date\":\"2026-05-04\",\"category\":\"Food\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.title").exists());
}
@Test
void addExpense_negativeAmount_returns400() throws Exception {
mockMvc.perform(post("/api/expenselist/1/add")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"Lunch\",\"owner\":\"alice\",\"amount\":-5.0,\"date\":\"2026-05-04\",\"category\":\"Food\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.amount").exists());
}
@Test
void addExpense_nullDate_returns400() throws Exception {
mockMvc.perform(post("/api/expenselist/1/add")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"Lunch\",\"owner\":\"alice\",\"amount\":10.0,\"category\":\"Food\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.date").exists());
}
@Test
void acceptInvite_blankCode_returns400() throws Exception {
mockMvc.perform(post("/api/expenselist/accept-invite")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"inviteCode\":\"\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.inviteCode").exists());
}
@Test
void acceptInvite_wrongCodeLength_returns400() throws Exception {
mockMvc.perform(post("/api/expenselist/accept-invite")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"inviteCode\":\"ABC\"}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.inviteCode").exists());
}
// --- Authorization tests ---
@Test
void getById_authenticatedUserNotMember_returns403() throws Exception {
AppUser owner = new AppUser(); owner.setId(1L);
AppUser requester = new AppUser(); requester.setId(2L);
ExpenseList list = new ExpenseList(); list.setId(1L); list.setOwner(owner);
when(expenseListService.findById(1L)).thenReturn(Optional.of(list));
when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(requester);
mockMvc.perform(get("/api/expenselist/byId").param("id", "1"))
.andExpect(status().isForbidden());
}
@Test
void getById_authenticatedUserIsOwner_returns200() throws Exception {
AppUser owner = new AppUser(); owner.setId(1L);
ExpenseList list = new ExpenseList(); list.setId(1L); list.setOwner(owner);
when(expenseListService.findById(1L)).thenReturn(Optional.of(list));
when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(owner);
mockMvc.perform(get("/api/expenselist/byId").param("id", "1"))
.andExpect(status().isOk());
}
@Test
void deleteList_nonOwner_returns403() throws Exception {
AppUser owner = new AppUser(); owner.setId(1L);
AppUser nonOwner = new AppUser(); nonOwner.setId(2L);
ExpenseList list = new ExpenseList(); list.setId(5L); list.setOwner(owner);
when(expenseListService.findById(5L)).thenReturn(Optional.of(list));
when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(nonOwner);
mockMvc.perform(delete("/api/expenselist/5"))
.andExpect(status().isForbidden());
}
@Test
void getMine_returnsCurrentUserLists() throws Exception {
AppUser user = new AppUser(); user.setId(3L);
when(authenticatedUserResolver.resolveCurrentUser(any())).thenReturn(user);
when(expenseListService.findByUserId(3L)).thenReturn(List.of(new ExpenseList()));
mockMvc.perform(get("/api/expenselist/mine"))
.andExpect(status().isOk());
}
}