From e8039bca5a2d9ff8fafbae946fb3033ff67024c4 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 18 Apr 2026 20:49:15 +0200 Subject: [PATCH] feat(auth): remove username field, migrate identity to email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AppUser entity: replace username with email (NOT NULL, UNIQUE, colon-pattern validated) - AppUserRepository: remove findByUsername, rename search JPQL to searchByEmailOrName (searches email + firstName + lastName) - CreateUserRequest: remove username, require email with colon guard - UserService: rename findByUsername→findByEmail, createUserOrUpdate upserts by email, blank-email guard throws instead of setting null - UserController + all other controllers: findByEmail(auth.getName()) - DataInitializer: email-based config and lookup, E2E users have email - V44 migration: pre-check + email NOT NULL + drop username column - All tests updated: .username() builders removed, mocks updated, NotificationRepositoryTest fixtures include email fields Co-Authored-By: Claude Sonnet 4.6 --- .../config/DataInitializer.java | 31 ++--- .../controller/AnnotationController.java | 2 +- .../controller/CommentController.java | 2 +- .../controller/NotificationController.java | 2 +- .../controller/OcrController.java | 2 +- .../TranscriptionBlockController.java | 2 +- .../controller/UserController.java | 6 +- .../familienarchiv/dto/CreateUserRequest.java | 6 +- .../raddatz/familienarchiv/model/AppUser.java | 43 +++---- .../repository/AppUserRepository.java | 9 +- .../service/CommentService.java | 2 +- .../service/DocumentVersionService.java | 4 +- .../service/UserSearchService.java | 2 +- .../familienarchiv/service/UserService.java | 15 ++- .../db/migration/V44__email_only_auth.sql | 11 ++ .../controller/AnnotationControllerTest.java | 4 +- .../controller/CommentControllerTest.java | 2 +- .../NotificationControllerTest.java | 64 +++++----- .../TranscriptionBlockControllerTest.java | 12 +- .../controller/UserControllerTest.java | 21 ++-- .../controller/UserSearchControllerTest.java | 4 +- .../NotificationRepositoryTest.java | 4 +- .../service/CommentServiceTest.java | 48 +++---- .../service/DocumentVersionServiceTest.java | 74 +++++------ .../service/NotificationServiceTest.java | 6 +- .../service/PasswordResetServiceTest.java | 2 +- .../service/UserSearchServiceTest.java | 14 +-- .../service/UserServiceTest.java | 118 ++++++++---------- 28 files changed, 247 insertions(+), 265 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V44__email_only_auth.sql diff --git a/backend/src/main/java/org/raddatz/familienarchiv/config/DataInitializer.java b/backend/src/main/java/org/raddatz/familienarchiv/config/DataInitializer.java index 9f2b7b75..67cf5fe5 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/config/DataInitializer.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/config/DataInitializer.java @@ -31,8 +31,8 @@ import java.util.Set; @DependsOn("flyway") public class DataInitializer { - @Value("${app.admin.username:admin}") - private String adminUsername; + @Value("${app.admin.email:admin@familyarchive.local}") + private String adminEmail; @Value("${app.admin.password:admin123}") private String adminPassword; @@ -43,26 +43,23 @@ public class DataInitializer { @Bean public CommandLineRunner initAdminUser(PasswordEncoder passwordEncoder) { return args -> { - if (userRepository.findByUsername(adminUsername).isEmpty()) { - log.info("Kein Admin-User '{}' gefunden. Erstelle Default-Admin...", adminUsername); + if (userRepository.findByEmail(adminEmail).isEmpty()) { + log.info("Kein Admin-User '{}' gefunden. Erstelle Default-Admin...", adminEmail); - // 1. Admin Gruppe erstellen UserGroup adminGroup = UserGroup.builder() .name("Administrators") .permissions(Set.of("ADMIN", "READ_ALL", "WRITE_ALL", "ANNOTATE_ALL", "ADMIN_USER", "ADMIN_TAG", "ADMIN_PERMISSION")) .build(); groupRepository.save(adminGroup); - // 2. Admin User erstellen AppUser admin = AppUser.builder() - .username(adminUsername) - .password(passwordEncoder.encode(adminPassword)) // Passwort verschlüsseln! - .email("admin@familyarchive.local") + .email(adminEmail) + .password(passwordEncoder.encode(adminPassword)) .groups(Set.of(adminGroup)) .build(); userRepository.save(admin); - log.info("Default Admin erstellt: User='{}'", adminUsername); + log.info("Default Admin erstellt: Email='{}'", adminEmail); } }; } @@ -84,16 +81,13 @@ public class DataInitializer { TagRepository tagRepo, PasswordEncoder passwordEncoder) { return args -> { - // Always reset the admin password to the configured value so a failed password-reset - // test from a previous run can never leave the account locked out. - userRepository.findByUsername(adminUsername).ifPresent(admin -> { + userRepository.findByEmail(adminEmail).ifPresent(admin -> { admin.setPassword(passwordEncoder.encode(adminPassword)); userRepository.save(admin); log.info("E2E seed: Admin-Passwort auf konfigurierten Wert zurückgesetzt."); }); - // Always ensure the read-only test user exists, even when seed data was already loaded. - if (userRepository.findByUsername("reader").isEmpty()) { + if (userRepository.findByEmail("reader@familyarchive.local").isEmpty()) { log.info("E2E seed: Erstelle 'reader'-Testbenutzer..."); UserGroup leserGroup = groupRepository.findByName("Leser").orElseGet(() -> groupRepository.save(UserGroup.builder() @@ -101,7 +95,7 @@ public class DataInitializer { .permissions(Set.of("READ_ALL")) .build())); userRepository.save(AppUser.builder() - .username("reader") + .email("reader@familyarchive.local") .password(passwordEncoder.encode("reader123")) .groups(Set.of(leserGroup)) .build()); @@ -131,7 +125,6 @@ public class DataInitializer { Tag tagUrlaub = tagRepo.save(Tag.builder().name("Urlaub").build()); // ── Documents ──────────────────────────────────────────────────── - // 1. Fully transcribed letter — used by search + detail E2E tests docRepo.save(Document.builder() .title("Geburtsurkunde Hans Müller") .originalFilename("geburtsurkunde_hans.pdf") @@ -144,7 +137,6 @@ public class DataInitializer { .transcription("Hiermit wird beurkundet, dass Hans Müller am 12. April 1923 in Berlin geboren wurde.") .build()); - // 2. Letter with multiple receivers and tags — tests multi-receiver display docRepo.save(Document.builder() .title("Brief aus dem Krieg") .originalFilename("brief_krieg_1944.pdf") @@ -157,7 +149,6 @@ public class DataInitializer { .transcription("Liebe Anna, ich schreibe dir aus der Front. Es geht mir den Umständen entsprechend gut.") .build()); - // 3. Postcard — no transcription, tests PLACEHOLDER status docRepo.save(Document.builder() .title("Urlaubspostkarte Ostsee") .originalFilename("postkarte_1965.jpg") @@ -169,7 +160,6 @@ public class DataInitializer { .tags(Set.of(tagUrlaub)) .build()); - // 4. Document with no sender — tests null-sender display ("Unbekannt") docRepo.save(Document.builder() .title("Unbekanntes Dokument") .originalFilename("unbekannt.pdf") @@ -179,7 +169,6 @@ public class DataInitializer { .receivers(Set.of(maria)) .build()); - // 5. Document with minimal metadata — tests sparse display docRepo.save(Document.builder() .title("Scan ohne Titel") .originalFilename("scan_ohne_titel.pdf") diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/AnnotationController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/AnnotationController.java index 01471222..8311cbe2 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/AnnotationController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/AnnotationController.java @@ -72,7 +72,7 @@ public class AnnotationController { private UUID resolveUserId(Authentication authentication) { if (authentication == null || !authentication.isAuthenticated()) return null; try { - AppUser user = userService.findByUsername(authentication.getName()); + AppUser user = userService.findByEmail(authentication.getName()); return user != null ? user.getId() : null; } catch (Exception e) { log.warn("Could not resolve user for annotation: {}", e.getMessage()); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/CommentController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/CommentController.java index cb6b6d70..d34ee9a9 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/CommentController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/CommentController.java @@ -144,7 +144,7 @@ public class CommentController { private AppUser resolveUser(Authentication authentication) { if (authentication == null || !authentication.isAuthenticated()) return null; try { - return userService.findByUsername(authentication.getName()); + return userService.findByEmail(authentication.getName()); } catch (Exception e) { log.warn("Could not resolve user for comment: {}", e.getMessage()); return null; diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java index ac85ae46..76781c0c 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/NotificationController.java @@ -100,6 +100,6 @@ public class NotificationController { // ─── private helpers ────────────────────────────────────────────────────── private AppUser resolveUser(Authentication authentication) { - return userService.findByUsername(authentication.getName()); + return userService.findByEmail(authentication.getName()); } } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java index 65db519c..785cb062 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/OcrController.java @@ -161,7 +161,7 @@ public class OcrController { private UUID resolveUserId(Authentication authentication) { if (authentication == null || !authentication.isAuthenticated()) return null; try { - AppUser user = userService.findByUsername(authentication.getName()); + AppUser user = userService.findByEmail(authentication.getName()); return user != null ? user.getId() : null; } catch (Exception e) { log.warn("Failed to resolve user ID for authentication: {}", authentication.getName(), e); diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/TranscriptionBlockController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/TranscriptionBlockController.java index fd52d8f4..55f059e0 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/TranscriptionBlockController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/TranscriptionBlockController.java @@ -101,7 +101,7 @@ public class TranscriptionBlockController { if (authentication == null || !authentication.isAuthenticated()) { throw DomainException.unauthorized("Authentication required"); } - AppUser user = userService.findByUsername(authentication.getName()); + AppUser user = userService.findByEmail(authentication.getName()); if (user == null) { throw DomainException.unauthorized("User not found"); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java b/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java index a7bdacd8..2c9bd968 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/controller/UserController.java @@ -38,7 +38,7 @@ public class UserController { if (authentication == null || !authentication.isAuthenticated()) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } - AppUser user = userService.findByUsername(authentication.getName()); + AppUser user = userService.findByEmail(authentication.getName()); user.setPassword(null); return ResponseEntity.ok(user); } @@ -46,7 +46,7 @@ public class UserController { @PutMapping("users/me") public ResponseEntity updateProfile(Authentication authentication, @RequestBody UpdateProfileDTO dto) { - AppUser current = userService.findByUsername(authentication.getName()); + AppUser current = userService.findByEmail(authentication.getName()); AppUser updated = userService.updateProfile(current.getId(), dto); updated.setPassword(null); return ResponseEntity.ok(updated); @@ -56,7 +56,7 @@ public class UserController { @ResponseStatus(HttpStatus.NO_CONTENT) public void changePassword(Authentication authentication, @RequestBody ChangePasswordDTO dto) { - AppUser current = userService.findByUsername(authentication.getName()); + AppUser current = userService.findByEmail(authentication.getName()); userService.changePassword(current.getId(), dto); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/dto/CreateUserRequest.java b/backend/src/main/java/org/raddatz/familienarchiv/dto/CreateUserRequest.java index 4e0b8705..2b474c5d 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/dto/CreateUserRequest.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/dto/CreateUserRequest.java @@ -1,6 +1,7 @@ package org.raddatz.familienarchiv.dto; - +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.Data; import java.time.LocalDate; @@ -9,7 +10,8 @@ import java.util.UUID; @Data public class CreateUserRequest { - private String username; + @NotBlank + @Pattern(regexp = "^[^:]+$", message = "Email must not contain a colon") private String email; private String initialPassword; private List groupIds; diff --git a/backend/src/main/java/org/raddatz/familienarchiv/model/AppUser.java b/backend/src/main/java/org/raddatz/familienarchiv/model/AppUser.java index 34b189db..89c340a3 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/model/AppUser.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/model/AppUser.java @@ -1,6 +1,8 @@ package org.raddatz.familienarchiv.model; import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; import lombok.*; import org.hibernate.annotations.CreationTimestamp; @@ -17,7 +19,7 @@ import java.util.Set; import java.util.UUID; @Entity -@Table(name = "users") // Tabellenname in Postgres +@Table(name = "users") @Data @NoArgsConstructor @AllArgsConstructor @@ -30,26 +32,25 @@ public class AppUser { private UUID id; @Column(unique = true, nullable = false) + @NotBlank + @Pattern(regexp = "^[^:]+$", message = "Email must not contain a colon") @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - private String username; + private String email; @Column(nullable = false) @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private String password; // Wird verschlüsselt gespeichert (BCrypt) + private String password; private String firstName; private String lastName; private LocalDate birthDate; - @Column(unique = true) - private String email; - @Column(columnDefinition = "TEXT") private String contact; @Builder.Default @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - private boolean enabled = true; // Um User zu sperren ohne sie zu löschen + private boolean enabled = true; @Column(nullable = false) @Builder.Default @@ -61,7 +62,6 @@ public class AppUser { @Schema(requiredMode = Schema.RequiredMode.REQUIRED) private boolean notifyOnMention = false; - // Ein User kann in mehreren Gruppen sein @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "users_groups", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "group_id")) @Builder.Default @@ -77,26 +77,21 @@ public class AppUser { return false; } return this.groups.stream().anyMatch(group -> group.getPermissions().contains(permission)); - } -public AppUser updateFromRequest(CreateUserRequest request, PasswordEncoder passwordEncoder, Set groups) { - if (request.getUsername() != null && !request.getUsername().isBlank()) { - this.username = request.getUsername(); - } + public AppUser updateFromRequest(CreateUserRequest request, PasswordEncoder passwordEncoder, Set groups) { + if (request.getEmail() != null && !request.getEmail().isBlank()) { + this.email = request.getEmail(); + } - if (request.getEmail() != null && !request.getEmail().isBlank()) { - this.email = request.getEmail(); - } + if (request.getInitialPassword() != null && !request.getInitialPassword().isBlank()) { + this.password = passwordEncoder.encode(request.getInitialPassword()); + } - if (request.getInitialPassword() != null && !request.getInitialPassword().isBlank()) { - this.password = passwordEncoder.encode(request.getInitialPassword()); - } + if (groups != null && !groups.isEmpty()) { + this.groups = groups; + } - if (groups != null && !groups.isEmpty()) { - this.groups = groups; + return this; } - - return this; -} } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/repository/AppUserRepository.java b/backend/src/main/java/org/raddatz/familienarchiv/repository/AppUserRepository.java index 63179e07..03d4674d 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/repository/AppUserRepository.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/repository/AppUserRepository.java @@ -13,11 +13,10 @@ import java.util.UUID; @Repository public interface AppUserRepository extends JpaRepository { - Optional findByUsername(String username); Optional findByEmail(String email); @Query("SELECT u FROM AppUser u WHERE " + - "LOWER(COALESCE(u.firstName, '') || ' ' || COALESCE(u.lastName, '')) LIKE LOWER(CONCAT('%', :q, '%')) " + - "OR LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%'))") - List searchByNameOrUsername(@Param("q") String q, Pageable pageable); -} \ No newline at end of file + "LOWER(u.email) LIKE LOWER(CONCAT('%', :q, '%')) " + + "OR LOWER(COALESCE(u.firstName, '') || ' ' || COALESCE(u.lastName, '')) LIKE LOWER(CONCAT('%', :q, '%'))") + List searchByEmailOrName(@Param("q") String q, Pageable pageable); +} diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java index bfd4b6df..f80955e2 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/CommentService.java @@ -175,7 +175,7 @@ public class CommentService { String first = author.getFirstName(); String last = author.getLastName(); if ((first == null || first.isBlank()) && (last == null || last.isBlank())) { - return author.getUsername(); + return author.getEmail(); } return ((first != null ? first : "") + " " + (last != null ? last : "")).strip(); } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentVersionService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentVersionService.java index a0092eec..c60e62df 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentVersionService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/DocumentVersionService.java @@ -100,7 +100,7 @@ public class DocumentVersionService { return null; } try { - return userService.findByUsername(auth.getName()); + return userService.findByEmail(auth.getName()); } catch (Exception e) { log.warn("Could not resolve editor for version snapshot: {}", e.getMessage()); return null; @@ -114,7 +114,7 @@ public class DocumentVersionService { if (first != null && !first.isBlank() && last != null && !last.isBlank()) { return first + " " + last; } - return user.getUsername(); + return user.getEmail(); } private String serializeSnapshot(Document doc) { diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/UserSearchService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/UserSearchService.java index 820622cd..a2232be8 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/UserSearchService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/UserSearchService.java @@ -18,6 +18,6 @@ public class UserSearchService { public List search(String query) { if (query == null || query.isBlank()) return List.of(); - return userRepository.searchByNameOrUsername(query.trim(), PageRequest.of(0, MAX_RESULTS)); + return userRepository.searchByEmailOrName(query.trim(), PageRequest.of(0, MAX_RESULTS)); } } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java index d7046243..406a53b7 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/UserService.java @@ -36,23 +36,22 @@ public class UserService { @Transactional public AppUser createUserOrUpdate(CreateUserRequest request) { - log.info("Creating or updating user: {}", request.getUsername()); + log.info("Creating or updating user: {}", request.getEmail()); Set groups = new HashSet<>(); if (request.getGroupIds() != null && !request.getGroupIds().isEmpty()) { groups.addAll(groupRepository.findAllById(request.getGroupIds())); } - Optional existingUser = userRepository.findByUsername(request.getUsername()); + Optional existingUser = userRepository.findByEmail(request.getEmail()); AppUser user; if (existingUser.isPresent()) { - log.info("User exists, updating: {}", request.getUsername()); + log.info("User exists, updating: {}", request.getEmail()); user = existingUser.get().updateFromRequest(request, passwordEncoder, groups); } else { - log.info("Creating new user: {}", request.getUsername()); + log.info("Creating new user: {}", request.getEmail()); user = AppUser.builder() - .username(request.getUsername()) .email(request.getEmail()) .password(passwordEncoder.encode(request.getInitialPassword())) .groups(groups) @@ -158,9 +157,9 @@ public class UserService { userRepository.save(user); } - public AppUser findByUsername(String username) { - return userRepository.findByUsername(username) - .orElseThrow(() -> DomainException.notFound(ErrorCode.USER_NOT_FOUND, "No user found for username: " + username)); + public AppUser findByEmail(String email) { + return userRepository.findByEmail(email) + .orElseThrow(() -> DomainException.notFound(ErrorCode.USER_NOT_FOUND, "No user found for email: " + email)); } public List getAllUsers() { diff --git a/backend/src/main/resources/db/migration/V44__email_only_auth.sql b/backend/src/main/resources/db/migration/V44__email_only_auth.sql new file mode 100644 index 00000000..e46cebb5 --- /dev/null +++ b/backend/src/main/resources/db/migration/V44__email_only_auth.sql @@ -0,0 +1,11 @@ +-- Abort if any user has no email address set. +-- All users must have an email before this migration can run. +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM users WHERE email IS NULL) THEN + RAISE EXCEPTION 'Migration aborted: some users have no email address. Set emails for all users before running this migration.'; + END IF; +END $$; + +ALTER TABLE users ALTER COLUMN email SET NOT NULL; +ALTER TABLE users DROP COLUMN username; diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java index 317cb4fa..e7246f77 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/AnnotationControllerTest.java @@ -280,7 +280,7 @@ class AnnotationControllerTest { void createAnnotation_resolvesNullUserId_whenUserServiceThrows() throws Exception { // findByUsername throws → catch block → resolveUserId returns null UUID docId = UUID.randomUUID(); - when(userService.findByUsername(any())).thenThrow(new RuntimeException("DB error")); + when(userService.findByEmail(any())).thenThrow(new RuntimeException("DB error")); DocumentAnnotation saved = DocumentAnnotation.builder() .id(UUID.randomUUID()).documentId(docId).pageNumber(1) .x(0.1).y(0.1).width(0.2).height(0.2).color("#ff0000").build(); @@ -298,7 +298,7 @@ class AnnotationControllerTest { void createAnnotation_resolvesNullUserId_whenUserServiceReturnsNull() throws Exception { // findByUsername returns null → user != null = false → resolveUserId returns null UUID docId = UUID.randomUUID(); - when(userService.findByUsername(any())).thenReturn(null); + when(userService.findByEmail(any())).thenReturn(null); DocumentAnnotation saved = DocumentAnnotation.builder() .id(UUID.randomUUID()).documentId(docId).pageNumber(1) .x(0.1).y(0.1).width(0.2).height(0.2).color("#ff0000").build(); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java index a556e676..94685447 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/CommentControllerTest.java @@ -270,7 +270,7 @@ class CommentControllerTest { @WithMockUser(authorities = "WRITE_ALL") void postDocumentComment_stillSucceeds_whenUserServiceThrows() throws Exception { // findByUsername throws → catch block in resolveUser → author null, saves anyway - when(userService.findByUsername(any())).thenThrow(new RuntimeException("DB error")); + when(userService.findByEmail(any())).thenThrow(new RuntimeException("DB error")); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(DOC_ID).content("Test comment").build(); when(commentService.postComment(any(), any(), any(), any(), any())).thenReturn(saved); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java index c85d8e12..8af8ed0e 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/NotificationControllerTest.java @@ -60,8 +60,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser") void getNotifications_returns200_whenAuthenticatedWithNoPermissions() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); when(notificationService.getNotifications(eq(USER_ID), any(), any(), any())) .thenReturn(new PageImpl<>(List.of())); @@ -72,12 +72,12 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void getNotifications_returns200WithList_whenAuthenticated() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); NotificationDTO dto = new NotificationDTO( UUID.randomUUID(), NotificationType.REPLY, UUID.randomUUID(), UUID.randomUUID(), null, false, LocalDateTime.now(), "Anna Smith", "Testdokument"); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); when(notificationService.getNotifications(eq(USER_ID), any(), any(), any())) .thenReturn(new PageImpl<>(List.of(dto), PageRequest.of(0, 10), 1)); @@ -89,8 +89,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void getNotifications_returnsOnlyCurrentUsersNotifications() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); when(notificationService.getNotifications(eq(USER_ID), any(), any(), any())) .thenReturn(new PageImpl<>(List.of())); @@ -103,8 +103,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void getNotifications_withTypeAndReadFalse_passesFiltersToService() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); when(notificationService.getNotifications(eq(USER_ID), eq(NotificationType.MENTION), eq(false), any())) .thenReturn(new PageImpl<>(List.of())); @@ -148,8 +148,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void markAllRead_returns204_whenAuthenticated() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); mockMvc.perform(post("/api/notifications/read-all")) .andExpect(status().isNoContent()); @@ -168,10 +168,10 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void markOneRead_returns403_whenNotificationBelongsToDifferentUser() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); UUID notifId = UUID.randomUUID(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); org.mockito.Mockito.doThrow( org.raddatz.familienarchiv.exception.DomainException.forbidden("not yours")) .when(notificationService).markRead(notifId, USER_ID); @@ -198,9 +198,9 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void getPreferences_returns200_whenUserHasReadAll() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser") + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(true).notifyOnMention(false).build(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); mockMvc.perform(get("/api/users/me/notification-preferences")) .andExpect(status().isOk()) @@ -211,9 +211,9 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"WRITE_ALL"}) void getPreferences_returns200_whenUserHasWriteAll() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser") + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(false).notifyOnMention(true).build(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); mockMvc.perform(get("/api/users/me/notification-preferences")) .andExpect(status().isOk()) @@ -223,9 +223,9 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"ANNOTATE_ALL"}) void getPreferences_returns200_whenUserHasAnnotateAll() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser") + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(false).notifyOnMention(false).build(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); mockMvc.perform(get("/api/users/me/notification-preferences")) .andExpect(status().isOk()); @@ -234,8 +234,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"WRITE_ALL"}) void getNotifications_returns200_whenUserHasOnlyWriteAll() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); when(notificationService.getNotifications(eq(USER_ID), any(), any(), any())) .thenReturn(new PageImpl<>(List.of())); @@ -248,11 +248,11 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void updatePreferences_persistsBothBooleans() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser") + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(false).notifyOnMention(false).build(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); - AppUser updated = AppUser.builder().id(USER_ID).username("testuser") + AppUser updated = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(true).notifyOnMention(true).build(); when(notificationService.updatePreferences(USER_ID, true, true)).thenReturn(updated); @@ -267,11 +267,11 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"WRITE_ALL"}) void updatePreferences_returns200_whenUserHasWriteAll() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser") + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(false).notifyOnMention(false).build(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); - AppUser updated = AppUser.builder().id(USER_ID).username("testuser") + AppUser updated = AppUser.builder().id(USER_ID).email("testuser@example.com") .notifyOnReply(true).notifyOnMention(false).build(); when(notificationService.updatePreferences(USER_ID, true, false)).thenReturn(updated); @@ -293,8 +293,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void countUnread_returns200WithCount_whenAuthenticated() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); when(notificationService.countUnread(USER_ID)).thenReturn(3L); mockMvc.perform(get("/api/notifications/unread-count")) @@ -316,8 +316,8 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void stream_returns200_whenAuthenticated() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); - when(userService.findByUsername("testuser")).thenReturn(user); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); + when(userService.findByEmail("testuser")).thenReturn(user); when(sseEmitterRegistry.register(USER_ID)).thenReturn(new org.springframework.web.servlet.mvc.method.annotation.SseEmitter()); mockMvc.perform(get("/api/notifications/stream") @@ -330,10 +330,10 @@ class NotificationControllerTest { @Test @WithMockUser(username = "testuser", authorities = {"READ_ALL"}) void markOneRead_returns404_whenNotificationDoesNotExist() throws Exception { - AppUser user = AppUser.builder().id(USER_ID).username("testuser").build(); + AppUser user = AppUser.builder().id(USER_ID).email("testuser@example.com").build(); UUID notifId = UUID.randomUUID(); - when(userService.findByUsername("testuser")).thenReturn(user); + when(userService.findByEmail("testuser")).thenReturn(user); doThrow(DomainException.notFound(ErrorCode.NOTIFICATION_NOT_FOUND, "Notification not found: " + notifId)) .when(notificationService).markRead(notifId, USER_ID); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/TranscriptionBlockControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/TranscriptionBlockControllerTest.java index 54a9be2a..84d58d79 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/TranscriptionBlockControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/TranscriptionBlockControllerTest.java @@ -54,7 +54,7 @@ class TranscriptionBlockControllerTest { "{\"blockIds\":[\"" + UUID.randomUUID() + "\",\"" + UUID.randomUUID() + "\"]}"; private AppUser mockUser() { - return AppUser.builder().id(UUID.randomUUID()).username("user").build(); + return AppUser.builder().id(UUID.randomUUID()).email("user@example.com").build(); } private TranscriptionBlock sampleBlock() { @@ -161,7 +161,7 @@ class TranscriptionBlockControllerTest { @Test @WithMockUser(authorities = "WRITE_ALL") void createBlock_returns201_withSavedBlock_whenAuthorised() throws Exception { - when(userService.findByUsername(any())).thenReturn(mockUser()); + when(userService.findByEmail(any())).thenReturn(mockUser()); when(transcriptionService.createBlock(eq(DOC_ID), any(), any())).thenReturn(sampleBlock()); mockMvc.perform(post(URL_BASE) @@ -175,7 +175,7 @@ class TranscriptionBlockControllerTest { @Test @WithMockUser(authorities = "WRITE_ALL") void createBlock_returns401_whenUserNotFoundInDatabase() throws Exception { - when(userService.findByUsername(any())).thenReturn(null); + when(userService.findByEmail(any())).thenReturn(null); mockMvc.perform(post(URL_BASE) .contentType(MediaType.APPLICATION_JSON) @@ -209,7 +209,7 @@ class TranscriptionBlockControllerTest { updated.setText("Neue Fassung"); updated.setLabel("Anrede"); - when(userService.findByUsername(any())).thenReturn(mockUser()); + when(userService.findByEmail(any())).thenReturn(mockUser()); when(transcriptionService.updateBlock(eq(DOC_ID), eq(BLOCK_ID), any(), any())) .thenReturn(updated); @@ -224,7 +224,7 @@ class TranscriptionBlockControllerTest { @Test @WithMockUser(authorities = "WRITE_ALL") void updateBlock_returns404_whenBlockDoesNotExist() throws Exception { - when(userService.findByUsername(any())).thenReturn(mockUser()); + when(userService.findByEmail(any())).thenReturn(mockUser()); when(transcriptionService.updateBlock(any(), any(), any(), any())) .thenThrow(DomainException.notFound(ErrorCode.TRANSCRIPTION_BLOCK_NOT_FOUND, "not found")); @@ -237,7 +237,7 @@ class TranscriptionBlockControllerTest { @Test @WithMockUser(authorities = "WRITE_ALL") void updateBlock_returns401_whenUserNotFoundInDatabase() throws Exception { - when(userService.findByUsername(any())).thenReturn(null); + when(userService.findByEmail(any())).thenReturn(null); mockMvc.perform(put(URL_BLOCK) .contentType(MediaType.APPLICATION_JSON) diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java index fa26f411..99b1d1f2 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/UserControllerTest.java @@ -31,33 +31,32 @@ class UserControllerTest { @MockitoBean UserService userService; @MockitoBean CustomUserDetailsService customUserDetailsService; - // ─── GET /api/users/me ──────────────────────────────────────────────────── + // ─── GET /api/users/me ──────────────────────────────────────────────────────── @Test void getCurrentUser_returns401_whenUnauthenticated() throws Exception { - // authentication == null → returns 401 (covers null/!isAuthenticated branch) mockMvc.perform(get("/api/users/me")) .andExpect(status().isUnauthorized()); } @Test - @WithMockUser(username = "anna") + @WithMockUser(username = "anna@example.com") void getCurrentUser_returns200_whenAuthenticated() throws Exception { - AppUser user = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); - when(userService.findByUsername("anna")).thenReturn(user); + AppUser user = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); + when(userService.findByEmail("anna@example.com")).thenReturn(user); mockMvc.perform(get("/api/users/me")) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username").value("anna")); + .andExpect(jsonPath("$.email").value("anna@example.com")); } // ─── GET /api/users/{id} ────────────────────────────────────────────────── @Test - @WithMockUser(username = "reader") + @WithMockUser(username = "reader@example.com") void getUser_returns403_whenCallerLacksAdminUserPermission() throws Exception { UUID id = UUID.randomUUID(); - AppUser target = AppUser.builder().id(id).username("target").build(); + AppUser target = AppUser.builder().id(id).email("target@example.com").build(); when(userService.getById(id)).thenReturn(target); mockMvc.perform(get("/api/users/" + id)) @@ -65,14 +64,14 @@ class UserControllerTest { } @Test - @WithMockUser(username = "admin", authorities = {"ADMIN_USER"}) + @WithMockUser(username = "admin@example.com", authorities = {"ADMIN_USER"}) void getUser_returns200_whenCallerHasAdminUserPermission() throws Exception { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("target").build(); + AppUser user = AppUser.builder().id(id).email("target@example.com").build(); when(userService.getById(id)).thenReturn(user); mockMvc.perform(get("/api/users/" + id)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username").value("target")); + .andExpect(jsonPath("$.email").value("target@example.com")); } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/controller/UserSearchControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/controller/UserSearchControllerTest.java index 0020a76c..85dbadba 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/controller/UserSearchControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/controller/UserSearchControllerTest.java @@ -60,7 +60,7 @@ class UserSearchControllerTest { @WithMockUser(authorities = {"READ_ALL"}) void search_returns200_whenAuthenticated() throws Exception { AppUser user = AppUser.builder().id(UUID.randomUUID()) - .firstName("Hans").lastName("Mueller").username("hans").build(); + .firstName("Hans").lastName("Mueller").email("hans@example.com").build(); when(userSearchService.search("Hans")).thenReturn(List.of(user)); mockMvc.perform(get("/api/users/search").param("q", "Hans")) @@ -83,7 +83,7 @@ class UserSearchControllerTest { void search_returnsAtMostTenResults() throws Exception { List elevenUsers = IntStream.range(0, 11) .mapToObj(i -> AppUser.builder().id(UUID.randomUUID()) - .firstName("User").lastName(String.valueOf(i)).username("u" + i).build()) + .firstName("User").lastName(String.valueOf(i)).email("u" + i + "@example.com").build()) .toList(); when(userSearchService.search(anyString())).thenReturn(elevenUsers.subList(0, 10)); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/repository/NotificationRepositoryTest.java b/backend/src/test/java/org/raddatz/familienarchiv/repository/NotificationRepositoryTest.java index 0f68d992..13d38fca 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/repository/NotificationRepositoryTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/repository/NotificationRepositoryTest.java @@ -31,8 +31,8 @@ class NotificationRepositoryTest { void setUp() { notificationRepository.deleteAll(); appUserRepository.deleteAll(); - userA = appUserRepository.save(AppUser.builder().username("userA").password("pw").build()); - userB = appUserRepository.save(AppUser.builder().username("userB").password("pw").build()); + userA = appUserRepository.save(AppUser.builder().email("userA@example.com").password("pw").build()); + userB = appUserRepository.save(AppUser.builder().email("userB@example.com").password("pw").build()); } // ─── findByRecipientIdAndTypeAndReadFalse ───────────────────────────────── diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java index 94851440..6a846f03 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/CommentServiceTest.java @@ -43,7 +43,7 @@ class CommentServiceTest { void postComment_capturesAuthorNameAtWriteTime() { UUID docId = UUID.randomUUID(); AppUser author = AppUser.builder() - .id(UUID.randomUUID()).username("hans").firstName("Hans").lastName("Müller").build(); + .id(UUID.randomUUID()).email("hans@example.com").firstName("Hans").lastName("Müller").build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("Hans Müller").content("Test").build(); when(commentRepository.save(any())).thenReturn(saved); @@ -56,7 +56,7 @@ class CommentServiceTest { @Test void postComment_fallsBackToUsername_whenNamesAreBlank() { UUID docId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("hans42").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("hans42@example.com").build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("hans42").content("Test").build(); when(commentRepository.save(any())).thenReturn(saved); @@ -70,8 +70,8 @@ class CommentServiceTest { void postComment_triggersNotifyMentions_whenMentionedUserIdsProvided() { UUID docId = UUID.randomUUID(); UUID mentionedId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("hans").firstName("Hans").lastName("M").build(); - AppUser mentioned = AppUser.builder().id(mentionedId).username("anna").firstName("Anna").lastName("S").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("hans@example.com").firstName("Hans").lastName("M").build(); + AppUser mentioned = AppUser.builder().id(mentionedId).email("anna@example.com").firstName("Anna").lastName("S").build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("Hans M").content("Hey @Anna S").build(); @@ -89,7 +89,7 @@ class CommentServiceTest { void replyToComment_throwsNotFound_whenTargetCommentMissing() { UUID docId = UUID.randomUUID(); UUID commentId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); when(commentRepository.findById(commentId)).thenReturn(Optional.empty()); assertThatThrownBy(() -> commentService.replyToComment(docId, commentId, "Reply", List.of(), author)) @@ -104,7 +104,7 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); UUID replyId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); DocumentComment root = DocumentComment.builder() .id(rootId).documentId(docId).parentId(null).content("Root").authorName("Hans").build(); @@ -127,7 +127,7 @@ class CommentServiceTest { void replyToComment_usesDirectComment_whenReplyingToTopLevel() { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); DocumentComment root = DocumentComment.builder() .id(rootId).documentId(docId).parentId(null).content("Root").authorName("Hans").build(); @@ -147,7 +147,7 @@ class CommentServiceTest { void replyToComment_triggersNotifyReply_afterSave() { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); DocumentComment root = DocumentComment.builder() .id(rootId).documentId(docId).parentId(null).content("Root").authorName("Hans").build(); @@ -168,8 +168,8 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); UUID mentionedId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); - AppUser mentioned = AppUser.builder().id(mentionedId).username("bob").firstName("Bob").lastName("J").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); + AppUser mentioned = AppUser.builder().id(mentionedId).email("bob@example.com").firstName("Bob").lastName("J").build(); DocumentComment root = DocumentComment.builder() .id(rootId).documentId(docId).parentId(null).content("Root").authorName("Hans").build(); @@ -193,7 +193,7 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID commentId = UUID.randomUUID(); UUID ownerId = UUID.randomUUID(); - AppUser other = AppUser.builder().id(UUID.randomUUID()).username("other").build(); + AppUser other = AppUser.builder().id(UUID.randomUUID()).email("other@example.com").build(); DocumentComment comment = DocumentComment.builder() .id(commentId).documentId(docId).authorId(ownerId).content("Original").authorName("Hans").build(); @@ -211,7 +211,7 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID commentId = UUID.randomUUID(); UUID authorId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(authorId).username("hans").build(); + AppUser author = AppUser.builder().id(authorId).email("hans@example.com").build(); LocalDateTime created = LocalDateTime.now().minusMinutes(5); DocumentComment comment = DocumentComment.builder() @@ -233,7 +233,7 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID commentId = UUID.randomUUID(); UUID ownerId = UUID.randomUUID(); - AppUser other = AppUser.builder().id(UUID.randomUUID()).username("other").build(); + AppUser other = AppUser.builder().id(UUID.randomUUID()).email("other@example.com").build(); DocumentComment comment = DocumentComment.builder() .id(commentId).documentId(docId).authorId(ownerId).authorName("Hans").content("X").build(); @@ -251,7 +251,7 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID commentId = UUID.randomUUID(); UUID authorId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(authorId).username("hans").build(); + AppUser author = AppUser.builder().id(authorId).email("hans@example.com").build(); DocumentComment comment = DocumentComment.builder() .id(commentId).documentId(docId).authorId(authorId).authorName("Hans").content("X").build(); @@ -306,7 +306,7 @@ class CommentServiceTest { void replyToComment_handlesNullAuthorId_inExistingReply() { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").firstName("Anna").lastName("S").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").firstName("Anna").lastName("S").build(); DocumentComment root = DocumentComment.builder() .id(rootId).documentId(docId).parentId(null).authorId(UUID.randomUUID()).content("Root").authorName("Root").build(); @@ -331,7 +331,7 @@ class CommentServiceTest { @Test void postComment_fallsBackToUsername_whenFirstNameBlankAndLastNameNull() { UUID docId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("user42") + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName(" ").lastName(null).build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("user42").content("Hi").build(); @@ -345,7 +345,7 @@ class CommentServiceTest { @Test void postComment_fallsBackToUsername_whenFirstNameNullAndLastNameBlank() { UUID docId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("user42") + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName(null).lastName(" ").build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("user42").content("Hi").build(); @@ -359,7 +359,7 @@ class CommentServiceTest { @Test void postComment_includesOnlyFirstName_whenLastNameIsNull() { UUID docId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("user42") + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName("Hans").lastName(null).build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("Hans").content("Hi").build(); @@ -374,7 +374,7 @@ class CommentServiceTest { @Test void postComment_includesOnlyLastName_whenFirstNameIsNull() { UUID docId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("user42") + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName(null).lastName("Müller").build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("Müller").content("Hi").build(); @@ -391,7 +391,7 @@ class CommentServiceTest { @Test void postComment_doesNotCallUserService_whenMentionedUserIdsIsNull() { UUID docId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("hans") + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("hans@example.com") .firstName("Hans").lastName("M").build(); DocumentComment saved = DocumentComment.builder() .id(UUID.randomUUID()).documentId(docId).authorName("Hans M").content("Hi").build(); @@ -409,7 +409,7 @@ class CommentServiceTest { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); UUID existingReplyAuthorId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); DocumentComment root = DocumentComment.builder() .id(rootId).documentId(docId).parentId(null).authorId(UUID.randomUUID()) @@ -437,7 +437,7 @@ class CommentServiceTest { void replyToComment_excludesNullAuthorIds_fromParticipantSet() { UUID docId = UUID.randomUUID(); UUID rootId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("anna").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("anna@example.com").build(); // Root with null authorId DocumentComment root = DocumentComment.builder() @@ -480,7 +480,7 @@ class CommentServiceTest { private AppUser buildAdmin() { return AppUser.builder() .id(UUID.randomUUID()) - .username("admin") + .email("admin@example.com") .groups(Set.of(UserGroup.builder() .id(UUID.randomUUID()) .name("admins") @@ -510,7 +510,7 @@ class CommentServiceTest { void postBlockComment_setsBlockIdOnComment() { UUID documentId = UUID.randomUUID(); UUID blockId = UUID.randomUUID(); - AppUser author = AppUser.builder().id(UUID.randomUUID()).username("felix").firstName("Felix").lastName("Brandt").build(); + AppUser author = AppUser.builder().id(UUID.randomUUID()).email("felix@example.com").firstName("Felix").lastName("Brandt").build(); when(commentRepository.save(any())).thenAnswer(inv -> { DocumentComment c = inv.getArgument(0); c.setId(UUID.randomUUID()); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentVersionServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentVersionServiceTest.java index cff939ee..7a41b4ef 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentVersionServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/DocumentVersionServiceTest.java @@ -53,8 +53,8 @@ class DocumentVersionServiceTest { @Test void recordVersion_usesFirstAndLastName_whenBothPresent() { authenticateAs("emma"); - when(userService.findByUsername("emma")).thenReturn( - AppUser.builder().id(UUID.randomUUID()).username("emma") + when(userService.findByEmail("emma")).thenReturn( + AppUser.builder().id(UUID.randomUUID()).email("emma@example.com") .firstName("Emma").lastName("Müller").build()); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -67,10 +67,10 @@ class DocumentVersionServiceTest { } @Test - void recordVersion_usesUsername_whenNamesAreBlank() { + void recordVersion_usesEmail_whenNamesAreBlank() { authenticateAs("otto99"); - when(userService.findByUsername("otto99")).thenReturn( - AppUser.builder().id(UUID.randomUUID()).username("otto99") + when(userService.findByEmail("otto99")).thenReturn( + AppUser.builder().id(UUID.randomUUID()).email("otto99@example.com") .firstName(null).lastName(null).build()); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -79,7 +79,7 @@ class DocumentVersionServiceTest { ArgumentCaptor captor = ArgumentCaptor.forClass(DocumentVersion.class); verify(versionRepository).save(captor.capture()); - assertThat(captor.getValue().getEditorName()).isEqualTo("otto99"); + assertThat(captor.getValue().getEditorName()).isEqualTo("otto99@example.com"); } // ─── recordVersion — snapshot ───────────────────────────────────────────── @@ -87,7 +87,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_savesSnapshotContainingTitle() { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -110,7 +110,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_changedFieldsIsEmpty_forFirstVersion() { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -124,7 +124,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_includesTitleInChangedFields_whenTitleChanged() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); Document oldDoc = Document.builder().id(UUID.randomUUID()).title("Alt").build(); @@ -154,7 +154,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_doesNotIncludeUnchangedFields_inChangedFields() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -181,7 +181,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_tracksSenderChange() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -209,7 +209,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_tracksReceiverChange() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -236,7 +236,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_tracksTagChange() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -410,7 +410,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_usesUnknown_whenUserServiceThrows() { authenticateAs("missinguser"); - when(userService.findByUsername("missinguser")).thenThrow(new RuntimeException("not found")); + when(userService.findByEmail("missinguser")).thenThrow(new RuntimeException("not found")); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -424,10 +424,10 @@ class DocumentVersionServiceTest { // ─── recordVersion — buildEditorName edge cases ─────────────────────────── @Test - void recordVersion_usesUsername_whenFirstNameIsNotBlankButLastNameIsNull() { + void recordVersion_usesEmail_whenFirstNameIsNotBlankButLastNameIsNull() { authenticateAs("user42"); - when(userService.findByUsername("user42")).thenReturn( - AppUser.builder().id(UUID.randomUUID()).username("user42") + when(userService.findByEmail("user42")).thenReturn( + AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName("Hans").lastName(null).build()); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -436,14 +436,14 @@ class DocumentVersionServiceTest { ArgumentCaptor captor = ArgumentCaptor.forClass(DocumentVersion.class); verify(versionRepository).save(captor.capture()); - assertThat(captor.getValue().getEditorName()).isEqualTo("user42"); + assertThat(captor.getValue().getEditorName()).isEqualTo("user42@example.com"); } @Test - void recordVersion_usesUsername_whenFirstNameIsBlankButLastNameIsPresent() { + void recordVersion_usesEmail_whenFirstNameIsBlankButLastNameIsPresent() { authenticateAs("user42"); - when(userService.findByUsername("user42")).thenReturn( - AppUser.builder().id(UUID.randomUUID()).username("user42") + when(userService.findByEmail("user42")).thenReturn( + AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName(" ").lastName("Müller").build()); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -452,14 +452,14 @@ class DocumentVersionServiceTest { ArgumentCaptor captor = ArgumentCaptor.forClass(DocumentVersion.class); verify(versionRepository).save(captor.capture()); - assertThat(captor.getValue().getEditorName()).isEqualTo("user42"); + assertThat(captor.getValue().getEditorName()).isEqualTo("user42@example.com"); } @Test - void recordVersion_usesUsername_whenLastNameIsBlankButFirstNameIsPresent() { + void recordVersion_usesEmail_whenLastNameIsBlankButFirstNameIsPresent() { authenticateAs("user42"); - when(userService.findByUsername("user42")).thenReturn( - AppUser.builder().id(UUID.randomUUID()).username("user42") + when(userService.findByEmail("user42")).thenReturn( + AppUser.builder().id(UUID.randomUUID()).email("user42@example.com") .firstName("Hans").lastName(" ").build()); when(versionRepository.findByDocumentIdOrderBySavedAtAsc(any())).thenReturn(List.of()); when(versionRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -468,7 +468,7 @@ class DocumentVersionServiceTest { ArgumentCaptor captor = ArgumentCaptor.forClass(DocumentVersion.class); verify(versionRepository).save(captor.capture()); - assertThat(captor.getValue().getEditorName()).isEqualTo("user42"); + assertThat(captor.getValue().getEditorName()).isEqualTo("user42@example.com"); } // ─── recordVersion — computeChangedFields with corrupt snapshot ────────── @@ -476,7 +476,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_returnsEmptyChangedFields_whenPreviousSnapshotIsInvalidJson() { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); UUID docId = UUID.randomUUID(); DocumentVersion previous = DocumentVersion.builder() @@ -499,7 +499,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_tracksSenderAdded_whenPreviousHadNoSender() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -525,7 +525,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_tracksReceiversAdded_whenPreviousHadNone() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -551,7 +551,7 @@ class DocumentVersionServiceTest { @Test void recordVersion_tracksTagsAdded_whenPreviousHadNone() throws Exception { authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -580,7 +580,7 @@ class DocumentVersionServiceTest { void recordVersion_senderChangedToPresent_whenPreviousSenderHasNullId() throws Exception { // Covers: prevSender instanceof Map = true, but id == null → prevId = null authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); UUID docId = UUID.randomUUID(); // Manually craft a JSON where sender object exists but id is null @@ -610,7 +610,7 @@ class DocumentVersionServiceTest { void recordVersion_doesNotTrackSender_whenSenderUnchanged() throws Exception { // Covers: !Objects.equals(currentId, prevId) = false → don't add "sender" authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -641,7 +641,7 @@ class DocumentVersionServiceTest { void recordVersion_tracksDocumentDate_whenCurrentDocHasNonNullDate() throws Exception { // current.getDocumentDate() != null = true → ternary true branch in computeChangedFields authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); ObjectMapper mapper = new ObjectMapper(); UUID docId = UUID.randomUUID(); @@ -671,7 +671,7 @@ class DocumentVersionServiceTest { void recordVersion_tracksReceivers_whenPreviousSnapshotHasNullReceivers() throws Exception { // prevReceivers NOT instanceof List → prevIds = Set.of() → if currentIds differ → added authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); UUID docId = UUID.randomUUID(); // Craft snapshot where "receivers" is JSON null → deserialized as null, NOT a List @@ -697,7 +697,7 @@ class DocumentVersionServiceTest { void recordVersion_tracksTags_whenPreviousSnapshotHasNullTags() throws Exception { // prevTags NOT instanceof List → prevNames = Set.of() → if currentNames differ → added authenticateAs("user1"); - when(userService.findByUsername("user1")).thenReturn(stubUser("user1")); + when(userService.findByEmail("user1")).thenReturn(stubUser("user1")); UUID docId = UUID.randomUUID(); // Craft snapshot where "tags" is JSON null → deserialized as null, NOT a List @@ -741,8 +741,8 @@ class DocumentVersionServiceTest { new UsernamePasswordAuthenticationToken(username, null, List.of())); } - private AppUser stubUser(String username) { - return AppUser.builder().id(UUID.randomUUID()).username(username) + private AppUser stubUser(String email) { + return AppUser.builder().id(UUID.randomUUID()).email(email) .firstName(null).lastName(null).build(); } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/NotificationServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/NotificationServiceTest.java index af59cd25..ab9a8b1d 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/NotificationServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/NotificationServiceTest.java @@ -50,13 +50,13 @@ class NotificationServiceTest { void setUp() { notificationService = new NotificationService(notificationRepository, userService, documentService, Optional.of(mailSender), sseEmitterRegistry); - userA = AppUser.builder().id(UUID.randomUUID()).username("userA") + userA = AppUser.builder().id(UUID.randomUUID()).email("userA@example.com") .firstName("Anna").lastName("Smith").email("a@test.com") .notifyOnReply(false).notifyOnMention(false).build(); - userB = AppUser.builder().id(UUID.randomUUID()).username("userB") + userB = AppUser.builder().id(UUID.randomUUID()).email("userB@example.com") .firstName("Bob").lastName("Jones").email("b@test.com") .notifyOnReply(false).notifyOnMention(false).build(); - userC = AppUser.builder().id(UUID.randomUUID()).username("userC") + userC = AppUser.builder().id(UUID.randomUUID()).email("userC@example.com") .firstName("Clara").lastName("Doe").email("c@test.com") .notifyOnReply(false).notifyOnMention(false).build(); } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/PasswordResetServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/PasswordResetServiceTest.java index 6f10df96..336ff0ff 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/PasswordResetServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/PasswordResetServiceTest.java @@ -42,7 +42,7 @@ class PasswordResetServiceTest { private AppUser makeUser(String email) { return AppUser.builder() .id(UUID.randomUUID()) - .username("testuser") + .email("testuser@example.com") .email(email) .password("hashed") .build(); diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/UserSearchServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/UserSearchServiceTest.java index 9b0dd5bf..a427f511 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/UserSearchServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/UserSearchServiceTest.java @@ -32,7 +32,7 @@ class UserSearchServiceTest { List result = userSearchService.search(null); assertThat(result).isEmpty(); - verify(userRepository, never()).searchByNameOrUsername(any(), any()); + verify(userRepository, never()).searchByEmailOrName(any(), any()); } @Test @@ -40,28 +40,28 @@ class UserSearchServiceTest { List result = userSearchService.search(" "); assertThat(result).isEmpty(); - verify(userRepository, never()).searchByNameOrUsername(any(), any()); + verify(userRepository, never()).searchByEmailOrName(any(), any()); } @Test void search_delegatesToRepository_whenQueryIsNonBlank() { - AppUser user = AppUser.builder().id(UUID.randomUUID()).username("hans").build(); - when(userRepository.searchByNameOrUsername(eq("hans"), any(PageRequest.class))) + AppUser user = AppUser.builder().id(UUID.randomUUID()).email("hans@example.com").build(); + when(userRepository.searchByEmailOrName(eq("hans"), any(PageRequest.class))) .thenReturn(List.of(user)); List result = userSearchService.search("hans"); assertThat(result).containsExactly(user); - verify(userRepository).searchByNameOrUsername(eq("hans"), any(PageRequest.class)); + verify(userRepository).searchByEmailOrName(eq("hans"), any(PageRequest.class)); } @Test void search_trimsQuery_beforeDelegating() { - when(userRepository.searchByNameOrUsername(eq("hans"), any(PageRequest.class))) + when(userRepository.searchByEmailOrName(eq("hans"), any(PageRequest.class))) .thenReturn(List.of()); userSearchService.search(" hans "); - verify(userRepository).searchByNameOrUsername(eq("hans"), any(PageRequest.class)); + verify(userRepository).searchByEmailOrName(eq("hans"), any(PageRequest.class)); } } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java index 317e206e..42715028 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/service/UserServiceTest.java @@ -35,22 +35,22 @@ class UserServiceTest { @Mock PasswordEncoder passwordEncoder; @InjectMocks UserService userService; - // ─── findByUsername ─────────────────────────────────────────────────────── + // ─── findByEmail ────────────────────────────────────────────────────────── @Test - void findByUsername_throwsNotFound_whenMissing() { - when(userRepository.findByUsername("ghost")).thenReturn(Optional.empty()); + void findByEmail_throwsNotFound_whenMissing() { + when(userRepository.findByEmail("ghost@example.com")).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.findByUsername("ghost")) + assertThatThrownBy(() -> userService.findByEmail("ghost@example.com")) .isInstanceOf(DomainException.class); } @Test - void findByUsername_returnsUser_whenFound() { - AppUser user = AppUser.builder().id(UUID.randomUUID()).username("admin").build(); - when(userRepository.findByUsername("admin")).thenReturn(Optional.of(user)); + void findByEmail_returnsUser_whenFound() { + AppUser user = AppUser.builder().id(UUID.randomUUID()).email("admin@example.com").build(); + when(userRepository.findByEmail("admin@example.com")).thenReturn(Optional.of(user)); - assertThat(userService.findByUsername("admin")).isEqualTo(user); + assertThat(userService.findByEmail("admin@example.com")).isEqualTo(user); } // ─── deleteUser ─────────────────────────────────────────────────────────── @@ -67,7 +67,7 @@ class UserServiceTest { @Test void deleteUser_deletesUser_whenFound() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("gast").build(); + AppUser user = AppUser.builder().id(id).email("gast@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); userService.deleteUser(id); @@ -80,14 +80,13 @@ class UserServiceTest { @Test void createUserOrUpdate_createsNewUser_whenNotExists() { CreateUserRequest req = new CreateUserRequest(); - req.setUsername("newuser"); req.setEmail("new@example.com"); req.setInitialPassword("secret"); req.setGroupIds(List.of()); - when(userRepository.findByUsername("newuser")).thenReturn(Optional.empty()); + when(userRepository.findByEmail("new@example.com")).thenReturn(Optional.empty()); when(passwordEncoder.encode("secret")).thenReturn("encoded"); - AppUser saved = AppUser.builder().id(UUID.randomUUID()).username("newuser").build(); + AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("new@example.com").build(); when(userRepository.save(any())).thenReturn(saved); AppUser result = userService.createUserOrUpdate(req); @@ -99,19 +98,17 @@ class UserServiceTest { @Test void createUserOrUpdate_updatesExistingUser_whenFound() { CreateUserRequest req = new CreateUserRequest(); - req.setUsername("existing"); req.setEmail("existing@example.com"); req.setInitialPassword("newpass"); req.setGroupIds(List.of()); - AppUser existing = AppUser.builder().id(UUID.randomUUID()).username("existing").build(); - when(userRepository.findByUsername("existing")).thenReturn(Optional.of(existing)); + AppUser existing = AppUser.builder().id(UUID.randomUUID()).email("existing@example.com").build(); + when(userRepository.findByEmail("existing@example.com")).thenReturn(Optional.of(existing)); when(passwordEncoder.encode(any())).thenReturn("encoded"); when(userRepository.save(any())).thenReturn(existing); userService.createUserOrUpdate(req); - // save called once with the updated existing user (no new user created) verify(userRepository, times(1)).save(existing); } @@ -129,7 +126,7 @@ class UserServiceTest { @Test void getById_returnsUser_whenFound() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); assertThat(userService.getById(id)).isEqualTo(user); @@ -140,7 +137,7 @@ class UserServiceTest { @Test void updateProfile_updatesFields() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.findByEmail("max@example.com")).thenReturn(Optional.empty()); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -158,8 +155,8 @@ class UserServiceTest { void updateProfile_throwsConflict_whenEmailTakenByAnotherUser() { UUID id = UUID.randomUUID(); UUID otherId = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").build(); - AppUser other = AppUser.builder().id(otherId).username("anna").email("taken@example.com").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").build(); + AppUser other = AppUser.builder().id(otherId).email("taken@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.findByEmail("taken@example.com")).thenReturn(Optional.of(other)); @@ -174,7 +171,7 @@ class UserServiceTest { @Test void updateProfile_allowsSameEmailForSameUser() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").email("max@example.com").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.findByEmail("max@example.com")).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -191,7 +188,7 @@ class UserServiceTest { @Test void changePassword_throwsBadRequest_whenCurrentPasswordWrong() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").password("hashed").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").password("hashed").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(passwordEncoder.matches("wrong", "hashed")).thenReturn(false); @@ -206,7 +203,7 @@ class UserServiceTest { @Test void changePassword_updatesHash_whenCurrentPasswordCorrect() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").password("hashed").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").password("hashed").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(passwordEncoder.matches("correct", "hashed")).thenReturn(true); when(passwordEncoder.encode("newpass")).thenReturn("newHash"); @@ -224,7 +221,7 @@ class UserServiceTest { @Test void adminUpdateUser_updatesNameFields() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -241,12 +238,12 @@ class UserServiceTest { void adminUpdateUser_preservesGroups_whenGroupIdsIsNull() { UUID id = UUID.randomUUID(); UserGroup adminGroup = UserGroup.builder().id(UUID.randomUUID()).name("Administrators").build(); - AppUser user = AppUser.builder().id(id).username("admin").groups(Set.of(adminGroup)).build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").groups(Set.of(adminGroup)).build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); AdminUpdateUserRequest dto = new AdminUpdateUserRequest(); - dto.setFirstName("Ada"); // groupIds left null → don't change groups + dto.setFirstName("Ada"); AppUser result = userService.adminUpdateUser(id, dto); @@ -258,7 +255,7 @@ class UserServiceTest { UUID id = UUID.randomUUID(); UserGroup oldGroup = UserGroup.builder().id(UUID.randomUUID()).name("Viewers").build(); UserGroup newGroup = UserGroup.builder().id(UUID.randomUUID()).name("Editors").build(); - AppUser user = AppUser.builder().id(id).username("max").groups(Set.of(oldGroup)).build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").groups(Set.of(oldGroup)).build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(groupRepository.findAllById(List.of(newGroup.getId()))).thenReturn(List.of(newGroup)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -273,18 +270,15 @@ class UserServiceTest { @Test void adminUpdateUser_clearsGroups_whenGroupIdsIsEmptyList() { - // Sending groupIds:[] is the explicit "remove from all groups" signal. - // The frontend must NEVER send [] accidentally — it must always include - // the currently-selected group checkboxes. UUID id = UUID.randomUUID(); UserGroup adminGroup = UserGroup.builder().id(UUID.randomUUID()).name("Administrators").build(); - AppUser user = AppUser.builder().id(id).username("admin").groups(Set.of(adminGroup)).build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").groups(Set.of(adminGroup)).build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(groupRepository.findAllById(List.of())).thenReturn(List.of()); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); AdminUpdateUserRequest dto = new AdminUpdateUserRequest(); - dto.setGroupIds(List.of()); // empty list → intentional "remove all groups" + dto.setGroupIds(List.of()); AppUser result = userService.adminUpdateUser(id, dto); @@ -308,15 +302,14 @@ class UserServiceTest { void createUserOrUpdate_loadsGroups_whenGroupIdsNonEmpty() { UserGroup group = UserGroup.builder().id(UUID.randomUUID()).name("Admins").build(); CreateUserRequest req = new CreateUserRequest(); - req.setUsername("newuser"); req.setEmail("u@example.com"); req.setInitialPassword("pass"); req.setGroupIds(List.of(group.getId())); - when(userRepository.findByUsername("newuser")).thenReturn(Optional.empty()); + when(userRepository.findByEmail("u@example.com")).thenReturn(Optional.empty()); when(groupRepository.findAllById(List.of(group.getId()))).thenReturn(List.of(group)); when(passwordEncoder.encode("pass")).thenReturn("encoded"); - AppUser saved = AppUser.builder().id(UUID.randomUUID()).username("newuser").build(); + AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("u@example.com").build(); when(userRepository.save(any())).thenReturn(saved); AppUser result = userService.createUserOrUpdate(req); @@ -325,7 +318,7 @@ class UserServiceTest { verify(groupRepository).findAllById(List.of(group.getId())); } - // ─── updateProfile — email edge cases ───────────────────────────────────── + // ─── updateProfile — blank email ────────────────────────────────────────── @Test void updateProfile_throwsBadRequest_whenEmailIsBlank() { @@ -344,12 +337,12 @@ class UserServiceTest { @Test void updateProfile_doesNotChangeEmail_whenEmailDtoIsNull() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").email("keep@example.com").build(); + AppUser user = AppUser.builder().id(id).email("keep@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); UpdateProfileDTO dto = new UpdateProfileDTO(); - dto.setEmail(null); // null — no change + dto.setEmail(null); AppUser result = userService.updateProfile(id, dto); @@ -359,7 +352,7 @@ class UserServiceTest { @Test void updateProfile_setsContactToNull_whenContactIsBlank() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -376,7 +369,7 @@ class UserServiceTest { @Test void adminUpdateUser_setsPassword_whenNewPasswordProvided() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").password("old").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").password("old").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(passwordEncoder.encode("newSecret")).thenReturn("newHashed"); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -392,7 +385,7 @@ class UserServiceTest { @Test void adminUpdateUser_doesNotChangePassword_whenNewPasswordIsBlank() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").password("original").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").password("original").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -423,8 +416,8 @@ class UserServiceTest { void adminUpdateUser_throwsConflict_whenEmailTakenByAnotherUser() { UUID id = UUID.randomUUID(); UUID otherId = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").build(); - AppUser other = AppUser.builder().id(otherId).username("anna").email("taken@example.com").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").build(); + AppUser other = AppUser.builder().id(otherId).email("taken@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.findByEmail("taken@example.com")).thenReturn(Optional.of(other)); @@ -494,14 +487,13 @@ class UserServiceTest { @Test void createUserOrUpdate_doesNotLoadGroups_whenGroupIdsIsEmpty() { CreateUserRequest req = new CreateUserRequest(); - req.setUsername("newuser"); req.setEmail("u@example.com"); req.setInitialPassword("pass"); - req.setGroupIds(List.of()); // empty, not null + req.setGroupIds(List.of()); - when(userRepository.findByUsername("newuser")).thenReturn(Optional.empty()); + when(userRepository.findByEmail("u@example.com")).thenReturn(Optional.empty()); when(passwordEncoder.encode("pass")).thenReturn("encoded"); - AppUser saved = AppUser.builder().id(UUID.randomUUID()).username("newuser").build(); + AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("u@example.com").build(); when(userRepository.save(any())).thenReturn(saved); userService.createUserOrUpdate(req); @@ -509,12 +501,12 @@ class UserServiceTest { verify(groupRepository, never()).findAllById(any()); } - // ─── updateProfile — contact null ───────────────────────────────────────── + // ─── updateProfile — contact ────────────────────────────────────────────── @Test void updateProfile_setsTrimmedContact_whenContactIsNonBlank() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -529,7 +521,7 @@ class UserServiceTest { @Test void updateProfile_setsNullContact_whenContactIsNull() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").contact("old contact").build(); + AppUser user = AppUser.builder().id(id).email("max@example.com").contact("old contact").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -544,15 +536,14 @@ class UserServiceTest { @Test void updateProfile_allowsSameEmail_whenEmailBelongsToSameUser() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("max").email("me@example.com").build(); + AppUser user = AppUser.builder().id(id).email("me@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); - when(userRepository.findByEmail("me@example.com")).thenReturn(Optional.of(user)); // same user + when(userRepository.findByEmail("me@example.com")).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); UpdateProfileDTO dto = new UpdateProfileDTO(); dto.setEmail("me@example.com"); - // Must not throw AppUser result = userService.updateProfile(id, dto); assertThat(result.getEmail()).isEqualTo("me@example.com"); } @@ -562,7 +553,7 @@ class UserServiceTest { @Test void adminUpdateUser_setsNullContact_whenContactIsNull() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").contact("old contact").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").contact("old contact").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -577,7 +568,7 @@ class UserServiceTest { @Test void adminUpdateUser_setsNullContact_whenContactIsBlank() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").contact("old").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").contact("old").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -592,7 +583,7 @@ class UserServiceTest { @Test void adminUpdateUser_setsTrimmedContact_whenContactIsNonBlank() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").build(); + AppUser user = AppUser.builder().id(id).email("admin@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -607,7 +598,7 @@ class UserServiceTest { @Test void adminUpdateUser_doesNotModifyEmail_whenEmailIsNull() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").email("keep@example.com").build(); + AppUser user = AppUser.builder().id(id).email("keep@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); @@ -622,15 +613,14 @@ class UserServiceTest { @Test void adminUpdateUser_allowsSameEmail_whenEmailBelongsToSameUser() { UUID id = UUID.randomUUID(); - AppUser user = AppUser.builder().id(id).username("admin").email("me@example.com").build(); + AppUser user = AppUser.builder().id(id).email("me@example.com").build(); when(userRepository.findById(id)).thenReturn(Optional.of(user)); - when(userRepository.findByEmail("me@example.com")).thenReturn(Optional.of(user)); // same user + when(userRepository.findByEmail("me@example.com")).thenReturn(Optional.of(user)); when(userRepository.save(any())).thenAnswer(inv -> inv.getArgument(0)); AdminUpdateUserRequest dto = new AdminUpdateUserRequest(); dto.setEmail("me@example.com"); - // Must not throw AppUser result = userService.adminUpdateUser(id, dto); assertThat(result.getEmail()).isEqualTo("me@example.com"); } @@ -639,16 +629,14 @@ class UserServiceTest { @Test void createUserOrUpdate_doesNotLoadGroups_whenGroupIdsIsNull() { - // request.getGroupIds() == null → short-circuit (A=false), groupRepository never called CreateUserRequest req = new CreateUserRequest(); - req.setUsername("nullgroups"); req.setEmail("ng@example.com"); req.setInitialPassword("pass"); - req.setGroupIds(null); // null → first condition false → short-circuit + req.setGroupIds(null); - when(userRepository.findByUsername("nullgroups")).thenReturn(Optional.empty()); + when(userRepository.findByEmail("ng@example.com")).thenReturn(Optional.empty()); when(passwordEncoder.encode("pass")).thenReturn("encoded"); - AppUser saved = AppUser.builder().id(UUID.randomUUID()).username("nullgroups").build(); + AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("ng@example.com").build(); when(userRepository.save(any())).thenReturn(saved); userService.createUserOrUpdate(req);