From 5df7c97977a2ab5d914799ff9a860448af9c02f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20M=C3=B6rling?= Date: Fri, 15 May 2026 12:15:06 +0200 Subject: [PATCH] test: add AdminControllerTest with 10 role-enforcement and validation cases - GET /api/admin/orders: - shouldReturn403WhenNotAuthenticated - shouldReturn403ForNonAdminUser (roles = USER) - shouldReturnAllOrdersForAdmin (roles = ADMIN, checks all response fields including email, plate, letterText, status) - shouldReturnEmptyArrayWhenNoOrders - PATCH /api/admin/orders/{id}/status: - shouldReturn403WhenPatchingStatusWithoutAuth - shouldReturn403WhenPatchingStatusAsNonAdmin - shouldUpdateOrderStatusSuccessfully (verifies response id matches path variable, status reflects update) - shouldReturn400WhenStatusIsInvalid (invalid_status rejected by @Pattern validator) - shouldReturn400WhenStatusIsBlank - shouldReturn404WhenOrderNotFound - Helper createOrder(UUID orderId, String plate, String email, OrderStatus) builds domain objects with User relationship for realistic response mapping --- .../controller/AdminControllerTest.java | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java diff --git a/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java b/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java new file mode 100644 index 0000000..0b6a5f6 --- /dev/null +++ b/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java @@ -0,0 +1,162 @@ +package se.bilhalsning.controller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +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 se.bilhalsning.entity.Order; +import se.bilhalsning.entity.OrderStatus; +import se.bilhalsning.entity.User; +import se.bilhalsning.exception.OrderNotFoundException; +import se.bilhalsning.service.OrderService; + +@SpringBootTest +@AutoConfigureMockMvc +class AdminControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private OrderService orderService; + + @Test + void shouldReturn403WhenNotAuthenticated() throws Exception { + mockMvc.perform(get("/api/admin/orders")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(username = "test@bilhalsning.se", roles = "USER") + void shouldReturn403ForNonAdminUser() throws Exception { + mockMvc.perform(get("/api/admin/orders")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") + void shouldReturnAllOrdersForAdmin() throws Exception { + Order order = createOrder(UUID.randomUUID(), "ABC123", "test@bilhalsning.se", OrderStatus.SENT); + when(orderService.getAllOrders()).thenReturn(List.of(order)); + + mockMvc.perform(get("/api/admin/orders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].id").value(order.getId().toString())) + .andExpect(jsonPath("$[0].email").value("test@bilhalsning.se")) + .andExpect(jsonPath("$[0].plate").value("ABC123")) + .andExpect(jsonPath("$[0].letterText").value("Test letter")) + .andExpect(jsonPath("$[0].status").value("sent")); + } + + @Test + @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") + void shouldReturnEmptyArrayWhenNoOrders() throws Exception { + when(orderService.getAllOrders()).thenReturn(List.of()); + + mockMvc.perform(get("/api/admin/orders")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").isEmpty()); + } + + @Test + void shouldReturn403WhenPatchingStatusWithoutAuth() throws Exception { + mockMvc.perform(patch("/api/admin/orders/{id}/status", + "c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"status\":\"paid\"}")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(username = "test@bilhalsning.se", roles = "USER") + void shouldReturn403WhenPatchingStatusAsNonAdmin() throws Exception { + mockMvc.perform(patch("/api/admin/orders/{id}/status", + "c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"status\":\"paid\"}")) + .andExpect(status().isForbidden()); + } + + @Test + @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") + void shouldUpdateOrderStatusSuccessfully() throws Exception { + UUID orderId = UUID.fromString("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); + Order order = createOrder(orderId, "ABC123", "test@bilhalsning.se", OrderStatus.PAID); + + when(orderService.updateOrderStatus(eq(orderId), eq("paid"))).thenReturn(order); + + mockMvc.perform(patch("/api/admin/orders/{id}/status", orderId) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"status\":\"paid\"}")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id").value(orderId.toString())) + .andExpect(jsonPath("$.status").value("paid")); + } + + @Test + @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") + void shouldReturn400WhenStatusIsInvalid() throws Exception { + mockMvc.perform(patch("/api/admin/orders/{id}/status", + "c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"status\":\"invalid_status\"}")) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") + void shouldReturn400WhenStatusIsBlank() throws Exception { + mockMvc.perform(patch("/api/admin/orders/{id}/status", + "c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"status\":\"\"}")) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") + void shouldReturn404WhenOrderNotFound() throws Exception { + UUID orderId = UUID.fromString("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); + when(orderService.updateOrderStatus(eq(orderId), eq("paid"))) + .thenThrow(new OrderNotFoundException(orderId)); + + mockMvc.perform(patch("/api/admin/orders/{id}/status", orderId) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"status\":\"paid\"}")) + .andExpect(status().isNotFound()); + } + + private Order createOrder(UUID orderId, String plate, String email, OrderStatus status) { + User user = new User(); + user.setEmail(email); + + Order order = new Order(); + order.setId(orderId); + order.setUser(user); + order.setPlate(plate); + order.setLetterText("Test letter"); + order.setStatus(status); + order.setTrackingId(null); + order.setAmountPaid(new BigDecimal("49.00")); + + return order; + } +}