diff --git a/backend/src/test/java/org/raddatz/familienarchiv/audit/UserManagementAuditIntegrationTest.java b/backend/src/test/java/org/raddatz/familienarchiv/audit/UserManagementAuditIntegrationTest.java new file mode 100644 index 00000000..1437bc83 --- /dev/null +++ b/backend/src/test/java/org/raddatz/familienarchiv/audit/UserManagementAuditIntegrationTest.java @@ -0,0 +1,75 @@ +package org.raddatz.familienarchiv.audit; + +import org.junit.jupiter.api.Test; +import org.raddatz.familienarchiv.PostgresContainerConfig; +import org.raddatz.familienarchiv.dto.CreateUserRequest; +import org.raddatz.familienarchiv.model.AppUser; +import org.raddatz.familienarchiv.repository.AppUserRepository; +import org.raddatz.familienarchiv.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.transaction.support.TransactionTemplate; +import software.amazon.awssdk.services.s3.S3Client; + +import java.util.List; +import java.util.UUID; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) +@ActiveProfiles("test") +@Import(PostgresContainerConfig.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +class UserManagementAuditIntegrationTest { + + @MockitoBean S3Client s3Client; + @Autowired UserService userService; + @Autowired AppUserRepository userRepository; + @Autowired AuditLogRepository auditLogRepository; + @Autowired AuditLogQueryService auditLogQueryService; + @Autowired TransactionTemplate transactionTemplate; + + @Test + void createAndDeleteUser_producesOrderedAuditEntries() { + // Create the actor (admin) user directly — bypasses audit logging so no FK issue + CreateUserRequest adminReq = new CreateUserRequest(); + adminReq.setEmail("admin@test.example.com"); + adminReq.setInitialPassword("admin-secret"); + AppUser actor = transactionTemplate.execute(status -> + userService.createUserOrUpdate(null, adminReq)); + UUID actorId = actor.getId(); + + // The admin creation is logged with null actorId — clear to start with a clean slate + await().atMost(5, SECONDS).until(() -> auditLogRepository.count() > 0); + transactionTemplate.execute(status -> { auditLogRepository.deleteAll(); return null; }); + + // Create the target user — should emit USER_CREATED + CreateUserRequest req = new CreateUserRequest(); + req.setEmail("audit-test@example.com"); + req.setInitialPassword("secret"); + transactionTemplate.execute(status -> { + userService.createUserOrUpdate(actorId, req); + return null; + }); + await().atMost(5, SECONDS).until(() -> auditLogRepository.count() > 0); + + // Delete the target user — should emit USER_DELETED + AppUser created = userRepository.findByEmail("audit-test@example.com").orElseThrow(); + transactionTemplate.execute(status -> { + userService.deleteUser(actorId, created.getId()); + return null; + }); + await().atMost(5, SECONDS).until(() -> auditLogRepository.count() >= 2); + + List events = auditLogQueryService.findRecentUserManagementEvents(10); + assertThat(events).hasSize(2); + assertThat(events.get(0).getKind()).isEqualTo(AuditKind.USER_DELETED); + assertThat(events.get(1).getKind()).isEqualTo(AuditKind.USER_CREATED); + } +}