fix(auth): add @Email validation and @Valid to enforce email format on user creation
Some checks failed
CI / Unit & Component Tests (push) Failing after 2m20s
CI / OCR Service Tests (push) Successful in 28s
CI / Backend Unit Tests (push) Failing after 2m43s

- Add @Email annotation to CreateUserRequest.email and AppUser.email
- Add @Valid to UserController.createUser to activate bean validation
- Add MigrationIntegrationTest cases for V44 NOT NULL and UNIQUE constraints
- Fix stale test comments (findByUsername → findByEmail)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #272.
This commit is contained in:
Marcel
2026-04-18 21:52:11 +02:00
committed by marcel
parent d816e94a90
commit e1ddd66704
7 changed files with 67 additions and 4 deletions

View File

@@ -278,7 +278,7 @@ class AnnotationControllerTest {
@Test
@WithMockUser(authorities = "ANNOTATE_ALL")
void createAnnotation_resolvesNullUserId_whenUserServiceThrows() throws Exception {
// findByUsername throws → catch block → resolveUserId returns null
// findByEmail throws → catch block → resolveUserId returns null
UUID docId = UUID.randomUUID();
when(userService.findByEmail(any())).thenThrow(new RuntimeException("DB error"));
DocumentAnnotation saved = DocumentAnnotation.builder()
@@ -296,7 +296,7 @@ class AnnotationControllerTest {
@Test
@WithMockUser(authorities = "ANNOTATE_ALL")
void createAnnotation_resolvesNullUserId_whenUserServiceReturnsNull() throws Exception {
// findByUsername returns null → user != null = false → resolveUserId returns null
// findByEmail returns null → user != null = false → resolveUserId returns null
UUID docId = UUID.randomUUID();
when(userService.findByEmail(any())).thenReturn(null);
DocumentAnnotation saved = DocumentAnnotation.builder()

View File

@@ -269,7 +269,7 @@ class CommentControllerTest {
@Test
@WithMockUser(authorities = "WRITE_ALL")
void postDocumentComment_stillSucceeds_whenUserServiceThrows() throws Exception {
// findByUsername throws → catch block in resolveUser → author null, saves anyway
// findByEmail throws → catch block in resolveUser → author null, saves anyway
when(userService.findByEmail(any())).thenThrow(new RuntimeException("DB error"));
DocumentComment saved = DocumentComment.builder()
.id(UUID.randomUUID()).documentId(DOC_ID).content("Test comment").build();

View File

@@ -19,6 +19,7 @@ import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
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.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -74,4 +75,33 @@ class UserControllerTest {
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("target@example.com"));
}
// ─── POST /api/users ──────────────────────────────────────────────────────
@Test
@WithMockUser(username = "admin@example.com", authorities = {"ADMIN_USER"})
void createUser_returns400_whenEmailIsNotValidEmailFormat() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.content("{\"email\":\"notanemail\",\"initialPassword\":\"secret123\"}"))
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser(username = "admin@example.com", authorities = {"ADMIN_USER"})
void createUser_returns400_whenEmailContainsColon() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.content("{\"email\":\"user:name@example.com\",\"initialPassword\":\"secret123\"}"))
.andExpect(status().isBadRequest());
}
@Test
@WithMockUser(username = "admin@example.com", authorities = {"ADMIN_USER"})
void createUser_returns400_whenEmailIsBlank() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.content("{\"email\":\"\",\"initialPassword\":\"secret123\"}"))
.andExpect(status().isBadRequest());
}
}

View File

@@ -274,6 +274,34 @@ class MigrationIntegrationTest {
assertThat(rows2).isEqualTo(1);
}
// ─── V44: email NOT NULL constraint ──────────────────────────────────────
@Test
void v44_emailNotNullConstraint_rejectsInsertWithNullEmail() {
assertThatThrownBy(() ->
jdbc.update("""
INSERT INTO users (id, email, password, enabled, notify_on_reply, notify_on_mention)
VALUES (gen_random_uuid(), NULL, 'hash', true, false, false)
""")
).isInstanceOf(DataIntegrityViolationException.class);
}
@Test
void v44_emailUniqueConstraint_rejectsDuplicateEmail() {
String email = "unique-test-" + UUID.randomUUID() + "@example.com";
jdbc.update("""
INSERT INTO users (id, email, password, enabled, notify_on_reply, notify_on_mention)
VALUES (gen_random_uuid(), ?, 'hash', true, false, false)
""", email);
assertThatThrownBy(() ->
jdbc.update("""
INSERT INTO users (id, email, password, enabled, notify_on_reply, notify_on_mention)
VALUES (gen_random_uuid(), ?, 'hash', true, false, false)
""", email)
).isInstanceOf(DataIntegrityViolationException.class);
}
// ─── helpers ─────────────────────────────────────────────────────────────
private UUID createPerson(String firstName, String lastName) {