From ef9a85eee81cb7a573800c1bc785e19640f9e21a Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 29 Mar 2026 19:35:59 +0200 Subject: [PATCH] feat(persons): add @Size constraints to PersonUpdateDTO + @Valid to controller firstName/lastName max 100, alias max 200, notes max 5000 chars. PUT /api/persons/{id} returns 400 for oversized fields. Co-Authored-By: Claude Sonnet 4.6 --- .../controller/PersonController.java | 5 +++- .../familienarchiv/dto/PersonUpdateDTO.java | 5 ++++ .../controller/PersonControllerTest.java | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java index 919e3ee6..9812c752 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/PersonController.java @@ -13,9 +13,12 @@ import org.raddatz.familienarchiv.service.DocumentService; import org.raddatz.familienarchiv.service.PersonService; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import jakarta.validation.Valid; + import lombok.RequiredArgsConstructor; @RestController @@ -66,7 +69,7 @@ public class PersonController { @PutMapping("/{id}") @RequirePermission(Permission.WRITE_ALL) - public ResponseEntity updatePerson(@PathVariable UUID id, @RequestBody PersonUpdateDTO dto) { + public ResponseEntity updatePerson(@PathVariable UUID id, @Valid @RequestBody PersonUpdateDTO dto) { if (dto.getFirstName() == null || dto.getFirstName().isBlank() || dto.getLastName() == null || dto.getLastName().isBlank()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Vor- und Nachname sind Pflichtfelder"); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dto/PersonUpdateDTO.java b/backend/src/main/java/org/raddatz/familienarchiv/dto/PersonUpdateDTO.java index 88d808c0..ff00f1e0 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/dto/PersonUpdateDTO.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/dto/PersonUpdateDTO.java @@ -1,12 +1,17 @@ package org.raddatz.familienarchiv.dto; +import jakarta.validation.constraints.Size; import lombok.Data; @Data public class PersonUpdateDTO { + @Size(max = 100) private String firstName; + @Size(max = 100) private String lastName; + @Size(max = 200) private String alias; + @Size(max = 5000) private String notes; private Integer birthYear; private Integer deathYear; diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java index b6444de1..cb384f69 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/PersonControllerTest.java @@ -305,6 +305,30 @@ class PersonControllerTest { .andExpect(status().isBadRequest()); } + // ─── Phase 1.2: @Size constraints ───────────────────────────────────────── + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void updatePerson_returns400_whenNotesExceed5000Chars() throws Exception { + String oversizedNotes = "x".repeat(5001); + UUID id = UUID.randomUUID(); + mockMvc.perform(put("/api/persons/{id}", id) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"Hans\",\"lastName\":\"Müller\",\"notes\":\"" + oversizedNotes + "\"}")) + .andExpect(status().isBadRequest()); + } + + @Test + @WithMockUser(authorities = "WRITE_ALL") + void updatePerson_returns400_whenFirstNameExceeds100Chars() throws Exception { + String oversizedFirstName = "x".repeat(101); + UUID id = UUID.randomUUID(); + mockMvc.perform(put("/api/persons/{id}", id) + .contentType(MediaType.APPLICATION_JSON) + .content("{\"firstName\":\"" + oversizedFirstName + "\",\"lastName\":\"Müller\"}")) + .andExpect(status().isBadRequest()); + } + // ─── Phase 1.1: @RequirePermission(WRITE_ALL) on write endpoints ────────── @Test