diff --git a/user-manager-server/src/main/java/de/itvsh/kop/user/User.java b/user-manager-server/src/main/java/de/itvsh/kop/user/User.java index f8f90170f7279d161c5ce28c6b6f601a79e2e728..2f9a17ac7f91003b55438437e371037d99c14174 100644 --- a/user-manager-server/src/main/java/de/itvsh/kop/user/User.java +++ b/user-manager-server/src/main/java/de/itvsh/kop/user/User.java @@ -72,7 +72,9 @@ public class User { private String keycloakUserId; private String firstName; private String lastName; + @JsonIgnore private String fullName; + @JsonIgnore private String fullNameReversed; private String username; private String email; diff --git a/user-manager-server/src/main/java/de/itvsh/kop/user/UserRepository.java b/user-manager-server/src/main/java/de/itvsh/kop/user/UserRepository.java index 4946984edb0ea91e9106763aed3e214762b8359f..58fe0f1468bfc2edf7b0699c5f775595d50dc9fc 100644 --- a/user-manager-server/src/main/java/de/itvsh/kop/user/UserRepository.java +++ b/user-manager-server/src/main/java/de/itvsh/kop/user/UserRepository.java @@ -28,13 +28,14 @@ import static de.itvsh.kop.user.User.*; import java.util.Optional; import java.util.stream.Stream; +import jakarta.enterprise.context.ApplicationScoped; + import org.bson.types.ObjectId; import de.itvsh.kop.common.logging.KopLogging; import de.itvsh.kop.user.common.errorhandling.ResourceNotFoundException; import io.quarkus.mongodb.panache.PanacheMongoRepository; import io.quarkus.panache.common.Parameters; -import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped @KopLogging @@ -46,7 +47,9 @@ class UserRepository implements PanacheMongoRepository<User> { static final String PARAM_NAME_TIMESTAMP = "timestamp"; private static final String LIKE_OR = " like " + param(PARAM_NAME_SEARCH_BY) + " or "; - private static final String SEARCH_QUERY = USERNAME_FIELD + LIKE_OR + + private static final String SEARCH_QUERY = LASTNAME_FIELD + LIKE_OR + + FIRSTNAME_FIELD + LIKE_OR + + USERNAME_FIELD + LIKE_OR + FULL_NAME_FIELD + LIKE_OR + FULL_NAME_REVERSED_FIELD + LIKE_OR + EMAIL_FIELD + " like " + param(PARAM_NAME_SEARCH_BY); @@ -104,7 +107,7 @@ class UserRepository implements PanacheMongoRepository<User> { } String toRegex(String query) { - return "/^" + query + "/i"; + return "/.*" + query + ".*/i"; } public long updateUnsyncedUsers(long lastSyncTimestamp) { diff --git a/user-manager-server/src/main/java/de/itvsh/kop/user/UserResourceMapper.java b/user-manager-server/src/main/java/de/itvsh/kop/user/UserResourceMapper.java index 3583e4daa51095b5e11d084a9d1393432baf1937..d15c1c2d10717d9087f5c857481d9b4d2ffda2c0 100644 --- a/user-manager-server/src/main/java/de/itvsh/kop/user/UserResourceMapper.java +++ b/user-manager-server/src/main/java/de/itvsh/kop/user/UserResourceMapper.java @@ -33,19 +33,18 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import jakarta.inject.Inject; + import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.admin.client.resource.UserResource; import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; import org.mapstruct.ReportingPolicy; import de.itvsh.kop.user.keycloak.KeycloakApiProperties; -import jakarta.inject.Inject; @Mapper(unmappedTargetPolicy = ReportingPolicy.WARN) public abstract class UserResourceMapper { @@ -60,6 +59,8 @@ public abstract class UserResourceMapper { @Mapping(target = "email", expression = "java(mapEmail(userRes))") @Mapping(target = "firstName", expression = "java(mapFirstName(userRes))") @Mapping(target = "lastName", expression = "java(mapLastName(userRes))") + @Mapping(target = "fullName", expression = "java(mapFullName(userRes))") + @Mapping(target = "fullNameReversed", expression = "java(mapFullNameReversed(userRes))") @Mapping(target = "username", expression = "java(mapUsername(userRes))") @Mapping(target = "externalId", expression = "java(mapId(userRes))") @Mapping(target = "keycloakUserId", expression = "java(mapKeycloakUserId(userRes))") @@ -137,17 +138,13 @@ public abstract class UserResourceMapper { return userRes.toRepresentation().getUsername(); } - @AfterMapping - protected void mapFullName(@MappingTarget User.UserBuilder user, UserResource userRes) { - user.fullName(String.join(" ", - Stream.of(userRes.toRepresentation().getFirstName(), userRes.toRepresentation().getLastName()) - .filter(Objects::nonNull).toArray(String[]::new))); + String mapFullName(UserResource userRes) { + return String.join(" ", Stream.of(userRes.toRepresentation().getFirstName(), userRes.toRepresentation().getLastName()) + .filter(Objects::nonNull).toArray(String[]::new)); } - @AfterMapping - protected void mapFullNameReversed(@MappingTarget User.UserBuilder user, UserResource userRes) { - user.fullNameReversed(String.join(" ", - Stream.of(userRes.toRepresentation().getLastName(), userRes.toRepresentation().getFirstName()) - .filter(Objects::nonNull).toArray(String[]::new))); + String mapFullNameReversed(UserResource userRes) { + return String.join(" ", Stream.of(userRes.toRepresentation().getLastName(), userRes.toRepresentation().getFirstName()) + .filter(Objects::nonNull).toArray(String[]::new)); } } diff --git a/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryITCase.java b/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryITCase.java index 610e7768637ca5d571fe37c9db02ae1cccd638d1..35691484fbd43a53dbf049ed4c2b04e85dd14d73 100644 --- a/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryITCase.java +++ b/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryITCase.java @@ -31,6 +31,8 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Stream; +import jakarta.inject.Inject; + import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -40,10 +42,11 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import com.thedeanda.lorem.LoremIpsum; + import de.itvsh.kop.user.common.MongoDbTestProfile; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.TestProfile; -import jakarta.inject.Inject; @QuarkusTest @TestProfile(MongoDbTestProfile.class) @@ -155,9 +158,9 @@ class UserRepositoryITCase { @Test void shouldFindNotDeletedUsers() { - User user1 = UserTestFactory.createBuilder().id(new ObjectId()).fullName("Max").deleted(true).build(); - User user2 = UserTestFactory.createBuilder().id(new ObjectId()).fullName("Maximilian").deleted(true).build(); - User user3 = UserTestFactory.createBuilder().id(new ObjectId()).fullName("Maximus").deleted(false).build(); + User user1 = UserTestFactory.createBuilder().id(new ObjectId()).firstName("Max").deleted(true).build(); + User user2 = UserTestFactory.createBuilder().id(new ObjectId()).firstName("Maximilian").deleted(true).build(); + User user3 = UserTestFactory.createBuilder().id(new ObjectId()).firstName("Maximus").deleted(false).build(); repository.persist(user1); repository.persist(user2); @@ -184,13 +187,16 @@ class UserRepositoryITCase { @BeforeEach void init() { repository.deleteAll(); + repository.persist(UserTestFactory.createBuilder().id(new ObjectId()).email(LoremIpsum.getInstance().getEmail()) + .externalId("additional-id").build()); + repository.persist(UserTestFactory.create()); } @Test void shouldFindDeletedAndNotDeletedUsers() { - User user1 = new User.UserBuilder().id(new ObjectId()).fullName("Max").deleted(true).build(); - User user2 = new User.UserBuilder().id(new ObjectId()).fullName("Maximilian").deleted(true).build(); - User user3 = new User.UserBuilder().id(new ObjectId()).fullName("Maximus").deleted(false).build(); + User user1 = UserTestFactory.createBuilder().id(new ObjectId()).firstName("Max").deleted(true).build(); + User user2 = UserTestFactory.createBuilder().id(new ObjectId()).firstName("Maximilian").deleted(true).build(); + User user3 = UserTestFactory.createBuilder().id(new ObjectId()).firstName("Maximus").deleted(false).build(); repository.persist(user1); repository.persist(user2); @@ -199,7 +205,7 @@ class UserRepositoryITCase { var foundUsers = repository.findUsers("Max", 10).toList(); assertThat(foundUsers).hasSizeGreaterThan(2); - List<User> startWithMax = foundUsers.stream().filter(u -> u.getFullName().startsWith("Max")).toList(); + List<User> startWithMax = foundUsers.stream().filter(u -> u.getFirstName().startsWith("Max")).toList(); assertThat(startWithMax).hasSize(3); assertThat(startWithMax.stream().filter(User::isDeleted).count()).isEqualTo(2); assertThat(startWithMax.stream().filter(not(User::isDeleted)).count()).isEqualTo(1); @@ -207,7 +213,7 @@ class UserRepositoryITCase { @Test void shouldNotFindAnyUser() { - repository.persist(new User.UserBuilder().id(null).fullName("Marvel").build()); + repository.persist(UserTestFactory.createBuilder().id(null).firstName("Marvel").build()); var foundUsers = repository.findUsers("Max", 10).toList(); @@ -216,8 +222,8 @@ class UserRepositoryITCase { @Test void shouldFindOneUser() { - User user1 = new User.UserBuilder().id(null).fullName("Maximilian").build(); - User user2 = new User.UserBuilder().id(null).fullName("Madleen").build(); + User user1 = UserTestFactory.createBuilder().id(null).firstName("Maximilian").build(); + User user2 = UserTestFactory.createBuilder().id(null).firstName("Madleen").build(); repository.persist(user1); repository.persist(user2); @@ -227,17 +233,26 @@ class UserRepositoryITCase { assertThat(foundUsers) .hasSize(1) .first() - .extracting(User::getFullName) + .extracting(User::getFirstName) .isEqualTo("Maximilian"); } + } + + @Nested + class TestFindByFullName { + + @BeforeEach + void init() { + repository.deleteAll(); + } @ParameterizedTest @MethodSource("provideDataForFindUsersByFullName") void shouldFindUsersByFullName(String query, String[] expectedUsernames) { Arrays.asList( - new User.UserBuilder().id(null).username("user1").fullName("Franz Vogel").build(), - new User.UserBuilder().id(null).username("user2").fullName("Franz von Holzhausen").build(), - new User.UserBuilder().id(null).username("user3").fullName("Peter Lustig").build() + UserTestFactory.createBuilder().id(null).username("user1").fullName("Franz Vogel").build(), + UserTestFactory.createBuilder().id(null).username("user2").fullName("Franz von Holzhausen").build(), + UserTestFactory.createBuilder().id(null).username("user3").fullName("Peter Lustig").build() ).forEach(repository::persist); var foundUsernames = repository.findUsers(query, 10).map(User::getUsername); @@ -257,14 +272,23 @@ class UserRepositoryITCase { Arguments.of("Peter V", new String[0]) ); } + } + + @Nested + class TestFindByFullNameReversed { + + @BeforeEach + void init() { + repository.deleteAll(); + } @ParameterizedTest @MethodSource("provideDataForFindUsersByFullNameReversed") void shouldFindUsersByFullNameReversed(String query, String[] expectedUsernames) { Arrays.asList( - new User.UserBuilder().id(null).username("user1").fullNameReversed("Langbein Franz").build(), - new User.UserBuilder().id(null).username("user2").fullNameReversed("Lustig Peter").build(), - new User.UserBuilder().id(null).username("user3").fullNameReversed("Ilona Nowak").build() + UserTestFactory.createBuilder().id(null).username("user1").fullNameReversed("Langbein Franz").build(), + UserTestFactory.createBuilder().id(null).username("user2").fullNameReversed("Lustig Peter").build(), + UserTestFactory.createBuilder().id(null).username("user3").fullNameReversed("Ilona Nowak").build() ).forEach(repository::persist); var foundUsernames = repository.findUsers(query, 10).map(User::getUsername); @@ -274,7 +298,7 @@ class UserRepositoryITCase { private static Stream<Arguments> provideDataForFindUsersByFullNameReversed() { return Stream.of( - Arguments.of("L", new String[] { "user1", "user2" }), + Arguments.of("L", new String[] { "user1", "user2", "user3" }), Arguments.of("Lustig", new String[] { "user2" }), Arguments.of("Lustig Peter", new String[] { "user2" }) ); @@ -368,9 +392,9 @@ class UserRepositoryITCase { @BeforeEach void init() { repository.deleteAll(); - User user1 = UserTestFactory.createBuilder().id(null).fullName("Max").deleted(true).build(); - User user2 = UserTestFactory.createBuilder().id(null).fullName("Maximilian").deleted(true).build(); - User user3 = UserTestFactory.createBuilder().id(null).fullName("Maximus").deleted(false).build(); + User user1 = UserTestFactory.createBuilder().id(null).firstName("Max").deleted(true).build(); + User user2 = UserTestFactory.createBuilder().id(null).firstName("Maximilian").deleted(true).build(); + User user3 = UserTestFactory.createBuilder().id(null).firstName("Maximus").deleted(false).build(); repository.persist(user1); repository.persist(user2); repository.persist(user3); diff --git a/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java b/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java index 9b1dbbfb2510925baaab5fb14a8ede96bcf642c9..409eb4b45dcdbd93f1ac2267463e5fd10b2cb180 100644 --- a/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java +++ b/user-manager-server/src/test/java/de/itvsh/kop/user/UserRepositoryTest.java @@ -31,7 +31,7 @@ import java.util.Date; import java.util.List; import java.util.Optional; -import org.assertj.core.api.*; +import org.assertj.core.api.Condition; import org.bson.types.ObjectId; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -198,7 +198,7 @@ class UserRepositoryTest { void shouldReturnRegex(){ var regex = userRepository.toRegex("abc"); - assertThat(regex).isEqualTo("/^abc/i"); + assertThat(regex).isEqualTo("/.*abc.*/i"); } } } diff --git a/user-manager-server/src/test/java/de/itvsh/kop/user/UserTestFactory.java b/user-manager-server/src/test/java/de/itvsh/kop/user/UserTestFactory.java index 270cf1dc4f1d3aa1d2c6e06690ed806e351f3439..d48a9ceb324cb0ebc215b177e608a0d84d406061 100644 --- a/user-manager-server/src/test/java/de/itvsh/kop/user/UserTestFactory.java +++ b/user-manager-server/src/test/java/de/itvsh/kop/user/UserTestFactory.java @@ -40,6 +40,8 @@ public class UserTestFactory { public static final String FIRST_NAME = LoremIpsum.getInstance().getFirstName(); public static final String LAST_NAME = LoremIpsum.getInstance().getLastName(); + public static final String FULL_NAME = FIRST_NAME + " " + LAST_NAME; + public static final String FULL_NAME_REVERSED = LAST_NAME + " " + FIRST_NAME; public static final String USER_NAME = LoremIpsum.getInstance().getName(); public static final long LAST_SYNC_TIMESTAMP = 1001L; public static final String EMAIL = LoremIpsum.getInstance().getEmail(); @@ -57,8 +59,8 @@ public class UserTestFactory { .keycloakUserId(KEYCLOAK_USER_ID) .firstName(FIRST_NAME) .lastName(LAST_NAME) - .fullName(FIRST_NAME + " " + LAST_NAME) - .fullNameReversed(LAST_NAME + " " + FIRST_NAME) + .fullName(FULL_NAME) + .fullNameReversed(FULL_NAME_REVERSED) .username(USER_NAME) .lastSyncTimestamp(LAST_SYNC_TIMESTAMP) .email(EMAIL)