mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 17:04:49 +01:00
refactor(usermanagement): implement code review findings for User Management BC
Address all 18 findings from security code review (5 critical, 7 medium, 6 low): Domain: make User and Role immutable with wither-pattern, add status transition guards (ACTIVE->LOCKED, LOCKED->ACTIVE, ACTIVE|LOCKED->INACTIVE, INACTIVE->ACTIVE) Application: enforce authorization via AuthorizationPort in all use cases, add input validation, introduce LockUserCommand/UnlockUserCommand/RemoveRoleCommand, fix audit event on password change failure (K5), use flatMap/mapError chains Infrastructure: JWT blacklist with TTL and scheduled cleanup, login rate limiting (5 attempts/15min), configurable CORS, generic error messages, conditional Swagger, seed data context restriction Tests: unit tests for all 10 use cases, adapted domain and integration tests
This commit is contained in:
parent
a1161cfbad
commit
05878b1ce9
45 changed files with 1989 additions and 2207 deletions
|
|
@ -0,0 +1,131 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.command.AssignRoleCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("AssignRole Use Case")
|
||||
class AssignRoleTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private RoleRepository roleRepository;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private AssignRole assignRole;
|
||||
private ActorId performedBy;
|
||||
private User testUser;
|
||||
private Role workerRole;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
assignRole = new AssignRole(userRepository, roleRepository, auditLogger, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
testUser = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
workerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Production Worker");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_AssignRole_When_ValidCommandProvided")
|
||||
void should_AssignRole_When_ValidCommandProvided() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_ASSIGN)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(workerRole)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = assignRole.execute(
|
||||
new AssignRoleCommand("user-1", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().roles()).hasSize(1);
|
||||
verify(userRepository).save(argThat(user -> user.roles().contains(workerRole)));
|
||||
verify(auditLogger).log(eq(AuditEvent.ROLE_ASSIGNED), eq("user-1"), anyString(), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_ASSIGN)).thenReturn(false);
|
||||
|
||||
Result<UserError, UserDTO> result = assignRole.execute(
|
||||
new AssignRoleCommand("user-1", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_UserIdIsBlank")
|
||||
void should_FailWithInvalidInput_When_UserIdIsBlank() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_ASSIGN)).thenReturn(true);
|
||||
|
||||
Result<UserError, UserDTO> result = assignRole.execute(
|
||||
new AssignRoleCommand("", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_RoleNameIsNull")
|
||||
void should_FailWithInvalidInput_When_RoleNameIsNull() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_ASSIGN)).thenReturn(true);
|
||||
|
||||
Result<UserError, UserDTO> result = assignRole.execute(
|
||||
new AssignRoleCommand("user-1", null), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_ASSIGN)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("nonexistent"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = assignRole.execute(
|
||||
new AssignRoleCommand("nonexistent", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithRoleNotFound_When_RoleDoesNotExist")
|
||||
void should_FailWithRoleNotFound_When_RoleDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_ASSIGN)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = assignRole.execute(
|
||||
new AssignRoleCommand("user-1", RoleName.ADMIN), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.RoleNotFound.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
}
|
||||
|
|
@ -21,25 +21,14 @@ import static org.assertj.core.api.Assertions.*;
|
|||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for AuthenticateUser Use Case.
|
||||
* Tests authentication flow, credential validation, status checks, and audit logging.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("AuthenticateUser Use Case")
|
||||
class AuthenticateUserTest {
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Mock
|
||||
private PasswordHasher passwordHasher;
|
||||
|
||||
@Mock
|
||||
private SessionManager sessionManager;
|
||||
|
||||
@Mock
|
||||
private AuditLogger auditLogger;
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private PasswordHasher passwordHasher;
|
||||
@Mock private SessionManager sessionManager;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
|
||||
@InjectMocks
|
||||
private AuthenticateUser authenticateUser;
|
||||
|
|
@ -55,15 +44,8 @@ class AuthenticateUserTest {
|
|||
validPasswordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
|
||||
|
||||
testUser = User.reconstitute(
|
||||
UserId.of("user-1"),
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
validPasswordHash,
|
||||
new HashSet<>(),
|
||||
"branch-1",
|
||||
UserStatus.ACTIVE,
|
||||
LocalDateTime.now(),
|
||||
null
|
||||
UserId.of("user-1"), "john.doe", "john@example.com", validPasswordHash,
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
|
||||
sessionToken = new SessionToken("jwt-token", "Bearer", 3600L, LocalDateTime.now().plusSeconds(3600), "refresh-token");
|
||||
|
|
@ -72,16 +54,13 @@ class AuthenticateUserTest {
|
|||
@Test
|
||||
@DisplayName("should_AuthenticateUser_When_ValidCredentialsProvided")
|
||||
void should_AuthenticateUser_When_ValidCredentialsProvided() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue()).isEqualTo(sessionToken);
|
||||
verify(userRepository).save(any());
|
||||
|
|
@ -91,14 +70,10 @@ class AuthenticateUserTest {
|
|||
@Test
|
||||
@DisplayName("should_FailWithInvalidCredentials_When_UserNotFound")
|
||||
void should_FailWithInvalidCredentials_When_UserNotFound() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("nonexistent")).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
// Act
|
||||
AuthenticateCommand command = new AuthenticateCommand("nonexistent", "Password123!");
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(command);
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(new AuthenticateCommand("nonexistent", "Password123!"));
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidCredentials.class);
|
||||
verify(auditLogger).log(eq(AuditEvent.LOGIN_FAILED), anyString());
|
||||
|
|
@ -107,25 +82,14 @@ class AuthenticateUserTest {
|
|||
@Test
|
||||
@DisplayName("should_FailWithLockedUser_When_UserStatusIsLocked")
|
||||
void should_FailWithLockedUser_When_UserStatusIsLocked() {
|
||||
// Arrange
|
||||
User lockedUser = User.reconstitute(
|
||||
UserId.of("user-2"),
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
validPasswordHash,
|
||||
new HashSet<>(),
|
||||
"branch-1",
|
||||
UserStatus.LOCKED,
|
||||
LocalDateTime.now(),
|
||||
null
|
||||
UserId.of("user-2"), "john.doe", "john@example.com", validPasswordHash,
|
||||
new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null
|
||||
);
|
||||
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(lockedUser)));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserLocked.class);
|
||||
verify(auditLogger).log(eq(AuditEvent.LOGIN_BLOCKED), anyString(), any());
|
||||
|
|
@ -134,209 +98,57 @@ class AuthenticateUserTest {
|
|||
@Test
|
||||
@DisplayName("should_FailWithInactiveUser_When_UserStatusIsInactive")
|
||||
void should_FailWithInactiveUser_When_UserStatusIsInactive() {
|
||||
// Arrange
|
||||
User inactiveUser = User.reconstitute(
|
||||
UserId.of("user-3"),
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
validPasswordHash,
|
||||
new HashSet<>(),
|
||||
"branch-1",
|
||||
UserStatus.INACTIVE,
|
||||
LocalDateTime.now(),
|
||||
null
|
||||
UserId.of("user-3"), "john.doe", "john@example.com", validPasswordHash,
|
||||
new HashSet<>(), "branch-1", UserStatus.INACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(inactiveUser)));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserInactive.class);
|
||||
verify(auditLogger).log(eq(AuditEvent.LOGIN_FAILED), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidCredentials_When_PasswordDoesNotMatch")
|
||||
void should_FailWithInvalidCredentials_When_PasswordDoesNotMatch() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("WrongPassword", validPasswordHash)).thenReturn(false);
|
||||
|
||||
// Act
|
||||
AuthenticateCommand command = new AuthenticateCommand("john.doe", "WrongPassword");
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(command);
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(new AuthenticateCommand("john.doe", "WrongPassword"));
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidCredentials.class);
|
||||
verify(auditLogger).log(eq(AuditEvent.LOGIN_FAILED), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CreateSessionToken_When_AuthenticationSucceeds")
|
||||
void should_CreateSessionToken_When_AuthenticationSucceeds() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(sessionManager).createSession(testUser);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateLastLoginTimestamp_When_AuthenticationSucceeds")
|
||||
void should_UpdateLastLoginTimestamp_When_AuthenticationSucceeds() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
LocalDateTime before = LocalDateTime.now();
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
LocalDateTime after = LocalDateTime.now();
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(testUser.lastLogin()).isBetween(before, after);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_SaveUserWithUpdatedLastLogin_When_AuthenticationSucceeds")
|
||||
void should_SaveUserWithUpdatedLastLogin_When_AuthenticationSucceeds() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LogLoginSuccessAuditEvent_When_AuthenticationSucceeds")
|
||||
void should_LogLoginSuccessAuditEvent_When_AuthenticationSucceeds() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(auditLogger).log(eq(AuditEvent.LOGIN_SUCCESS), eq("user-1"), any(ActorId.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LogLoginFailureAuditEvent_When_PasswordIncorrect")
|
||||
void should_LogLoginFailureAuditEvent_When_PasswordIncorrect() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("WrongPassword", validPasswordHash)).thenReturn(false);
|
||||
|
||||
// Act
|
||||
AuthenticateCommand command = new AuthenticateCommand("john.doe", "WrongPassword");
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(command);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
verify(auditLogger).log(eq(AuditEvent.LOGIN_FAILED), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_VerifyPasswordBeforeCheckingStatus_When_UserExists")
|
||||
void should_VerifyPasswordBeforeCheckingStatus_When_UserExists() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
verify(passwordHasher).verify("Password123!", validPasswordHash);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CheckStatusBeforeCreatingSession_When_UserActive")
|
||||
void should_CheckStatusBeforeCreatingSession_When_UserActive() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
when(sessionManager.createSession(testUser)).thenReturn(sessionToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
// Session should be created only for active users
|
||||
verify(sessionManager).createSession(testUser);
|
||||
// Verify save was called with a user (immutable, so it's a new instance)
|
||||
verify(userRepository).save(argThat(user -> user.lastLogin() != null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_NotCreateSession_When_UserLocked")
|
||||
void should_NotCreateSession_When_UserLocked() {
|
||||
// Arrange
|
||||
User lockedUser = User.reconstitute(
|
||||
UserId.of("user-4"),
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
validPasswordHash,
|
||||
new HashSet<>(),
|
||||
"branch-1",
|
||||
UserStatus.LOCKED,
|
||||
LocalDateTime.now(),
|
||||
null
|
||||
UserId.of("user-4"), "john.doe", "john@example.com", validPasswordHash,
|
||||
new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null
|
||||
);
|
||||
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(lockedUser)));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
verify(sessionManager, never()).createSession(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnSessionToken_When_AuthenticationSucceeds")
|
||||
void should_ReturnSessionToken_When_AuthenticationSucceeds() {
|
||||
// Arrange
|
||||
when(userRepository.findByUsername("john.doe")).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("Password123!", validPasswordHash)).thenReturn(true);
|
||||
SessionToken expectedToken = new SessionToken("jwt-xyz", "Bearer", 3600L, LocalDateTime.now().plusSeconds(3600), "refresh-xyz");
|
||||
when(sessionManager.createSession(testUser)).thenReturn(expectedToken);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, SessionToken> result = authenticateUser.execute(validCommand);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue()).isEqualTo(expectedToken);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import de.effigenix.application.usermanagement.command.ChangePasswordCommand;
|
|||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
|
|
@ -20,26 +20,16 @@ import static org.assertj.core.api.Assertions.*;
|
|||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for ChangePassword Use Case.
|
||||
* Tests password verification, password validation, update logic, and audit logging.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("ChangePassword Use Case")
|
||||
class ChangePasswordTest {
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private PasswordHasher passwordHasher;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
@Mock
|
||||
private PasswordHasher passwordHasher;
|
||||
|
||||
@Mock
|
||||
private AuditLogger auditLogger;
|
||||
|
||||
@InjectMocks
|
||||
private ChangePassword changePassword;
|
||||
|
||||
private User testUser;
|
||||
private PasswordHash oldPasswordHash;
|
||||
private PasswordHash newPasswordHash;
|
||||
|
|
@ -48,59 +38,58 @@ class ChangePasswordTest {
|
|||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
changePassword = new ChangePassword(userRepository, passwordHasher, auditLogger, authPort);
|
||||
oldPasswordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
|
||||
newPasswordHash = new PasswordHash("$2b$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
|
||||
|
||||
testUser = User.reconstitute(
|
||||
UserId.of("user-123"),
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
oldPasswordHash,
|
||||
new HashSet<>(),
|
||||
"branch-1",
|
||||
UserStatus.ACTIVE,
|
||||
LocalDateTime.now(),
|
||||
null
|
||||
UserId.of("user-123"), "john.doe", "john@example.com", oldPasswordHash,
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
|
||||
validCommand = new ChangePasswordCommand("user-123", "OldPassword123!", "NewPassword456!");
|
||||
performedBy = ActorId.of("user-123");
|
||||
performedBy = ActorId.of("user-123"); // self-service
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ChangePassword_When_ValidCurrentPasswordProvided")
|
||||
void should_ChangePassword_When_ValidCurrentPasswordProvided() {
|
||||
// Arrange
|
||||
@DisplayName("should_ChangePassword_When_ValidCurrentPasswordProvided_SelfService")
|
||||
void should_ChangePassword_When_ValidCurrentPasswordProvided_SelfService() {
|
||||
// Self-service: no authPort check needed
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(userRepository).save(any(User.class));
|
||||
verify(auditLogger).log(eq(AuditEvent.PASSWORD_CHANGED), eq("user-123"), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_DifferentActorWithoutPermission")
|
||||
void should_FailWithUnauthorized_When_DifferentActorWithoutPermission() {
|
||||
ActorId otherActor = ActorId.of("other-user");
|
||||
when(authPort.can(otherActor, UserManagementAction.PASSWORD_CHANGE)).thenReturn(false);
|
||||
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, otherActor);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserIdDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserIdDoesNotExist() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("nonexistent-id"))).thenReturn(Result.success(Optional.empty()));
|
||||
ChangePasswordCommand command = new ChangePasswordCommand("nonexistent-id", "OldPassword123!", "NewPassword456!");
|
||||
ActorId actor = ActorId.of("nonexistent-id");
|
||||
|
||||
ChangePasswordCommand command = new ChangePasswordCommand(
|
||||
"nonexistent-id",
|
||||
"OldPassword123!",
|
||||
"NewPassword456!"
|
||||
);
|
||||
Result<UserError, Void> result = changePassword.execute(command, actor);
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(command, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
|
|
@ -109,235 +98,41 @@ class ChangePasswordTest {
|
|||
@Test
|
||||
@DisplayName("should_FailWithInvalidCredentials_When_CurrentPasswordIncorrect")
|
||||
void should_FailWithInvalidCredentials_When_CurrentPasswordIncorrect() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("WrongPassword", oldPasswordHash)).thenReturn(false);
|
||||
ChangePasswordCommand command = new ChangePasswordCommand("user-123", "WrongPassword", "NewPassword456!");
|
||||
|
||||
ChangePasswordCommand command = new ChangePasswordCommand(
|
||||
"user-123",
|
||||
"WrongPassword",
|
||||
"NewPassword456!"
|
||||
);
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(command, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidCredentials.class);
|
||||
verify(auditLogger).log(eq(AuditEvent.PASSWORD_CHANGE_FAILED), eq("user-123"), eq(performedBy));
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidPassword_When_NewPasswordTooWeak")
|
||||
void should_FailWithInvalidPassword_When_NewPasswordTooWeak() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("weak")).thenReturn(false);
|
||||
|
||||
ChangePasswordCommand command = new ChangePasswordCommand("user-123", "OldPassword123!", "weak");
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(command, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidPassword.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_VerifyCurrentPasswordBeforeValidatingNewPassword")
|
||||
void should_VerifyCurrentPasswordBeforeValidatingNewPassword() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(false);
|
||||
@DisplayName("should_FailWithInvalidInput_When_BlankUserId")
|
||||
void should_FailWithInvalidInput_When_BlankUserId() {
|
||||
ChangePasswordCommand command = new ChangePasswordCommand("", "OldPassword123!", "NewPassword456!");
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
// Password validation should not be called if current password is wrong
|
||||
verify(passwordHasher, never()).isValidPassword(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_HashNewPassword_When_AllValidationsPass")
|
||||
void should_HashNewPassword_When_AllValidationsPass() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
verify(passwordHasher).hash("NewPassword456!");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateUserPassword_When_NewHashObtained")
|
||||
void should_UpdateUserPassword_When_NewHashObtained() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(testUser.passwordHash()).isEqualTo(newPasswordHash);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_SaveUpdatedUserToRepository_When_PasswordChanged")
|
||||
void should_SaveUpdatedUserToRepository_When_PasswordChanged() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LogPasswordChangedAuditEvent_When_PasswordSuccessfullyChanged")
|
||||
void should_LogPasswordChangedAuditEvent_When_PasswordSuccessfullyChanged() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(auditLogger).log(eq(AuditEvent.PASSWORD_CHANGED), eq("user-123"), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LogFailureAuditEvent_When_CurrentPasswordIncorrect")
|
||||
void should_LogFailureAuditEvent_When_CurrentPasswordIncorrect() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("WrongPassword", oldPasswordHash)).thenReturn(false);
|
||||
|
||||
ChangePasswordCommand command = new ChangePasswordCommand(
|
||||
"user-123",
|
||||
"WrongPassword",
|
||||
"NewPassword456!"
|
||||
);
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(command, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
verify(auditLogger).log(
|
||||
eq(AuditEvent.PASSWORD_CHANGED),
|
||||
eq("user-123"),
|
||||
eq(performedBy)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnSuccess_When_PasswordChangedSuccessfully")
|
||||
void should_ReturnSuccess_When_PasswordChangedSuccessfully() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_UserNotFound")
|
||||
void should_ReturnFailure_When_UserNotFound() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("invalid-id"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
ChangePasswordCommand command = new ChangePasswordCommand(
|
||||
"invalid-id",
|
||||
"OldPassword123!",
|
||||
"NewPassword456!"
|
||||
);
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(command, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_AllowPasswordChangeForActiveUser")
|
||||
void should_AllowPasswordChangeForActiveUser() {
|
||||
// Arrange
|
||||
testUser = User.reconstitute(
|
||||
UserId.of("user-123"),
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
oldPasswordHash,
|
||||
new HashSet<>(),
|
||||
"branch-1",
|
||||
UserStatus.ACTIVE,
|
||||
LocalDateTime.now(),
|
||||
null
|
||||
);
|
||||
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UsePasswordHasherToVerifyCurrentPassword")
|
||||
void should_UsePasswordHasherToVerifyCurrentPassword() {
|
||||
// Arrange
|
||||
when(userRepository.findById(UserId.of("user-123"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(passwordHasher.verify("OldPassword123!", oldPasswordHash)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("NewPassword456!")).thenReturn(true);
|
||||
when(passwordHasher.hash("NewPassword456!")).thenReturn(newPasswordHash);
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
changePassword.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
verify(passwordHasher).verify("OldPassword123!", oldPasswordHash);
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import de.effigenix.application.usermanagement.command.CreateUserCommand;
|
|||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
|
|
@ -21,58 +21,35 @@ import static org.mockito.ArgumentMatchers.any;
|
|||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for CreateUser Use Case.
|
||||
* Tests validation, uniqueness checks, role loading, user creation, and audit logging.
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("CreateUser Use Case")
|
||||
class CreateUserTest {
|
||||
|
||||
@Mock
|
||||
private UserRepository userRepository;
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private RoleRepository roleRepository;
|
||||
@Mock private PasswordHasher passwordHasher;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
@Mock
|
||||
private RoleRepository roleRepository;
|
||||
|
||||
@Mock
|
||||
private PasswordHasher passwordHasher;
|
||||
|
||||
@Mock
|
||||
private AuditLogger auditLogger;
|
||||
|
||||
@InjectMocks
|
||||
private CreateUser createUser;
|
||||
|
||||
private CreateUserCommand validCommand;
|
||||
private ActorId performedBy;
|
||||
private PasswordHash validPasswordHash;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
createUser = new CreateUser(userRepository, roleRepository, passwordHasher, auditLogger, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
validPasswordHash = new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
|
||||
|
||||
validCommand = new CreateUserCommand(
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
"Password123!",
|
||||
Set.of(RoleName.PRODUCTION_WORKER),
|
||||
"branch-1"
|
||||
);
|
||||
validCommand = new CreateUserCommand("john.doe", "john@example.com", "Password123!", Set.of(RoleName.PRODUCTION_WORKER), "branch-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CreateUser_When_ValidCommandAndUniqueDetailsProvided")
|
||||
void should_CreateUser_When_ValidCommandAndUniqueDetailsProvided() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(
|
||||
RoleId.generate(),
|
||||
RoleName.PRODUCTION_WORKER,
|
||||
new HashSet<>(),
|
||||
"Production Worker"
|
||||
);
|
||||
Role role = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Production Worker");
|
||||
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
|
|
@ -80,157 +57,103 @@ class CreateUserTest {
|
|||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().username()).isEqualTo("john.doe");
|
||||
assertThat(result.unsafeGetValue().email()).isEqualTo("john@example.com");
|
||||
|
||||
verify(userRepository).save(any());
|
||||
verify(auditLogger).log(AuditEvent.USER_CREATED, result.unsafeGetValue().id(), performedBy);
|
||||
verify(auditLogger).log(eq(AuditEvent.USER_CREATED), anyString(), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(false);
|
||||
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_EmptyRoleNamesProvided")
|
||||
void should_FailWithInvalidInput_When_EmptyRoleNamesProvided() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
var cmd = new CreateUserCommand("john.doe", "john@example.com", "Password123!", Set.of(), "branch-1");
|
||||
|
||||
var result = createUser.execute(cmd, performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidPassword_When_WeakPasswordProvided")
|
||||
void should_FailWithInvalidPassword_When_WeakPasswordProvided() {
|
||||
// Arrange
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(false);
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidPassword.class);
|
||||
assertThat(result.unsafeGetError().message()).contains("at least 8 characters");
|
||||
|
||||
verify(userRepository, never()).save(any());
|
||||
verify(auditLogger, never()).log(any(AuditEvent.class), anyString(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUsernameExists_When_DuplicateUsernameProvided")
|
||||
void should_FailWithUsernameExists_When_DuplicateUsernameProvided() {
|
||||
// Arrange
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(true));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UsernameAlreadyExists.class);
|
||||
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithEmailExists_When_DuplicateEmailProvided")
|
||||
void should_FailWithEmailExists_When_DuplicateEmailProvided() {
|
||||
// Arrange
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(true));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.EmailAlreadyExists.class);
|
||||
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UsePasswordHasherToHashPassword_When_PasswordValid")
|
||||
void should_UsePasswordHasherToHashPassword_When_PasswordValid() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker");
|
||||
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
when(passwordHasher.hash("Password123!")).thenReturn(validPasswordHash);
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(passwordHasher).hash("Password123!");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LoadRolesByName_When_RoleNamesProvided")
|
||||
void should_LoadRolesByName_When_RoleNamesProvided() {
|
||||
// Arrange
|
||||
Role role1 = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker");
|
||||
Role role2 = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_MANAGER, new HashSet<>(), "Manager");
|
||||
|
||||
CreateUserCommand commandWithMultipleRoles = new CreateUserCommand(
|
||||
"john.doe",
|
||||
"john@example.com",
|
||||
"Password123!",
|
||||
Set.of(RoleName.PRODUCTION_WORKER, RoleName.PRODUCTION_MANAGER),
|
||||
"branch-1"
|
||||
);
|
||||
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
when(passwordHasher.hash("Password123!")).thenReturn(validPasswordHash);
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role1)));
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_MANAGER)).thenReturn(Result.success(Optional.of(role2)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(commandWithMultipleRoles, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(roleRepository).findByName(RoleName.PRODUCTION_WORKER);
|
||||
verify(roleRepository).findByName(RoleName.PRODUCTION_MANAGER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithRoleNotFound_When_RoleNotFound")
|
||||
void should_FailWithRoleNotFound_When_RoleNotFound() {
|
||||
// Arrange
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
when(passwordHasher.hash("Password123!")).thenReturn(validPasswordHash);
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.RoleNotFound.class);
|
||||
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CreateActiveUser_When_UserCreatedWithFactoryMethod")
|
||||
void should_CreateActiveUser_When_UserCreatedWithFactoryMethod() {
|
||||
// Arrange
|
||||
@DisplayName("should_CreateActiveUser_When_UserCreatedSuccessfully")
|
||||
void should_CreateActiveUser_When_UserCreatedSuccessfully() {
|
||||
Role role = Role.reconstitute(RoleId.generate(), RoleName.ADMIN, new HashSet<>(), "Admin");
|
||||
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_CREATE)).thenReturn(true);
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
|
|
@ -238,79 +161,9 @@ class CreateUserTest {
|
|||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
var result = createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.ACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LogUserCreatedAuditEvent_When_UserSuccessfullyCreated")
|
||||
void should_LogUserCreatedAuditEvent_When_UserSuccessfullyCreated() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker");
|
||||
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
when(passwordHasher.hash("Password123!")).thenReturn(validPasswordHash);
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(auditLogger).log(eq(AuditEvent.USER_CREATED), anyString(), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_SaveUserToRepository_When_AllValidationsPass")
|
||||
void should_SaveUserToRepository_When_AllValidationsPass() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker");
|
||||
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
when(passwordHasher.hash("Password123!")).thenReturn(validPasswordHash);
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnUserDTO_When_UserCreatedSuccessfully")
|
||||
void should_ReturnUserDTO_When_UserCreatedSuccessfully() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Worker");
|
||||
|
||||
when(passwordHasher.isValidPassword("Password123!")).thenReturn(true);
|
||||
when(userRepository.existsByUsername("john.doe")).thenReturn(Result.success(false));
|
||||
when(userRepository.existsByEmail("john@example.com")).thenReturn(Result.success(false));
|
||||
when(passwordHasher.hash("Password123!")).thenReturn(validPasswordHash);
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(role)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
// Act
|
||||
Result<UserError, de.effigenix.application.usermanagement.dto.UserDTO> result =
|
||||
createUser.execute(validCommand, performedBy);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
var userDTO = result.unsafeGetValue();
|
||||
assertThat(userDTO.username()).isEqualTo("john.doe");
|
||||
assertThat(userDTO.email()).isEqualTo("john@example.com");
|
||||
assertThat(userDTO.status()).isEqualTo(UserStatus.ACTIVE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("GetUser Use Case")
|
||||
class GetUserTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private GetUser getUser;
|
||||
private ActorId performedBy;
|
||||
private User testUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
getUser = new GetUser(userRepository, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
testUser = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnUser_When_UserExistsAndAuthorized")
|
||||
void should_ReturnUser_When_UserExistsAndAuthorized() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_VIEW)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
|
||||
Result<UserError, UserDTO> result = getUser.execute("user-1", performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().username()).isEqualTo("john.doe");
|
||||
assertThat(result.unsafeGetValue().email()).isEqualTo("john@example.com");
|
||||
assertThat(result.unsafeGetValue().id()).isEqualTo("user-1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_VIEW)).thenReturn(false);
|
||||
|
||||
Result<UserError, UserDTO> result = getUser.execute("user-1", performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).findById(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_VIEW)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("nonexistent"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = getUser.execute("nonexistent", performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import de.effigenix.shared.security.BranchId;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("ListUsers Use Case")
|
||||
class ListUsersTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private ListUsers listUsers;
|
||||
private ActorId performedBy;
|
||||
private User user1;
|
||||
private User user2;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
listUsers = new ListUsers(userRepository, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
user1 = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
user2 = User.reconstitute(
|
||||
UserId.of("user-2"), "jane.doe", "jane@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnAllUsers_When_Authorized")
|
||||
void should_ReturnAllUsers_When_Authorized() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LIST)).thenReturn(true);
|
||||
when(userRepository.findAll()).thenReturn(Result.success(List.of(user1, user2)));
|
||||
|
||||
Result<UserError, List<UserDTO>> result = listUsers.execute(performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue()).hasSize(2);
|
||||
assertThat(result.unsafeGetValue()).extracting(UserDTO::username)
|
||||
.containsExactlyInAnyOrder("john.doe", "jane.doe");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnEmptyList_When_NoUsersExist")
|
||||
void should_ReturnEmptyList_When_NoUsersExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LIST)).thenReturn(true);
|
||||
when(userRepository.findAll()).thenReturn(Result.success(List.of()));
|
||||
|
||||
Result<UserError, List<UserDTO>> result = listUsers.execute(performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LIST)).thenReturn(false);
|
||||
|
||||
Result<UserError, List<UserDTO>> result = listUsers.execute(performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).findAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnBranchUsers_When_FilteredByBranch")
|
||||
void should_ReturnBranchUsers_When_FilteredByBranch() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LIST)).thenReturn(true);
|
||||
when(userRepository.findByBranchId("branch-1")).thenReturn(Result.success(List.of(user1, user2)));
|
||||
|
||||
Result<UserError, List<UserDTO>> result = listUsers.executeForBranch(BranchId.of("branch-1"), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue()).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermissionForBranchList")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermissionForBranchList() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LIST)).thenReturn(false);
|
||||
|
||||
Result<UserError, List<UserDTO>> result = listUsers.executeForBranch(BranchId.of("branch-1"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).findByBranchId(anyString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.command.LockUserCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("LockUser Use Case")
|
||||
class LockUserTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private LockUser lockUser;
|
||||
private ActorId performedBy;
|
||||
private User activeUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
lockUser = new LockUser(userRepository, auditLogger, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
activeUser = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LockUser_When_UserIsActive")
|
||||
void should_LockUser_When_UserIsActive() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(activeUser)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = lockUser.execute(new LockUserCommand("user-1"), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.LOCKED);
|
||||
verify(userRepository).save(argThat(user -> user.status() == UserStatus.LOCKED));
|
||||
verify(auditLogger).log(eq(AuditEvent.USER_LOCKED), eq("user-1"), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(false);
|
||||
|
||||
Result<UserError, UserDTO> result = lockUser.execute(new LockUserCommand("user-1"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_UserIdIsBlank")
|
||||
void should_FailWithInvalidInput_When_UserIdIsBlank() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true);
|
||||
|
||||
Result<UserError, UserDTO> result = lockUser.execute(new LockUserCommand(""), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("nonexistent"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = lockUser.execute(new LockUserCommand("nonexistent"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidStatusTransition_When_UserAlreadyLocked")
|
||||
void should_FailWithInvalidStatusTransition_When_UserAlreadyLocked() {
|
||||
User lockedUser = User.reconstitute(
|
||||
UserId.of("user-2"), "jane.doe", "jane@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null
|
||||
);
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-2"))).thenReturn(Result.success(Optional.of(lockedUser)));
|
||||
|
||||
Result<UserError, UserDTO> result = lockUser.execute(new LockUserCommand("user-2"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidStatusTransition_When_UserIsInactive")
|
||||
void should_FailWithInvalidStatusTransition_When_UserIsInactive() {
|
||||
User inactiveUser = User.reconstitute(
|
||||
UserId.of("user-3"), "bob", "bob@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.INACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_LOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-3"))).thenReturn(Result.success(Optional.of(inactiveUser)));
|
||||
|
||||
Result<UserError, UserDTO> result = lockUser.execute(new LockUserCommand("user-3"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.command.RemoveRoleCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("RemoveRole Use Case")
|
||||
class RemoveRoleTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private RoleRepository roleRepository;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private RemoveRole removeRole;
|
||||
private ActorId performedBy;
|
||||
private Role workerRole;
|
||||
private User userWithRole;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
removeRole = new RemoveRole(userRepository, roleRepository, auditLogger, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
workerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, new HashSet<>(), "Production Worker");
|
||||
userWithRole = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(Set.of(workerRole)), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_RemoveRole_When_ValidCommandProvided")
|
||||
void should_RemoveRole_When_ValidCommandProvided() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(userWithRole)));
|
||||
when(roleRepository.findByName(RoleName.PRODUCTION_WORKER)).thenReturn(Result.success(Optional.of(workerRole)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = removeRole.execute(
|
||||
new RemoveRoleCommand("user-1", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().roles()).isEmpty();
|
||||
verify(userRepository).save(argThat(user -> user.roles().isEmpty()));
|
||||
verify(auditLogger).log(eq(AuditEvent.ROLE_REMOVED), eq("user-1"), anyString(), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)).thenReturn(false);
|
||||
|
||||
Result<UserError, UserDTO> result = removeRole.execute(
|
||||
new RemoveRoleCommand("user-1", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_UserIdIsBlank")
|
||||
void should_FailWithInvalidInput_When_UserIdIsBlank() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)).thenReturn(true);
|
||||
|
||||
Result<UserError, UserDTO> result = removeRole.execute(
|
||||
new RemoveRoleCommand("", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_RoleNameIsNull")
|
||||
void should_FailWithInvalidInput_When_RoleNameIsNull() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)).thenReturn(true);
|
||||
|
||||
Result<UserError, UserDTO> result = removeRole.execute(
|
||||
new RemoveRoleCommand("user-1", null), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("nonexistent"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = removeRole.execute(
|
||||
new RemoveRoleCommand("nonexistent", RoleName.PRODUCTION_WORKER), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithRoleNotFound_When_RoleDoesNotExist")
|
||||
void should_FailWithRoleNotFound_When_RoleDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.ROLE_REMOVE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(userWithRole)));
|
||||
when(roleRepository.findByName(RoleName.ADMIN)).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = removeRole.execute(
|
||||
new RemoveRoleCommand("user-1", RoleName.ADMIN), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.RoleNotFound.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.command.UnlockUserCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("UnlockUser Use Case")
|
||||
class UnlockUserTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private UnlockUser unlockUser;
|
||||
private ActorId performedBy;
|
||||
private User lockedUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
unlockUser = new UnlockUser(userRepository, auditLogger, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
lockedUser = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.LOCKED, LocalDateTime.now(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UnlockUser_When_UserIsLocked")
|
||||
void should_UnlockUser_When_UserIsLocked() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(lockedUser)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = unlockUser.execute(new UnlockUserCommand("user-1"), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.ACTIVE);
|
||||
verify(userRepository).save(argThat(user -> user.status() == UserStatus.ACTIVE));
|
||||
verify(auditLogger).log(eq(AuditEvent.USER_UNLOCKED), eq("user-1"), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(false);
|
||||
|
||||
Result<UserError, UserDTO> result = unlockUser.execute(new UnlockUserCommand("user-1"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidInput_When_UserIdIsBlank")
|
||||
void should_FailWithInvalidInput_When_UserIdIsBlank() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true);
|
||||
|
||||
Result<UserError, UserDTO> result = unlockUser.execute(new UnlockUserCommand(""), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidInput.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("nonexistent"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = unlockUser.execute(new UnlockUserCommand("nonexistent"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidStatusTransition_When_UserIsActive")
|
||||
void should_FailWithInvalidStatusTransition_When_UserIsActive() {
|
||||
User activeUser = User.reconstitute(
|
||||
UserId.of("user-2"), "jane.doe", "jane@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-2"))).thenReturn(Result.success(Optional.of(activeUser)));
|
||||
|
||||
Result<UserError, UserDTO> result = unlockUser.execute(new UnlockUserCommand("user-2"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithInvalidStatusTransition_When_UserIsInactive")
|
||||
void should_FailWithInvalidStatusTransition_When_UserIsInactive() {
|
||||
User inactiveUser = User.reconstitute(
|
||||
UserId.of("user-3"), "bob", "bob@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.INACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UNLOCK)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-3"))).thenReturn(Result.success(Optional.of(inactiveUser)));
|
||||
|
||||
Result<UserError, UserDTO> result = unlockUser.execute(new UnlockUserCommand("user-3"), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
package de.effigenix.application.usermanagement;
|
||||
|
||||
import de.effigenix.application.usermanagement.command.UpdateUserCommand;
|
||||
import de.effigenix.application.usermanagement.dto.UserDTO;
|
||||
import de.effigenix.domain.usermanagement.*;
|
||||
import de.effigenix.shared.common.Result;
|
||||
import de.effigenix.shared.security.ActorId;
|
||||
import de.effigenix.shared.security.AuthorizationPort;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DisplayName("UpdateUser Use Case")
|
||||
class UpdateUserTest {
|
||||
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private AuditLogger auditLogger;
|
||||
@Mock private AuthorizationPort authPort;
|
||||
|
||||
private UpdateUser updateUser;
|
||||
private ActorId performedBy;
|
||||
private User testUser;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
updateUser = new UpdateUser(userRepository, auditLogger, authPort);
|
||||
performedBy = ActorId.of("admin-user");
|
||||
testUser = User.reconstitute(
|
||||
UserId.of("user-1"), "john.doe", "john@example.com",
|
||||
new PasswordHash("$2a$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW"),
|
||||
new HashSet<>(), "branch-1", UserStatus.ACTIVE, LocalDateTime.now(), null
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateEmail_When_NewEmailProvided")
|
||||
void should_UpdateEmail_When_NewEmailProvided() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(userRepository.existsByEmail("newemail@example.com")).thenReturn(Result.success(false));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("user-1", "newemail@example.com", null), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().email()).isEqualTo("newemail@example.com");
|
||||
verify(userRepository).save(argThat(user -> "newemail@example.com".equals(user.email())));
|
||||
verify(auditLogger).log(eq(AuditEvent.USER_UPDATED), eq("user-1"), eq(performedBy));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateBranch_When_NewBranchProvided")
|
||||
void should_UpdateBranch_When_NewBranchProvided() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("user-1", null, "branch-2"), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().branchId()).isEqualTo("branch-2");
|
||||
verify(userRepository).save(argThat(user -> "branch-2".equals(user.branchId())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateEmailAndBranch_When_BothProvided")
|
||||
void should_UpdateEmailAndBranch_When_BothProvided() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(userRepository.existsByEmail("new@example.com")).thenReturn(Result.success(false));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("user-1", "new@example.com", "branch-2"), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().email()).isEqualTo("new@example.com");
|
||||
assertThat(result.unsafeGetValue().branchId()).isEqualTo("branch-2");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUnauthorized_When_ActorLacksPermission")
|
||||
void should_FailWithUnauthorized_When_ActorLacksPermission() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(false);
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("user-1", "new@example.com", null), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.Unauthorized.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithUserNotFound_When_UserDoesNotExist")
|
||||
void should_FailWithUserNotFound_When_UserDoesNotExist() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("nonexistent"))).thenReturn(Result.success(Optional.empty()));
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("nonexistent", "new@example.com", null), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.UserNotFound.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailWithEmailAlreadyExists_When_DuplicateEmail")
|
||||
void should_FailWithEmailAlreadyExists_When_DuplicateEmail() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(userRepository.existsByEmail("taken@example.com")).thenReturn(Result.success(true));
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("user-1", "taken@example.com", null), performedBy);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.EmailAlreadyExists.class);
|
||||
verify(userRepository, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_NotCheckEmailUniqueness_When_EmailUnchanged")
|
||||
void should_NotCheckEmailUniqueness_When_EmailUnchanged() {
|
||||
when(authPort.can(performedBy, UserManagementAction.USER_UPDATE)).thenReturn(true);
|
||||
when(userRepository.findById(UserId.of("user-1"))).thenReturn(Result.success(Optional.of(testUser)));
|
||||
when(userRepository.save(any())).thenReturn(Result.success(null));
|
||||
|
||||
Result<UserError, UserDTO> result = updateUser.execute(
|
||||
new UpdateUserCommand("user-1", "john@example.com", "branch-2"), performedBy);
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
verify(userRepository, never()).existsByEmail(anyString());
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ import static org.assertj.core.api.Assertions.*;
|
|||
|
||||
/**
|
||||
* Unit tests for Role Entity.
|
||||
* Tests validation, permission management, factory methods, and equality.
|
||||
* Tests validation, immutable permission management, factory methods, and equality.
|
||||
*/
|
||||
@DisplayName("Role Entity")
|
||||
class RoleTest {
|
||||
|
|
@ -25,21 +25,15 @@ class RoleTest {
|
|||
void setUp() {
|
||||
roleId = RoleId.generate();
|
||||
roleName = RoleName.ADMIN;
|
||||
permissions = new HashSet<>(Set.of(
|
||||
Permission.USER_READ,
|
||||
Permission.USER_WRITE,
|
||||
Permission.ROLE_READ
|
||||
));
|
||||
permissions = new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE, Permission.ROLE_READ));
|
||||
description = "Administrator role with full access";
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CreateRole_When_ValidDataProvided")
|
||||
void should_CreateRole_When_ValidDataProvided() {
|
||||
// Act
|
||||
Role role = Role.reconstitute(roleId, roleName, permissions, description);
|
||||
|
||||
// Assert
|
||||
assertThat(role.id()).isEqualTo(roleId);
|
||||
assertThat(role.name()).isEqualTo(roleName);
|
||||
assertThat(role.permissions()).contains(Permission.USER_READ, Permission.USER_WRITE);
|
||||
|
|
@ -49,10 +43,8 @@ class RoleTest {
|
|||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullRoleNameProvided")
|
||||
void should_ReturnFailure_When_NullRoleNameProvided() {
|
||||
// Act
|
||||
Result<UserError, Role> result = Role.create(null, permissions, description);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullRole.class);
|
||||
}
|
||||
|
|
@ -60,30 +52,24 @@ class RoleTest {
|
|||
@Test
|
||||
@DisplayName("should_CreateRoleWithEmptyPermissions_When_NullPermissionsProvided")
|
||||
void should_CreateRoleWithEmptyPermissions_When_NullPermissionsProvided() {
|
||||
// Act
|
||||
Role role = Role.reconstitute(roleId, roleName, null, description);
|
||||
|
||||
// Assert
|
||||
assertThat(role.permissions()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CreateRoleWithNullDescription_When_DescriptionNotProvided")
|
||||
void should_CreateRoleWithNullDescription_When_DescriptionNotProvided() {
|
||||
// Act
|
||||
Role role = Role.reconstitute(roleId, roleName, permissions, null);
|
||||
|
||||
// Assert
|
||||
assertThat(role.description()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CreateRole_When_FactoryMethodCalled")
|
||||
void should_CreateRole_When_FactoryMethodCalled() {
|
||||
// Act
|
||||
Role role = Role.create(roleName, permissions, description).unsafeGetValue();
|
||||
|
||||
// Assert
|
||||
assertThat(role.id()).isNotNull();
|
||||
assertThat(role.name()).isEqualTo(roleName);
|
||||
assertThat(role.permissions()).isEqualTo(permissions);
|
||||
|
|
@ -91,135 +77,106 @@ class RoleTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_AddPermission_When_ValidPermissionProvided")
|
||||
void should_AddPermission_When_ValidPermissionProvided() {
|
||||
// Arrange
|
||||
@DisplayName("should_ReturnNewRoleWithPermission_When_AddPermissionCalled")
|
||||
void should_ReturnNewRoleWithPermission_When_AddPermissionCalled() {
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = role.addPermission(Permission.USER_DELETE);
|
||||
Result<UserError, Role> result = role.addPermission(Permission.USER_DELETE);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(role.permissions()).contains(Permission.USER_DELETE);
|
||||
assertThat(result.unsafeGetValue().permissions()).contains(Permission.USER_DELETE);
|
||||
assertThat(role.permissions()).doesNotContain(Permission.USER_DELETE); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullPermissionProvidedToAddPermission")
|
||||
void should_ReturnFailure_When_NullPermissionProvidedToAddPermission() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = role.addPermission(null);
|
||||
Result<UserError, Role> result = role.addPermission(null);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullRole.class);
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullPermission.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_AddMultiplePermissions_When_MethodCalledRepeatedly")
|
||||
void should_AddMultiplePermissions_When_MethodCalledRepeatedly() {
|
||||
// Arrange
|
||||
@DisplayName("should_ReturnNewRoleWithMultiplePermissions_When_AddedSequentially")
|
||||
void should_ReturnNewRoleWithMultiplePermissions_When_AddedSequentially() {
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);
|
||||
|
||||
// Act
|
||||
role.addPermission(Permission.USER_READ);
|
||||
role.addPermission(Permission.USER_WRITE);
|
||||
role.addPermission(Permission.USER_DELETE);
|
||||
Role updated = role.addPermission(Permission.USER_READ).unsafeGetValue()
|
||||
.addPermission(Permission.USER_WRITE).unsafeGetValue()
|
||||
.addPermission(Permission.USER_DELETE).unsafeGetValue();
|
||||
|
||||
// Assert
|
||||
assertThat(role.permissions()).hasSize(3);
|
||||
assertThat(role.permissions()).contains(
|
||||
Permission.USER_READ,
|
||||
Permission.USER_WRITE,
|
||||
Permission.USER_DELETE
|
||||
);
|
||||
assertThat(updated.permissions()).hasSize(3);
|
||||
assertThat(updated.permissions()).contains(Permission.USER_READ, Permission.USER_WRITE, Permission.USER_DELETE);
|
||||
assertThat(role.permissions()).isEmpty(); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_NotAddDuplicatePermission_When_SamePermissionAddedTwice")
|
||||
void should_NotAddDuplicatePermission_When_SamePermissionAddedTwice() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);
|
||||
|
||||
// Act
|
||||
role.addPermission(Permission.USER_READ);
|
||||
role.addPermission(Permission.USER_READ);
|
||||
Role updated = role.addPermission(Permission.USER_READ).unsafeGetValue()
|
||||
.addPermission(Permission.USER_READ).unsafeGetValue();
|
||||
|
||||
// Assert
|
||||
assertThat(role.permissions()).hasSize(1);
|
||||
assertThat(updated.permissions()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_RemovePermission_When_PermissionProvided")
|
||||
void should_RemovePermission_When_PermissionProvided() {
|
||||
// Arrange
|
||||
Set<Permission> initialPermissions = new HashSet<>(Set.of(
|
||||
Permission.USER_READ,
|
||||
Permission.USER_WRITE
|
||||
));
|
||||
@DisplayName("should_ReturnNewRoleWithoutPermission_When_RemovePermissionCalled")
|
||||
void should_ReturnNewRoleWithoutPermission_When_RemovePermissionCalled() {
|
||||
Set<Permission> initialPermissions = new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE));
|
||||
Role role = Role.reconstitute(roleId, roleName, initialPermissions, description);
|
||||
|
||||
// Act
|
||||
role.removePermission(Permission.USER_READ);
|
||||
Result<UserError, Role> result = role.removePermission(Permission.USER_READ);
|
||||
|
||||
// Assert
|
||||
assertThat(role.permissions()).doesNotContain(Permission.USER_READ);
|
||||
assertThat(role.permissions()).contains(Permission.USER_WRITE);
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().permissions()).doesNotContain(Permission.USER_READ);
|
||||
assertThat(result.unsafeGetValue().permissions()).contains(Permission.USER_WRITE);
|
||||
assertThat(role.permissions()).contains(Permission.USER_READ); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_NotThrowException_When_RemovingNonExistentPermission")
|
||||
void should_NotThrowException_When_RemovingNonExistentPermission() {
|
||||
// Arrange
|
||||
@DisplayName("should_ReturnFailure_When_NullPermissionProvidedToRemovePermission")
|
||||
void should_ReturnFailure_When_NullPermissionProvidedToRemovePermission() {
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(), description);
|
||||
|
||||
// Act & Assert
|
||||
assertThatCode(() -> role.removePermission(Permission.USER_READ))
|
||||
.doesNotThrowAnyException();
|
||||
Result<UserError, Role> result = role.removePermission(null);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullPermission.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateDescription_When_NewDescriptionProvided")
|
||||
void should_UpdateDescription_When_NewDescriptionProvided() {
|
||||
// Arrange
|
||||
@DisplayName("should_ReturnNewRoleWithDescription_When_UpdateDescriptionCalled")
|
||||
void should_ReturnNewRoleWithDescription_When_UpdateDescriptionCalled() {
|
||||
Role role = Role.reconstitute(roleId, roleName, permissions, description);
|
||||
String newDescription = "Updated administrator role";
|
||||
|
||||
// Act
|
||||
role.updateDescription(newDescription);
|
||||
Result<UserError, Role> result = role.updateDescription(newDescription);
|
||||
|
||||
// Assert
|
||||
assertThat(role.description()).isEqualTo(newDescription);
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().description()).isEqualTo(newDescription);
|
||||
assertThat(role.description()).isEqualTo(description); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_SetDescriptionToNull_When_NullDescriptionProvided")
|
||||
void should_SetDescriptionToNull_When_NullDescriptionProvided() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(roleId, roleName, permissions, description);
|
||||
|
||||
// Act
|
||||
role.updateDescription(null);
|
||||
Role updated = role.updateDescription(null).unsafeGetValue();
|
||||
|
||||
// Assert
|
||||
assertThat(role.description()).isNull();
|
||||
assertThat(updated.description()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CheckPermission_When_RoleHasPermission")
|
||||
void should_CheckPermission_When_RoleHasPermission() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(
|
||||
roleId,
|
||||
roleName,
|
||||
new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE)),
|
||||
description
|
||||
);
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(Set.of(Permission.USER_READ, Permission.USER_WRITE)), description);
|
||||
|
||||
// Act & Assert
|
||||
assertThat(role.hasPermission(Permission.USER_READ)).isTrue();
|
||||
assertThat(role.hasPermission(Permission.USER_WRITE)).isTrue();
|
||||
}
|
||||
|
|
@ -227,26 +184,17 @@ class RoleTest {
|
|||
@Test
|
||||
@DisplayName("should_CheckPermission_When_RoleLacksPermission")
|
||||
void should_CheckPermission_When_RoleLacksPermission() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(
|
||||
roleId,
|
||||
roleName,
|
||||
new HashSet<>(Set.of(Permission.USER_READ)),
|
||||
description
|
||||
);
|
||||
Role role = Role.reconstitute(roleId, roleName, new HashSet<>(Set.of(Permission.USER_READ)), description);
|
||||
|
||||
// Act & Assert
|
||||
assertThat(role.hasPermission(Permission.USER_DELETE)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_BeEqualToAnother_When_BothHaveSameId")
|
||||
void should_BeEqualToAnother_When_BothHaveSameId() {
|
||||
// Arrange
|
||||
Role role1 = Role.reconstitute(roleId, RoleName.ADMIN, permissions, description);
|
||||
Role role2 = Role.reconstitute(roleId, RoleName.PRODUCTION_MANAGER, new HashSet<>(), "Different role");
|
||||
|
||||
// Act & Assert
|
||||
assertThat(role1).isEqualTo(role2);
|
||||
assertThat(role1.hashCode()).isEqualTo(role2.hashCode());
|
||||
}
|
||||
|
|
@ -254,24 +202,19 @@ class RoleTest {
|
|||
@Test
|
||||
@DisplayName("should_NotBeEqual_When_DifferentIds")
|
||||
void should_NotBeEqual_When_DifferentIds() {
|
||||
// Arrange
|
||||
Role role1 = Role.reconstitute(RoleId.generate(), roleName, permissions, description);
|
||||
Role role2 = Role.reconstitute(RoleId.generate(), roleName, permissions, description);
|
||||
|
||||
// Act & Assert
|
||||
assertThat(role1).isNotEqualTo(role2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnUnmodifiablePermissionSet_When_PermissionsRetrieved")
|
||||
void should_ReturnUnmodifiablePermissionSet_When_PermissionsRetrieved() {
|
||||
// Arrange
|
||||
Role role = Role.reconstitute(roleId, roleName, permissions, description);
|
||||
|
||||
// Act
|
||||
Set<Permission> retrievedPermissions = role.permissions();
|
||||
|
||||
// Assert
|
||||
assertThatThrownBy(() -> retrievedPermissions.add(Permission.USER_DELETE))
|
||||
.isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
|
@ -279,49 +222,23 @@ class RoleTest {
|
|||
@Test
|
||||
@DisplayName("should_PreserveImmutabilityOfPermissions_When_PermissionsModified")
|
||||
void should_PreserveImmutabilityOfPermissions_When_PermissionsModified() {
|
||||
// Arrange
|
||||
Set<Permission> initialPermissions = new HashSet<>(Set.of(Permission.USER_READ));
|
||||
Role role = Role.reconstitute(roleId, roleName, initialPermissions, description);
|
||||
|
||||
// Act - Modify the original set passed to constructor
|
||||
initialPermissions.add(Permission.USER_WRITE);
|
||||
|
||||
// Assert - Role should not be affected
|
||||
assertThat(role.permissions()).doesNotContain(Permission.USER_WRITE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_SupportMultipleRoleNames_When_DifferentNamesUsed")
|
||||
void should_SupportMultipleRoleNames_When_DifferentNamesUsed() {
|
||||
// Arrange & Act
|
||||
Role adminRole = Role.reconstitute(RoleId.generate(), RoleName.ADMIN, permissions, "Admin");
|
||||
Role managerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_MANAGER, permissions, "Manager");
|
||||
Role workerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, permissions, "Worker");
|
||||
|
||||
// Assert
|
||||
assertThat(adminRole.name()).isEqualTo(RoleName.ADMIN);
|
||||
assertThat(managerRole.name()).isEqualTo(RoleName.PRODUCTION_MANAGER);
|
||||
assertThat(workerRole.name()).isEqualTo(RoleName.PRODUCTION_WORKER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_AllowDifferentPermissionSets_When_MultipleRolesCreated")
|
||||
void should_AllowDifferentPermissionSets_When_MultipleRolesCreated() {
|
||||
// Arrange
|
||||
Set<Permission> adminPerms = new HashSet<>(Set.of(
|
||||
Permission.USER_READ, Permission.USER_WRITE, Permission.USER_DELETE
|
||||
));
|
||||
Set<Permission> readerPerms = new HashSet<>(Set.of(
|
||||
Permission.USER_READ
|
||||
));
|
||||
|
||||
// Act
|
||||
Role adminRole = Role.reconstitute(RoleId.generate(), RoleName.ADMIN, adminPerms, "Admin");
|
||||
Role readerRole = Role.reconstitute(RoleId.generate(), RoleName.PRODUCTION_WORKER, readerPerms, "Reader");
|
||||
|
||||
// Assert
|
||||
assertThat(adminRole.permissions()).hasSize(3);
|
||||
assertThat(readerRole.permissions()).hasSize(1);
|
||||
assertThat(adminRole.permissions()).isNotEqualTo(readerRole.permissions());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.*;
|
|||
|
||||
/**
|
||||
* Unit tests for User Entity.
|
||||
* Tests validation, business methods, status management, role assignment, and permissions.
|
||||
* Tests validation, immutable business methods, status transitions, role assignment, and permissions.
|
||||
*/
|
||||
@DisplayName("User Entity")
|
||||
class UserTest {
|
||||
|
|
@ -41,20 +41,8 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_CreateUser_When_ValidDataProvided")
|
||||
void should_CreateUser_When_ValidDataProvided() {
|
||||
// Act
|
||||
User user = User.reconstitute(
|
||||
userId,
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
roles,
|
||||
branchId,
|
||||
UserStatus.ACTIVE,
|
||||
createdAt,
|
||||
null
|
||||
);
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.ACTIVE, createdAt, null);
|
||||
|
||||
// Assert
|
||||
assertThat(user.id()).isEqualTo(userId);
|
||||
assertThat(user.username()).isEqualTo(username);
|
||||
assertThat(user.email()).isEqualTo(email);
|
||||
|
|
@ -68,22 +56,10 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_SetDefaultCreatedAtToNow_When_NullProvidedForCreatedAt")
|
||||
void should_SetDefaultCreatedAtToNow_When_NullProvidedForCreatedAt() {
|
||||
// Act
|
||||
LocalDateTime before = LocalDateTime.now();
|
||||
User user = User.reconstitute(
|
||||
userId,
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
roles,
|
||||
branchId,
|
||||
UserStatus.ACTIVE,
|
||||
null,
|
||||
null
|
||||
);
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.ACTIVE, null, null);
|
||||
LocalDateTime after = LocalDateTime.now();
|
||||
|
||||
// Assert
|
||||
assertThat(user.createdAt()).isNotNull();
|
||||
assertThat(user.createdAt()).isBetween(before, after);
|
||||
}
|
||||
|
|
@ -91,10 +67,8 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullUsernameProvided")
|
||||
void should_ReturnFailure_When_NullUsernameProvided() {
|
||||
// Act
|
||||
Result<UserError, User> result = User.create(null, email, passwordHash, roles, branchId);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidUsername.class);
|
||||
}
|
||||
|
|
@ -102,10 +76,8 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_EmptyUsernameProvided")
|
||||
void should_ReturnFailure_When_EmptyUsernameProvided() {
|
||||
// Act
|
||||
Result<UserError, User> result = User.create("", email, passwordHash, roles, branchId);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidUsername.class);
|
||||
}
|
||||
|
|
@ -113,10 +85,8 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_InvalidEmailProvided")
|
||||
void should_ReturnFailure_When_InvalidEmailProvided() {
|
||||
// Act
|
||||
Result<UserError, User> result = User.create(username, "invalid-email", passwordHash, roles, branchId);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidEmail.class);
|
||||
}
|
||||
|
|
@ -125,10 +95,8 @@ class UserTest {
|
|||
@ValueSource(strings = {"", " ", "notanemail"})
|
||||
@DisplayName("should_ReturnFailure_When_InvalidEmailFormatsProvided")
|
||||
void should_ReturnFailure_When_InvalidEmailFormatsProvided(String invalidEmail) {
|
||||
// Act
|
||||
Result<UserError, User> result = User.create(username, invalidEmail, passwordHash, roles, branchId);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidEmail.class);
|
||||
}
|
||||
|
|
@ -136,10 +104,8 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullPasswordHashProvided")
|
||||
void should_ReturnFailure_When_NullPasswordHashProvided() {
|
||||
// Act
|
||||
Result<UserError, User> result = User.create(username, email, null, roles, branchId);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullPasswordHash.class);
|
||||
}
|
||||
|
|
@ -147,16 +113,8 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_CreateUser_When_FactoryMethodCalled")
|
||||
void should_CreateUser_When_FactoryMethodCalled() {
|
||||
// Act
|
||||
User user = User.create(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
roles,
|
||||
branchId
|
||||
).unsafeGetValue();
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
// Assert
|
||||
assertThat(user.username()).isEqualTo(username);
|
||||
assertThat(user.email()).isEqualTo(email);
|
||||
assertThat(user.passwordHash()).isEqualTo(passwordHash);
|
||||
|
|
@ -168,137 +126,181 @@ class UserTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateLastLogin_When_MethodCalled")
|
||||
void should_UpdateLastLogin_When_MethodCalled() {
|
||||
// Arrange
|
||||
@DisplayName("should_ReturnNewUserWithLastLogin_When_WithLastLoginCalled")
|
||||
void should_ReturnNewUserWithLastLogin_When_WithLastLoginCalled() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
// Act
|
||||
user.updateLastLogin(now);
|
||||
User updated = user.withLastLogin(now).unsafeGetValue();
|
||||
|
||||
// Assert
|
||||
assertThat(user.lastLogin()).isEqualTo(now);
|
||||
assertThat(updated.lastLogin()).isEqualTo(now);
|
||||
assertThat(user.lastLogin()).isNull(); // original unchanged
|
||||
assertThat(updated.id()).isEqualTo(user.id());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ChangePassword_When_NewHashProvided")
|
||||
void should_ChangePassword_When_NewHashProvided() {
|
||||
// Arrange
|
||||
@DisplayName("should_ReturnNewUserWithPassword_When_ChangePasswordCalled")
|
||||
void should_ReturnNewUserWithPassword_When_ChangePasswordCalled() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
PasswordHash newHash = new PasswordHash("$2b$12$R9h/cIPz0gi.URNN3kh2OPST9EBwVeL00lzQRYe3z08MZx3e8YCWW");
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = user.changePassword(newHash);
|
||||
Result<UserError, User> result = user.changePassword(newHash);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(user.passwordHash()).isEqualTo(newHash);
|
||||
assertThat(user.passwordHash()).isNotEqualTo(passwordHash);
|
||||
assertThat(result.unsafeGetValue().passwordHash()).isEqualTo(newHash);
|
||||
assertThat(user.passwordHash()).isEqualTo(passwordHash); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullPasswordHashProvidedToChangePassword")
|
||||
void should_ReturnFailure_When_NullPasswordHashProvidedToChangePassword() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = user.changePassword(null);
|
||||
Result<UserError, User> result = user.changePassword(null);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullPasswordHash.class);
|
||||
}
|
||||
|
||||
// ==================== Status Transition Tests ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("should_LockUser_When_LockMethodCalled")
|
||||
void should_LockUser_When_LockMethodCalled() {
|
||||
// Arrange
|
||||
@DisplayName("should_LockUser_When_StatusIsActive")
|
||||
void should_LockUser_When_StatusIsActive() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
assertThat(user.status()).isEqualTo(UserStatus.ACTIVE);
|
||||
|
||||
// Act
|
||||
user.lock();
|
||||
Result<UserError, User> result = user.lock();
|
||||
|
||||
// Assert
|
||||
assertThat(user.status()).isEqualTo(UserStatus.LOCKED);
|
||||
assertThat(user.isLocked()).isTrue();
|
||||
assertThat(user.isActive()).isFalse();
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.LOCKED);
|
||||
assertThat(result.unsafeGetValue().isLocked()).isTrue();
|
||||
assertThat(user.status()).isEqualTo(UserStatus.ACTIVE); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UnlockUser_When_UnlockMethodCalled")
|
||||
void should_UnlockUser_When_UnlockMethodCalled() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
user.lock();
|
||||
assertThat(user.status()).isEqualTo(UserStatus.LOCKED);
|
||||
@DisplayName("should_FailLock_When_StatusIsLocked")
|
||||
void should_FailLock_When_StatusIsLocked() {
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.LOCKED, createdAt, null);
|
||||
|
||||
// Act
|
||||
user.unlock();
|
||||
Result<UserError, User> result = user.lock();
|
||||
|
||||
// Assert
|
||||
assertThat(user.status()).isEqualTo(UserStatus.ACTIVE);
|
||||
assertThat(user.isActive()).isTrue();
|
||||
assertThat(user.isLocked()).isFalse();
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_DeactivateUser_When_DeactivateMethodCalled")
|
||||
void should_DeactivateUser_When_DeactivateMethodCalled() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
@DisplayName("should_FailLock_When_StatusIsInactive")
|
||||
void should_FailLock_When_StatusIsInactive() {
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.INACTIVE, createdAt, null);
|
||||
|
||||
// Act
|
||||
user.deactivate();
|
||||
Result<UserError, User> result = user.lock();
|
||||
|
||||
// Assert
|
||||
assertThat(user.status()).isEqualTo(UserStatus.INACTIVE);
|
||||
assertThat(user.isActive()).isFalse();
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ActivateUser_When_ActivateMethodCalled")
|
||||
void should_ActivateUser_When_ActivateMethodCalled() {
|
||||
// Arrange
|
||||
@DisplayName("should_UnlockUser_When_StatusIsLocked")
|
||||
void should_UnlockUser_When_StatusIsLocked() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
user.deactivate();
|
||||
assertThat(user.status()).isEqualTo(UserStatus.INACTIVE);
|
||||
User locked = user.lock().unsafeGetValue();
|
||||
|
||||
// Act
|
||||
user.activate();
|
||||
Result<UserError, User> result = locked.unlock();
|
||||
|
||||
// Assert
|
||||
assertThat(user.status()).isEqualTo(UserStatus.ACTIVE);
|
||||
assertThat(user.isActive()).isTrue();
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.ACTIVE);
|
||||
assertThat(result.unsafeGetValue().isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailUnlock_When_StatusIsActive")
|
||||
void should_FailUnlock_When_StatusIsActive() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
Result<UserError, User> result = user.unlock();
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_DeactivateUser_When_StatusIsActive")
|
||||
void should_DeactivateUser_When_StatusIsActive() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
Result<UserError, User> result = user.deactivate();
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.INACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_DeactivateUser_When_StatusIsLocked")
|
||||
void should_DeactivateUser_When_StatusIsLocked() {
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.LOCKED, createdAt, null);
|
||||
|
||||
Result<UserError, User> result = user.deactivate();
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.INACTIVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailDeactivate_When_StatusIsInactive")
|
||||
void should_FailDeactivate_When_StatusIsInactive() {
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.INACTIVE, createdAt, null);
|
||||
|
||||
Result<UserError, User> result = user.deactivate();
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ActivateUser_When_StatusIsInactive")
|
||||
void should_ActivateUser_When_StatusIsInactive() {
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.INACTIVE, createdAt, null);
|
||||
|
||||
Result<UserError, User> result = user.activate();
|
||||
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().status()).isEqualTo(UserStatus.ACTIVE);
|
||||
assertThat(result.unsafeGetValue().isActive()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_FailActivate_When_StatusIsActive")
|
||||
void should_FailActivate_When_StatusIsActive() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
Result<UserError, User> result = user.activate();
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidStatusTransition.class);
|
||||
}
|
||||
|
||||
// ==================== Role Tests ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("should_AssignRole_When_RoleProvided")
|
||||
void should_AssignRole_When_RoleProvided() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(), branchId).unsafeGetValue();
|
||||
Role role = createTestRole("ADMIN");
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = user.assignRole(role);
|
||||
Result<UserError, User> result = user.assignRole(role);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(user.roles()).contains(role);
|
||||
assertThat(result.unsafeGetValue().roles()).contains(role);
|
||||
assertThat(user.roles()).doesNotContain(role); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullRoleProvidedToAssignRole")
|
||||
void should_ReturnFailure_When_NullRoleProvidedToAssignRole() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = user.assignRole(null);
|
||||
Result<UserError, User> result = user.assignRole(null);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullRole.class);
|
||||
}
|
||||
|
|
@ -306,43 +308,49 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_RemoveRole_When_RoleProvided")
|
||||
void should_RemoveRole_When_RoleProvided() {
|
||||
// Arrange
|
||||
Role role = createTestRole("ADMIN");
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(Set.of(role)), branchId).unsafeGetValue();
|
||||
assertThat(user.roles()).contains(role);
|
||||
|
||||
// Act
|
||||
user.removeRole(role);
|
||||
Result<UserError, User> result = user.removeRole(role);
|
||||
|
||||
// Assert
|
||||
assertThat(user.roles()).doesNotContain(role);
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().roles()).doesNotContain(role);
|
||||
assertThat(user.roles()).contains(role); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_NullRoleProvidedToRemoveRole")
|
||||
void should_ReturnFailure_When_NullRoleProvidedToRemoveRole() {
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
Result<UserError, User> result = user.removeRole(null);
|
||||
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.NullRole.class);
|
||||
}
|
||||
|
||||
// ==================== Email & Branch Tests ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("should_UpdateEmail_When_ValidEmailProvided")
|
||||
void should_UpdateEmail_When_ValidEmailProvided() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
String newEmail = "newemail@example.com";
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = user.updateEmail(newEmail);
|
||||
Result<UserError, User> result = user.updateEmail(newEmail);
|
||||
|
||||
// Assert
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(user.email()).isEqualTo(newEmail);
|
||||
assertThat(result.unsafeGetValue().email()).isEqualTo(newEmail);
|
||||
assertThat(user.email()).isEqualTo(email); // original unchanged
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnFailure_When_InvalidEmailProvidedToUpdateEmail")
|
||||
void should_ReturnFailure_When_InvalidEmailProvidedToUpdateEmail() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Result<UserError, Void> result = user.updateEmail("invalid-email");
|
||||
Result<UserError, User> result = user.updateEmail("invalid-email");
|
||||
|
||||
// Assert
|
||||
assertThat(result.isFailure()).isTrue();
|
||||
assertThat(result.unsafeGetError()).isInstanceOf(UserError.InvalidEmail.class);
|
||||
}
|
||||
|
|
@ -350,71 +358,46 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_UpdateBranch_When_BranchIdProvided")
|
||||
void should_UpdateBranch_When_BranchIdProvided() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
String newBranchId = "branch-2";
|
||||
|
||||
// Act
|
||||
user.updateBranch(newBranchId);
|
||||
Result<UserError, User> result = user.updateBranch(newBranchId);
|
||||
|
||||
// Assert
|
||||
assertThat(user.branchId()).isEqualTo(newBranchId);
|
||||
assertThat(result.isSuccess()).isTrue();
|
||||
assertThat(result.unsafeGetValue().branchId()).isEqualTo(newBranchId);
|
||||
assertThat(user.branchId()).isEqualTo(branchId); // original unchanged
|
||||
}
|
||||
|
||||
// ==================== Permission Tests ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnAllPermissions_When_GetAllPermissionsMethodCalled")
|
||||
void should_ReturnAllPermissions_When_GetAllPermissionsMethodCalled() {
|
||||
// Arrange
|
||||
Set<Permission> role1Perms = Set.of(Permission.USER_READ, Permission.USER_WRITE);
|
||||
Set<Permission> role2Perms = Set.of(Permission.ROLE_READ, Permission.ROLE_WRITE);
|
||||
Role role1 = createRoleWithPermissions("ADMIN", role1Perms);
|
||||
Role role2 = createRoleWithPermissions("PRODUCTION_MANAGER", role2Perms);
|
||||
User user = User.create(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
new HashSet<>(Set.of(role1, role2)),
|
||||
branchId
|
||||
).unsafeGetValue();
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(Set.of(role1, role2)), branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Set<Permission> allPermissions = user.getAllPermissions();
|
||||
|
||||
// Assert
|
||||
assertThat(allPermissions).contains(Permission.USER_READ, Permission.USER_WRITE,
|
||||
Permission.ROLE_READ, Permission.ROLE_WRITE);
|
||||
assertThat(allPermissions).contains(Permission.USER_READ, Permission.USER_WRITE, Permission.ROLE_READ, Permission.ROLE_WRITE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnEmptyPermissions_When_UserHasNoRoles")
|
||||
void should_ReturnEmptyPermissions_When_UserHasNoRoles() {
|
||||
// Arrange
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(), branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Set<Permission> permissions = user.getAllPermissions();
|
||||
|
||||
// Assert
|
||||
assertThat(permissions).isEmpty();
|
||||
assertThat(user.getAllPermissions()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_CheckPermission_When_UserHasPermission")
|
||||
void should_CheckPermission_When_UserHasPermission() {
|
||||
// Arrange
|
||||
Role role = createRoleWithPermissions(
|
||||
"ADMIN",
|
||||
Set.of(Permission.USER_READ, Permission.USER_WRITE)
|
||||
);
|
||||
User user = User.create(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
new HashSet<>(Set.of(role)),
|
||||
branchId
|
||||
).unsafeGetValue();
|
||||
Role role = createRoleWithPermissions("ADMIN", Set.of(Permission.USER_READ, Permission.USER_WRITE));
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(Set.of(role)), branchId).unsafeGetValue();
|
||||
|
||||
// Act & Assert
|
||||
assertThat(user.hasPermission(Permission.USER_READ)).isTrue();
|
||||
assertThat(user.hasPermission(Permission.USER_WRITE)).isTrue();
|
||||
}
|
||||
|
|
@ -422,51 +405,20 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_CheckPermission_When_UserLacksPermission")
|
||||
void should_CheckPermission_When_UserLacksPermission() {
|
||||
// Arrange
|
||||
Role role = createRoleWithPermissions(
|
||||
"PRODUCTION_WORKER",
|
||||
Set.of(Permission.USER_READ)
|
||||
);
|
||||
User user = User.create(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
new HashSet<>(Set.of(role)),
|
||||
branchId
|
||||
).unsafeGetValue();
|
||||
Role role = createRoleWithPermissions("PRODUCTION_WORKER", Set.of(Permission.USER_READ));
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(Set.of(role)), branchId).unsafeGetValue();
|
||||
|
||||
// Act & Assert
|
||||
assertThat(user.hasPermission(Permission.USER_DELETE)).isFalse();
|
||||
}
|
||||
|
||||
// ==================== Equality Tests ====================
|
||||
|
||||
@Test
|
||||
@DisplayName("should_BeEqualToAnother_When_BothHaveSameId")
|
||||
void should_BeEqualToAnother_When_BothHaveSameId() {
|
||||
// Arrange
|
||||
User user1 = User.reconstitute(
|
||||
userId,
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
roles,
|
||||
branchId,
|
||||
UserStatus.ACTIVE,
|
||||
createdAt,
|
||||
null
|
||||
);
|
||||
User user2 = User.reconstitute(
|
||||
userId,
|
||||
"different_username",
|
||||
"different@example.com",
|
||||
passwordHash,
|
||||
roles,
|
||||
"different-branch",
|
||||
UserStatus.INACTIVE,
|
||||
createdAt,
|
||||
null
|
||||
);
|
||||
User user1 = User.reconstitute(userId, username, email, passwordHash, roles, branchId, UserStatus.ACTIVE, createdAt, null);
|
||||
User user2 = User.reconstitute(userId, "different_username", "different@example.com", passwordHash, roles, "different-branch", UserStatus.INACTIVE, createdAt, null);
|
||||
|
||||
// Act & Assert
|
||||
assertThat(user1).isEqualTo(user2);
|
||||
assertThat(user1.hashCode()).isEqualTo(user2.hashCode());
|
||||
}
|
||||
|
|
@ -474,31 +426,20 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_NotBeEqual_When_DifferentIds")
|
||||
void should_NotBeEqual_When_DifferentIds() {
|
||||
// Arrange
|
||||
User user1 = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
User user2 = User.create(username, email, passwordHash, roles, branchId).unsafeGetValue();
|
||||
|
||||
// Act & Assert
|
||||
assertThat(user1).isNotEqualTo(user2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("should_ReturnUnmodifiableRoleSet_When_RolesRetrieved")
|
||||
void should_ReturnUnmodifiableRoleSet_When_RolesRetrieved() {
|
||||
// Arrange
|
||||
Role role = createTestRole("ADMIN");
|
||||
User user = User.create(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
new HashSet<>(Set.of(role)),
|
||||
branchId
|
||||
).unsafeGetValue();
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(Set.of(role)), branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Set<Role> retrievedRoles = user.roles();
|
||||
|
||||
// Assert
|
||||
assertThatThrownBy(() -> retrievedRoles.add(createTestRole("PRODUCTION_MANAGER")))
|
||||
.isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
|
@ -506,20 +447,11 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_ReturnUnmodifiablePermissionSet_When_PermissionsRetrieved")
|
||||
void should_ReturnUnmodifiablePermissionSet_When_PermissionsRetrieved() {
|
||||
// Arrange
|
||||
Role role = createRoleWithPermissions("ADMIN", Set.of(Permission.USER_READ));
|
||||
User user = User.create(
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
new HashSet<>(Set.of(role)),
|
||||
branchId
|
||||
).unsafeGetValue();
|
||||
User user = User.create(username, email, passwordHash, new HashSet<>(Set.of(role)), branchId).unsafeGetValue();
|
||||
|
||||
// Act
|
||||
Set<Permission> permissions = user.getAllPermissions();
|
||||
|
||||
// Assert
|
||||
assertThatThrownBy(() -> permissions.add(Permission.USER_WRITE))
|
||||
.isInstanceOf(UnsupportedOperationException.class);
|
||||
}
|
||||
|
|
@ -527,40 +459,18 @@ class UserTest {
|
|||
@Test
|
||||
@DisplayName("should_PreserveNullRolesAsEmptySet_When_NullRolesProvided")
|
||||
void should_PreserveNullRolesAsEmptySet_When_NullRolesProvided() {
|
||||
// Act
|
||||
User user = User.reconstitute(
|
||||
userId,
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
null,
|
||||
branchId,
|
||||
UserStatus.ACTIVE,
|
||||
createdAt,
|
||||
null
|
||||
);
|
||||
User user = User.reconstitute(userId, username, email, passwordHash, null, branchId, UserStatus.ACTIVE, createdAt, null);
|
||||
|
||||
// Assert
|
||||
assertThat(user.roles()).isEmpty();
|
||||
}
|
||||
|
||||
// ==================== Helper Methods ====================
|
||||
|
||||
private Role createTestRole(String roleName) {
|
||||
return Role.reconstitute(
|
||||
RoleId.generate(),
|
||||
RoleName.valueOf(roleName),
|
||||
new HashSet<>(),
|
||||
"Test role: " + roleName
|
||||
);
|
||||
return Role.reconstitute(RoleId.generate(), RoleName.valueOf(roleName), new HashSet<>(), "Test role: " + roleName);
|
||||
}
|
||||
|
||||
private Role createRoleWithPermissions(String roleName, Set<Permission> permissions) {
|
||||
return Role.reconstitute(
|
||||
RoleId.generate(),
|
||||
RoleName.valueOf(roleName),
|
||||
new HashSet<>(permissions),
|
||||
"Test role: " + roleName
|
||||
);
|
||||
return Role.reconstitute(RoleId.generate(), RoleName.valueOf(roleName), new HashSet<>(permissions), "Test role: " + roleName);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -564,7 +564,7 @@ class UserControllerIntegrationTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Change password for non-existent user should return 404 Not Found")
|
||||
@DisplayName("Change password for non-existent user should return 403 when not self-service")
|
||||
void testChangePasswordForNonExistentUser() throws Exception {
|
||||
String nonExistentId = UUID.randomUUID().toString();
|
||||
ChangePasswordRequest request = new ChangePasswordRequest(
|
||||
|
|
@ -572,11 +572,12 @@ class UserControllerIntegrationTest {
|
|||
"NewSecurePass456!"
|
||||
);
|
||||
|
||||
// Regular user has no PASSWORD_CHANGE permission for other users → 403 before user lookup
|
||||
mockMvc.perform(put("/api/users/{id}/password", nonExistentId)
|
||||
.header("Authorization", "Bearer " + regularUserToken)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isNotFound())
|
||||
.andExpect(status().isForbidden())
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue