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@bilhej.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@bilhej.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@bilhej.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@bilhej.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@bilhej.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()); } @Test void shouldReturn403WhenPatchingTrackingWithoutAuth() throws Exception { mockMvc.perform(patch("/api/admin/orders/{id}", "c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") .contentType(MediaType.APPLICATION_JSON) .content("{\"trackingId\":\"PN123456789\"}")) .andExpect(status().isForbidden()); } @Test @WithMockUser(username = "test@bilhej.se", roles = "USER") void shouldReturn403WhenPatchingTrackingAsNonAdmin() throws Exception { mockMvc.perform(patch("/api/admin/orders/{id}", "c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11") .contentType(MediaType.APPLICATION_JSON) .content("{\"trackingId\":\"PN123456789\"}")) .andExpect(status().isForbidden()); } @Test @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") void shouldUpdateTrackingSuccessfully() throws Exception { UUID orderId = UUID.fromString("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); Order order = createOrder(orderId, "ABC123", "test@bilhej.se", OrderStatus.SENT); order.setTrackingId("PN123456789"); when(orderService.updateTracking(eq(orderId), eq("PN123456789"))).thenReturn(order); mockMvc.perform(patch("/api/admin/orders/{id}", orderId) .contentType(MediaType.APPLICATION_JSON) .content("{\"trackingId\":\"PN123456789\"}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(orderId.toString())) .andExpect(jsonPath("$.trackingId").value("PN123456789")); } @Test @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") void shouldClearTrackingWhenNull() throws Exception { UUID orderId = UUID.fromString("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); Order order = createOrder(orderId, "ABC123", "test@bilhej.se", OrderStatus.SENT); order.setTrackingId(null); when(orderService.updateTracking(eq(orderId), eq(null))).thenReturn(order); mockMvc.perform(patch("/api/admin/orders/{id}", orderId) .contentType(MediaType.APPLICATION_JSON) .content("{\"trackingId\":null}")) .andExpect(status().isOk()) .andExpect(jsonPath("$.trackingId").doesNotExist()); } @Test @WithMockUser(username = "admin@bilhalsning.se", roles = "ADMIN") void shouldReturn404WhenOrderNotFoundForTracking() throws Exception { UUID orderId = UUID.fromString("c1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"); when(orderService.updateTracking(eq(orderId), eq("PN123456789"))) .thenThrow(new OrderNotFoundException(orderId)); mockMvc.perform(patch("/api/admin/orders/{id}", orderId) .contentType(MediaType.APPLICATION_JSON) .content("{\"trackingId\":\"PN123456789\"}")) .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; } }