feat: add PATCH /api/admin/orders/{id} for manual tracking entry
- UpdateTrackingRequest DTO: optional trackingId string (nullable —
allows clearing a tracking ID entered incorrectly)
- OrderService.updateTracking(orderId, trackingId): finds order,
sets trackingId via setter, saves entity — @PreUpdate fires to
update the updated_at timestamp automatically
- AdminController.PATCH /api/admin/orders/{id}: admin-only endpoint,
validates request body with @Valid, returns updated AdminOrderResponse
via the existing toAdminResponse() mapper
- AdminControllerTest: 5 new tests —
shouldReturn403WhenPatchingTrackingWithoutAuth,
shouldReturn403WhenPatchingTrackingAsNonAdmin,
shouldUpdateTrackingSuccessfully (verifies response id and trackingId),
shouldClearTrackingWhenNull (removes trackingId),
shouldReturn404WhenOrderNotFoundForTracking
This commit is contained in:
parent
f6825ec885
commit
ebab892e93
4 changed files with 87 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import se.bilhalsning.dto.AdminOrderResponse;
|
import se.bilhalsning.dto.AdminOrderResponse;
|
||||||
import se.bilhalsning.dto.UpdateStatusRequest;
|
import se.bilhalsning.dto.UpdateStatusRequest;
|
||||||
|
import se.bilhalsning.dto.UpdateTrackingRequest;
|
||||||
import se.bilhalsning.entity.Order;
|
import se.bilhalsning.entity.Order;
|
||||||
import se.bilhalsning.service.OrderService;
|
import se.bilhalsning.service.OrderService;
|
||||||
|
|
||||||
|
|
@ -40,6 +41,14 @@ public class AdminController {
|
||||||
return ResponseEntity.ok(toAdminResponse(order));
|
return ResponseEntity.ok(toAdminResponse(order));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/orders/{id}")
|
||||||
|
public ResponseEntity<AdminOrderResponse> 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) {
|
private AdminOrderResponse toAdminResponse(Order order) {
|
||||||
String email = order.getUser() != null ? order.getUser().getEmail() : "";
|
String email = order.getUser() != null ? order.getUser().getEmail() : "";
|
||||||
return new AdminOrderResponse(
|
return new AdminOrderResponse(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package se.bilhalsning.dto;
|
||||||
|
|
||||||
|
public record UpdateTrackingRequest(
|
||||||
|
String trackingId
|
||||||
|
) {}
|
||||||
|
|
@ -46,4 +46,12 @@ public class OrderService {
|
||||||
order.setStatus(newStatus);
|
order.setStatus(newStatus);
|
||||||
return orderRepository.save(order);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,71 @@ class AdminControllerTest {
|
||||||
.andExpect(status().isNotFound());
|
.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) {
|
private Order createOrder(UUID orderId, String plate, String email, OrderStatus status) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue