diff --git a/backend/lined/src/main/java/io/backend/lined/LinedBackendApplication.java b/backend/lined/src/main/java/io/backend/lined/LinedBackendApplication.java index cba5500..3a4d303 100644 --- a/backend/lined/src/main/java/io/backend/lined/LinedBackendApplication.java +++ b/backend/lined/src/main/java/io/backend/lined/LinedBackendApplication.java @@ -4,8 +4,10 @@ import java.util.TimeZone; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class LinedBackendApplication { public static void main(String[] args) { diff --git a/backend/lined/src/main/java/io/backend/lined/app/AccountApplicationServiceImpl.java b/backend/lined/src/main/java/io/backend/lined/app/AccountApplicationServiceImpl.java index a26e8ca..4f1eb4f 100644 --- a/backend/lined/src/main/java/io/backend/lined/app/AccountApplicationServiceImpl.java +++ b/backend/lined/src/main/java/io/backend/lined/app/AccountApplicationServiceImpl.java @@ -26,12 +26,13 @@ public class AccountApplicationServiceImpl implements AccountApplicationService @Override @Transactional public UserDto registerUser(UserCreateDto createDto) { + AccountProvisioningSpec provisioning = provisioningPolicy.defaultRegistration(); UserDto user = userService.create(createDto); - roleService.setUserRoles(user.id(), provisioningPolicy.defaultRoles()); + roleService.setUserRoles(user.id(), provisioning.roleNames()); - PlanDto defaultPlan = planService.getByName(provisioningPolicy.defaultPlanName()); + PlanDto defaultPlan = planService.getByName(provisioning.planName()); subscriptionService.start( - user.id(), defaultPlan.id(), null, null, provisioningPolicy.defaultSubscriptionActive()); + user.id(), defaultPlan.id(), null, null, provisioning.activeSubscription()); return userService.getById(user.id()); } diff --git a/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningPolicy.java b/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningPolicy.java index 596c876..4b2fe3a 100644 --- a/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningPolicy.java +++ b/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningPolicy.java @@ -1,30 +1,19 @@ package io.backend.lined.app; import java.util.Set; -import org.springframework.beans.factory.annotation.Value; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class AccountProvisioningPolicy { - @Value("${lined.provisioning.default-role:ROLE_USER}") - private String defaultRole; + private final AccountProvisioningProperties properties; - @Value("${lined.provisioning.default-plan:FREE}") - private String defaultPlan; - - @Value("${lined.provisioning.default-subscription-active:true}") - private boolean subscriptionActive; - - public Set defaultRoles() { - return Set.of(defaultRole); - } - - public String defaultPlanName() { - return defaultPlan; - } - - public boolean defaultSubscriptionActive() { - return subscriptionActive; + public AccountProvisioningSpec defaultRegistration() { + return new AccountProvisioningSpec( + Set.of(properties.defaultRole()), + properties.defaultPlan(), + properties.defaultSubscriptionActive()); } } diff --git a/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningProperties.java b/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningProperties.java new file mode 100644 index 0000000..eb12df8 --- /dev/null +++ b/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningProperties.java @@ -0,0 +1,29 @@ +package io.backend.lined.app; + +import io.backend.lined.plan.domain.BuiltInPlan; +import io.backend.lined.role.domain.BuiltInRole; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("lined.provisioning") +public record AccountProvisioningProperties( + String defaultRole, + String defaultPlan, + Boolean defaultSubscriptionActive +) { + + public AccountProvisioningProperties { + if (defaultRole == null || defaultRole.isBlank()) { + defaultRole = BuiltInRole.USER.value(); + } else { + defaultRole = defaultRole.trim(); + } + if (defaultPlan == null || defaultPlan.isBlank()) { + defaultPlan = BuiltInPlan.FREE.value(); + } else { + defaultPlan = defaultPlan.trim(); + } + if (defaultSubscriptionActive == null) { + defaultSubscriptionActive = true; + } + } +} diff --git a/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningSpec.java b/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningSpec.java new file mode 100644 index 0000000..7cfd8e9 --- /dev/null +++ b/backend/lined/src/main/java/io/backend/lined/app/AccountProvisioningSpec.java @@ -0,0 +1,14 @@ +package io.backend.lined.app; + +import java.util.Set; + +public record AccountProvisioningSpec( + Set roleNames, + String planName, + boolean activeSubscription +) { + + public AccountProvisioningSpec { + roleNames = roleNames == null ? Set.of() : Set.copyOf(roleNames); + } +} diff --git a/backend/lined/src/main/java/io/backend/lined/plan/domain/BuiltInPlan.java b/backend/lined/src/main/java/io/backend/lined/plan/domain/BuiltInPlan.java new file mode 100644 index 0000000..e4a3f5a --- /dev/null +++ b/backend/lined/src/main/java/io/backend/lined/plan/domain/BuiltInPlan.java @@ -0,0 +1,18 @@ +package io.backend.lined.plan.domain; + +public enum BuiltInPlan { + + FREE("FREE"), + PRO("PRO"), + FAMILY("FAMILY"); + + private final String value; + + BuiltInPlan(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/backend/lined/src/main/java/io/backend/lined/role/domain/BuiltInRole.java b/backend/lined/src/main/java/io/backend/lined/role/domain/BuiltInRole.java new file mode 100644 index 0000000..1d0ef0d --- /dev/null +++ b/backend/lined/src/main/java/io/backend/lined/role/domain/BuiltInRole.java @@ -0,0 +1,17 @@ +package io.backend.lined.role.domain; + +public enum BuiltInRole { + + USER("ROLE_USER"), + ADMIN("ROLE_ADMIN"); + + private final String value; + + BuiltInRole(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/backend/lined/src/main/java/io/backend/lined/user/service/UserServiceImpl.java b/backend/lined/src/main/java/io/backend/lined/user/service/UserServiceImpl.java index a8101b6..b0f8ac3 100644 --- a/backend/lined/src/main/java/io/backend/lined/user/service/UserServiceImpl.java +++ b/backend/lined/src/main/java/io/backend/lined/user/service/UserServiceImpl.java @@ -51,10 +51,6 @@ public UserDto create(UserCreateDto dto) { UserEntity entity = userMapper.toEntity(dto); entity.setPassword(passwordEncoder.encode(dto.password())); - if (dto.roles() != null && !dto.roles().isEmpty()) { - entity.setRoles(roleResolver.resolve(dto.roles())); - } - try { return userMapper.toDto(userRepository.save(entity)); } catch (DataIntegrityViolationException ex) { @@ -89,10 +85,6 @@ public UserDto update(Long id, UserUpdateDto dto) { userMapper.updateEntity(entity, dto); - if (dto.roles() != null) { - entity.setRoles(roleResolver.resolve(dto.roles())); - } - try { return userMapper.toDto(userRepository.save(entity)); } catch (DataIntegrityViolationException ex) { diff --git a/backend/lined/src/test/java/io/backend/lined/app/AccountApplicationServiceImplTest.java b/backend/lined/src/test/java/io/backend/lined/app/AccountApplicationServiceImplTest.java index 9b53115..7d1c3d0 100644 --- a/backend/lined/src/test/java/io/backend/lined/app/AccountApplicationServiceImplTest.java +++ b/backend/lined/src/test/java/io/backend/lined/app/AccountApplicationServiceImplTest.java @@ -9,7 +9,9 @@ import static org.mockito.Mockito.when; import io.backend.lined.plan.api.PlanDto; +import io.backend.lined.plan.domain.BuiltInPlan; import io.backend.lined.plan.service.PlanService; +import io.backend.lined.role.domain.BuiltInRole; import io.backend.lined.role.service.RoleService; import io.backend.lined.subscription.api.SubscriptionDto; import io.backend.lined.subscription.service.SubscriptionService; @@ -29,6 +31,26 @@ @ExtendWith(MockitoExtension.class) class AccountApplicationServiceImplTest { + private static final long USER_ID = 1L; + private static final long ADMIN_USER_ID = 2L; + private static final long FREE_PLAN_ID = 10L; + private static final long STARTER_PLAN_ID = 20L; + private static final long PRO_PLAN_ID = 20L; + private static final long SUBSCRIPTION_ID = 100L; + private static final int PLAN_DURATION_DAYS = 30; + private static final String USERNAME = "testuser"; + private static final String USER_EMAIL = "test@example.com"; + private static final String PASSWORD = "password"; + private static final String ADMIN_USERNAME = "adminuser"; + private static final String ADMIN_EMAIL = "admin@example.com"; + private static final String USER_ROLE = BuiltInRole.USER.value(); + private static final String ADMIN_ROLE = BuiltInRole.ADMIN.value(); + private static final String MODERATOR_ROLE = "ROLE_MODERATOR"; + private static final String FREE_PLAN_NAME = BuiltInPlan.FREE.value(); + private static final String STARTER_PLAN_NAME = "STARTER"; + private static final String PRO_PLAN_NAME = "PRO_MONTHLY"; + private static final String PRO_PLAN_PRICE = "9.99"; + @Mock private UserService userService; @Mock @@ -53,15 +75,15 @@ class AccountApplicationServiceImplTest { void setUp() { now = OffsetDateTime.now(); - createDto = new UserCreateDto("testuser", "test@example.com", "password", Set.of()); + createDto = new UserCreateDto(USERNAME, USER_EMAIL, PASSWORD, Set.of()); - userDto = new UserDto(1L, "testuser", "test@example.com", now, Set.of(), null, null); + userDto = new UserDto(USER_ID, USERNAME, USER_EMAIL, now, Set.of(), null, null); - freePlan = new PlanDto(10L, "FREE", BigDecimal.ZERO, 30, now); + freePlan = new PlanDto(FREE_PLAN_ID, FREE_PLAN_NAME, BigDecimal.ZERO, PLAN_DURATION_DAYS, now); subscriptionDto = new SubscriptionDto( - 100L, 1L, 10L, "FREE", - now, now.plusDays(30), true, now + SUBSCRIPTION_ID, USER_ID, FREE_PLAN_ID, FREE_PLAN_NAME, + now, now.plusDays(PLAN_DURATION_DAYS), true, now ); } @@ -72,85 +94,83 @@ void setUp() { @Test void registerUser_success_appliesDefaultProvisioningPolicy() { when(userService.create(createDto)).thenReturn(userDto); - when(userService.getById(1L)).thenReturn(userDto); - when(provisioningPolicy.defaultRoles()).thenReturn(Set.of("ROLE_USER")); - when(provisioningPolicy.defaultPlanName()).thenReturn("FREE"); - when(provisioningPolicy.defaultSubscriptionActive()).thenReturn(true); - when(planService.getByName("FREE")).thenReturn(freePlan); + when(userService.getById(USER_ID)).thenReturn(userDto); + when(provisioningPolicy.defaultRegistration()) + .thenReturn(new AccountProvisioningSpec(Set.of(USER_ROLE), FREE_PLAN_NAME, true)); + when(planService.getByName(FREE_PLAN_NAME)).thenReturn(freePlan); UserDto result = accountService.registerUser(createDto); assertThat(result).isEqualTo(userDto); - verify(roleService).setUserRoles(1L, Set.of("ROLE_USER")); - verify(subscriptionService).start(eq(1L), eq(10L), isNull(), isNull(), eq(true)); + verify(roleService).setUserRoles(USER_ID, Set.of(USER_ROLE)); + verify(subscriptionService).start(eq(USER_ID), eq(FREE_PLAN_ID), isNull(), isNull(), eq(true)); } @Test void registerUser_usesPolicyPlanNameForDefaultSubscription() { - PlanDto starterPlan = new PlanDto(20L, "STARTER", BigDecimal.ZERO, 30, now); + PlanDto starterPlan = + new PlanDto(STARTER_PLAN_ID, STARTER_PLAN_NAME, BigDecimal.ZERO, PLAN_DURATION_DAYS, now); when(userService.create(createDto)).thenReturn(userDto); - when(userService.getById(1L)).thenReturn(userDto); - when(provisioningPolicy.defaultRoles()).thenReturn(Set.of("ROLE_USER")); - when(provisioningPolicy.defaultPlanName()).thenReturn("STARTER"); - when(provisioningPolicy.defaultSubscriptionActive()).thenReturn(true); - when(planService.getByName("STARTER")).thenReturn(starterPlan); + when(userService.getById(USER_ID)).thenReturn(userDto); + when(provisioningPolicy.defaultRegistration()) + .thenReturn(new AccountProvisioningSpec(Set.of(USER_ROLE), STARTER_PLAN_NAME, true)); + when(planService.getByName(STARTER_PLAN_NAME)).thenReturn(starterPlan); accountService.registerUser(createDto); - verify(roleService).setUserRoles(1L, Set.of("ROLE_USER")); - verify(planService).getByName("STARTER"); - verify(subscriptionService).start(eq(1L), eq(20L), isNull(), isNull(), eq(true)); + verify(roleService).setUserRoles(USER_ID, Set.of(USER_ROLE)); + verify(planService).getByName(STARTER_PLAN_NAME); + verify(subscriptionService) + .start(eq(USER_ID), eq(STARTER_PLAN_ID), isNull(), isNull(), eq(true)); } @Test void registerUser_usesPolicyActiveFlagForDefaultSubscription() { when(userService.create(createDto)).thenReturn(userDto); - when(userService.getById(1L)).thenReturn(userDto); - when(provisioningPolicy.defaultRoles()).thenReturn(Set.of("ROLE_USER")); - when(provisioningPolicy.defaultPlanName()).thenReturn("FREE"); - when(provisioningPolicy.defaultSubscriptionActive()).thenReturn(false); - when(planService.getByName("FREE")).thenReturn(freePlan); + when(userService.getById(USER_ID)).thenReturn(userDto); + when(provisioningPolicy.defaultRegistration()) + .thenReturn(new AccountProvisioningSpec(Set.of(USER_ROLE), FREE_PLAN_NAME, false)); + when(planService.getByName(FREE_PLAN_NAME)).thenReturn(freePlan); accountService.registerUser(createDto); - verify(roleService).setUserRoles(1L, Set.of("ROLE_USER")); - verify(subscriptionService).start(eq(1L), eq(10L), isNull(), isNull(), eq(false)); + verify(roleService).setUserRoles(USER_ID, Set.of(USER_ROLE)); + verify(subscriptionService).start(eq(USER_ID), eq(FREE_PLAN_ID), isNull(), isNull(), eq(false)); } @Test void registerUser_policyRolesReplaceAnyDtoRoles() { UserCreateDto adminDto = - new UserCreateDto("adminuser", "admin@example.com", "password", Set.of("ROLE_ADMIN")); + new UserCreateDto(ADMIN_USERNAME, ADMIN_EMAIL, PASSWORD, Set.of(ADMIN_ROLE)); UserDto adminUserDto = - new UserDto(2L, "adminuser", "admin@example.com", now, Set.of("ROLE_ADMIN"), null, null); - Set policyRoles = Set.of("ROLE_USER"); + new UserDto(ADMIN_USER_ID, ADMIN_USERNAME, ADMIN_EMAIL, now, Set.of(ADMIN_ROLE), null, null); + Set policyRoles = Set.of(USER_ROLE); when(userService.create(adminDto)).thenReturn(adminUserDto); - when(userService.getById(2L)).thenReturn(adminUserDto); - when(provisioningPolicy.defaultRoles()).thenReturn(policyRoles); - when(provisioningPolicy.defaultPlanName()).thenReturn("FREE"); - when(provisioningPolicy.defaultSubscriptionActive()).thenReturn(true); - when(planService.getByName("FREE")).thenReturn(freePlan); + when(userService.getById(ADMIN_USER_ID)).thenReturn(adminUserDto); + when(provisioningPolicy.defaultRegistration()) + .thenReturn(new AccountProvisioningSpec(policyRoles, FREE_PLAN_NAME, true)); + when(planService.getByName(FREE_PLAN_NAME)).thenReturn(freePlan); accountService.registerUser(adminDto); - verify(roleService).setUserRoles(2L, policyRoles); + verify(roleService).setUserRoles(ADMIN_USER_ID, policyRoles); verify(roleService, never()).addUserRoles(any(), any()); } @Test void registerUser_returnsRefreshedUser_fromGetById() { UserDto refreshedUser = new UserDto( - 1L, "testuser", "test@example.com", now, Set.of("ROLE_USER"), "FREE", now.plusDays(30) + USER_ID, USERNAME, USER_EMAIL, now, Set.of(USER_ROLE), FREE_PLAN_NAME, + now.plusDays(PLAN_DURATION_DAYS) ); when(userService.create(createDto)).thenReturn(userDto); - when(provisioningPolicy.defaultRoles()).thenReturn(Set.of("ROLE_USER")); - when(provisioningPolicy.defaultPlanName()).thenReturn("FREE"); - when(provisioningPolicy.defaultSubscriptionActive()).thenReturn(true); - when(planService.getByName("FREE")).thenReturn(freePlan); - when(userService.getById(1L)).thenReturn(refreshedUser); + when(provisioningPolicy.defaultRegistration()) + .thenReturn(new AccountProvisioningSpec(Set.of(USER_ROLE), FREE_PLAN_NAME, true)); + when(planService.getByName(FREE_PLAN_NAME)).thenReturn(freePlan); + when(userService.getById(USER_ID)).thenReturn(refreshedUser); UserDto result = accountService.registerUser(createDto); @@ -163,40 +183,40 @@ void registerUser_returnsRefreshedUser_fromGetById() { @Test void setRoles_success() { - Set roles = Set.of("ROLE_ADMIN", "ROLE_USER"); + Set roles = Set.of(ADMIN_ROLE, USER_ROLE); - when(userService.getById(1L)).thenReturn(userDto); + when(userService.getById(USER_ID)).thenReturn(userDto); - UserDto result = accountService.setRoles(1L, roles); + UserDto result = accountService.setRoles(USER_ID, roles); assertThat(result).isEqualTo(userDto); - verify(roleService).setUserRoles(1L, roles); - verify(userService).getById(1L); + verify(roleService).setUserRoles(USER_ID, roles); + verify(userService).getById(USER_ID); } @Test void setRoles_returnsRefreshedUser_afterRoleChange() { - Set roles = Set.of("ROLE_ADMIN"); + Set roles = Set.of(ADMIN_ROLE); UserDto updatedUser = new UserDto( - 1L, "testuser", "test@example.com", now, Set.of("ROLE_ADMIN"), null, null + USER_ID, USERNAME, USER_EMAIL, now, Set.of(ADMIN_ROLE), null, null ); - when(userService.getById(1L)).thenReturn(updatedUser); + when(userService.getById(USER_ID)).thenReturn(updatedUser); - UserDto result = accountService.setRoles(1L, roles); + UserDto result = accountService.setRoles(USER_ID, roles); - assertThat(result.roles()).contains("ROLE_ADMIN"); + assertThat(result.roles()).contains(ADMIN_ROLE); } @Test void setRoles_delegatesExactRolesToRoleService() { - Set roles = Set.of("ROLE_MODERATOR"); + Set roles = Set.of(MODERATOR_ROLE); - when(userService.getById(1L)).thenReturn(userDto); + when(userService.getById(USER_ID)).thenReturn(userDto); - accountService.setRoles(1L, roles); + accountService.setRoles(USER_ID, roles); - verify(roleService).setUserRoles(eq(1L), eq(roles)); + verify(roleService).setUserRoles(eq(USER_ID), eq(roles)); } /* ======================= @@ -205,38 +225,39 @@ void setRoles_delegatesExactRolesToRoleService() { @Test void activatePlan_success() { - when(planService.getByName("FREE")).thenReturn(freePlan); - when(subscriptionService.start(eq(1L), eq(10L), isNull(), isNull(), eq(true))) + when(planService.getByName(FREE_PLAN_NAME)).thenReturn(freePlan); + when(subscriptionService.start(eq(USER_ID), eq(FREE_PLAN_ID), isNull(), isNull(), eq(true))) .thenReturn(subscriptionDto); - SubscriptionDto result = accountService.activatePlan(1L, "FREE"); + SubscriptionDto result = accountService.activatePlan(USER_ID, FREE_PLAN_NAME); assertThat(result).isEqualTo(subscriptionDto); - verify(planService).getByName("FREE"); - verify(subscriptionService).start(eq(1L), eq(10L), isNull(), isNull(), eq(true)); + verify(planService).getByName(FREE_PLAN_NAME); + verify(subscriptionService).start(eq(USER_ID), eq(FREE_PLAN_ID), isNull(), isNull(), eq(true)); } @Test void activatePlan_looksPlanUpByName() { - PlanDto proPlan = new PlanDto(20L, "PRO_MONTHLY", new BigDecimal("9.99"), 30, now); + PlanDto proPlan = new PlanDto( + PRO_PLAN_ID, PRO_PLAN_NAME, new BigDecimal(PRO_PLAN_PRICE), PLAN_DURATION_DAYS, now); - when(planService.getByName("PRO_MONTHLY")).thenReturn(proPlan); - when(subscriptionService.start(eq(1L), eq(20L), isNull(), isNull(), eq(true))) + when(planService.getByName(PRO_PLAN_NAME)).thenReturn(proPlan); + when(subscriptionService.start(eq(USER_ID), eq(PRO_PLAN_ID), isNull(), isNull(), eq(true))) .thenReturn(subscriptionDto); - accountService.activatePlan(1L, "PRO_MONTHLY"); + accountService.activatePlan(USER_ID, PRO_PLAN_NAME); - verify(planService).getByName("PRO_MONTHLY"); - verify(subscriptionService).start(eq(1L), eq(20L), isNull(), isNull(), eq(true)); + verify(planService).getByName(PRO_PLAN_NAME); + verify(subscriptionService).start(eq(USER_ID), eq(PRO_PLAN_ID), isNull(), isNull(), eq(true)); } @Test void activatePlan_alwaysStartsActiveSubscription() { - when(planService.getByName("FREE")).thenReturn(freePlan); + when(planService.getByName(FREE_PLAN_NAME)).thenReturn(freePlan); when(subscriptionService.start(any(), any(), any(), any(), eq(true))) .thenReturn(subscriptionDto); - accountService.activatePlan(1L, "FREE"); + accountService.activatePlan(USER_ID, FREE_PLAN_NAME); // Verify active=true is always passed verify(subscriptionService).start(any(), any(), isNull(), isNull(), eq(true)); diff --git a/backend/lined/src/test/java/io/backend/lined/app/AccountProvisioningPolicyTest.java b/backend/lined/src/test/java/io/backend/lined/app/AccountProvisioningPolicyTest.java new file mode 100644 index 0000000..e3054e5 --- /dev/null +++ b/backend/lined/src/test/java/io/backend/lined/app/AccountProvisioningPolicyTest.java @@ -0,0 +1,42 @@ +package io.backend.lined.app; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.backend.lined.plan.domain.BuiltInPlan; +import io.backend.lined.role.domain.BuiltInRole; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class AccountProvisioningPolicyTest { + + private static final String USER_ROLE = BuiltInRole.USER.value(); + private static final String ADMIN_ROLE = BuiltInRole.ADMIN.value(); + private static final String FREE_PLAN_NAME = BuiltInPlan.FREE.value(); + private static final String STARTER_PLAN_NAME = "STARTER"; + private static final String PADDED_ADMIN_ROLE = " " + ADMIN_ROLE + " "; + private static final String PADDED_STARTER_PLAN_NAME = " " + STARTER_PLAN_NAME + " "; + + @Test + void defaultRegistration_returnsBuiltInDefaults() { + AccountProvisioningPolicy policy = + new AccountProvisioningPolicy(new AccountProvisioningProperties(null, null, null)); + + AccountProvisioningSpec result = policy.defaultRegistration(); + + assertThat(result.roleNames()).isEqualTo(Set.of(USER_ROLE)); + assertThat(result.planName()).isEqualTo(FREE_PLAN_NAME); + assertThat(result.activeSubscription()).isTrue(); + } + + @Test + void defaultRegistration_returnsConfiguredValues() { + AccountProvisioningPolicy policy = new AccountProvisioningPolicy( + new AccountProvisioningProperties(PADDED_ADMIN_ROLE, PADDED_STARTER_PLAN_NAME, false)); + + AccountProvisioningSpec result = policy.defaultRegistration(); + + assertThat(result.roleNames()).isEqualTo(Set.of(ADMIN_ROLE)); + assertThat(result.planName()).isEqualTo(STARTER_PLAN_NAME); + assertThat(result.activeSubscription()).isFalse(); + } +} diff --git a/backend/lined/src/test/java/io/backend/lined/user/service/UserServiceImplTest.java b/backend/lined/src/test/java/io/backend/lined/user/service/UserServiceImplTest.java index 8b197f6..04d6359 100644 --- a/backend/lined/src/test/java/io/backend/lined/user/service/UserServiceImplTest.java +++ b/backend/lined/src/test/java/io/backend/lined/user/service/UserServiceImplTest.java @@ -10,7 +10,7 @@ import io.backend.lined.common.exception.ConflictException; import io.backend.lined.common.exception.NotFoundException; -import io.backend.lined.role.domain.RoleEntity; +import io.backend.lined.role.domain.BuiltInRole; import io.backend.lined.role.service.RoleResolver; import io.backend.lined.user.api.UserCreateDto; import io.backend.lined.user.api.UserDto; @@ -31,6 +31,25 @@ @ExtendWith(MockitoExtension.class) class UserServiceImplTest { + private static final long USER_ID = 1L; + private static final long MISSING_USER_ID = 99L; + private static final String USERNAME = "testuser"; + private static final String USER_EMAIL = "test@example.com"; + private static final String PASSWORD = "password"; + private static final String ENCODED_PASSWORD = "encoded_password"; + private static final String ENCODED_VALUE = "encoded"; + private static final String NEW_USERNAME = "newuser"; + private static final String TAKEN_USERNAME = "takenuser"; + private static final String TAKEN_EMAIL = "taken@example.com"; + private static final String NEW_EMAIL = "new@example.com"; + private static final String NEW_PASSWORD = "newpassword"; + private static final String ENCODED_NEW_PASSWORD = "encoded_new"; + private static final String ADMIN_ROLE = BuiltInRole.ADMIN.value(); + private static final String UNKNOWN_ROLE = "UNKNOWN_ROLE"; + private static final String USER_NOT_FOUND_MESSAGE = "User not found"; + private static final String USERNAME_EXISTS_MESSAGE = "Username already exists"; + private static final String EMAIL_EXISTS_MESSAGE = "Email already exists"; + @Mock private UserRepository userRepository; @Mock @@ -49,12 +68,12 @@ class UserServiceImplTest { @BeforeEach void setUp() { testUser = new UserEntity(); - testUser.setId(1L); - testUser.setUsername("testuser"); - testUser.setEmail("test@example.com"); - testUser.setPassword("encoded_password"); + testUser.setId(USER_ID); + testUser.setUsername(USERNAME); + testUser.setEmail(USER_EMAIL); + testUser.setPassword(ENCODED_PASSWORD); - expectedDto = new UserDto(1L, "testuser", "test@example.com", null, Set.of(), null, null); + expectedDto = new UserDto(USER_ID, USERNAME, USER_EMAIL, null, Set.of(), null, null); } /* ======================= @@ -63,12 +82,12 @@ void setUp() { @Test void create_success() { - UserCreateDto dto = new UserCreateDto("testuser", "test@example.com", "password", Set.of()); + UserCreateDto dto = new UserCreateDto(USERNAME, USER_EMAIL, PASSWORD, Set.of()); - when(userRepository.existsByUsernameIgnoreCase("testuser")).thenReturn(false); - when(userRepository.existsByEmailIgnoreCase("test@example.com")).thenReturn(false); + when(userRepository.existsByUsernameIgnoreCase(USERNAME)).thenReturn(false); + when(userRepository.existsByEmailIgnoreCase(USER_EMAIL)).thenReturn(false); when(userMapper.toEntity(dto)).thenReturn(testUser); - when(passwordEncoder.encode("password")).thenReturn("encoded_password"); + when(passwordEncoder.encode(PASSWORD)).thenReturn(ENCODED_PASSWORD); when(userRepository.save(testUser)).thenReturn(testUser); when(userMapper.toDto(testUser)).thenReturn(expectedDto); @@ -80,68 +99,66 @@ void create_success() { @Test void create_throwsConflict_whenUsernameExists() { - UserCreateDto dto = new UserCreateDto("testuser", "test@example.com", "password", Set.of()); + UserCreateDto dto = new UserCreateDto(USERNAME, USER_EMAIL, PASSWORD, Set.of()); - when(userRepository.existsByUsernameIgnoreCase("testuser")).thenReturn(true); + when(userRepository.existsByUsernameIgnoreCase(USERNAME)).thenReturn(true); assertThatThrownBy(() -> userService.create(dto)) .isInstanceOf(ConflictException.class) - .hasMessageContaining("Username already exists"); + .hasMessageContaining(USERNAME_EXISTS_MESSAGE); verify(userRepository, never()).save(any()); } @Test void create_throwsConflict_whenEmailExists() { - UserCreateDto dto = new UserCreateDto("testuser", "test@example.com", "password", Set.of()); + UserCreateDto dto = new UserCreateDto(USERNAME, USER_EMAIL, PASSWORD, Set.of()); - when(userRepository.existsByUsernameIgnoreCase("testuser")).thenReturn(false); - when(userRepository.existsByEmailIgnoreCase("test@example.com")).thenReturn(true); + when(userRepository.existsByUsernameIgnoreCase(USERNAME)).thenReturn(false); + when(userRepository.existsByEmailIgnoreCase(USER_EMAIL)).thenReturn(true); assertThatThrownBy(() -> userService.create(dto)) .isInstanceOf(ConflictException.class) - .hasMessageContaining("Email already exists"); + .hasMessageContaining(EMAIL_EXISTS_MESSAGE); verify(userRepository, never()).save(any()); } @Test - void create_withRoles_resolvesRolesCorrectly() { + void create_ignoresSuppliedRoles() { UserCreateDto dto = - new UserCreateDto("testuser", "test@example.com", "password", Set.of("ADMIN")); - RoleEntity role = new RoleEntity(); - role.setName("ADMIN"); + new UserCreateDto(USERNAME, USER_EMAIL, PASSWORD, Set.of(ADMIN_ROLE)); when(userRepository.existsByUsernameIgnoreCase(anyString())).thenReturn(false); when(userRepository.existsByEmailIgnoreCase(anyString())).thenReturn(false); when(userMapper.toEntity(dto)).thenReturn(testUser); - when(passwordEncoder.encode(anyString())).thenReturn("encoded"); - when(roleResolver.resolve(Set.of("ADMIN"))).thenReturn(Set.of(role)); + when(passwordEncoder.encode(anyString())).thenReturn(ENCODED_VALUE); when(userRepository.save(testUser)).thenReturn(testUser); when(userMapper.toDto(testUser)).thenReturn(expectedDto); UserDto result = userService.create(dto); assertThat(result).isNotNull(); - assertThat(testUser.getRoles()).containsExactly(role); - verify(roleResolver).resolve(Set.of("ADMIN")); + assertThat(testUser.getRoles()).isNullOrEmpty(); + verify(roleResolver, never()).resolve(any()); } @Test - void create_throwsNotFound_whenRoleDoesNotExist() { + void create_doesNotResolveUnknownSuppliedRoles() { UserCreateDto dto = - new UserCreateDto("testuser", "test@example.com", "password", Set.of("UNKNOWN_ROLE")); + new UserCreateDto(USERNAME, USER_EMAIL, PASSWORD, Set.of(UNKNOWN_ROLE)); when(userRepository.existsByUsernameIgnoreCase(anyString())).thenReturn(false); when(userRepository.existsByEmailIgnoreCase(anyString())).thenReturn(false); when(userMapper.toEntity(dto)).thenReturn(testUser); - when(passwordEncoder.encode(anyString())).thenReturn("encoded"); - when(roleResolver.resolve(Set.of("UNKNOWN_ROLE"))) - .thenThrow(new NotFoundException("Role not found: UNKNOWN_ROLE")); + when(passwordEncoder.encode(anyString())).thenReturn(ENCODED_VALUE); + when(userRepository.save(testUser)).thenReturn(testUser); + when(userMapper.toDto(testUser)).thenReturn(expectedDto); - assertThatThrownBy(() -> userService.create(dto)) - .isInstanceOf(NotFoundException.class) - .hasMessageContaining("Role not found"); + UserDto result = userService.create(dto); + + assertThat(result).isEqualTo(expectedDto); + verify(roleResolver, never()).resolve(any()); } /* ======================= @@ -150,21 +167,21 @@ void create_throwsNotFound_whenRoleDoesNotExist() { @Test void getById_success() { - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); when(userMapper.toDto(testUser)).thenReturn(expectedDto); - UserDto result = userService.getById(1L); + UserDto result = userService.getById(USER_ID); assertThat(result).isEqualTo(expectedDto); } @Test void getById_throwsNotFound_whenUserDoesNotExist() { - when(userRepository.findById(99L)).thenReturn(Optional.empty()); + when(userRepository.findById(MISSING_USER_ID)).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.getById(99L)) + assertThatThrownBy(() -> userService.getById(MISSING_USER_ID)) .isInstanceOf(NotFoundException.class) - .hasMessageContaining("User not found"); + .hasMessageContaining(USER_NOT_FOUND_MESSAGE); } /* ======================= @@ -173,52 +190,68 @@ void getById_throwsNotFound_whenUserDoesNotExist() { @Test void update_success() { - UserUpdateDto dto = new UserUpdateDto("newuser", null, null, null); + UserUpdateDto dto = new UserUpdateDto(NEW_USERNAME, null, null, null); - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); - when(userRepository.existsByUsernameIgnoreCase("newuser")).thenReturn(false); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); + when(userRepository.existsByUsernameIgnoreCase(NEW_USERNAME)).thenReturn(false); when(userRepository.save(testUser)).thenReturn(testUser); when(userMapper.toDto(testUser)).thenReturn(expectedDto); - UserDto result = userService.update(1L, dto); + UserDto result = userService.update(USER_ID, dto); assertThat(result).isNotNull(); verify(userMapper).updateEntity(testUser, dto); } + @Test + void update_ignoresSuppliedRoles() { + UserUpdateDto dto = new UserUpdateDto(null, null, null, Set.of(ADMIN_ROLE)); + + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); + when(userRepository.save(testUser)).thenReturn(testUser); + when(userMapper.toDto(testUser)).thenReturn(expectedDto); + + UserDto result = userService.update(USER_ID, dto); + + assertThat(result).isEqualTo(expectedDto); + assertThat(testUser.getRoles()).isNullOrEmpty(); + verify(userMapper).updateEntity(testUser, dto); + verify(roleResolver, never()).resolve(any()); + } + @Test void update_throwsNotFound_whenUserDoesNotExist() { - UserUpdateDto dto = new UserUpdateDto("newuser", null, null, null); + UserUpdateDto dto = new UserUpdateDto(NEW_USERNAME, null, null, null); - when(userRepository.findById(99L)).thenReturn(Optional.empty()); + when(userRepository.findById(MISSING_USER_ID)).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.update(99L, dto)) + assertThatThrownBy(() -> userService.update(MISSING_USER_ID, dto)) .isInstanceOf(NotFoundException.class) - .hasMessageContaining("User not found"); + .hasMessageContaining(USER_NOT_FOUND_MESSAGE); } @Test void update_throwsConflict_whenNewUsernameAlreadyTaken() { - UserUpdateDto dto = new UserUpdateDto("takenuser", null, null, null); + UserUpdateDto dto = new UserUpdateDto(TAKEN_USERNAME, null, null, null); - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); - when(userRepository.existsByUsernameIgnoreCase("takenuser")).thenReturn(true); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); + when(userRepository.existsByUsernameIgnoreCase(TAKEN_USERNAME)).thenReturn(true); - assertThatThrownBy(() -> userService.update(1L, dto)) + assertThatThrownBy(() -> userService.update(USER_ID, dto)) .isInstanceOf(ConflictException.class) - .hasMessageContaining("Username already exists"); + .hasMessageContaining(USERNAME_EXISTS_MESSAGE); } @Test void update_throwsConflict_whenNewEmailAlreadyTaken() { - UserUpdateDto dto = new UserUpdateDto(null, "taken@example.com", null, null); + UserUpdateDto dto = new UserUpdateDto(null, TAKEN_EMAIL, null, null); - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); - when(userRepository.existsByEmailIgnoreCase("taken@example.com")).thenReturn(true); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); + when(userRepository.existsByEmailIgnoreCase(TAKEN_EMAIL)).thenReturn(true); - assertThatThrownBy(() -> userService.update(1L, dto)) + assertThatThrownBy(() -> userService.update(USER_ID, dto)) .isInstanceOf(ConflictException.class) - .hasMessageContaining("Email already exists"); + .hasMessageContaining(EMAIL_EXISTS_MESSAGE); } /* ======================= @@ -227,20 +260,20 @@ void update_throwsConflict_whenNewEmailAlreadyTaken() { @Test void delete_success() { - when(userRepository.existsById(1L)).thenReturn(true); + when(userRepository.existsById(USER_ID)).thenReturn(true); - userService.delete(1L); + userService.delete(USER_ID); - verify(userRepository).deleteById(1L); + verify(userRepository).deleteById(USER_ID); } @Test void delete_throwsNotFound_whenUserDoesNotExist() { - when(userRepository.existsById(99L)).thenReturn(false); + when(userRepository.existsById(MISSING_USER_ID)).thenReturn(false); - assertThatThrownBy(() -> userService.delete(99L)) + assertThatThrownBy(() -> userService.delete(MISSING_USER_ID)) .isInstanceOf(NotFoundException.class) - .hasMessageContaining("User not found"); + .hasMessageContaining(USER_NOT_FOUND_MESSAGE); verify(userRepository, never()).deleteById(any()); } @@ -251,22 +284,22 @@ void delete_throwsNotFound_whenUserDoesNotExist() { @Test void changePassword_success() { - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); - when(passwordEncoder.encode("newpassword")).thenReturn("encoded_new"); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); + when(passwordEncoder.encode(NEW_PASSWORD)).thenReturn(ENCODED_NEW_PASSWORD); - userService.changePassword(1L, "newpassword"); + userService.changePassword(USER_ID, NEW_PASSWORD); - assertThat(testUser.getPassword()).isEqualTo("encoded_new"); + assertThat(testUser.getPassword()).isEqualTo(ENCODED_NEW_PASSWORD); verify(userRepository).save(testUser); } @Test void changePassword_throwsNotFound_whenUserDoesNotExist() { - when(userRepository.findById(99L)).thenReturn(Optional.empty()); + when(userRepository.findById(MISSING_USER_ID)).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.changePassword(99L, "newpassword")) + assertThatThrownBy(() -> userService.changePassword(MISSING_USER_ID, NEW_PASSWORD)) .isInstanceOf(NotFoundException.class) - .hasMessageContaining("User not found"); + .hasMessageContaining(USER_NOT_FOUND_MESSAGE); verify(userRepository, never()).save(any()); } @@ -277,36 +310,36 @@ void changePassword_throwsNotFound_whenUserDoesNotExist() { @Test void changeEmail_success() { - when(userRepository.existsByEmailIgnoreCase("new@example.com")).thenReturn(false); - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.existsByEmailIgnoreCase(NEW_EMAIL)).thenReturn(false); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); when(userRepository.save(testUser)).thenReturn(testUser); when(userMapper.toDto(testUser)).thenReturn(expectedDto); - UserDto result = userService.changeEmail(1L, "new@example.com"); + UserDto result = userService.changeEmail(USER_ID, NEW_EMAIL); assertThat(result).isNotNull(); - assertThat(testUser.getEmail()).isEqualTo("new@example.com"); + assertThat(testUser.getEmail()).isEqualTo(NEW_EMAIL); } @Test void changeEmail_throwsConflict_whenEmailAlreadyExists() { - when(userRepository.existsByEmailIgnoreCase("taken@example.com")).thenReturn(true); + when(userRepository.existsByEmailIgnoreCase(TAKEN_EMAIL)).thenReturn(true); - assertThatThrownBy(() -> userService.changeEmail(1L, "taken@example.com")) + assertThatThrownBy(() -> userService.changeEmail(USER_ID, TAKEN_EMAIL)) .isInstanceOf(ConflictException.class) - .hasMessageContaining("Email already exists"); + .hasMessageContaining(EMAIL_EXISTS_MESSAGE); verify(userRepository, never()).save(any()); } @Test void changeEmail_throwsNotFound_whenUserDoesNotExist() { - when(userRepository.existsByEmailIgnoreCase("new@example.com")).thenReturn(false); - when(userRepository.findById(99L)).thenReturn(Optional.empty()); + when(userRepository.existsByEmailIgnoreCase(NEW_EMAIL)).thenReturn(false); + when(userRepository.findById(MISSING_USER_ID)).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.changeEmail(99L, "new@example.com")) + assertThatThrownBy(() -> userService.changeEmail(MISSING_USER_ID, NEW_EMAIL)) .isInstanceOf(NotFoundException.class) - .hasMessageContaining("User not found"); + .hasMessageContaining(USER_NOT_FOUND_MESSAGE); } /* ======================= @@ -315,35 +348,35 @@ void changeEmail_throwsNotFound_whenUserDoesNotExist() { @Test void changeUsername_success() { - when(userRepository.existsByUsernameIgnoreCase("newuser")).thenReturn(false); - when(userRepository.findById(1L)).thenReturn(Optional.of(testUser)); + when(userRepository.existsByUsernameIgnoreCase(NEW_USERNAME)).thenReturn(false); + when(userRepository.findById(USER_ID)).thenReturn(Optional.of(testUser)); when(userRepository.save(testUser)).thenReturn(testUser); when(userMapper.toDto(testUser)).thenReturn(expectedDto); - UserDto result = userService.changeUsername(1L, "newuser"); + UserDto result = userService.changeUsername(USER_ID, NEW_USERNAME); assertThat(result).isNotNull(); - assertThat(testUser.getUsername()).isEqualTo("newuser"); + assertThat(testUser.getUsername()).isEqualTo(NEW_USERNAME); } @Test void changeUsername_throwsConflict_whenUsernameAlreadyExists() { - when(userRepository.existsByUsernameIgnoreCase("takenuser")).thenReturn(true); + when(userRepository.existsByUsernameIgnoreCase(TAKEN_USERNAME)).thenReturn(true); - assertThatThrownBy(() -> userService.changeUsername(1L, "takenuser")) + assertThatThrownBy(() -> userService.changeUsername(USER_ID, TAKEN_USERNAME)) .isInstanceOf(ConflictException.class) - .hasMessageContaining("Username already exists"); + .hasMessageContaining(USERNAME_EXISTS_MESSAGE); verify(userRepository, never()).save(any()); } @Test void changeUsername_throwsNotFound_whenUserDoesNotExist() { - when(userRepository.existsByUsernameIgnoreCase("newuser")).thenReturn(false); - when(userRepository.findById(99L)).thenReturn(Optional.empty()); + when(userRepository.existsByUsernameIgnoreCase(NEW_USERNAME)).thenReturn(false); + when(userRepository.findById(MISSING_USER_ID)).thenReturn(Optional.empty()); - assertThatThrownBy(() -> userService.changeUsername(99L, "newuser")) + assertThatThrownBy(() -> userService.changeUsername(MISSING_USER_ID, NEW_USERNAME)) .isInstanceOf(NotFoundException.class) - .hasMessageContaining("User not found"); + .hasMessageContaining(USER_NOT_FOUND_MESSAGE); } }