Public Access
feat(user): optional display name (player tag)
users.display_name (V6), 3-24 letters, digits, space, _ . -, unique ignoring case. Set in settings (409 DISPLAY_NAME_ALREADY_IN_USE, also for the DB constraint race) and optionally at registration. Returned by /api/users/me.
This commit is contained in:
1 parent
4579b05d2c
commit
2941e3f905
29 files changed
+320
-23
No files matched your search
+7
@@ -3,6 +3,7 @@ package auth_service.modules.registration.api;
|
|||||||
import auth_service.shared.api.dto.response.ErrorResponse;
|
import auth_service.shared.api.dto.response.ErrorResponse;
|
||||||
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||||
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
||||||
|
import auth_service.modules.registration.exception.DisplayNameAlreadyRegisteredException;
|
||||||
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -35,6 +36,12 @@ public class RegistrationExceptionController {
|
|||||||
return response(body, HttpStatus.BAD_REQUEST);
|
return response(body, HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(DisplayNameAlreadyRegisteredException.class)
|
||||||
|
public ResponseEntity<ErrorResponse> clientError(DisplayNameAlreadyRegisteredException ex) {
|
||||||
|
var body = new ErrorResponse("DISPLAY_NAME_ALREADY_IN_USE", ex.getMessage());
|
||||||
|
return response(body, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(SteamIdAlreadyRegisteredException.class)
|
@ExceptionHandler(SteamIdAlreadyRegisteredException.class)
|
||||||
public ResponseEntity<ErrorResponse> clientError(SteamIdAlreadyRegisteredException ex) {
|
public ResponseEntity<ErrorResponse> clientError(SteamIdAlreadyRegisteredException ex) {
|
||||||
var body = new ErrorResponse("STEAM_ID_ALREADY_IN_USE", ex.getMessage());
|
var body = new ErrorResponse("STEAM_ID_ALREADY_IN_USE", ex.getMessage());
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public abstract class ApiMapper {
|
|||||||
email(registrationRequest.email()).
|
email(registrationRequest.email()).
|
||||||
rawPassword(registrationRequest.password()).
|
rawPassword(registrationRequest.password()).
|
||||||
inviteCode(registrationRequest.inviteCode()).
|
inviteCode(registrationRequest.inviteCode()).
|
||||||
|
displayName(registrationRequest.displayName()).
|
||||||
steamId(registrationRequest.steamId() == null ? null : Long.valueOf(registrationRequest.steamId())).
|
steamId(registrationRequest.steamId() == null ? null : Long.valueOf(registrationRequest.steamId())).
|
||||||
build();
|
build();
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,6 @@
|
|||||||
package auth_service.modules.registration.api.dto.request;
|
package auth_service.modules.registration.api.dto.request;
|
||||||
|
|
||||||
|
import auth_service.shared.api.validation.DisplayNameFormat;
|
||||||
import auth_service.shared.api.validation.SteamIdDeserializer;
|
import auth_service.shared.api.validation.SteamIdDeserializer;
|
||||||
import auth_service.shared.api.validation.SteamIdFormat;
|
import auth_service.shared.api.validation.SteamIdFormat;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
@@ -9,5 +10,6 @@ import tools.jackson.databind.annotation.JsonDeserialize;
|
|||||||
@Builder
|
@Builder
|
||||||
public record RegistrationRequest(String email, String password, String inviteCode,
|
public record RegistrationRequest(String email, String password, String inviteCode,
|
||||||
@Pattern(regexp = SteamIdFormat.PATTERN)
|
@Pattern(regexp = SteamIdFormat.PATTERN)
|
||||||
@JsonDeserialize(using = SteamIdDeserializer.class) String steamId) {
|
@JsonDeserialize(using = SteamIdDeserializer.class) String steamId,
|
||||||
|
@Pattern(regexp = DisplayNameFormat.PATTERN) String displayName) {
|
||||||
}
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package auth_service.modules.registration.exception;
|
||||||
|
|
||||||
|
public class DisplayNameAlreadyRegisteredException extends RuntimeException {
|
||||||
|
public DisplayNameAlreadyRegisteredException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,4 +11,5 @@ public class Registration {
|
|||||||
private String encryptedPassword;
|
private String encryptedPassword;
|
||||||
private String inviteCode;
|
private String inviteCode;
|
||||||
private Long steamId;
|
private Long steamId;
|
||||||
|
private String displayName;
|
||||||
}
|
}
|
||||||
+2
@@ -7,5 +7,7 @@ public interface RegisteredUserRepository {
|
|||||||
|
|
||||||
boolean existsBySteamId(long steamId);
|
boolean existsBySteamId(long steamId);
|
||||||
|
|
||||||
|
boolean existsByDisplayName(String displayName);
|
||||||
|
|
||||||
long save(Registration registration);
|
long save(Registration registration);
|
||||||
}
|
}
|
||||||
+10
@@ -34,6 +34,16 @@ public class RegisteredUserRepositoryImpl implements RegisteredUserRepository {
|
|||||||
return count != null && count > 0;
|
return count != null && count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean existsByDisplayName(String displayName) {
|
||||||
|
var jpql = "SELECT COUNT(u) FROM UserEntity u WHERE LOWER(u.displayName) = LOWER(:displayName)";
|
||||||
|
var count = entityManager.createQuery(jpql, Long.class)
|
||||||
|
.setParameter("displayName", displayName)
|
||||||
|
.getSingleResult();
|
||||||
|
|
||||||
|
return count != null && count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long save(Registration registration) {
|
public long save(Registration registration) {
|
||||||
var userToRegisterEntity = EntityMapper.mapToPersistence(registration);
|
var userToRegisterEntity = EntityMapper.mapToPersistence(registration);
|
||||||
|
|||||||
+3
@@ -30,6 +30,9 @@ public class RegisteredUserEntity {
|
|||||||
@Column(name = "steam_id", unique = true)
|
@Column(name = "steam_id", unique = true)
|
||||||
private Long steamId;
|
private Long steamId;
|
||||||
|
|
||||||
|
@Column(name = "display_name", length = 24)
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
@Column(name = "registration_date", nullable = false)
|
@Column(name = "registration_date", nullable = false)
|
||||||
private LocalDateTime registrationDate;
|
private LocalDateTime registrationDate;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ public abstract class EntityMapper {
|
|||||||
password(userModel.getEncryptedPassword()).
|
password(userModel.getEncryptedPassword()).
|
||||||
enabled(true).
|
enabled(true).
|
||||||
steamId(userModel.getSteamId()).
|
steamId(userModel.getSteamId()).
|
||||||
|
displayName(userModel.getDisplayName()).
|
||||||
lastLoginDate(null).
|
lastLoginDate(null).
|
||||||
build();
|
build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package auth_service.modules.registration.service;
|
|||||||
|
|
||||||
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||||
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
||||||
|
import auth_service.modules.registration.exception.DisplayNameAlreadyRegisteredException;
|
||||||
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
||||||
import auth_service.modules.registration.model.Registration;
|
import auth_service.modules.registration.model.Registration;
|
||||||
import auth_service.modules.registration.persistence.RegisteredUserRepository;
|
import auth_service.modules.registration.persistence.RegisteredUserRepository;
|
||||||
@@ -30,6 +31,10 @@ public class RegistrationService {
|
|||||||
throw new SteamIdAlreadyRegisteredException("The steam id is already in use.");
|
throw new SteamIdAlreadyRegisteredException("The steam id is already in use.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (registration.getDisplayName() != null && registeredUserRepository.existsByDisplayName(registration.getDisplayName())) {
|
||||||
|
throw new DisplayNameAlreadyRegisteredException("The display name is already in use.");
|
||||||
|
}
|
||||||
|
|
||||||
var rawPassword = registration.getRawPassword();
|
var rawPassword = registration.getRawPassword();
|
||||||
registration.setEncryptedPassword(passwordEncoder.encode(rawPassword));
|
registration.setEncryptedPassword(passwordEncoder.encode(rawPassword));
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class UserController {
|
|||||||
public ResponseEntity<UserSettingsResponse> updateUserSettings(@AuthenticationPrincipal UserPrincipal principal,
|
public ResponseEntity<UserSettingsResponse> updateUserSettings(@AuthenticationPrincipal UserPrincipal principal,
|
||||||
@Valid @RequestBody UserSettingsRequest request) {
|
@Valid @RequestBody UserSettingsRequest request) {
|
||||||
var token = this.userService.updateUserSettings(principal.getIdAsLong(), principal.email(),
|
var token = this.userService.updateUserSettings(principal.getIdAsLong(), principal.email(),
|
||||||
ApiMapper.toSteamId(request), ApiMapper.toDomain(request));
|
ApiMapper.toSteamId(request), request.displayName(), ApiMapper.toDomain(request));
|
||||||
return ResponseEntity.ok(new UserSettingsResponse(token));
|
return ResponseEntity.ok(new UserSettingsResponse(token));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package auth_service.modules.user.api;
|
package auth_service.modules.user.api;
|
||||||
|
|
||||||
import auth_service.shared.api.dto.response.ErrorResponse;
|
import auth_service.shared.api.dto.response.ErrorResponse;
|
||||||
|
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.UserNotFoundException;
|
import auth_service.modules.user.exception.UserNotFoundException;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
@@ -17,6 +18,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class UserExceptionController {
|
public class UserExceptionController {
|
||||||
private static final String STEAM_ID_CONSTRAINT = "uc_steam_id";
|
private static final String STEAM_ID_CONSTRAINT = "uc_steam_id";
|
||||||
|
private static final String DISPLAY_NAME_CONSTRAINT = "uq_users_display_name_lower";
|
||||||
|
|
||||||
private static @NonNull ResponseEntity<ErrorResponse> response(ErrorResponse body, HttpStatus status) {
|
private static @NonNull ResponseEntity<ErrorResponse> response(ErrorResponse body, HttpStatus status) {
|
||||||
return ResponseEntity.status(status.value())
|
return ResponseEntity.status(status.value())
|
||||||
@@ -42,17 +44,30 @@ public class UserExceptionController {
|
|||||||
return steamIdInUse();
|
return steamIdInUse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A parallel request can save the same Steam ID after the service check. Then the DB constraint fails.
|
// A parallel request can save the same Steam ID or display name after the service check. Then the DB constraint fails.
|
||||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||||
public ResponseEntity<ErrorResponse> dataIntegrityError(DataIntegrityViolationException ex) {
|
public ResponseEntity<ErrorResponse> dataIntegrityError(DataIntegrityViolationException ex) {
|
||||||
var cause = ex.getMostSpecificCause().getMessage();
|
var cause = ex.getMostSpecificCause().getMessage();
|
||||||
if (cause != null && cause.contains(STEAM_ID_CONSTRAINT)) {
|
if (cause != null && cause.contains(STEAM_ID_CONSTRAINT)) {
|
||||||
return steamIdInUse();
|
return steamIdInUse();
|
||||||
}
|
}
|
||||||
|
if (cause != null && cause.contains(DISPLAY_NAME_CONSTRAINT)) {
|
||||||
|
return displayNameInUse();
|
||||||
|
}
|
||||||
log.error("Data integrity violation in user request", ex);
|
log.error("Data integrity violation in user request", ex);
|
||||||
return response(new ErrorResponse("INTERNAL_ERROR", "Unexpected database error."), HttpStatus.INTERNAL_SERVER_ERROR);
|
return response(new ErrorResponse("INTERNAL_ERROR", "Unexpected database error."), HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(DisplayNameAlreadyInUseException.class)
|
||||||
|
public ResponseEntity<ErrorResponse> clientError(DisplayNameAlreadyInUseException ex) {
|
||||||
|
return displayNameInUse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResponseEntity<ErrorResponse> displayNameInUse() {
|
||||||
|
var body = new ErrorResponse("DISPLAY_NAME_ALREADY_IN_USE", "The display name is already in use.");
|
||||||
|
return response(body, HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
private static ResponseEntity<ErrorResponse> steamIdInUse() {
|
private static ResponseEntity<ErrorResponse> steamIdInUse() {
|
||||||
var body = new ErrorResponse("STEAM_ID_ALREADY_IN_USE", "The steam id is already in use.");
|
var body = new ErrorResponse("STEAM_ID_ALREADY_IN_USE", "The steam id is already in use.");
|
||||||
return response(body, HttpStatus.CONFLICT);
|
return response(body, HttpStatus.CONFLICT);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package auth_service.modules.user.api.dto.request;
|
package auth_service.modules.user.api.dto.request;
|
||||||
|
|
||||||
|
import auth_service.shared.api.validation.DisplayNameFormat;
|
||||||
import auth_service.shared.api.validation.SteamIdDeserializer;
|
import auth_service.shared.api.validation.SteamIdDeserializer;
|
||||||
import auth_service.shared.api.validation.SteamIdFormat;
|
import auth_service.shared.api.validation.SteamIdFormat;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
@@ -7,8 +8,10 @@ import tools.jackson.databind.annotation.JsonDeserialize;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param steamId null removes the Steam ID
|
* @param steamId null removes the Steam ID
|
||||||
|
* @param displayName null removes the display name
|
||||||
*/
|
*/
|
||||||
public record UserSettingsRequest(boolean useDarkTheme, String language,
|
public record UserSettingsRequest(boolean useDarkTheme, String language,
|
||||||
@Pattern(regexp = SteamIdFormat.PATTERN)
|
@Pattern(regexp = SteamIdFormat.PATTERN)
|
||||||
@JsonDeserialize(using = SteamIdDeserializer.class) String steamId) {
|
@JsonDeserialize(using = SteamIdDeserializer.class) String steamId,
|
||||||
|
@Pattern(regexp = DisplayNameFormat.PATTERN) String displayName) {
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,5 @@ import lombok.Builder;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Builder
|
@Builder
|
||||||
public record UserProfileResponse(long id, String email, String role, LocalDateTime lastLogin, LocalDateTime createdDate, String steamId, SettingsResponse settings) {
|
public record UserProfileResponse(long id, String email, String role, LocalDateTime lastLogin, LocalDateTime createdDate, String steamId, String displayName, SettingsResponse settings) {
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ public abstract class ApiMapper {
|
|||||||
.createdDate(user.getCreatedDate())
|
.createdDate(user.getCreatedDate())
|
||||||
.lastLogin(user.getLastLogin())
|
.lastLogin(user.getLastLogin())
|
||||||
.steamId(user.getSteamId() == null ? null : String.valueOf(user.getSteamId()))
|
.steamId(user.getSteamId() == null ? null : String.valueOf(user.getSteamId()))
|
||||||
|
.displayName(user.getDisplayName())
|
||||||
.role("USER")
|
.role("USER")
|
||||||
.settings(SettingsResponse.builder()
|
.settings(SettingsResponse.builder()
|
||||||
.id(user.getId())
|
.id(user.getId())
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package auth_service.modules.user.exception;
|
||||||
|
|
||||||
|
public class DisplayNameAlreadyInUseException extends RuntimeException {
|
||||||
|
public DisplayNameAlreadyInUseException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ public class User {
|
|||||||
private LocalDateTime lastLogin;
|
private LocalDateTime lastLogin;
|
||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
private Long steamId;
|
private Long steamId;
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
private UserSettings settings;
|
private UserSettings settings;
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,9 @@ public interface UserRepository {
|
|||||||
UserSettings findSettingsByUserId(String userId);
|
UserSettings findSettingsByUserId(String userId);
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
boolean updateSettings(long userId, Long steamId, UserSettings settings);
|
boolean updateSettings(long userId, Long steamId, String displayName, UserSettings settings);
|
||||||
|
|
||||||
boolean isSteamIdUsedByOtherUser(long userId, long steamId);
|
boolean isSteamIdUsedByOtherUser(long userId, long steamId);
|
||||||
|
|
||||||
|
boolean isDisplayNameUsedByOtherUser(long userId, String displayName);
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public boolean updateSettings(long userId, Long steamId, UserSettings settings) {
|
public boolean updateSettings(long userId, Long steamId, String displayName, UserSettings settings) {
|
||||||
var userEntity = entityManager.find(UserEntity.class, userId);
|
var userEntity = entityManager.find(UserEntity.class, userId);
|
||||||
if (userEntity == null) {
|
if (userEntity == null) {
|
||||||
throw new UserNotFoundException("Could not find user with id " + userId);
|
throw new UserNotFoundException("Could not find user with id " + userId);
|
||||||
@@ -80,6 +80,7 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
|
|
||||||
boolean steamIdChanged = !Objects.equals(userEntity.getSteamId(), steamId);
|
boolean steamIdChanged = !Objects.equals(userEntity.getSteamId(), steamId);
|
||||||
userEntity.setSteamId(steamId);
|
userEntity.setSteamId(steamId);
|
||||||
|
userEntity.setDisplayName(displayName);
|
||||||
|
|
||||||
return steamIdChanged;
|
return steamIdChanged;
|
||||||
}
|
}
|
||||||
@@ -93,4 +94,14 @@ public class UserRepositoryImpl implements UserRepository {
|
|||||||
.getSingleResult();
|
.getSingleResult();
|
||||||
return count > 0;
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisplayNameUsedByOtherUser(long userId, String displayName) {
|
||||||
|
var count = entityManager.createQuery(
|
||||||
|
"SELECT COUNT(u) FROM UserEntity u WHERE LOWER(u.displayName) = LOWER(:displayName) AND u.id <> :userId", Long.class)
|
||||||
|
.setParameter("displayName", displayName)
|
||||||
|
.setParameter("userId", userId)
|
||||||
|
.getSingleResult();
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,9 @@ public class UserEntity {
|
|||||||
@Column(name = "steam_id", unique = true)
|
@Column(name = "steam_id", unique = true)
|
||||||
private Long steamId;
|
private Long steamId;
|
||||||
|
|
||||||
|
@Column(name = "display_name", length = 24)
|
||||||
|
private String displayName;
|
||||||
|
|
||||||
@Column(name = "registration_date", nullable = false)
|
@Column(name = "registration_date", nullable = false)
|
||||||
private LocalDateTime registrationDate;
|
private LocalDateTime registrationDate;
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ public abstract class EntityMapper {
|
|||||||
.lastLogin(entity.getLastLoginDate())
|
.lastLogin(entity.getLastLoginDate())
|
||||||
.createdDate(entity.getRegistrationDate())
|
.createdDate(entity.getRegistrationDate())
|
||||||
.steamId(entity.getSteamId())
|
.steamId(entity.getSteamId())
|
||||||
|
.displayName(entity.getDisplayName())
|
||||||
.settings(settingsModel)
|
.settings(settingsModel)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package auth_service.modules.user.service;
|
package auth_service.modules.user.service;
|
||||||
|
|
||||||
|
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.UserNotFoundException;
|
import auth_service.modules.user.exception.UserNotFoundException;
|
||||||
import auth_service.modules.user.model.User;
|
import auth_service.modules.user.model.User;
|
||||||
@@ -34,11 +35,14 @@ public class UserService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@CacheEvict(value = "usersByEmail", allEntries = true)
|
@CacheEvict(value = "usersByEmail", allEntries = true)
|
||||||
public String updateUserSettings(long userId, String email, Long steamId, UserSettings userSettings) {
|
public String updateUserSettings(long userId, String email, Long steamId, String displayName, UserSettings userSettings) {
|
||||||
if (steamId != null && userRepository.isSteamIdUsedByOtherUser(userId, steamId)) {
|
if (steamId != null && userRepository.isSteamIdUsedByOtherUser(userId, steamId)) {
|
||||||
throw new SteamIdAlreadyInUseException("The steam id is already in use.");
|
throw new SteamIdAlreadyInUseException("The steam id is already in use.");
|
||||||
}
|
}
|
||||||
var updated = userRepository.updateSettings(userId, steamId, userSettings);
|
if (displayName != null && userRepository.isDisplayNameUsedByOtherUser(userId, displayName)) {
|
||||||
|
throw new DisplayNameAlreadyInUseException("The display name is already in use.");
|
||||||
|
}
|
||||||
|
var updated = userRepository.updateSettings(userId, steamId, displayName, userSettings);
|
||||||
if (updated) {
|
if (updated) {
|
||||||
log.info("Steam id has changed for user {}", userId);
|
log.info("Steam id has changed for user {}", userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package auth_service.shared.api.validation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3 to 24 letters, digits, spaces, "_", "." or "-". No space at the start or end.
|
||||||
|
*/
|
||||||
|
public final class DisplayNameFormat {
|
||||||
|
public static final String PATTERN = "[\\p{L}\\p{N}_.-][\\p{L}\\p{N} _.-]{1,22}[\\p{L}\\p{N}_.-]";
|
||||||
|
|
||||||
|
private DisplayNameFormat() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- Player tag shown instead of the email prefix. Optional, unique ignoring case.
|
||||||
|
ALTER TABLE users ADD COLUMN display_name VARCHAR(24);
|
||||||
|
CREATE UNIQUE INDEX uq_users_display_name_lower ON users (LOWER(display_name));
|
||||||
@@ -168,4 +168,33 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
|||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.content(registrationRequestJson.write(request).getJson())));
|
.content(registrationRequestJson.write(request).getJson())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void register_InvalidDisplayName() throws Exception {
|
||||||
|
mockMvc.perform(post("/api/registration/new")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","displayName":"x"}"""))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
|
||||||
|
verifyNoInteractions(registrationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void register_PassesDisplayName() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
when(registrationService.register(any(Registration.class))).thenReturn(42L);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
mockMvc.perform(post("/api/registration/new")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","displayName":"Lobo"}"""))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var captor = ArgumentCaptor.forClass(Registration.class);
|
||||||
|
verify(registrationService).register(captor.capture());
|
||||||
|
assertEquals("Lobo", captor.getValue().getDisplayName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package auth_service.modules.registration.service;
|
|||||||
|
|
||||||
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||||
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
||||||
|
import auth_service.modules.registration.exception.DisplayNameAlreadyRegisteredException;
|
||||||
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
import auth_service.modules.registration.exception.SteamIdAlreadyRegisteredException;
|
||||||
import auth_service.modules.registration.model.Registration;
|
import auth_service.modules.registration.model.Registration;
|
||||||
import auth_service.modules.registration.persistence.RegisteredUserRepository;
|
import auth_service.modules.registration.persistence.RegisteredUserRepository;
|
||||||
@@ -120,4 +121,18 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
|||||||
assertEquals(42L, result);
|
assertEquals(42L, result);
|
||||||
verify(registeredUserRepository, never()).existsBySteamId(anyLong());
|
verify(registeredUserRepository, never()).existsBySteamId(anyLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void register_DisplayNameTaken() {
|
||||||
|
// Arrange
|
||||||
|
validRegistration.setDisplayName("Lobo");
|
||||||
|
when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true);
|
||||||
|
when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(false);
|
||||||
|
when(registeredUserRepository.existsBySteamId(validRegistration.getSteamId())).thenReturn(false);
|
||||||
|
when(registeredUserRepository.existsByDisplayName("Lobo")).thenReturn(true);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(DisplayNameAlreadyRegisteredException.class, () -> registrationService.register(validRegistration));
|
||||||
|
verify(registeredUserRepository, never()).save(any());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package auth_service.modules.user.api;
|
|||||||
import auth_service.configuration.security.JwtAuthenticationFilter;
|
import auth_service.configuration.security.JwtAuthenticationFilter;
|
||||||
import auth_service.configuration.security.JwtService;
|
import auth_service.configuration.security.JwtService;
|
||||||
import auth_service.shared.security.UserPrincipal;
|
import auth_service.shared.security.UserPrincipal;
|
||||||
|
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.UserNotFoundException;
|
import auth_service.modules.user.exception.UserNotFoundException;
|
||||||
import auth_service.modules.user.model.User;
|
import auth_service.modules.user.model.User;
|
||||||
@@ -11,6 +12,8 @@ import auth_service.modules.user.service.UserService;
|
|||||||
import auth_service.shared.test.AbstractWebMvcTest;
|
import auth_service.shared.test.AbstractWebMvcTest;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
|
import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters;
|
||||||
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
|
||||||
@@ -136,7 +139,7 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
SecurityContextHolder.getContext().setAuthentication(
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
);
|
);
|
||||||
when(userService.updateUserSettings(eq(1L), eq(email), eq(76561198005679788L), any())).thenReturn("new-token");
|
when(userService.updateUserSettings(eq(1L), eq(email), eq(76561198005679788L), any(), any())).thenReturn("new-token");
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
mockMvc.perform(put("/api/users/settings")
|
mockMvc.perform(put("/api/users/settings")
|
||||||
@@ -163,7 +166,7 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
{"useDarkTheme":true,"language":"en","steamId":null}"""))
|
{"useDarkTheme":true,"language":"en","steamId":null}"""))
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
verify(userService).updateUserSettings(eq(1L), eq(email), isNull(), any());
|
verify(userService).updateUserSettings(eq(1L), eq(email), isNull(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -192,7 +195,7 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
SecurityContextHolder.getContext().setAuthentication(
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
);
|
);
|
||||||
when(userService.updateUserSettings(eq(1L), eq(email), eq(76561198005679788L), any()))
|
when(userService.updateUserSettings(eq(1L), eq(email), eq(76561198005679788L), any(), any()))
|
||||||
.thenThrow(new DataIntegrityViolationException("duplicate key value violates unique constraint \"uc_steam_id\""));
|
.thenThrow(new DataIntegrityViolationException("duplicate key value violates unique constraint \"uc_steam_id\""));
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
@@ -230,7 +233,7 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
SecurityContextHolder.getContext().setAuthentication(
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
);
|
);
|
||||||
when(userService.updateUserSettings(eq(1L), eq(email), eq(76561198005679788L), any()))
|
when(userService.updateUserSettings(eq(1L), eq(email), eq(76561198005679788L), any(), any()))
|
||||||
.thenThrow(new SteamIdAlreadyInUseException("taken"));
|
.thenThrow(new SteamIdAlreadyInUseException("taken"));
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
@@ -250,7 +253,7 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
SecurityContextHolder.getContext().setAuthentication(
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
);
|
);
|
||||||
when(userService.updateUserSettings(eq(1L), eq(email), isNull(), any()))
|
when(userService.updateUserSettings(eq(1L), eq(email), isNull(), any(), any()))
|
||||||
.thenThrow(new DataIntegrityViolationException("value too long for type character varying(10)"));
|
.thenThrow(new DataIntegrityViolationException("value too long for type character varying(10)"));
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
@@ -261,4 +264,98 @@ class UserControllerTest extends AbstractWebMvcTest {
|
|||||||
.andExpect(status().isInternalServerError())
|
.andExpect(status().isInternalServerError())
|
||||||
.andExpect(jsonPath("$.code").value("INTERNAL_ERROR"));
|
.andExpect(jsonPath("$.code").value("INTERNAL_ERROR"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getCurrentUserV1_ReturnsDisplayName() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
var email = "test@example.com";
|
||||||
|
var settings = UserSettings.builder().language("en").theme("dark").build();
|
||||||
|
var user = User.builder().email(email).displayName("Lobo").settings(settings).build();
|
||||||
|
var principal = new UserPrincipal("1", email, null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
|
);
|
||||||
|
when(userService.getUserByEmail(email)).thenReturn(user);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
mockMvc.perform(get("/api/users/me").accept(MediaType.APPLICATION_JSON))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.displayName").value("Lobo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUserSettings_PassesDisplayName() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
var email = "test@example.com";
|
||||||
|
var principal = new UserPrincipal("1", email, null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
|
);
|
||||||
|
when(userService.updateUserSettings(eq(1L), eq(email), isNull(), eq("Lobo Corp_1"), any())).thenReturn("t");
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
mockMvc.perform(put("/api/users/settings")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{"useDarkTheme":true,"language":"en","steamId":null,"displayName":"Lobo Corp_1"}"""))
|
||||||
|
.andExpect(status().isOk());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"ab", " Lobo", "Lobo ", "<script>", "abcdefghijklmnopqrstuvwxy"})
|
||||||
|
void updateUserSettings_InvalidDisplayName(String displayName) throws Exception {
|
||||||
|
// Arrange
|
||||||
|
var principal = new UserPrincipal("1", "test@example.com", null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
mockMvc.perform(put("/api/users/settings")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("{\"useDarkTheme\":true,\"language\":\"en\",\"displayName\":\"" + displayName + "\"}"))
|
||||||
|
.andExpect(status().isBadRequest());
|
||||||
|
|
||||||
|
verifyNoInteractions(userService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUserSettings_DisplayNameTaken() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
var email = "test@example.com";
|
||||||
|
var principal = new UserPrincipal("1", email, null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
|
);
|
||||||
|
when(userService.updateUserSettings(eq(1L), eq(email), any(), eq("Lobo"), any()))
|
||||||
|
.thenThrow(new DisplayNameAlreadyInUseException("taken"));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
mockMvc.perform(put("/api/users/settings")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{"useDarkTheme":true,"language":"en","displayName":"Lobo"}"""))
|
||||||
|
.andExpect(status().isConflict())
|
||||||
|
.andExpect(jsonPath("$.code").value("DISPLAY_NAME_ALREADY_IN_USE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUserSettings_DisplayNameRaceIsConflict() throws Exception {
|
||||||
|
// Arrange
|
||||||
|
var email = "test@example.com";
|
||||||
|
var principal = new UserPrincipal("1", email, null);
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(
|
||||||
|
new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList())
|
||||||
|
);
|
||||||
|
when(userService.updateUserSettings(eq(1L), eq(email), any(), eq("Lobo"), any()))
|
||||||
|
.thenThrow(new DataIntegrityViolationException("duplicate key value violates unique constraint \"uq_users_display_name_lower\""));
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
mockMvc.perform(put("/api/users/settings")
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.content("""
|
||||||
|
{"useDarkTheme":true,"language":"en","displayName":"Lobo"}"""))
|
||||||
|
.andExpect(status().isConflict())
|
||||||
|
.andExpect(jsonPath("$.code").value("DISPLAY_NAME_ALREADY_IN_USE"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var updated = userRepository.updateSettings(userEntity.getId(), 654321L, settings);
|
var updated = userRepository.updateSettings(userEntity.getId(), 654321L, null, settings);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertTrue(updated);
|
assertTrue(updated);
|
||||||
@@ -109,7 +109,7 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
|||||||
var settings = UserSettings.builder().language("en").theme("light").build();
|
var settings = UserSettings.builder().language("en").theme("light").build();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
userRepository.updateSettings(userEntity.getId(), null, settings);
|
userRepository.updateSettings(userEntity.getId(), null, null, settings);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertNull(entityManager.find(UserEntity.class, userEntity.getId()).getSteamId());
|
assertNull(entityManager.find(UserEntity.class, userEntity.getId()).getSteamId());
|
||||||
@@ -143,7 +143,7 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
|||||||
void updateSettings_UnknownUser() {
|
void updateSettings_UnknownUser() {
|
||||||
var settings = UserSettings.builder().language("en").theme("light").build();
|
var settings = UserSettings.builder().language("en").theme("light").build();
|
||||||
|
|
||||||
assertThrows(UserNotFoundException.class, () -> userRepository.updateSettings(-1L, null, settings));
|
assertThrows(UserNotFoundException.class, () -> userRepository.updateSettings(-1L, null, null, settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -151,4 +151,33 @@ class UserRepositoryTest extends AbstractPersistenceTest {
|
|||||||
// V1 seeded test@example.com with a known password; it must not exist after all migrations.
|
// V1 seeded test@example.com with a known password; it must not exist after all migrations.
|
||||||
assertTrue(userRepository.findByEmail("test@example.com").isEmpty());
|
assertTrue(userRepository.findByEmail("test@example.com").isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void displayName_StoredAndCheckedCaseInsensitive() {
|
||||||
|
// Arrange
|
||||||
|
var owner = entityManager.persist(UserEntity.builder()
|
||||||
|
.email("owner2@example.com")
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.registrationDate(LocalDateTime.now())
|
||||||
|
.build());
|
||||||
|
var other = entityManager.persist(UserEntity.builder()
|
||||||
|
.email("other2@example.com")
|
||||||
|
.password("password")
|
||||||
|
.enabled(true)
|
||||||
|
.registrationDate(LocalDateTime.now())
|
||||||
|
.build());
|
||||||
|
entityManager.flush();
|
||||||
|
var settings = UserSettings.builder().language("en").theme("light").build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userRepository.updateSettings(owner.getId(), null, "Lobo", settings);
|
||||||
|
entityManager.flush();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("Lobo", userRepository.findByEmail("owner2@example.com").orElseThrow().getDisplayName());
|
||||||
|
assertFalse(userRepository.isDisplayNameUsedByOtherUser(owner.getId(), "lobo"));
|
||||||
|
assertTrue(userRepository.isDisplayNameUsedByOtherUser(other.getId(), "LOBO"));
|
||||||
|
assertFalse(userRepository.isDisplayNameUsedByOtherUser(other.getId(), "Wolf"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package auth_service.modules.user.service;
|
package auth_service.modules.user.service;
|
||||||
|
|
||||||
|
import auth_service.modules.user.exception.DisplayNameAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
import auth_service.modules.user.exception.SteamIdAlreadyInUseException;
|
||||||
import auth_service.modules.user.exception.UserNotFoundException;
|
import auth_service.modules.user.exception.UserNotFoundException;
|
||||||
import auth_service.modules.user.model.User;
|
import auth_service.modules.user.model.User;
|
||||||
@@ -68,11 +69,11 @@ class UserServiceTest extends AbstractUnitTest {
|
|||||||
when(tokenGenerator.generateToken("1", email, steamId)).thenReturn("new-token");
|
when(tokenGenerator.generateToken("1", email, steamId)).thenReturn("new-token");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var token = userService.updateUserSettings(userId, email, steamId, userSettings);
|
var token = userService.updateUserSettings(userId, email, steamId, null, userSettings);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertEquals("new-token", token);
|
assertEquals("new-token", token);
|
||||||
verify(userRepository).updateSettings(userId, steamId, userSettings);
|
verify(userRepository).updateSettings(userId, steamId, null, userSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -84,11 +85,11 @@ class UserServiceTest extends AbstractUnitTest {
|
|||||||
when(tokenGenerator.generateToken("1", email, null)).thenReturn("new-token");
|
when(tokenGenerator.generateToken("1", email, null)).thenReturn("new-token");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var token = userService.updateUserSettings(userId, email, null, userSettings);
|
var token = userService.updateUserSettings(userId, email, null, null, userSettings);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
assertEquals("new-token", token);
|
assertEquals("new-token", token);
|
||||||
verify(userRepository).updateSettings(userId, null, userSettings);
|
verify(userRepository).updateSettings(userId, null, null, userSettings);
|
||||||
verify(userRepository, never()).isSteamIdUsedByOtherUser(anyLong(), anyLong());
|
verify(userRepository, never()).isSteamIdUsedByOtherUser(anyLong(), anyLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +103,32 @@ class UserServiceTest extends AbstractUnitTest {
|
|||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
assertThrows(SteamIdAlreadyInUseException.class,
|
assertThrows(SteamIdAlreadyInUseException.class,
|
||||||
() -> userService.updateUserSettings(userId, "test@example.com", steamId, userSettings));
|
() -> userService.updateUserSettings(userId, "test@example.com", steamId, null, userSettings));
|
||||||
verify(userRepository, never()).updateSettings(anyLong(), any(), any());
|
verify(userRepository, never()).updateSettings(anyLong(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUserSettings_DisplayNameOfOtherUser() {
|
||||||
|
// Arrange
|
||||||
|
var userSettings = UserSettings.builder().language("en").build();
|
||||||
|
when(userRepository.isDisplayNameUsedByOtherUser(1L, "Lobo")).thenReturn(true);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(DisplayNameAlreadyInUseException.class,
|
||||||
|
() -> userService.updateUserSettings(1L, "test@example.com", null, "Lobo", userSettings));
|
||||||
|
verify(userRepository, never()).updateSettings(anyLong(), any(), any(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateUserSettings_StoresDisplayName() {
|
||||||
|
// Arrange
|
||||||
|
var userSettings = UserSettings.builder().language("en").build();
|
||||||
|
when(userRepository.isDisplayNameUsedByOtherUser(1L, "Lobo")).thenReturn(false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
userService.updateUserSettings(1L, "test@example.com", null, "Lobo", userSettings);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(userRepository).updateSettings(1L, null, "Lobo", userSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user