Public Access
refactor(users): login and registration use JdbcTemplate for users
This commit is contained in:
1 parent
b6be1879f0
commit
d082f3befd
12 files changed
+145
-262
No files matched your search
@@ -131,6 +131,9 @@ layers:
|
||||
| Domain | `model`, `service`, `exception` | Plain Java models and business logic. Uses repositories, never API classes, entities or `EntityMapper`. |
|
||||
| Persistence | `persistence`, `repo` | JPA entities, Spring Data repositories, `EntityMapper`; `repo` holds `JdbcTemplate` queries. Entities never reach the API layer. |
|
||||
|
||||
`user` owns the tables `users` and `user_settings` through the one JPA entity `UserEntity`. `login` and
|
||||
`registration` read and write `users` with `JdbcTemplate` (refresh tokens stay JPA). Every query for "a user who
|
||||
may log in and is shown to others" uses `shared/account/ActiveUser`.
|
||||
`profile` and `stats` only read data. They have no entities and use `JdbcTemplate` queries in a `repo` package.
|
||||
`version` has only an `api` package: `GET /api/version` answers the commit and build time of the running image.
|
||||
`account` (delete and nightly cleanup) sets the flag with `JdbcTemplate` in a `repo` package too, and calls Core
|
||||
|
||||
+24
-29
@@ -1,13 +1,11 @@
|
||||
package auth_service.modules.login.persistence;
|
||||
|
||||
import auth_service.shared.account.EmailAddress;
|
||||
import auth_service.modules.login.model.User;
|
||||
import auth_service.modules.login.persistence.entities.MinimalUserEntity;
|
||||
import auth_service.modules.login.persistence.mapper.EntityMapper;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.transaction.Transactional;
|
||||
import auth_service.shared.account.EmailAddress;
|
||||
import auth_service.shared.security.Role;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.time.Clock;
|
||||
@@ -15,39 +13,36 @@ import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class MinimalUserRepositoryImpl implements MinimalUserRepository {
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
private static final String SELECT_USER =
|
||||
"SELECT id, email, password, steam_id, enabled, deleted_at IS NOT NULL AS deleted, role FROM users ";
|
||||
|
||||
private static final RowMapper<User> USER_ROW = (rs, row) -> User.builder()
|
||||
.id(rs.getLong("id"))
|
||||
.email(rs.getString("email"))
|
||||
.password(rs.getString("password"))
|
||||
.steamId(rs.getObject("steam_id", Long.class))
|
||||
.enabled(rs.getBoolean("enabled"))
|
||||
.deleted(rs.getBoolean("deleted"))
|
||||
.role(Role.parse(rs.getString("role")))
|
||||
.build();
|
||||
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
@Override
|
||||
public Optional<User> findByEmail(String email) {
|
||||
var criteriaBuilder = entityManager.getCriteriaBuilder();
|
||||
var userEntityCriteriaQuery = criteriaBuilder.createQuery(MinimalUserEntity.class);
|
||||
var userEntity = userEntityCriteriaQuery.from(MinimalUserEntity.class);
|
||||
|
||||
userEntityCriteriaQuery.select(userEntity).where(criteriaBuilder.equal(
|
||||
userEntity.get("email"), EmailAddress.normalize(email)));
|
||||
|
||||
TypedQuery<MinimalUserEntity> typedQuery = entityManager.createQuery(userEntityCriteriaQuery);
|
||||
|
||||
return typedQuery.getResultStream()
|
||||
.findFirst()
|
||||
.map(EntityMapper::mapToModel);
|
||||
return jdbc.query(SELECT_USER + "WHERE email = ?", USER_ROW, EmailAddress.normalize(email)).stream().findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<User> findById(long userId) {
|
||||
return Optional.ofNullable(entityManager.find(MinimalUserEntity.class, userId)).map(EntityMapper::mapToModel);
|
||||
return jdbc.query(SELECT_USER + "WHERE id = ?", USER_ROW, userId).stream().findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLoginDate(long userId) {
|
||||
var userEntity = entityManager.find(MinimalUserEntity.class, userId);
|
||||
|
||||
if (userEntity != null) {
|
||||
userEntity.setLastLoginDate(LocalDateTime.now(Clock.systemUTC()));
|
||||
entityManager.flush();
|
||||
}
|
||||
jdbc.update("UPDATE users SET last_login_date = ? WHERE id = ?", LocalDateTime.now(Clock.systemUTC()), userId);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package auth_service.modules.login.persistence.entities;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Represents a user entity mapped to the 'users' table in PostgreSQL.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class MinimalUserEntity {
|
||||
|
||||
@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 = "last_login_date")
|
||||
private LocalDateTime lastLoginDate;
|
||||
|
||||
@Column(name = "steam_id")
|
||||
private Long steamId;
|
||||
|
||||
@Column(name = "deleted_at", insertable = false, updatable = false)
|
||||
private Instant deletedAt;
|
||||
|
||||
// Set by SQL only (the first admin).
|
||||
@Column(name = "role", insertable = false, updatable = false)
|
||||
private String role;
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package auth_service.modules.login.persistence.mapper;
|
||||
|
||||
import auth_service.modules.login.model.RefreshToken;
|
||||
import auth_service.modules.login.model.User;
|
||||
import auth_service.modules.login.persistence.entities.MinimalUserEntity;
|
||||
import auth_service.modules.login.persistence.entities.RefreshTokenEntity;
|
||||
import auth_service.shared.security.Role;
|
||||
|
||||
@@ -11,22 +9,6 @@ public abstract class EntityMapper {
|
||||
private EntityMapper() {
|
||||
}
|
||||
|
||||
public static User mapToModel(MinimalUserEntity entity) {
|
||||
User model = User.builder().build();
|
||||
|
||||
if (entity != null) {
|
||||
model.setId(entity.getId());
|
||||
model.setEmail(entity.getEmail());
|
||||
model.setPassword(entity.getPassword());
|
||||
model.setSteamId(entity.getSteamId());
|
||||
model.setEnabled(entity.isEnabled());
|
||||
model.setDeleted(entity.getDeletedAt() != null);
|
||||
model.setRole(Role.parse(entity.getRole()));
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
public static RefreshToken mapToModel(RefreshTokenEntity entity) {
|
||||
return new RefreshToken(entity.getId(), entity.getUserId(), entity.getFamilyId(), entity.getExpiresAt(),
|
||||
entity.getRotatedAt(), entity.getRevokedAt(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package auth_service.modules.profile.repo;
|
||||
|
||||
import auth_service.modules.profile.model.PublicProfile;
|
||||
import auth_service.shared.account.ActiveUser;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@@ -32,8 +33,8 @@ public class PublicProfileRepository {
|
||||
SELECT u.id, u.display_name, u.registration_date, s.profile_accent, s.profile_corners,
|
||||
s.profile_banner_appid, s.profile_background_appid, s.profile_tiles
|
||||
FROM users u JOIN user_settings s ON s.user_id = u.id
|
||||
WHERE LOWER(u.display_name) = LOWER(?) AND u.enabled AND u.deleted_at IS NULL AND s.public_profile
|
||||
""",
|
||||
WHERE LOWER(u.display_name) = LOWER(?) AND %s AND s.public_profile
|
||||
""".formatted(ActiveUser.of("u")),
|
||||
(rs, row) -> new PublicProfile(rs.getLong("id"), rs.getString("display_name"),
|
||||
rs.getTimestamp("registration_date") != null
|
||||
? rs.getTimestamp("registration_date").toLocalDateTime().toLocalDate() : null,
|
||||
|
||||
+23
-23
@@ -1,44 +1,44 @@
|
||||
package auth_service.modules.registration.persistence;
|
||||
|
||||
import auth_service.shared.account.EmailAddress;
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.registration.persistence.mapper.EntityMapper;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import jakarta.transaction.Transactional;
|
||||
import auth_service.shared.account.EmailAddress;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Repository
|
||||
@Transactional
|
||||
@RequiredArgsConstructor
|
||||
public class RegisteredUserRepositoryImpl implements RegisteredUserRepository {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
@Override
|
||||
public boolean existsByEmail(String email) {
|
||||
var jpql = "SELECT COUNT(u) FROM UserEntity u WHERE u.email = :email";
|
||||
var count = entityManager.createQuery(jpql, Long.class)
|
||||
.setParameter("email", EmailAddress.normalize(email))
|
||||
.getSingleResult();
|
||||
|
||||
return count != null && count > 0;
|
||||
return Boolean.TRUE.equals(jdbc.queryForObject("SELECT EXISTS (SELECT 1 FROM users WHERE email = ?)",
|
||||
Boolean.class, EmailAddress.normalize(email)));
|
||||
}
|
||||
|
||||
@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;
|
||||
return Boolean.TRUE.equals(jdbc.queryForObject(
|
||||
"SELECT EXISTS (SELECT 1 FROM users WHERE LOWER(display_name) = LOWER(?))", Boolean.class, displayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public long save(Registration registration) {
|
||||
var userToRegisterEntity = EntityMapper.mapToPersistence(registration);
|
||||
entityManager.persist(userToRegisterEntity);
|
||||
return userToRegisterEntity.getId();
|
||||
Long id = jdbc.queryForObject("""
|
||||
INSERT INTO users (email, password, enabled, display_name, registration_date)
|
||||
VALUES (?, ?, TRUE, ?, ?) RETURNING id""", Long.class,
|
||||
registration.getEmail(), registration.getEncryptedPassword(), registration.getDisplayName(),
|
||||
LocalDateTime.now(Clock.systemUTC()));
|
||||
if (id == null) {
|
||||
throw new IllegalStateException("No id for the new user");
|
||||
}
|
||||
return id;
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package auth_service.modules.registration.persistence.entities;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
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 = "display_name", length = 24)
|
||||
private String displayName;
|
||||
|
||||
@Column(name = "registration_date", nullable = false)
|
||||
private LocalDateTime registrationDate;
|
||||
|
||||
@Column(name = "last_login_date")
|
||||
private LocalDateTime lastLoginDate;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package auth_service.modules.registration.persistence.mapper;
|
||||
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.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).
|
||||
displayName(userModel.getDisplayName()).
|
||||
lastLoginDate(null).
|
||||
build();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package auth_service.modules.stats.repo;
|
||||
|
||||
import auth_service.shared.account.ActiveUser;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -15,13 +16,13 @@ public class StatsRepository {
|
||||
}
|
||||
|
||||
public long countTotalUsers() {
|
||||
Long v = jdbc.queryForObject("SELECT COUNT(*) FROM users WHERE enabled = TRUE AND deleted_at IS NULL", Long.class);
|
||||
Long v = jdbc.queryForObject("SELECT COUNT(*) FROM users u WHERE " + ActiveUser.of("u"), Long.class);
|
||||
return v != null ? v : 0L;
|
||||
}
|
||||
|
||||
public long countUsersWithSteamLinked() {
|
||||
Long v = jdbc.queryForObject(
|
||||
"SELECT COUNT(*) FROM users WHERE enabled = TRUE AND deleted_at IS NULL AND steam_id IS NOT NULL", Long.class);
|
||||
"SELECT COUNT(*) FROM users u WHERE " + ActiveUser.of("u") + " AND u.steam_id IS NOT NULL", Long.class);
|
||||
return v != null ? v : 0L;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package auth_service.shared.account;
|
||||
|
||||
/**
|
||||
* The SQL rule for a user who may log in and is shown to others: not blocked and not deleted.
|
||||
*/
|
||||
public final class ActiveUser {
|
||||
|
||||
private ActiveUser() {
|
||||
}
|
||||
|
||||
public static String of(String alias) {
|
||||
return alias + ".enabled AND " + alias + ".deleted_at IS NULL";
|
||||
}
|
||||
}
|
||||
+39
-39
@@ -1,13 +1,19 @@
|
||||
package auth_service.modules.login.persistence;
|
||||
|
||||
import auth_service.modules.login.persistence.entities.MinimalUserEntity;
|
||||
import auth_service.modules.login.model.User;
|
||||
import auth_service.shared.security.Role;
|
||||
import auth_service.shared.test.AbstractPersistenceTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Import(MinimalUserRepositoryImpl.class)
|
||||
class MinimalUserRepositoryTest extends AbstractPersistenceTest {
|
||||
@@ -16,53 +22,47 @@ class MinimalUserRepositoryTest extends AbstractPersistenceTest {
|
||||
private MinimalUserRepository minimalUserRepository;
|
||||
|
||||
@Autowired
|
||||
private TestEntityManager entityManager;
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
@Test
|
||||
void findByEmail_Success() {
|
||||
// Arrange
|
||||
var email = "minimal@example.com";
|
||||
var entity = MinimalUserEntity.builder()
|
||||
.email(email)
|
||||
.password("password")
|
||||
.enabled(true)
|
||||
.build();
|
||||
entityManager.persist(entity);
|
||||
entityManager.flush();
|
||||
|
||||
// Act
|
||||
var result = minimalUserRepository.findByEmail(email);
|
||||
|
||||
// Assert
|
||||
assertTrue(result.isPresent());
|
||||
assertEquals(email, result.get().getEmail());
|
||||
private long insertUser(String email) {
|
||||
Long id = jdbc.queryForObject("INSERT INTO users (email, password, enabled, registration_date) "
|
||||
+ "VALUES (?, 'password', TRUE, ?) RETURNING id", Long.class, email, LocalDateTime.now());
|
||||
return id == null ? 0 : id;
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLoginDate_Success() {
|
||||
// Arrange
|
||||
var entity = MinimalUserEntity.builder()
|
||||
.email("login@example.com")
|
||||
.password("password")
|
||||
.enabled(true)
|
||||
.build();
|
||||
entity = entityManager.persist(entity);
|
||||
entityManager.flush();
|
||||
void findByEmail_MapsTheUser() {
|
||||
long id = insertUser("minimal@example.com");
|
||||
jdbc.update("UPDATE users SET steam_id = 76561198000000001, deleted_at = now() WHERE id = ?", id);
|
||||
|
||||
// Act
|
||||
minimalUserRepository.updateLoginDate(entity.getId());
|
||||
entityManager.clear(); // Clear cache to force reload from DB
|
||||
User user = minimalUserRepository.findByEmail("minimal@example.com").orElseThrow();
|
||||
|
||||
// Assert
|
||||
var updated = entityManager.find(MinimalUserEntity.class, entity.getId());
|
||||
assertNotNull(updated.getLastLoginDate());
|
||||
assertEquals(id, user.getId());
|
||||
assertEquals("password", user.getPassword());
|
||||
assertEquals(76561198000000001L, user.getSteamId());
|
||||
assertTrue(user.isEnabled());
|
||||
assertTrue(user.isDeleted());
|
||||
assertEquals(Role.USER, user.getRole());
|
||||
}
|
||||
|
||||
@Test
|
||||
void findById_UnknownUser_IsEmpty() {
|
||||
assertFalse(minimalUserRepository.findById(-1).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLoginDate_StoresTheDate() {
|
||||
long id = insertUser("login@example.com");
|
||||
|
||||
minimalUserRepository.updateLoginDate(id);
|
||||
|
||||
assertNotNull(jdbc.queryForObject("SELECT last_login_date FROM users WHERE id = ?", LocalDateTime.class, id));
|
||||
}
|
||||
|
||||
// An access token from before V19 still carries the email as it was typed.
|
||||
@Test
|
||||
void findByEmail_IgnoresCase() {
|
||||
entityManager.persist(MinimalUserEntity.builder().email("case@example.com").password("password").enabled(true).build());
|
||||
entityManager.flush();
|
||||
insertUser("case@example.com");
|
||||
|
||||
assertTrue(minimalUserRepository.findByEmail("Case@Example.com").isPresent());
|
||||
}
|
||||
|
||||
+36
-41
@@ -1,14 +1,19 @@
|
||||
package auth_service.modules.registration.persistence;
|
||||
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.registration.persistence.entities.RegisteredUserEntity;
|
||||
import auth_service.shared.test.AbstractPersistenceTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Import(RegisteredUserRepositoryImpl.class)
|
||||
class RegisteredUserRepositoryTest extends AbstractPersistenceTest {
|
||||
@@ -17,59 +22,49 @@ class RegisteredUserRepositoryTest extends AbstractPersistenceTest {
|
||||
private RegisteredUserRepository registeredUserRepository;
|
||||
|
||||
@Autowired
|
||||
private TestEntityManager entityManager;
|
||||
private JdbcTemplate jdbc;
|
||||
|
||||
private static Registration registration(String email, String displayName) {
|
||||
return Registration.builder().email(email).encryptedPassword("hashed").displayName(displayName).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
void existsByEmail_Success() {
|
||||
// Arrange
|
||||
var email = "exists@example.com";
|
||||
var entity = RegisteredUserEntity.builder()
|
||||
.email(email)
|
||||
.password("password")
|
||||
.enabled(true)
|
||||
.registrationDate(java.time.LocalDateTime.now())
|
||||
.build();
|
||||
entityManager.persist(entity);
|
||||
entityManager.flush();
|
||||
void existsByEmail_OnlyForAStoredEmail() {
|
||||
registeredUserRepository.save(registration("exists@example.com", null));
|
||||
|
||||
// Act & Assert
|
||||
assertTrue(registeredUserRepository.existsByEmail(email));
|
||||
assertTrue(registeredUserRepository.existsByEmail("exists@example.com"));
|
||||
assertFalse(registeredUserRepository.existsByEmail("other@example.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_Success() {
|
||||
// Arrange
|
||||
var registration = Registration.builder()
|
||||
.email("save@example.com")
|
||||
.encryptedPassword("hashed")
|
||||
.build();
|
||||
void existsByDisplayName_IgnoresCase() {
|
||||
registeredUserRepository.save(registration("named@example.com", "Lobo"));
|
||||
|
||||
// Act
|
||||
long id = registeredUserRepository.save(registration);
|
||||
assertTrue(registeredUserRepository.existsByDisplayName("lobo"));
|
||||
assertFalse(registeredUserRepository.existsByDisplayName("Wolf"));
|
||||
}
|
||||
|
||||
// Assert
|
||||
assertTrue(id > 0);
|
||||
var saved = entityManager.find(RegisteredUserEntity.class, id);
|
||||
assertNotNull(saved);
|
||||
assertEquals("save@example.com", saved.getEmail());
|
||||
@Test
|
||||
void save_StoresAnEnabledUser() {
|
||||
long id = registeredUserRepository.save(registration("save@example.com", "Saver"));
|
||||
|
||||
Map<String, Object> saved = jdbc.queryForMap("SELECT * FROM users WHERE id = ?", id);
|
||||
assertEquals("save@example.com", saved.get("email"));
|
||||
assertEquals("hashed", saved.get("password"));
|
||||
assertEquals(true, saved.get("enabled"));
|
||||
assertEquals("Saver", saved.get("display_name"));
|
||||
assertEquals("USER", saved.get("role"));
|
||||
}
|
||||
|
||||
// V19: a second account under the same email in other case is refused by the database too.
|
||||
@Test
|
||||
void emailsThatDifferOnlyInCase_AreOneAccount() {
|
||||
entityManager.persist(RegisteredUserEntity.builder().email("dup@example.com").password("password")
|
||||
.enabled(true).registrationDate(java.time.LocalDateTime.now()).build());
|
||||
entityManager.flush();
|
||||
registeredUserRepository.save(registration("dup@example.com", null));
|
||||
|
||||
assertTrue(registeredUserRepository.existsByEmail("Dup@Example.com"));
|
||||
var refused = assertThrows(org.hibernate.exception.ConstraintViolationException.class, () -> {
|
||||
entityManager.persist(RegisteredUserEntity.builder().email("Dup@example.com").password("password")
|
||||
.enabled(true).registrationDate(java.time.LocalDateTime.now()).build());
|
||||
entityManager.flush();
|
||||
});
|
||||
// GlobalExceptionController decides on the state; on an English server Hibernate also gives the name.
|
||||
assertEquals("23505", refused.getSQLState());
|
||||
assertEquals("users_email_lower", refused.getConstraintName());
|
||||
DuplicateKeyException refused = assertThrows(DuplicateKeyException.class,
|
||||
() -> registeredUserRepository.save(registration("Dup@example.com", null)));
|
||||
// GlobalExceptionController finds the name in the message, in every server language.
|
||||
assertTrue(refused.getMostSpecificCause().getMessage().contains("users_email_lower"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user