feat(audit): emit USER_CREATED when admin creates a new user
Adds USER_CREATED, USER_DELETED, GROUP_MEMBERSHIP_CHANGED to AuditKind. Injects AuditService into UserService; changes createUserOrUpdate to accept actorId and emits logAfterCommit(USER_CREATED) only on the new-user branch. Updates UserController to resolve and pass actorId. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,12 @@ package org.raddatz.familienarchiv.service;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.raddatz.familienarchiv.audit.AuditKind;
|
||||
import org.raddatz.familienarchiv.audit.AuditService;
|
||||
import org.raddatz.familienarchiv.dto.AdminUpdateUserRequest;
|
||||
import org.raddatz.familienarchiv.dto.ChangePasswordDTO;
|
||||
import org.raddatz.familienarchiv.dto.CreateUserRequest;
|
||||
@@ -34,6 +37,7 @@ class UserServiceTest {
|
||||
@Mock AppUserRepository userRepository;
|
||||
@Mock UserGroupRepository groupRepository;
|
||||
@Mock PasswordEncoder passwordEncoder;
|
||||
@Mock AuditService auditService;
|
||||
@InjectMocks UserService userService;
|
||||
|
||||
// ─── findByEmail ──────────────────────────────────────────────────────────
|
||||
@@ -90,7 +94,7 @@ class UserServiceTest {
|
||||
AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("new@example.com").build();
|
||||
when(userRepository.save(any())).thenReturn(saved);
|
||||
|
||||
AppUser result = userService.createUserOrUpdate(req);
|
||||
AppUser result = userService.createUserOrUpdate(UUID.randomUUID(), req);
|
||||
|
||||
assertThat(result).isEqualTo(saved);
|
||||
verify(userRepository).save(any());
|
||||
@@ -108,7 +112,7 @@ class UserServiceTest {
|
||||
when(passwordEncoder.encode(any())).thenReturn("encoded");
|
||||
when(userRepository.save(any())).thenReturn(existing);
|
||||
|
||||
userService.createUserOrUpdate(req);
|
||||
userService.createUserOrUpdate(UUID.randomUUID(), req);
|
||||
|
||||
verify(userRepository, times(1)).save(existing);
|
||||
}
|
||||
@@ -313,7 +317,7 @@ class UserServiceTest {
|
||||
AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("u@example.com").build();
|
||||
when(userRepository.save(any())).thenReturn(saved);
|
||||
|
||||
AppUser result = userService.createUserOrUpdate(req);
|
||||
AppUser result = userService.createUserOrUpdate(UUID.randomUUID(), req);
|
||||
|
||||
assertThat(result).isEqualTo(saved);
|
||||
verify(groupRepository).findAllById(List.of(group.getId()));
|
||||
@@ -497,7 +501,7 @@ class UserServiceTest {
|
||||
AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("u@example.com").build();
|
||||
when(userRepository.save(any())).thenReturn(saved);
|
||||
|
||||
userService.createUserOrUpdate(req);
|
||||
userService.createUserOrUpdate(UUID.randomUUID(), req);
|
||||
|
||||
verify(groupRepository, never()).findAllById(any());
|
||||
}
|
||||
@@ -640,7 +644,7 @@ class UserServiceTest {
|
||||
AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("ng@example.com").build();
|
||||
when(userRepository.save(any())).thenReturn(saved);
|
||||
|
||||
userService.createUserOrUpdate(req);
|
||||
userService.createUserOrUpdate(UUID.randomUUID(), req);
|
||||
|
||||
verify(groupRepository, never()).findAllById(any());
|
||||
}
|
||||
@@ -699,6 +703,52 @@ class UserServiceTest {
|
||||
assertThat(result).containsExactly(g);
|
||||
}
|
||||
|
||||
// ─── audit: USER_CREATED ──────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
void createUserOrUpdate_logsUserCreated_whenUserIsNew() {
|
||||
UUID actorId = UUID.randomUUID();
|
||||
CreateUserRequest req = new CreateUserRequest();
|
||||
req.setEmail("new@example.com");
|
||||
req.setInitialPassword("secret");
|
||||
req.setGroupIds(List.of());
|
||||
|
||||
when(userRepository.findByEmail("new@example.com")).thenReturn(Optional.empty());
|
||||
when(passwordEncoder.encode("secret")).thenReturn("encoded");
|
||||
AppUser saved = AppUser.builder().id(UUID.randomUUID()).email("new@example.com").build();
|
||||
when(userRepository.save(any())).thenReturn(saved);
|
||||
|
||||
userService.createUserOrUpdate(actorId, req);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ArgumentCaptor<java.util.Map<String, Object>> payloadCaptor = ArgumentCaptor.forClass(java.util.Map.class);
|
||||
verify(auditService).logAfterCommit(
|
||||
org.mockito.ArgumentMatchers.eq(AuditKind.USER_CREATED),
|
||||
org.mockito.ArgumentMatchers.eq(actorId),
|
||||
org.mockito.ArgumentMatchers.isNull(),
|
||||
payloadCaptor.capture());
|
||||
assertThat(payloadCaptor.getValue()).containsKey("userId");
|
||||
assertThat(payloadCaptor.getValue()).containsEntry("email", "new@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createUserOrUpdate_doesNotLogUserCreated_whenUserAlreadyExists() {
|
||||
UUID actorId = UUID.randomUUID();
|
||||
CreateUserRequest req = new CreateUserRequest();
|
||||
req.setEmail("existing@example.com");
|
||||
req.setInitialPassword("pass");
|
||||
req.setGroupIds(List.of());
|
||||
|
||||
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(actorId, req);
|
||||
|
||||
verify(auditService, never()).logAfterCommit(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
// ─── createGroup ──────────────────────────────────────────────────────────
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user