diff --git a/backend/src/main/java/se/bilhalsning/controller/AdminController.java b/backend/src/main/java/se/bilhalsning/controller/AdminController.java index 5b3f2a8..3bc1726 100644 --- a/backend/src/main/java/se/bilhalsning/controller/AdminController.java +++ b/backend/src/main/java/se/bilhalsning/controller/AdminController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import se.bilhalsning.dto.AdminOrderResponse; import se.bilhalsning.dto.UpdateStatusRequest; +import se.bilhalsning.dto.UpdateTrackingRequest; import se.bilhalsning.entity.Order; import se.bilhalsning.service.OrderService; @@ -40,6 +41,14 @@ public class AdminController { return ResponseEntity.ok(toAdminResponse(order)); } + @PatchMapping("/orders/{id}") + public ResponseEntity updateTracking( + @PathVariable UUID id, + @Valid @RequestBody UpdateTrackingRequest request) { + Order order = orderService.updateTracking(id, request.trackingId()); + return ResponseEntity.ok(toAdminResponse(order)); + } + private AdminOrderResponse toAdminResponse(Order order) { String email = order.getUser() != null ? order.getUser().getEmail() : ""; return new AdminOrderResponse( diff --git a/backend/src/main/java/se/bilhalsning/dto/UpdateTrackingRequest.java b/backend/src/main/java/se/bilhalsning/dto/UpdateTrackingRequest.java new file mode 100644 index 0000000..1eabfbe --- /dev/null +++ b/backend/src/main/java/se/bilhalsning/dto/UpdateTrackingRequest.java @@ -0,0 +1,5 @@ +package se.bilhalsning.dto; + +public record UpdateTrackingRequest( + String trackingId +) {} diff --git a/backend/src/main/java/se/bilhalsning/service/OrderService.java b/backend/src/main/java/se/bilhalsning/service/OrderService.java index 2591fe4..5e9f7df 100644 --- a/backend/src/main/java/se/bilhalsning/service/OrderService.java +++ b/backend/src/main/java/se/bilhalsning/service/OrderService.java @@ -46,4 +46,12 @@ public class OrderService { order.setStatus(newStatus); return orderRepository.save(order); } + + public Order updateTracking(UUID orderId, String trackingId) { + Order order = orderRepository.findById(orderId) + .orElseThrow(() -> new OrderNotFoundException(orderId)); + + order.setTrackingId(trackingId); + return orderRepository.save(order); + } } diff --git a/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java b/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java index 0b6a5f6..ec76e08 100644 --- a/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java +++ b/backend/src/test/java/se/bilhalsning/controller/AdminControllerTest.java @@ -144,6 +144,71 @@ class AdminControllerTest { .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@bilhalsning.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@bilhalsning.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@bilhalsning.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);