From afbe950ba498c1168a481accfec569c5a7cc6c5c Mon Sep 17 00:00:00 2001 From: Lobo Date: Mon, 6 Apr 2026 09:12:45 +0200 Subject: [PATCH] Finished registration Process and added tests --- .../entities/MinimalUserEntity.java | 2 +- .../controller/RegistrationController.java | 28 ++++++ .../controller/dto/mapper/ApiMapper.java | 19 ++++ .../dto/request/RegistrationRequest.java | 7 ++ .../dto/response/RegistrationResponse.java | 7 ++ .../registration/model/RegisteredUser.java | 7 -- .../persistence/RegisteredUserRepository.java | 3 +- .../RegisteredUserRepositoryImpl.java | 30 +++++- .../entities/RegisteredUserEntity.java | 37 ++++++++ .../persistence/mapper/EntityMapper.java | 24 +++++ .../service/RegistrationService.java | 3 +- .../user/persistence/entities/UserEntity.java | 2 +- .../RegistrationControllerTest.java | 93 +++++++++++++++++++ .../service/RegistrationServiceTest.java | 54 ++++++----- 14 files changed, 274 insertions(+), 42 deletions(-) create mode 100644 src/main/java/auth_service/domain/registration/controller/RegistrationController.java create mode 100644 src/main/java/auth_service/domain/registration/controller/dto/mapper/ApiMapper.java create mode 100644 src/main/java/auth_service/domain/registration/controller/dto/request/RegistrationRequest.java create mode 100644 src/main/java/auth_service/domain/registration/controller/dto/response/RegistrationResponse.java delete mode 100644 src/main/java/auth_service/domain/registration/model/RegisteredUser.java create mode 100644 src/main/java/auth_service/domain/registration/persistence/entities/RegisteredUserEntity.java create mode 100644 src/main/java/auth_service/domain/registration/persistence/mapper/EntityMapper.java create mode 100644 src/test/java/auth_service/domain/registration/controller/RegistrationControllerTest.java diff --git a/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java b/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java index ecd098d..bf57179 100644 --- a/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java +++ b/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java @@ -30,6 +30,6 @@ public class MinimalUserEntity { @Column(nullable = false) private boolean enabled; - @Column(name = "last_login_date", nullable = false) + @Column(name = "last_login_date") private LocalDateTime lastLoginDate; } \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/controller/RegistrationController.java b/src/main/java/auth_service/domain/registration/controller/RegistrationController.java new file mode 100644 index 0000000..6982b02 --- /dev/null +++ b/src/main/java/auth_service/domain/registration/controller/RegistrationController.java @@ -0,0 +1,28 @@ +package auth_service.domain.registration.controller; + +import auth_service.domain.registration.controller.dto.mapper.ApiMapper; +import auth_service.domain.registration.controller.dto.request.RegistrationRequest; +import auth_service.domain.registration.controller.dto.response.RegistrationResponse; +import auth_service.domain.registration.service.RegistrationService; +import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/registration") +@RequiredArgsConstructor +public class RegistrationController { + + private final RegistrationService registrationService; + + @PostMapping(value = "/new", version = "1.0", produces = "application/json", consumes = "application/json") + @Timed(value = "auth.registration.new.requests.time", description = "Time taken to process registration requests") + public ResponseEntity registration(@RequestBody RegistrationRequest request) { + var userId = registrationService.register(ApiMapper.mapToModel(request)); + return ResponseEntity.ok(RegistrationResponse.builder().userId(userId).build()); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/controller/dto/mapper/ApiMapper.java b/src/main/java/auth_service/domain/registration/controller/dto/mapper/ApiMapper.java new file mode 100644 index 0000000..7b7b376 --- /dev/null +++ b/src/main/java/auth_service/domain/registration/controller/dto/mapper/ApiMapper.java @@ -0,0 +1,19 @@ +package auth_service.domain.registration.controller.dto.mapper; + +import auth_service.domain.registration.controller.dto.request.RegistrationRequest; +import auth_service.domain.registration.model.Registration; + +public abstract class ApiMapper { + private ApiMapper() { + } + + public static Registration mapToModel(RegistrationRequest registrationRequest) { + return Registration.builder(). + email(registrationRequest.email()). + rawPassword(registrationRequest.password()). + inviteCode(registrationRequest.inviteCode()). + steamId(registrationRequest.steamId()). + build(); + } + +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/controller/dto/request/RegistrationRequest.java b/src/main/java/auth_service/domain/registration/controller/dto/request/RegistrationRequest.java new file mode 100644 index 0000000..dfac693 --- /dev/null +++ b/src/main/java/auth_service/domain/registration/controller/dto/request/RegistrationRequest.java @@ -0,0 +1,7 @@ +package auth_service.domain.registration.controller.dto.request; + +import lombok.Builder; + +@Builder +public record RegistrationRequest(String email, String password, String inviteCode, long steamId) { +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/controller/dto/response/RegistrationResponse.java b/src/main/java/auth_service/domain/registration/controller/dto/response/RegistrationResponse.java new file mode 100644 index 0000000..cf2170e --- /dev/null +++ b/src/main/java/auth_service/domain/registration/controller/dto/response/RegistrationResponse.java @@ -0,0 +1,7 @@ +package auth_service.domain.registration.controller.dto.response; + +import lombok.Builder; + +@Builder +public record RegistrationResponse(long userId) { +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/model/RegisteredUser.java b/src/main/java/auth_service/domain/registration/model/RegisteredUser.java deleted file mode 100644 index 85073e2..0000000 --- a/src/main/java/auth_service/domain/registration/model/RegisteredUser.java +++ /dev/null @@ -1,7 +0,0 @@ -package auth_service.domain.registration.model; - -public record RegisteredUser( - String id, - String email -) { -} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepository.java b/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepository.java index 04ed917..543ecf4 100644 --- a/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepository.java +++ b/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepository.java @@ -1,6 +1,5 @@ package auth_service.domain.registration.persistence; -import auth_service.domain.registration.model.RegisteredUser; import auth_service.domain.registration.model.Registration; public interface RegisteredUserRepository { @@ -8,5 +7,5 @@ public interface RegisteredUserRepository { boolean existsBySteamId(long steamId); - RegisteredUser save(Registration registration); + long save(Registration registration); } \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepositoryImpl.java b/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepositoryImpl.java index 529af99..3b80c9b 100644 --- a/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepositoryImpl.java +++ b/src/main/java/auth_service/domain/registration/persistence/RegisteredUserRepositoryImpl.java @@ -1,23 +1,43 @@ package auth_service.domain.registration.persistence; -import auth_service.domain.registration.model.RegisteredUser; import auth_service.domain.registration.model.Registration; +import auth_service.domain.registration.persistence.mapper.EntityMapper; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.transaction.Transactional; import org.springframework.stereotype.Repository; @Repository +@Transactional public class RegisteredUserRepositoryImpl implements RegisteredUserRepository { + + @PersistenceContext + private EntityManager entityManager; + @Override public boolean existsByEmail(String email) { - return false; + var jpql = "SELECT COUNT(u) FROM UserEntity u WHERE u.email = :email"; + var count = entityManager.createQuery(jpql, Long.class) + .setParameter("email", email) + .getSingleResult(); + + return count != null && count > 0; } @Override public boolean existsBySteamId(long steamId) { - return false; + var jpql = "SELECT COUNT(u) FROM UserEntity u WHERE u.steamId = :steamId"; + var count = entityManager.createQuery(jpql, Long.class) + .setParameter("steamId", steamId) + .getSingleResult(); + + return count != null && count > 0; } @Override - public RegisteredUser save(Registration registration) { - return null; + public long save(Registration registration) { + var userToRegisterEntity = EntityMapper.mapToPersistence(registration); + entityManager.persist(userToRegisterEntity); + return userToRegisterEntity.getId(); } } \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/persistence/entities/RegisteredUserEntity.java b/src/main/java/auth_service/domain/registration/persistence/entities/RegisteredUserEntity.java new file mode 100644 index 0000000..9cad15f --- /dev/null +++ b/src/main/java/auth_service/domain/registration/persistence/entities/RegisteredUserEntity.java @@ -0,0 +1,37 @@ +package auth_service.domain.registration.persistence.entities; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "users") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class RegisteredUserEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private boolean enabled; + + @Column(name = "steam_id", unique = true, nullable = false) + private Long steamId; + + @Column(name = "registration_date", nullable = false) + private LocalDateTime registrationDate; + + @Column(name = "last_login_date") + private LocalDateTime lastLoginDate; +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/persistence/mapper/EntityMapper.java b/src/main/java/auth_service/domain/registration/persistence/mapper/EntityMapper.java new file mode 100644 index 0000000..0d5366d --- /dev/null +++ b/src/main/java/auth_service/domain/registration/persistence/mapper/EntityMapper.java @@ -0,0 +1,24 @@ +package auth_service.domain.registration.persistence.mapper; + +import auth_service.domain.registration.model.Registration; +import auth_service.domain.registration.persistence.entities.RegisteredUserEntity; + +import java.time.Clock; +import java.time.LocalDateTime; + +public abstract class EntityMapper { + + private EntityMapper() { + } + + public static RegisteredUserEntity mapToPersistence(Registration userModel) { + return RegisteredUserEntity.builder(). + email(userModel.getEmail()). + registrationDate(LocalDateTime.now(Clock.systemUTC())). + password(userModel.getEncryptedPassword()). + enabled(true). + steamId(userModel.getSteamId()). + lastLoginDate(null). + build(); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/registration/service/RegistrationService.java b/src/main/java/auth_service/domain/registration/service/RegistrationService.java index bc4f1ca..817844a 100644 --- a/src/main/java/auth_service/domain/registration/service/RegistrationService.java +++ b/src/main/java/auth_service/domain/registration/service/RegistrationService.java @@ -3,7 +3,6 @@ package auth_service.domain.registration.service; import auth_service.domain.registration.exception.EmailAlreadyInUseException; import auth_service.domain.registration.exception.InvalidInvitationCodeException; import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException; -import auth_service.domain.registration.model.RegisteredUser; import auth_service.domain.registration.model.Registration; import auth_service.domain.registration.persistence.RegisteredUserRepository; import lombok.RequiredArgsConstructor; @@ -18,7 +17,7 @@ public class RegistrationService { private final RegisteredUserRepository registeredUserRepository; private final PasswordEncoder passwordEncoder; - public RegisteredUser register(Registration registration) { + public long register(Registration registration) { if (!codeValidationService.isCodeValid(registration.getInviteCode())) { throw new InvalidInvitationCodeException("The provided invite code is invalid or has already been used."); } diff --git a/src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java b/src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java index db0f5bc..a016897 100644 --- a/src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java +++ b/src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java @@ -36,6 +36,6 @@ public class UserEntity { @Column(name = "registration_date", nullable = false) private LocalDateTime registrationDate; - @Column(name = "last_login_date", nullable = false) + @Column(name = "last_login_date") private LocalDateTime lastLoginDate; } \ No newline at end of file diff --git a/src/test/java/auth_service/domain/registration/controller/RegistrationControllerTest.java b/src/test/java/auth_service/domain/registration/controller/RegistrationControllerTest.java new file mode 100644 index 0000000..ccdfac4 --- /dev/null +++ b/src/test/java/auth_service/domain/registration/controller/RegistrationControllerTest.java @@ -0,0 +1,93 @@ +package auth_service.domain.registration.controller; + +import auth_service.configuration.security.JwtAuthenticationFilter; +import auth_service.configuration.security.JwtService; +import auth_service.domain.registration.controller.dto.request.RegistrationRequest; +import auth_service.domain.registration.exception.EmailAlreadyInUseException; +import auth_service.domain.registration.model.Registration; +import auth_service.domain.registration.service.RegistrationService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Tests for RegistrationController. + */ +@WebMvcTest(RegistrationController.class) +@AutoConfigureMockMvc(addFilters = false) // Skip security filters for simple controller test +@AutoConfigureJsonTesters +class RegistrationControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private RegistrationService registrationService; + + // Needed because they are likely used in SecurityConfig and required by the ApplicationContext + @MockitoBean + private JwtService jwtService; + + @MockitoBean + private JwtAuthenticationFilter jwtAuthenticationFilter; + + @Autowired + private JacksonTester registrationRequestJson; + + @Test + void register_Success() throws Exception { + // Arrange + var request = RegistrationRequest.builder() + .email("test@example.com") + .password("SuperSecret123!") + .inviteCode("test-invite-code") + .steamId(314159L) + .build(); + + long expectedUserId = 42L; + + // Mock the service call, ignoring the exact static mapping output + when(registrationService.register(any(Registration.class))).thenReturn(expectedUserId); + + // Act & Assert + mockMvc.perform(post("/api/registration/new") + .contentType(MediaType.APPLICATION_JSON) + .content(registrationRequestJson.write(request).getJson())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.userId").value(expectedUserId)); + } + + @Test + void register_Failure_EmailAlreadyInUse() throws Exception { + // Arrange + var request = RegistrationRequest.builder() + .email("test@example.com") + .password("SuperSecret123!") + .inviteCode("test-invite-code") + .steamId(314159L) + .build(); + + when(registrationService.register(any(Registration.class))) + .thenThrow(new EmailAlreadyInUseException("The provided email address is already in use.")); + + // Act & Assert + // Depending on your GlobalExceptionHandler, this could also be 400 or 409. + // Assuming standard behavior without advice or mapping to 400 for now. + mockMvc.perform(post("/api/registration/new") + .contentType(MediaType.APPLICATION_JSON) + .content(registrationRequestJson.write(request).getJson())) + .andExpect(status().isBadRequest()); // Adjust status based on your @ExceptionHandler + } +} \ No newline at end of file diff --git a/src/test/java/auth_service/domain/registration/service/RegistrationServiceTest.java b/src/test/java/auth_service/domain/registration/service/RegistrationServiceTest.java index 2890d33..682154c 100644 --- a/src/test/java/auth_service/domain/registration/service/RegistrationServiceTest.java +++ b/src/test/java/auth_service/domain/registration/service/RegistrationServiceTest.java @@ -3,7 +3,6 @@ package auth_service.domain.registration.service; import auth_service.domain.registration.exception.EmailAlreadyInUseException; import auth_service.domain.registration.exception.InvalidInvitationCodeException; import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException; -import auth_service.domain.registration.model.RegisteredUser; import auth_service.domain.registration.model.Registration; import auth_service.domain.registration.persistence.RegisteredUserRepository; import org.junit.jupiter.api.BeforeEach; @@ -14,9 +13,14 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.crypto.password.PasswordEncoder; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +/** + * Tests for RegistrationService. + */ @ExtendWith(MockitoExtension.class) class RegistrationServiceTest { @@ -39,60 +43,62 @@ class RegistrationServiceTest { validRegistration = Registration.builder() .email("test@example.com") .rawPassword("SuperSecret123!") - .encryptedPassword("") - .inviteCode("TBP-INVITE-2026") + .inviteCode("test-invite-code") .steamId(314159L) .build(); } @Test - void register_withValidData_shouldReturnRegisteredUserAndMarkCodeAsUsed() { + void register_Success() { // Arrange - when(codeValidationService.isCodeValid("TBP-INVITE-2026")).thenReturn(true); - when(registeredUserRepository.existsByEmail("test@example.com")).thenReturn(false); - when(passwordEncoder.encode("SuperSecret123!")).thenReturn("hashedPasswordXYZ"); - - RegisteredUser expectedUser = new RegisteredUser("uuid-1234", "test@example.com"); - when(registeredUserRepository.save(validRegistration)).thenReturn(expectedUser); + long expectedUserId = 42L; + when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true); + when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(false); + when(registeredUserRepository.existsBySteamId(validRegistration.getSteamId())).thenReturn(false); + when(passwordEncoder.encode(validRegistration.getRawPassword())).thenReturn("hashedPasswordXYZ"); + when(registeredUserRepository.save(any(Registration.class))).thenReturn(expectedUserId); // Act - RegisteredUser result = registrationService.register(validRegistration); + long result = registrationService.register(validRegistration); // Assert - assertNotNull(result); - assertEquals("uuid-1234", result.id()); - assertEquals("test@example.com", result.email()); + assertEquals(expectedUserId, result); + verify(passwordEncoder).encode("SuperSecret123!"); + verify(registeredUserRepository).save(validRegistration); + assertEquals("hashedPasswordXYZ", validRegistration.getEncryptedPassword()); } @Test - void register_withInvalidInviteCode_shouldThrowException() { + void register_InvalidInviteCode() { // Arrange - when(codeValidationService.isCodeValid("TBP-INVITE-2026")).thenReturn(false); + when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(false); // Act & Assert assertThrows(InvalidInvitationCodeException.class, () -> registrationService.register(validRegistration)); + verify(registeredUserRepository, never()).existsByEmail(anyString()); verify(registeredUserRepository, never()).save(any()); } @Test - void register_withExistingEmail_shouldThrowException() { + void register_EmailAlreadyInUse() { // Arrange - when(codeValidationService.isCodeValid("TBP-INVITE-2026")).thenReturn(true); - when(registeredUserRepository.existsByEmail("test@example.com")).thenReturn(true); + when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true); + when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(true); // Act & Assert assertThrows(EmailAlreadyInUseException.class, () -> registrationService.register(validRegistration)); + verify(registeredUserRepository, never()).existsBySteamId(anyLong()); verify(registeredUserRepository, never()).save(any()); } @Test - void register_withExistingSteamId_shouldThrowException() { + void register_SteamIdAlreadyRegistered() { // Arrange - when(codeValidationService.isCodeValid("TBP-INVITE-2026")).thenReturn(true); - when(registeredUserRepository.existsByEmail("test@example.com")).thenReturn(false); - when(registeredUserRepository.existsBySteamId(314159L)).thenReturn(true); + when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true); + when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(false); + when(registeredUserRepository.existsBySteamId(validRegistration.getSteamId())).thenReturn(true); // Act & Assert assertThrows(SteamIdAlreadyRegisteredException.class, () -> registrationService.register(validRegistration));