feat: add POST /api/orders endpoint with validation
- Create CreateOrderRequest DTO with jakarta.validation annotations - Validate plate format (ABC123 or ABC12A) via @Pattern regex - Validate letter text: @NotBlank, @Size(min=1, max=1000) - Validate template name: optional, @Size(max=50) - Add POST /api/orders endpoint to OrderController (auth required) - Return 201 Created with OrderResponse on success - Add 5 controller tests: no auth (403), create success, invalid plate, empty text, text over 1000 chars - All messages in Swedish (Ogiltigt registreringsnummer, Brevtext krävs, etc.)
This commit is contained in:
parent
0c62d7e60a
commit
55f0fd8771
3 changed files with 109 additions and 0 deletions
|
|
@ -1,12 +1,17 @@
|
||||||
package se.bilhalsning.controller;
|
package se.bilhalsning.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
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.CreateOrderRequest;
|
||||||
import se.bilhalsning.dto.OrderResponse;
|
import se.bilhalsning.dto.OrderResponse;
|
||||||
import se.bilhalsning.entity.Order;
|
import se.bilhalsning.entity.Order;
|
||||||
import se.bilhalsning.entity.User;
|
import se.bilhalsning.entity.User;
|
||||||
|
|
@ -36,6 +41,23 @@ public class OrderController {
|
||||||
return ResponseEntity.ok(orders);
|
return ResponseEntity.ok(orders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<OrderResponse> create(
|
||||||
|
@Valid @RequestBody CreateOrderRequest request,
|
||||||
|
@AuthenticationPrincipal UserDetails userDetails) {
|
||||||
|
User user = userService.findByEmail(userDetails.getUsername())
|
||||||
|
.orElseThrow(InvalidCredentialsException::new);
|
||||||
|
|
||||||
|
Order order = orderService.createOrder(
|
||||||
|
user.getId(),
|
||||||
|
request.plate(),
|
||||||
|
request.template(),
|
||||||
|
request.letterText()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(order));
|
||||||
|
}
|
||||||
|
|
||||||
private OrderResponse toResponse(Order order) {
|
private OrderResponse toResponse(Order order) {
|
||||||
return new OrderResponse(
|
return new OrderResponse(
|
||||||
order.getId(),
|
order.getId(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package se.bilhalsning.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Pattern;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public record CreateOrderRequest(
|
||||||
|
@NotBlank(message = "Registreringsnummer krävs")
|
||||||
|
@Pattern(regexp = "^[A-Za-z]{3}\\d{2}[A-Za-z0-9]$", message = "Ogiltigt registreringsnummer")
|
||||||
|
String plate,
|
||||||
|
|
||||||
|
@Size(max = 50, message = "Mallnamn får vara max 50 tecken")
|
||||||
|
String template,
|
||||||
|
|
||||||
|
@NotBlank(message = "Brevtext krävs")
|
||||||
|
@Size(min = 1, max = 1000, message = "Brevtexten måste vara mellan 1 och 1000 tecken")
|
||||||
|
String letterText
|
||||||
|
) {}
|
||||||
|
|
@ -2,6 +2,7 @@ package se.bilhalsning.controller;
|
||||||
|
|
||||||
import static org.mockito.Mockito.when;
|
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.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
|
@ -101,4 +102,72 @@ class OrderControllerTest {
|
||||||
mockMvc.perform(get("/api/orders"))
|
mockMvc.perform(get("/api/orders"))
|
||||||
.andExpect(status().isUnauthorized());
|
.andExpect(status().isUnauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturn403WhenPostingWithoutAuth() throws Exception {
|
||||||
|
mockMvc.perform(post("/api/orders")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("{\"plate\":\"ABC123\",\"letterText\":\"Hej\"}"))
|
||||||
|
.andExpect(status().isForbidden());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "test@bilhalsning.se")
|
||||||
|
void shouldCreateOrderSuccessfully() throws Exception {
|
||||||
|
UUID userId = UUID.fromString("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
|
||||||
|
User user = new User();
|
||||||
|
user.setId(userId);
|
||||||
|
user.setEmail("test@bilhalsning.se");
|
||||||
|
|
||||||
|
when(userService.findByEmail("test@bilhalsning.se")).thenReturn(Optional.of(user));
|
||||||
|
|
||||||
|
se.bilhalsning.entity.Order savedOrder = new se.bilhalsning.entity.Order();
|
||||||
|
savedOrder.setId(UUID.fromString("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"));
|
||||||
|
savedOrder.setUserId(userId);
|
||||||
|
savedOrder.setPlate("ABC123");
|
||||||
|
savedOrder.setTemplate("Komplimang");
|
||||||
|
savedOrder.setLetterText("Hej fin bil!");
|
||||||
|
savedOrder.setStatus(se.bilhalsning.entity.OrderStatus.PENDING_PAYMENT);
|
||||||
|
|
||||||
|
when(orderService.createOrder(userId, "ABC123", "Komplimang", "Hej fin bil!"))
|
||||||
|
.thenReturn(savedOrder);
|
||||||
|
|
||||||
|
mockMvc.perform(post("/api/orders")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("{\"plate\":\"ABC123\",\"template\":\"Komplimang\",\"letterText\":\"Hej fin bil!\"}"))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$.id").value("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))
|
||||||
|
.andExpect(jsonPath("$.plate").value("ABC123"))
|
||||||
|
.andExpect(jsonPath("$.template").value("Komplimang"))
|
||||||
|
.andExpect(jsonPath("$.status").value("pending_payment"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "test@bilhalsning.se")
|
||||||
|
void shouldRejectInvalidPlateFormat() throws Exception {
|
||||||
|
mockMvc.perform(post("/api/orders")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("{\"plate\":\"INVALID\",\"letterText\":\"Hej\"}"))
|
||||||
|
.andExpect(status().isBadRequest())
|
||||||
|
.andExpect(jsonPath("$.message").value(org.hamcrest.Matchers.containsString("Ogiltigt registreringsnummer")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "test@bilhalsning.se")
|
||||||
|
void shouldRejectEmptyLetterText() throws Exception {
|
||||||
|
mockMvc.perform(post("/api/orders")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("{\"plate\":\"ABC123\",\"letterText\":\"\"}"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "test@bilhalsning.se")
|
||||||
|
void shouldRejectLetterTextOver1000Chars() throws Exception {
|
||||||
|
String longText = "a".repeat(1001);
|
||||||
|
mockMvc.perform(post("/api/orders")
|
||||||
|
.contentType("application/json")
|
||||||
|
.content("{\"plate\":\"ABC123\",\"letterText\":\"" + longText + "\"}"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue