From 76b72978f58470fbc319ac5977be6d9d3d88201a Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 01:09:59 +0100 Subject: [PATCH 01/19] feat: add client ID and client secret for Google --- README.md | 4 ++++ backend/src/main/resources/application.properties | 3 +++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 6211715..43d2c41 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ * client id from OAuth2 app in GitHub * `OAUTH_GITHUB_CLIENT_SECRET` * client secret from OAuth2 app in GitHub +* `OAUTH_GOOGLE_CLIENT_ID` + * client id from OAuth2 app in Google +* `OAUTH_GOOGLE_CLIENT_SECRET` + * client secret from OAuth2 app in Google * `INITIAL_ADMIN` * user id of first admin: `github{ ID of GitHub Account }` * to have an admin if user database is initially empty diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 5d2150a..f304dbf 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -6,4 +6,7 @@ app.openai-api-url=https://api.openai.com/v1/chat/completions spring.security.oauth2.client.registration.github.client-id=${OAUTH_GITHUB_CLIENT_ID} spring.security.oauth2.client.registration.github.client-secret=${OAUTH_GITHUB_CLIENT_SECRET} spring.security.oauth2.client.registration.github.scope=none +spring.security.oauth2.client.registration.google.client-id=${OAUTH_GOOGLE_CLIENT_ID} +spring.security.oauth2.client.registration.google.client-secret=${OAUTH_GOOGLE_CLIENT_SECRET} +spring.security.oauth2.client.registration.google.scope=none app.security.initial-admin=${INITIAL_ADMIN} \ No newline at end of file From 026c8702dee9fc556711336efb5d58b7c57f43bd Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 01:19:11 +0100 Subject: [PATCH 02/19] feat: add login buttons in frontend --- frontend/src/App.tsx | 12 +++++++++--- frontend/src/pages/Main/MainPage.tsx | 12 ++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 068219c..76550b8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -37,11 +37,16 @@ export default function App() { document.body.classList.add(state); } - function login() { + function loginWithGitHub() { const host = window.location.host === 'localhost:5173' ? 'http://localhost:8080': window.location.origin; window.open(host + '/oauth2/authorization/github', '_self'); } + function loginWithGoogle() { + const host = window.location.host === 'localhost:5173' ? 'http://localhost:8080': window.location.origin; + window.open(host + '/oauth2/authorization/google', '_self'); + } + function logout() { BackendAPI.logout("App.logout()", ()=>setUser(undefined)); } @@ -62,7 +67,8 @@ export default function App() {
- {!user?.isAuthenticated && } + {!user?.isAuthenticated && } + {!user?.isAuthenticated && } { user?.isAuthenticated && }
@@ -92,7 +98,7 @@ export default function App() {

ChatGPT PromptOptimizer

- }/> + }/> }> }/> diff --git a/frontend/src/pages/Main/MainPage.tsx b/frontend/src/pages/Main/MainPage.tsx index 4e23af8..6d21063 100644 --- a/frontend/src/pages/Main/MainPage.tsx +++ b/frontend/src/pages/Main/MainPage.tsx @@ -5,14 +5,22 @@ import {MainCard} from "../../components/StandardStyledComponents.tsx"; type Props = { user?: UserInfo - login: ()=>void + logins: { + github: ()=>void, + google: ()=>void, + } logout: ()=>void } export default function MainPage( props: Readonly ) { if (!props.user?.isAuthenticated) - return Please + return + Please + + or + + if (!props.user.isUser && !props.user.isAdmin) return From 3cdbce150d107f0c744cf821b71b96240001736c Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 11:24:33 +0100 Subject: [PATCH 03/19] feat: added basic functions for Google login tests compile, but are not tested --- .../backend/security/SecurityConfig.java | 17 +++- .../services/StoredUserInfoService.java | 85 +++++++++++++------ .../security/services/UserService.java | 45 +++++++--- .../src/main/resources/application.properties | 2 +- .../backend/security/SecurityConfigTest.java | 15 ++-- .../services/StoredUserInfoServiceTest.java | 11 +-- frontend/src/pages/Main/MainPage.tsx | 4 +- 7 files changed, 123 insertions(+), 56 deletions(-) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java index 6f35be1..6744f5f 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java @@ -3,6 +3,8 @@ import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserService; + import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -92,14 +94,21 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, String registrationId = request.getClientRegistration().getRegistrationId(); String userDbId = registrationId + user.getName(); - newAttributes.put("UserDbId", userDbId); + + System.out.println("User: ["+ registrationId +"] "+ user.getName()); + newAttributes.forEach((key, value) -> + System.out.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) + ); + + newAttributes.put(UserService.ATTR_USER_DB_ID, userDbId); + newAttributes.put(UserService.ATTR_REGISTRATION_ID, registrationId); Role role = null; final Optional storedUserInfoOpt = storedUserInfoService.getUserById(userDbId); if (storedUserInfoOpt.isPresent()) { final StoredUserInfo storedUserInfo = storedUserInfoOpt.get(); role = storedUserInfo.role(); - storedUserInfoService.updateUserIfNeeded(storedUserInfo, newAttributes); + storedUserInfoService.updateUserIfNeeded(storedUserInfo, registrationId, newAttributes); } if (role==null && initialAdmin.equals(userDbId)) @@ -109,10 +118,10 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, role = Role.UNKNOWN_ACCOUNT; if (storedUserInfoOpt.isEmpty()) - storedUserInfoService.addUser(role, registrationId, newAttributes); + storedUserInfoService.addUser(userDbId, registrationId, role, newAttributes); newAuthorities.add(new SimpleGrantedAuthority(role.getLong())); - return new DefaultOAuth2User(newAuthorities, newAttributes, "id"); + return new DefaultOAuth2User(newAuthorities, newAttributes, UserService.ATTR_USER_DB_ID); } } diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java index f686207..a5981b2 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java @@ -26,35 +26,66 @@ public Optional getUserById(String userDbId) { return storedUserInfoRepository.findById(userDbId); } - public void addUser(Role role, String registrationId, Map newAttributes) { - storedUserInfoRepository.save(new StoredUserInfo( - Objects.toString(newAttributes.get(UserService.ATTR_USER_DB_ID ), null), - role, - registrationId, - Objects.toString(newAttributes.get(UserService.ATTR_ORIGINAL_ID), null), - Objects.toString(newAttributes.get(UserService.ATTR_LOGIN ), null), - Objects.toString(newAttributes.get(UserService.ATTR_NAME ), null), - Objects.toString(newAttributes.get(UserService.ATTR_LOCATION ), null), - Objects.toString(newAttributes.get(UserService.ATTR_URL ), null), - Objects.toString(newAttributes.get(UserService.ATTR_AVATAR_URL ), null), - null - )); + public void addUser(@NonNull String userDbId, String registrationId, @NonNull Role role, @NonNull Map newAttributes) { + StoredUserInfo storedUserInfo = null; + + if (registrationId!=null) + switch (registrationId) { + case "github": + storedUserInfo = new StoredUserInfo( + userDbId, + role, + registrationId, + Objects.toString(newAttributes.get(UserService.ATTR_ORIGINAL_ID), null), + Objects.toString(newAttributes.get(UserService.ATTR_LOGIN ), null), + Objects.toString(newAttributes.get(UserService.ATTR_NAME ), null), + Objects.toString(newAttributes.get(UserService.ATTR_LOCATION ), null), + Objects.toString(newAttributes.get(UserService.ATTR_URL ), null), + Objects.toString(newAttributes.get(UserService.ATTR_AVATAR_URL ), null), + null + ); + break; + } + + if (storedUserInfo==null) + storedUserInfo = new StoredUserInfo( + userDbId, + role, + registrationId, + null, + null, + null, + null, + null, + null, + null + ); + + storedUserInfoRepository.save(storedUserInfo); } - public void updateUserIfNeeded(StoredUserInfo storedUserInfo, Map newAttributes) { - StoredUserInfo updatedUserInfo = new StoredUserInfo( - storedUserInfo.id(), - storedUserInfo.role(), - storedUserInfo.registrationId(), - Objects.toString(newAttributes.get(UserService.ATTR_ORIGINAL_ID), storedUserInfo.originalId()), - Objects.toString(newAttributes.get(UserService.ATTR_LOGIN ), storedUserInfo.login ()), - Objects.toString(newAttributes.get(UserService.ATTR_NAME ), storedUserInfo.name ()), - Objects.toString(newAttributes.get(UserService.ATTR_LOCATION ), storedUserInfo.location ()), - Objects.toString(newAttributes.get(UserService.ATTR_URL ), storedUserInfo.url ()), - Objects.toString(newAttributes.get(UserService.ATTR_AVATAR_URL ), storedUserInfo.avatar_url()), - storedUserInfo.denialReason() - ); - if (!updatedUserInfo.equals(storedUserInfo)) + public void updateUserIfNeeded(StoredUserInfo storedUserInfo, String registrationId, Map newAttributes) { + StoredUserInfo updatedUserInfo = null; + + if (registrationId!=null) + switch (registrationId) { + case "github": + updatedUserInfo = new StoredUserInfo( + storedUserInfo.id(), + storedUserInfo.role(), + storedUserInfo.registrationId(), + Objects.toString(newAttributes.get(UserService.ATTR_ORIGINAL_ID), storedUserInfo.originalId()), + Objects.toString(newAttributes.get(UserService.ATTR_LOGIN ), storedUserInfo.login ()), + Objects.toString(newAttributes.get(UserService.ATTR_NAME ), storedUserInfo.name ()), + Objects.toString(newAttributes.get(UserService.ATTR_LOCATION ), storedUserInfo.location ()), + Objects.toString(newAttributes.get(UserService.ATTR_URL ), storedUserInfo.url ()), + Objects.toString(newAttributes.get(UserService.ATTR_AVATAR_URL ), storedUserInfo.avatar_url()), + storedUserInfo.denialReason() + ); + break; + } + + if (updatedUserInfo!=null && !updatedUserInfo.equals(storedUserInfo)) storedUserInfoRepository.save(updatedUserInfo); } diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java index b7060fa..cb099bc 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java @@ -16,8 +16,10 @@ @RequiredArgsConstructor public class UserService { + public static final String ATTR_USER_DB_ID = "UserDbId"; + public static final String ATTR_REGISTRATION_ID = "RegistrationId"; + static final String ATTR_ORIGINAL_ID = "id"; - static final String ATTR_USER_DB_ID = "UserDbId"; static final String ATTR_LOGIN = "login"; static final String ATTR_NAME = "name"; static final String ATTR_LOCATION = "location"; @@ -37,22 +39,43 @@ public class UserService { // if (principal!=null) DEBUG_OUT.println("Principal: "+principal.getClass()+" -> "+principal); if (principal instanceof OAuth2AuthenticatedPrincipal user) { -// DEBUG_OUT.println("User Attributes:"); -// user.getAttributes().forEach((key, value) -> -// DEBUG_OUT.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) -// ); + /* + DEBUG_OUT.println("User Attributes:"); + user.getAttributes().forEach((key, value) -> + DEBUG_OUT.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) + ); + */ + + String registrationId = Objects.toString( user.getAttribute(ATTR_REGISTRATION_ID), null ); + + if (registrationId!=null) + switch (registrationId) { + case "github": + return new UserInfo( + true, + hasRole(user, Role.USER), + hasRole(user, Role.ADMIN), + Objects.toString( user.getAttribute(ATTR_ORIGINAL_ID), null ), + Objects.toString( user.getAttribute(ATTR_USER_DB_ID ), null ), + Objects.toString( user.getAttribute(ATTR_LOGIN ), null ), + Objects.toString( user.getAttribute(ATTR_NAME ), null ), + Objects.toString( user.getAttribute(ATTR_LOCATION ), null ), + Objects.toString( user.getAttribute(ATTR_URL ), null ), + Objects.toString( user.getAttribute(ATTR_AVATAR_URL ), null ) + ); + } return new UserInfo( true, hasRole(user, Role.USER), hasRole(user, Role.ADMIN), - Objects.toString( user.getAttribute(ATTR_ORIGINAL_ID), null ), + null, Objects.toString( user.getAttribute(ATTR_USER_DB_ID ), null ), - Objects.toString( user.getAttribute(ATTR_LOGIN ), null ), - Objects.toString( user.getAttribute(ATTR_NAME ), null ), - Objects.toString( user.getAttribute(ATTR_LOCATION ), null ), - Objects.toString( user.getAttribute(ATTR_URL ), null ), - Objects.toString( user.getAttribute(ATTR_AVATAR_URL ), null ) + null, + null, + null, + null, + null ); } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index f304dbf..89711df 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -8,5 +8,5 @@ spring.security.oauth2.client.registration.github.client-secret=${OAUTH_GITHUB_C spring.security.oauth2.client.registration.github.scope=none spring.security.oauth2.client.registration.google.client-id=${OAUTH_GOOGLE_CLIENT_ID} spring.security.oauth2.client.registration.google.client-secret=${OAUTH_GOOGLE_CLIENT_SECRET} -spring.security.oauth2.client.registration.google.scope=none +spring.security.oauth2.client.registration.google.scope=https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile app.security.initial-admin=${INITIAL_ADMIN} \ No newline at end of file diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java index 38772fe..193b9fb 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java @@ -10,6 +10,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.springframework.lang.NonNull; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; @@ -18,6 +19,7 @@ import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,7 +48,7 @@ void setup() { @Test void whenConfigureUserData_isCalledWithEmptyDbByAnotherUser() { whenConfigureUserData_isCalledWithEmptyDb("UserID", Role.UNKNOWN_ACCOUNT); } - private void whenConfigureUserData_isCalledWithEmptyDb(String userID, Role expectedRole) { + private void whenConfigureUserData_isCalledWithEmptyDb(String userID, @NonNull Role expectedRole) { //Given when(delegate.loadUser(oAuth2UserRequest)).thenReturn(new DefaultOAuth2User( List.of(), Map.of("id", userID), "id" @@ -59,13 +61,13 @@ private void whenConfigureUserData_isCalledWithEmptyDb(String userID, Role expec DefaultOAuth2User actual = securityConfig.configureUserData(storedUserInfoService, delegate, oAuth2UserRequest); //Then - Map newAttributes = Map.of( + Map newAttributes = Objects.requireNonNull( Map.of( "id", userID, "UserDbId", "RegistrationId" + userID - ); + ) ); verify(storedUserInfoService).getUserById("RegistrationId" + userID); - verify(storedUserInfoService, times(0)).updateUserIfNeeded(any(),any()); - verify(storedUserInfoService).addUser(expectedRole, "RegistrationId", newAttributes); + verify(storedUserInfoService, times(0)).updateUserIfNeeded(any(),any(),any()); + verify(storedUserInfoService).addUser("RegistrationId" + userID, "RegistrationId", expectedRole, newAttributes); DefaultOAuth2User expected = new DefaultOAuth2User( List.of(new SimpleGrantedAuthority(expectedRole.getLong())), newAttributes, @@ -96,9 +98,10 @@ void whenConfigureUserData_isCalledStoredUser(Role expectedRole) { verify(storedUserInfoService).getUserById("RegistrationId" + "userID"); verify(storedUserInfoService).updateUserIfNeeded( createStoredUserInfo(expectedRole), + "RegistrationId", newAttributes ); - verify(storedUserInfoService, times(0)).addUser(any(),any(),any()); + verify(storedUserInfoService, times(0)).addUser(any(),any(),any(),any()); DefaultOAuth2User expected = new DefaultOAuth2User( List.of(new SimpleGrantedAuthority(expectedRole.getLong())), newAttributes, diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java index d33d9b6..3c6a647 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -87,7 +88,7 @@ void whenGetUserById_getsKnownId_returnsOptionalWithData() { void whenAddUser_isCalled() { // Given // When - Map attrs = Map.of( + Map attrs = Objects.requireNonNull( Map.of( "UserDbId" , "RegistrationIDuserID1", "id" , "userID1" , "login" , "login1" , @@ -95,8 +96,8 @@ void whenAddUser_isCalled() { "location" , "location1", "html_url" , "url1" , "avatar_url", "avatarUrl1" - ); - storedUserInfoService.addUser(Role.UNKNOWN_ACCOUNT, "RegistrationID", attrs); + ) ); + storedUserInfoService.addUser("RegistrationIDuserID1", "RegistrationID", Role.UNKNOWN_ACCOUNT, attrs); // Then verify(storedUserInfoRepository).save(new StoredUserInfo( @@ -128,7 +129,7 @@ void whenUpdateUserIfNeeded_isCalledWithNewData() { "userID1", "login2", "name2", "location2", "url2", "avatarUrl2", "reason1" ); - storedUserInfoService.updateUserIfNeeded(storedUserInfo, attrs); + storedUserInfoService.updateUserIfNeeded(storedUserInfo, "RegistrationID", attrs); // Then verify(storedUserInfoRepository).save(new StoredUserInfo( @@ -161,7 +162,7 @@ private void whenUpdateUserIfNeeded_isCalled_andNothingIsWrittenToRepo(Map ) { if (!props.user?.isAuthenticated) return - Please + {"Please "} - or + {" or "} From 1981b7a3fc8809ef513fdb71e483ad4a64181a16 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:27:23 +0100 Subject: [PATCH 04/19] feat: added field definition for user data from Google --- .../backend/security/SecurityConfig.java | 23 ++--- .../backend/security/UserAttributes.java | 89 +++++++++++++++++++ .../services/StoredUserInfoService.java | 83 ++++++----------- .../security/services/UserService.java | 62 +++---------- 4 files changed, 141 insertions(+), 116 deletions(-) create mode 100644 backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserAttributes.java diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java index 6744f5f..e63275d 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java @@ -1,9 +1,12 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; -import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; -import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserService; +import static org.springframework.security.config.Customizer.withDefaults; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -24,9 +27,9 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.HttpStatusEntryPoint; -import java.util.*; - -import static org.springframework.security.config.Customizer.withDefaults; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; @Configuration @EnableWebSecurity @@ -100,8 +103,8 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, System.out.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) ); - newAttributes.put(UserService.ATTR_USER_DB_ID, userDbId); - newAttributes.put(UserService.ATTR_REGISTRATION_ID, registrationId); + newAttributes.put(UserAttributes.ATTR_USER_DB_ID, userDbId); + newAttributes.put(UserAttributes.ATTR_REGISTRATION_ID, registrationId); Role role = null; final Optional storedUserInfoOpt = storedUserInfoService.getUserById(userDbId); @@ -121,7 +124,7 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, storedUserInfoService.addUser(userDbId, registrationId, role, newAttributes); newAuthorities.add(new SimpleGrantedAuthority(role.getLong())); - return new DefaultOAuth2User(newAuthorities, newAttributes, UserService.ATTR_USER_DB_ID); + return new DefaultOAuth2User(newAuthorities, newAttributes, UserAttributes.ATTR_USER_DB_ID); } } diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserAttributes.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserAttributes.java new file mode 100644 index 0000000..f3cc6fb --- /dev/null +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserAttributes.java @@ -0,0 +1,89 @@ +package net.schwarzbaer.spring.promptoptimizer.backend.security; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.springframework.lang.NonNull; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; + +public class UserAttributes { + + public static final String ATTR_USER_DB_ID = "UserDbId"; + public static final String ATTR_REGISTRATION_ID = "RegistrationId"; + public static final String REGID_GITHUB = "github"; + public static final String REGID_GOOGLE = "google"; + + public enum Field { + ORIGINAL_ID, + LOGIN , + NAME , + LOCATION , + URL , + AVATAR_URL , + } + + + private static final Map> config = createConfig(); + + private static Map> createConfig() { + HashMap> newConfig = new HashMap<>(); + EnumMap fields; + + newConfig.put(REGID_GITHUB, fields = new EnumMap<>(Field.class)); + fields.put( Field.ORIGINAL_ID, "id" ); + fields.put( Field.LOGIN , "login" ); + fields.put( Field.NAME , "name" ); + fields.put( Field.LOCATION , "location" ); + fields.put( Field.URL , "html_url" ); + fields.put( Field.AVATAR_URL , "avatar_url" ); + + newConfig.put(REGID_GOOGLE, fields = new EnumMap<>(Field.class)); + fields.put( Field.ORIGINAL_ID, "sub" ); + fields.put( Field.LOGIN , "email" ); + fields.put( Field.NAME , "name" ); + fields.put( Field.LOCATION , "locale" ); + // fields.put( Field.URL , "html_url" ); + fields.put( Field.AVATAR_URL , "picture" ); + + return newConfig; + } + + + public static String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, @NonNull String field, String nullDefault ) + { + return Objects.toString( user.getAttribute(field), nullDefault ); + } + + public static String getAttribute( @NonNull Map userAttributes, @NonNull String field, String nullDefault ) + { + return Objects.toString( userAttributes.get(field), nullDefault ); + } + + public static String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, String registrationId, Field field, String nullDefault ) + { + return getAttribute( user, registrationId, field, nullDefault, UserAttributes::getAttribute ); + } + + public static String getAttribute( @NonNull Map userAttributes, String registrationId, Field field, String nullDefault ) + { + return getAttribute( userAttributes, registrationId, field, nullDefault, UserAttributes::getAttribute ); + } + + + private interface GetAttributeFunction { + String getAttribute( @NonNull Source source, @NonNull String field, String nullDefault); + } + + private static String getAttribute( @NonNull Source source, String registrationId, Field field, String nullDefault, GetAttributeFunction getAttribute ) + { + Map attrNames = config.get(registrationId); + if (attrNames==null) return nullDefault; + + String attrName = attrNames.get(field); + if (attrName==null) return nullDefault; + + return getAttribute.getAttribute( source, attrName, nullDefault ); + } +} diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java index a5981b2..3716f5d 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java @@ -1,6 +1,7 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security.services; import lombok.RequiredArgsConstructor; +import net.schwarzbaer.spring.promptoptimizer.backend.security.UserAttributes; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; @@ -27,65 +28,35 @@ public Optional getUserById(String userDbId) { } public void addUser(@NonNull String userDbId, String registrationId, @NonNull Role role, @NonNull Map newAttributes) { - StoredUserInfo storedUserInfo = null; - - if (registrationId!=null) - switch (registrationId) { - case "github": - storedUserInfo = new StoredUserInfo( - userDbId, - role, - registrationId, - Objects.toString(newAttributes.get(UserService.ATTR_ORIGINAL_ID), null), - Objects.toString(newAttributes.get(UserService.ATTR_LOGIN ), null), - Objects.toString(newAttributes.get(UserService.ATTR_NAME ), null), - Objects.toString(newAttributes.get(UserService.ATTR_LOCATION ), null), - Objects.toString(newAttributes.get(UserService.ATTR_URL ), null), - Objects.toString(newAttributes.get(UserService.ATTR_AVATAR_URL ), null), - null - ); - break; - } - - if (storedUserInfo==null) - storedUserInfo = new StoredUserInfo( - userDbId, - role, - registrationId, - null, - null, - null, - null, - null, - null, - null - ); - - storedUserInfoRepository.save(storedUserInfo); + storedUserInfoRepository.save(new StoredUserInfo( + userDbId, + role, + registrationId, + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.ORIGINAL_ID, null ), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.LOGIN , null ), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.NAME , null ), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.LOCATION , null ), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.URL , null ), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.AVATAR_URL , null ), + null + )); } - public void updateUserIfNeeded(StoredUserInfo storedUserInfo, String registrationId, Map newAttributes) { - StoredUserInfo updatedUserInfo = null; - - if (registrationId!=null) - switch (registrationId) { - case "github": - updatedUserInfo = new StoredUserInfo( - storedUserInfo.id(), - storedUserInfo.role(), - storedUserInfo.registrationId(), - Objects.toString(newAttributes.get(UserService.ATTR_ORIGINAL_ID), storedUserInfo.originalId()), - Objects.toString(newAttributes.get(UserService.ATTR_LOGIN ), storedUserInfo.login ()), - Objects.toString(newAttributes.get(UserService.ATTR_NAME ), storedUserInfo.name ()), - Objects.toString(newAttributes.get(UserService.ATTR_LOCATION ), storedUserInfo.location ()), - Objects.toString(newAttributes.get(UserService.ATTR_URL ), storedUserInfo.url ()), - Objects.toString(newAttributes.get(UserService.ATTR_AVATAR_URL ), storedUserInfo.avatar_url()), - storedUserInfo.denialReason() - ); - break; - } + public void updateUserIfNeeded(@NonNull StoredUserInfo storedUserInfo, String registrationId, @NonNull Map newAttributes) { + StoredUserInfo updatedUserInfo = new StoredUserInfo( + storedUserInfo.id(), + storedUserInfo.role(), + storedUserInfo.registrationId(), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.ORIGINAL_ID, storedUserInfo.originalId()), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.LOGIN , storedUserInfo.login ()), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.NAME , storedUserInfo.name ()), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.LOCATION , storedUserInfo.location ()), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.URL , storedUserInfo.url ()), + UserAttributes.getAttribute( newAttributes, registrationId, UserAttributes.Field.AVATAR_URL , storedUserInfo.avatar_url()), + storedUserInfo.denialReason() + ); - if (updatedUserInfo!=null && !updatedUserInfo.equals(storedUserInfo)) + if (registrationId!=null && !updatedUserInfo.equals(storedUserInfo)) storedUserInfoRepository.save(updatedUserInfo); } diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java index cb099bc..a844bff 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java @@ -1,8 +1,5 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security.services; -import lombok.RequiredArgsConstructor; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; import org.springframework.lang.NonNull; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -10,25 +7,15 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.stereotype.Service; -import java.util.Objects; +import lombok.RequiredArgsConstructor; +import net.schwarzbaer.spring.promptoptimizer.backend.security.UserAttributes; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; @Service @RequiredArgsConstructor public class UserService { - public static final String ATTR_USER_DB_ID = "UserDbId"; - public static final String ATTR_REGISTRATION_ID = "RegistrationId"; - - static final String ATTR_ORIGINAL_ID = "id"; - static final String ATTR_LOGIN = "login"; - static final String ATTR_NAME = "name"; - static final String ATTR_LOCATION = "location"; - static final String ATTR_URL = "html_url"; - static final String ATTR_AVATAR_URL = "avatar_url"; - - // @SuppressWarnings("java:S106") - // private static final PrintStream DEBUG_OUT = System.out; - // #################################################################################### // Called by and allowed for all users (authorized or not) // #################################################################################### @@ -36,46 +23,21 @@ public class UserService { public @NonNull UserInfo getCurrentUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication!=null ? authentication.getPrincipal() : null; -// if (principal!=null) DEBUG_OUT.println("Principal: "+principal.getClass()+" -> "+principal); if (principal instanceof OAuth2AuthenticatedPrincipal user) { - /* - DEBUG_OUT.println("User Attributes:"); - user.getAttributes().forEach((key, value) -> - DEBUG_OUT.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) - ); - */ - - String registrationId = Objects.toString( user.getAttribute(ATTR_REGISTRATION_ID), null ); - - if (registrationId!=null) - switch (registrationId) { - case "github": - return new UserInfo( - true, - hasRole(user, Role.USER), - hasRole(user, Role.ADMIN), - Objects.toString( user.getAttribute(ATTR_ORIGINAL_ID), null ), - Objects.toString( user.getAttribute(ATTR_USER_DB_ID ), null ), - Objects.toString( user.getAttribute(ATTR_LOGIN ), null ), - Objects.toString( user.getAttribute(ATTR_NAME ), null ), - Objects.toString( user.getAttribute(ATTR_LOCATION ), null ), - Objects.toString( user.getAttribute(ATTR_URL ), null ), - Objects.toString( user.getAttribute(ATTR_AVATAR_URL ), null ) - ); - } + String registrationId = UserAttributes.getAttribute( user, UserAttributes.ATTR_REGISTRATION_ID, null ); return new UserInfo( true, hasRole(user, Role.USER), hasRole(user, Role.ADMIN), - null, - Objects.toString( user.getAttribute(ATTR_USER_DB_ID ), null ), - null, - null, - null, - null, - null + UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.ORIGINAL_ID, null ), + UserAttributes.getAttribute( user, UserAttributes.ATTR_USER_DB_ID, null ), + UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.LOGIN , null ), + UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.NAME , null ), + UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.LOCATION , null ), + UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.URL , null ), + UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.AVATAR_URL , null ) ); } From 683188a26b5f89b9337efa120acac5fcce3442c7 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:37:16 +0100 Subject: [PATCH 05/19] feat: changed alt strings of avatar images --- frontend/src/App.tsx | 2 +- .../src/pages/UserManagement/components/UserTableRow.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 76550b8..46eb8b1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -88,7 +88,7 @@ export default function App() { { user.login && <> - {user.avatar_url && user avatar image} + {user.avatar_url && [Avatar]} {" "+user.login} } diff --git a/frontend/src/pages/UserManagement/components/UserTableRow.tsx b/frontend/src/pages/UserManagement/components/UserTableRow.tsx index 43a5353..9a94b9e 100644 --- a/frontend/src/pages/UserManagement/components/UserTableRow.tsx +++ b/frontend/src/pages/UserManagement/components/UserTableRow.tsx @@ -87,7 +87,7 @@ export default function UserTableRow( props_:Readonly ) { return ( - {user.avatar_url && {"Avatar} + {user.avatar_url && {"[Avatar]"}} {" "} {getReplacementIfNeeded(user.name, "------")} @@ -115,7 +115,8 @@ export default function UserTableRow( props_:Readonly ) { { user.denialReason && showEditReasonDialog({ user })}> - { trimLongText( user.denialReason, 35 )+" " } + { trimLongText( user.denialReason, 35 )} + {" "} { SVGsInVars.Edit } } From 85467b069cc670a11eecc55fb5e32a873cfdc13c Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:55:40 +0100 Subject: [PATCH 06/19] test: updated SecurityConfigTest --- .../services/StoredUserInfoService.java | 11 +++++- .../backend/security/SecurityConfigTest.java | 38 +++++++++++-------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java index 3716f5d..ec4e7f8 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java @@ -27,7 +27,11 @@ public Optional getUserById(String userDbId) { return storedUserInfoRepository.findById(userDbId); } - public void addUser(@NonNull String userDbId, String registrationId, @NonNull Role role, @NonNull Map newAttributes) { + public void addUser(String userDbId, String registrationId, Role role, Map newAttributes) { + userDbId = Objects.requireNonNull(userDbId); + role = Objects.requireNonNull(role); + newAttributes = Objects.requireNonNull(newAttributes); + storedUserInfoRepository.save(new StoredUserInfo( userDbId, role, @@ -42,7 +46,10 @@ public void addUser(@NonNull String userDbId, String registrationId, @NonNull Ro )); } - public void updateUserIfNeeded(@NonNull StoredUserInfo storedUserInfo, String registrationId, @NonNull Map newAttributes) { + public void updateUserIfNeeded(StoredUserInfo storedUserInfo, String registrationId, Map newAttributes) { + storedUserInfo = Objects.requireNonNull(storedUserInfo); + newAttributes = Objects.requireNonNull(newAttributes); + StoredUserInfo updatedUserInfo = new StoredUserInfo( storedUserInfo.id(), storedUserInfo.role(), diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java index 193b9fb..1135312 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java @@ -1,8 +1,16 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; -import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,13 +25,9 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; class SecurityConfigTest { @@ -61,17 +65,18 @@ private void whenConfigureUserData_isCalledWithEmptyDb(String userID, @NonNull R DefaultOAuth2User actual = securityConfig.configureUserData(storedUserInfoService, delegate, oAuth2UserRequest); //Then - Map newAttributes = Objects.requireNonNull( Map.of( + Map newAttributes = Map.of( "id", userID, - "UserDbId", "RegistrationId" + userID - ) ); + "UserDbId", "RegistrationId" + userID, + "RegistrationId", "RegistrationId" + ); verify(storedUserInfoService).getUserById("RegistrationId" + userID); verify(storedUserInfoService, times(0)).updateUserIfNeeded(any(),any(),any()); verify(storedUserInfoService).addUser("RegistrationId" + userID, "RegistrationId", expectedRole, newAttributes); DefaultOAuth2User expected = new DefaultOAuth2User( List.of(new SimpleGrantedAuthority(expectedRole.getLong())), newAttributes, - "id" + "UserDbId" ); assertEquals(expected, actual); } @@ -93,7 +98,8 @@ void whenConfigureUserData_isCalledStoredUser(Role expectedRole) { //Then Map newAttributes = Map.of( "id", "userID", - "UserDbId", "RegistrationId" + "userID" + "UserDbId", "RegistrationId" + "userID", + "RegistrationId", "RegistrationId" ); verify(storedUserInfoService).getUserById("RegistrationId" + "userID"); verify(storedUserInfoService).updateUserIfNeeded( @@ -105,7 +111,7 @@ void whenConfigureUserData_isCalledStoredUser(Role expectedRole) { DefaultOAuth2User expected = new DefaultOAuth2User( List.of(new SimpleGrantedAuthority(expectedRole.getLong())), newAttributes, - "id" + "UserDbId" ); assertEquals(expected, actual); } From 0bdbbbc9ac1b7b7fb13df8e1283108db04f41f33 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:17:05 +0100 Subject: [PATCH 07/19] refactor: converted UserAttributes into a service --- .../backend/security/SecurityConfig.java | 7 ++--- .../services/StoredUserInfoService.java | 26 +++++++++---------- .../UserAttributesService.java} | 23 +++++++++------- .../security/services/UserService.java | 19 +++++++------- 4 files changed, 41 insertions(+), 34 deletions(-) rename backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/{UserAttributes.java => services/UserAttributesService.java} (70%) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java index e63275d..e68c269 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java @@ -30,6 +30,7 @@ import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService; @Configuration @EnableWebSecurity @@ -103,8 +104,8 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, System.out.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) ); - newAttributes.put(UserAttributes.ATTR_USER_DB_ID, userDbId); - newAttributes.put(UserAttributes.ATTR_REGISTRATION_ID, registrationId); + newAttributes.put(UserAttributesService.ATTR_USER_DB_ID, userDbId); + newAttributes.put(UserAttributesService.ATTR_REGISTRATION_ID, registrationId); Role role = null; final Optional storedUserInfoOpt = storedUserInfoService.getUserById(userDbId); @@ -124,7 +125,7 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, storedUserInfoService.addUser(userDbId, registrationId, role, newAttributes); newAuthorities.add(new SimpleGrantedAuthority(role.getLong())); - return new DefaultOAuth2User(newAuthorities, newAttributes, UserAttributes.ATTR_USER_DB_ID); + return new DefaultOAuth2User(newAuthorities, newAttributes, UserAttributesService.ATTR_USER_DB_ID); } } diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java index ec4e7f8..af0e12e 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java @@ -1,7 +1,6 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security.services; import lombok.RequiredArgsConstructor; -import net.schwarzbaer.spring.promptoptimizer.backend.security.UserAttributes; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; @@ -18,6 +17,7 @@ public class StoredUserInfoService { private final StoredUserInfoRepository storedUserInfoRepository; private final UserService userService; + private final UserAttributesService userAttributesService; // #################################################################################### // Called by SecurityConfig @@ -36,12 +36,12 @@ public void addUser(String userDbId, String registrationId, Role role, Map> createConfig() { } - public static String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, @NonNull String field, String nullDefault ) + public String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, @NonNull String field, String nullDefault ) { return Objects.toString( user.getAttribute(field), nullDefault ); } - public static String getAttribute( @NonNull Map userAttributes, @NonNull String field, String nullDefault ) + public String getAttribute( @NonNull Map userAttributes, @NonNull String field, String nullDefault ) { return Objects.toString( userAttributes.get(field), nullDefault ); } - public static String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, String registrationId, Field field, String nullDefault ) + public String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, String registrationId, Field field, String nullDefault ) { - return getAttribute( user, registrationId, field, nullDefault, UserAttributes::getAttribute ); + return getAttribute( user, registrationId, field, nullDefault, this::getAttribute ); } - public static String getAttribute( @NonNull Map userAttributes, String registrationId, Field field, String nullDefault ) + public String getAttribute( @NonNull Map userAttributes, String registrationId, Field field, String nullDefault ) { - return getAttribute( userAttributes, registrationId, field, nullDefault, UserAttributes::getAttribute ); + return getAttribute( userAttributes, registrationId, field, nullDefault, this::getAttribute ); } @@ -76,7 +81,7 @@ private interface GetAttributeFunction { String getAttribute( @NonNull Source source, @NonNull String field, String nullDefault); } - private static String getAttribute( @NonNull Source source, String registrationId, Field field, String nullDefault, GetAttributeFunction getAttribute ) + private String getAttribute( @NonNull Source source, String registrationId, Field field, String nullDefault, GetAttributeFunction getAttribute ) { Map attrNames = config.get(registrationId); if (attrNames==null) return nullDefault; diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java index a844bff..7cf3dc5 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserService.java @@ -8,7 +8,6 @@ import org.springframework.stereotype.Service; import lombok.RequiredArgsConstructor; -import net.schwarzbaer.spring.promptoptimizer.backend.security.UserAttributes; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; @@ -16,6 +15,8 @@ @RequiredArgsConstructor public class UserService { + private final UserAttributesService userAttributesService; + // #################################################################################### // Called by and allowed for all users (authorized or not) // #################################################################################### @@ -25,19 +26,19 @@ public class UserService { Object principal = authentication!=null ? authentication.getPrincipal() : null; if (principal instanceof OAuth2AuthenticatedPrincipal user) { - String registrationId = UserAttributes.getAttribute( user, UserAttributes.ATTR_REGISTRATION_ID, null ); + String registrationId = userAttributesService.getAttribute( user, UserAttributesService.ATTR_REGISTRATION_ID, null ); return new UserInfo( true, hasRole(user, Role.USER), hasRole(user, Role.ADMIN), - UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.ORIGINAL_ID, null ), - UserAttributes.getAttribute( user, UserAttributes.ATTR_USER_DB_ID, null ), - UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.LOGIN , null ), - UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.NAME , null ), - UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.LOCATION , null ), - UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.URL , null ), - UserAttributes.getAttribute( user, registrationId, UserAttributes.Field.AVATAR_URL , null ) + userAttributesService.getAttribute( user, registrationId, UserAttributesService.Field.ORIGINAL_ID, null ), + userAttributesService.getAttribute( user, UserAttributesService.ATTR_USER_DB_ID, null ), + userAttributesService.getAttribute( user, registrationId, UserAttributesService.Field.LOGIN , null ), + userAttributesService.getAttribute( user, registrationId, UserAttributesService.Field.NAME , null ), + userAttributesService.getAttribute( user, registrationId, UserAttributesService.Field.LOCATION , null ), + userAttributesService.getAttribute( user, registrationId, UserAttributesService.Field.URL , null ), + userAttributesService.getAttribute( user, registrationId, UserAttributesService.Field.AVATAR_URL , null ) ); } From dac7ef0fc19762863293f888540e2251ab21523c Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:49:33 +0100 Subject: [PATCH 08/19] test: updated UserServiceTest --- .../security/services/UserServiceTest.java | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserServiceTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserServiceTest.java index be74ce5..1be8052 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserServiceTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserServiceTest.java @@ -1,7 +1,11 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security.services; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -15,16 +19,14 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import java.util.List; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.UserInfo; class UserServiceTest { @Mock private SecurityContext securityContext; @Mock private Authentication authentication; + @Mock private UserAttributesService userAttributesService; @InjectMocks private UserService userService; @BeforeEach @@ -33,22 +35,32 @@ void setUp() { } @NonNull - public static DefaultOAuth2User buildUser(@Nullable Role role, @NonNull String id, @NonNull String login) { + private static DefaultOAuth2User buildUser(@Nullable Role role, @NonNull String id, @NonNull String registrationId, @NonNull String login) { return new DefaultOAuth2User( role==null ? List.of() : List.of( new SimpleGrantedAuthority( role.getLong() ) ), Map.of( - "id", id, + "originalId", id, + UserAttributesService.ATTR_USER_DB_ID, registrationId + id, + UserAttributesService.ATTR_REGISTRATION_ID, registrationId, "login", login ), - "id"); + UserAttributesService.ATTR_USER_DB_ID); } - private void initSecurityContext(@Nullable Role role, @NonNull String id, @NonNull String login) { + private void initSecurityContext(@Nullable Role role, @NonNull String id, @NonNull String registrationId, @NonNull String login) { + DefaultOAuth2User user = buildUser(role, id, registrationId, login); + when(securityContext.getAuthentication()).thenReturn(authentication); - when(authentication.getPrincipal()).thenReturn(buildUser(role, id, login)); + when(authentication.getPrincipal()).thenReturn(user); when(authentication.getName()).thenThrow(IllegalStateException.class); + + when(userAttributesService.getAttribute(user, UserAttributesService.ATTR_REGISTRATION_ID, null)).thenReturn(registrationId); + when(userAttributesService.getAttribute(user, UserAttributesService.ATTR_USER_DB_ID , null)).thenReturn(registrationId + id); + when(userAttributesService.getAttribute(user, registrationId, UserAttributesService.Field.ORIGINAL_ID, null)).thenReturn(id); + when(userAttributesService.getAttribute(user, registrationId, UserAttributesService.Field.LOGIN , null)).thenReturn(login); + SecurityContextHolder.setContext(securityContext); } @@ -119,7 +131,7 @@ private void whenGetCurrentUser_isCalled( boolean isUser, boolean isAdmin ) { // Given - initSecurityContext(loggedUserRole, loggedUserID, loggedUserLogin); + initSecurityContext(loggedUserRole, loggedUserID, "Registration1", loggedUserLogin); // When UserInfo actual = userService.getCurrentUser(); @@ -127,7 +139,7 @@ private void whenGetCurrentUser_isCalled( // Then UserInfo expected = new UserInfo( true, isUser, isAdmin, - loggedUserID, null, loggedUserLogin, null, null, null, null + loggedUserID, "Registration1"+loggedUserID, loggedUserLogin, null, null, null, null ); assertEquals(expected, actual); } From 0187eb7d4980ac733edcf8990ff13eca5fade33d Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 14:16:48 +0100 Subject: [PATCH 09/19] tests: updated StoredUserInfoServiceTest --- .../services/StoredUserInfoService.java | 2 +- .../services/StoredUserInfoServiceTest.java | 61 +++++++++---------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java index af0e12e..7a73b1f 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoService.java @@ -63,7 +63,7 @@ public void updateUserIfNeeded(StoredUserInfo storedUserInfo, String registratio storedUserInfo.denialReason() ); - if (registrationId!=null && !updatedUserInfo.equals(storedUserInfo)) + if (!updatedUserInfo.equals(storedUserInfo)) storedUserInfoRepository.save(updatedUserInfo); } diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java index 3c6a647..eaa0899 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/StoredUserInfoServiceTest.java @@ -27,6 +27,7 @@ class StoredUserInfoServiceTest { @Mock private StoredUserInfoRepository storedUserInfoRepository; @Mock private UserService userService; + @Mock private UserAttributesService userAttributesService; @InjectMocks private StoredUserInfoService storedUserInfoService; @BeforeEach @@ -87,16 +88,16 @@ void whenGetUserById_getsKnownId_returnsOptionalWithData() { @Test void whenAddUser_isCalled() { // Given + Map attrs = Objects.requireNonNull( Map.of() ); + String registrationId = "RegistrationID"; + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.ORIGINAL_ID, null)).thenReturn("userID1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.LOGIN , null)).thenReturn("login1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.NAME , null)).thenReturn("name1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.LOCATION , null)).thenReturn("location1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.URL , null)).thenReturn("url1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.AVATAR_URL , null)).thenReturn("avatarUrl1"); + // When - Map attrs = Objects.requireNonNull( Map.of( - "UserDbId" , "RegistrationIDuserID1", - "id" , "userID1" , - "login" , "login1" , - "name" , "name1" , - "location" , "location1", - "html_url" , "url1" , - "avatar_url", "avatarUrl1" - ) ); storedUserInfoService.addUser("RegistrationIDuserID1", "RegistrationID", Role.UNKNOWN_ACCOUNT, attrs); // Then @@ -114,16 +115,16 @@ void whenAddUser_isCalled() { @Test void whenUpdateUserIfNeeded_isCalledWithNewData() { // Given + Map attrs = Objects.requireNonNull( Map.of() ); + String registrationId = "RegistrationID"; + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.ORIGINAL_ID, "userID1" )).thenReturn("userID1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.LOGIN , "login2" )).thenReturn("login1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.NAME , "name2" )).thenReturn("name1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.LOCATION , "location2" )).thenReturn("location1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.URL , "url2" )).thenReturn("url1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.AVATAR_URL , "avatarUrl2")).thenReturn("avatarUrl1"); + // When - Map attrs = Map.of( - "UserDbId" , "RegistrationIDuserID1", - "id" , "userID1" , - "login" , "login1" , - "name" , "name1" , - "location" , "location1", - "html_url" , "url1" , - "avatar_url", "avatarUrl1" - ); StoredUserInfo storedUserInfo = new StoredUserInfo( "RegistrationIDuserID1", Role.UNKNOWN_ACCOUNT, "RegistrationID", "userID1", "login2", @@ -139,22 +140,16 @@ void whenUpdateUserIfNeeded_isCalledWithNewData() { )); } - @Test void whenUpdateUserIfNeeded_isCalledWithNoData() { - whenUpdateUserIfNeeded_isCalled_andNothingIsWrittenToRepo(Map.of()); - } - @Test void whenUpdateUserIfNeeded_isCalledWithSameData() { - whenUpdateUserIfNeeded_isCalled_andNothingIsWrittenToRepo(Map.of( - "UserDbId" , "RegistrationIDuserID1", - "id" , "userID1" , - "login" , "login1" , - "name" , "name1" , - "location" , "location1", - "html_url" , "url1" , - "avatar_url", "avatarUrl1" - )); - } - private void whenUpdateUserIfNeeded_isCalled_andNothingIsWrittenToRepo(Map attrs) { + @Test void whenUpdateUserIfNeeded_isCalledWithNoDataOrSameData() { // Given + Map attrs = Objects.requireNonNull( Map.of() ); + String registrationId = "RegistrationID"; + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.ORIGINAL_ID, "userID1" )).thenReturn("userID1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.LOGIN , "login1" )).thenReturn("login1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.NAME , "name1" )).thenReturn("name1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.LOCATION , "location1" )).thenReturn("location1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.URL , "url1" )).thenReturn("url1" ); + when(userAttributesService.getAttribute(attrs, registrationId, UserAttributesService.Field.AVATAR_URL , "avatarUrl1")).thenReturn("avatarUrl1"); // When StoredUserInfo storedUserInfo = new StoredUserInfo( From e406470d65581c6cee23c2d857746ff67500e5a9 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 15:59:50 +0100 Subject: [PATCH 10/19] tests: updated UserManagementIntegrationTest --- .../backend/security/SecurityConfig.java | 12 +- .../services/UserAttributesService.java | 25 ++- .../backend/security/SecurityConfigTest.java | 6 +- .../backend/security/SecurityTestTools.java | 53 ++++++- .../UserManagementIntegrationTest.java | 144 +++++++++++------- 5 files changed, 168 insertions(+), 72 deletions(-) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java index e68c269..bfad21c 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java @@ -86,12 +86,17 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { } @Bean - public OAuth2UserService oauth2UserService(StoredUserInfoService storedUserInfoService) { + public OAuth2UserService oauth2UserService(StoredUserInfoService storedUserInfoService, UserAttributesService userAttributesService) { DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); - return request -> configureUserData(storedUserInfoService, delegate, request); + return request -> configureUserData(storedUserInfoService, userAttributesService, delegate, request); } - DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, DefaultOAuth2UserService delegate, OAuth2UserRequest request) { + DefaultOAuth2User configureUserData( + StoredUserInfoService storedUserInfoService, + UserAttributesService userAttributesService, + DefaultOAuth2UserService delegate, + OAuth2UserRequest request + ) { OAuth2User user = delegate.loadUser(request); Collection newAuthorities = new ArrayList<>(user.getAuthorities()); Map newAttributes = new HashMap<>(user.getAttributes()); @@ -107,6 +112,7 @@ DefaultOAuth2User configureUserData(StoredUserInfoService storedUserInfoService, newAttributes.put(UserAttributesService.ATTR_USER_DB_ID, userDbId); newAttributes.put(UserAttributesService.ATTR_REGISTRATION_ID, registrationId); Role role = null; + userAttributesService.fixAttributesIfNeeded(newAttributes, registrationId); final Optional storedUserInfoOpt = storedUserInfoService.getUserById(userDbId); if (storedUserInfoOpt.isPresent()) { diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java index 8b00c69..ce96bfc 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java @@ -17,8 +17,6 @@ public class UserAttributesService { public static final String ATTR_USER_DB_ID = "UserDbId"; public static final String ATTR_REGISTRATION_ID = "RegistrationId"; - public static final String REGID_GITHUB = "github"; - public static final String REGID_GOOGLE = "google"; public enum Field { ORIGINAL_ID, @@ -29,6 +27,16 @@ public enum Field { AVATAR_URL , } + public enum Registration { + GITHUB("github"), + GOOGLE("google"), + ; + public final String id; + private Registration(String id) { + this.id = id; + } + } + private static final Map> config = createConfig(); @@ -36,7 +44,7 @@ private static Map> createConfig() { HashMap> newConfig = new HashMap<>(); EnumMap fields; - newConfig.put(REGID_GITHUB, fields = new EnumMap<>(Field.class)); + newConfig.put(Registration.GITHUB.id, fields = new EnumMap<>(Field.class)); fields.put( Field.ORIGINAL_ID, "id" ); fields.put( Field.LOGIN , "login" ); fields.put( Field.NAME , "name" ); @@ -44,8 +52,8 @@ private static Map> createConfig() { fields.put( Field.URL , "html_url" ); fields.put( Field.AVATAR_URL , "avatar_url" ); - newConfig.put(REGID_GOOGLE, fields = new EnumMap<>(Field.class)); - fields.put( Field.ORIGINAL_ID, "sub" ); + newConfig.put(Registration.GOOGLE.id, fields = new EnumMap<>(Field.class)); + fields.put( Field.ORIGINAL_ID, "original_Id"); fields.put( Field.LOGIN , "email" ); fields.put( Field.NAME , "name" ); fields.put( Field.LOCATION , "locale" ); @@ -56,6 +64,13 @@ private static Map> createConfig() { } + public void fixAttributesIfNeeded(Map attributes, String registrationId) { + if (Registration.GOOGLE.id.equals(registrationId)) { + attributes.put("original_Id", attributes.get("sub")); + } + } + + public String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, @NonNull String field, String nullDefault ) { return Objects.toString( user.getAttribute(field), nullDefault ); diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java index 1135312..2480c27 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfigTest.java @@ -28,6 +28,7 @@ import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; import net.schwarzbaer.spring.promptoptimizer.backend.security.services.StoredUserInfoService; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService; class SecurityConfigTest { @@ -35,6 +36,7 @@ class SecurityConfigTest { @Mock private DefaultOAuth2UserService delegate; @Mock private OAuth2UserRequest oAuth2UserRequest; @Mock private StoredUserInfoService storedUserInfoService; + @Mock private UserAttributesService userAttributesService; @BeforeEach void setup() { @@ -62,7 +64,7 @@ private void whenConfigureUserData_isCalledWithEmptyDb(String userID, @NonNull R ); //When - DefaultOAuth2User actual = securityConfig.configureUserData(storedUserInfoService, delegate, oAuth2UserRequest); + DefaultOAuth2User actual = securityConfig.configureUserData(storedUserInfoService, userAttributesService, delegate, oAuth2UserRequest); //Then Map newAttributes = Map.of( @@ -93,7 +95,7 @@ void whenConfigureUserData_isCalledStoredUser(Role expectedRole) { ); //When - DefaultOAuth2User actual = securityConfig.configureUserData(storedUserInfoService, delegate, oAuth2UserRequest); + DefaultOAuth2User actual = securityConfig.configureUserData(storedUserInfoService, userAttributesService, delegate, oAuth2UserRequest); //Then Map newAttributes = Map.of( diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java index b08430e..60b435d 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java @@ -1,6 +1,8 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security; import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService; + import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; @@ -14,22 +16,57 @@ import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; public class SecurityTestTools { + public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( + Role role + ) { + throw new UnsupportedOperationException(); + } + + public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( + Role role, + String id, + String login + ) { + throw new UnsupportedOperationException(); + } - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser(@Nullable Role role) { - return buildUser(role, "TestID", null, "TestLogin"); + public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( + Role role, + String id, + String userDbId, + String login + ) { + throw new UnsupportedOperationException(); } - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser(@Nullable Role role, @NonNull String id, @NonNull String login) { - return buildUser(role, id, null, login); + public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( + @Nullable Role role, + @NonNull UserAttributesService.Registration registration + ) { + return buildUser(role, "TestID", registration, "TestLogin"); } - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser(@Nullable Role role, @NonNull String id, @Nullable String userDbId, @NonNull String login) { + public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( + @Nullable Role role, + @NonNull String id, + @NonNull UserAttributesService.Registration registration, + @NonNull String login + ) { return oidcLogin() .authorities(new SimpleGrantedAuthority(role == null ? "DummyAuthority" : role.getLong())) .userInfoToken(token -> { - token.claim("id", id); - token.claim("login", login); - if (userDbId!=null) token.claim("UserDbId", userDbId); + switch (registration) { + case GITHUB: + token.claim("id", id); + token.claim("login", login); + break; + case GOOGLE: + token.claim("original_Id", id); + token.claim("email", login); + break; + } + token.claim(UserAttributesService.ATTR_USER_DB_ID, registration.id + id); + token.claim(UserAttributesService.ATTR_REGISTRATION_ID, registration.id); }); } diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserManagementIntegrationTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserManagementIntegrationTest.java index bb76413..e9e2cb3 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserManagementIntegrationTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/UserManagementIntegrationTest.java @@ -1,8 +1,15 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; -import net.schwarzbaer.spring.promptoptimizer.backend.security.repositories.StoredUserInfoRepository; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -20,13 +27,10 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.junit.jupiter.api.Assertions.*; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.StoredUserInfo; +import net.schwarzbaer.spring.promptoptimizer.backend.security.repositories.StoredUserInfoRepository; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; @SpringBootTest @AutoConfigureMockMvc @@ -49,7 +53,7 @@ static void setUrlDynamically(DynamicPropertyRegistry reg) { private static String buildCurrentUserResponse( boolean isAuthenticated, boolean isUser, boolean isAdmin, - @NonNull String id, @Nullable String login + @NonNull String id, @Nullable String userDbId, @Nullable String login ) { return """ { @@ -57,7 +61,7 @@ private static String buildCurrentUserResponse( "isUser" : %s, "isAdmin" : %s, "id" : "%s", - "userDbId" : null, + "userDbId" : %s, "login" : %s, "name" : null, "location" : null, @@ -70,7 +74,8 @@ private static String buildCurrentUserResponse( isUser, isAdmin, id, - login==null ? null : "\"%s\"".formatted( login ) + userDbId==null ? null : "\"%s\"".formatted( userDbId ), + login ==null ? null : "\"%s\"".formatted( login ) ); } @@ -100,39 +105,52 @@ private void whenGetCurrentUser_isCalledWithUnauthenticatedUser(@NonNull String .andExpect(status().isOk()) .andExpect(content().json(buildCurrentUserResponse( false, false, false, - id, null) + id, null, null) )) ; } @Test void whenGetCurrentUser_isCalledWithAuthenticatedUser() throws Exception { - whenGetCurrentUser_isCalledWithAuthenticatedUser(null, false, false); + whenGetCurrentUser_isCalledWithAuthenticatedUser(null, false, false, Registration.GITHUB); + } + @Test void whenGetCurrentUser_isCalledWithUnknownGithubAccount() throws Exception { + whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.UNKNOWN_ACCOUNT, false, false, Registration.GITHUB); } - @Test void whenGetCurrentUser_isCalledWithUnknownAccount() throws Exception { - whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.UNKNOWN_ACCOUNT, false, false); + @Test void whenGetCurrentUser_isCalledWithUnknownGoogleAccount() throws Exception { + whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.UNKNOWN_ACCOUNT, false, false, Registration.GOOGLE); } - @Test void whenGetCurrentUser_isCalledWithUSER() throws Exception { - whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.USER, true, false); + @Test void whenGetCurrentUser_isCalledWithGithubUSER() throws Exception { + whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.USER, true, false, Registration.GITHUB); } - @Test void whenGetCurrentUser_isCalledWithADMIN() throws Exception { - whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.ADMIN, false, true); + @Test void whenGetCurrentUser_isCalledWithGoogleUSER() throws Exception { + whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.USER, true, false, Registration.GOOGLE); } - private void whenGetCurrentUser_isCalledWithAuthenticatedUser(Role role, boolean isUser, boolean isAdmin) throws Exception { + @Test void whenGetCurrentUser_isCalledWithGithubADMIN() throws Exception { + whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.ADMIN, false, true, Registration.GITHUB); + } + @Test void whenGetCurrentUser_isCalledWithGoogleADMIN() throws Exception { + whenGetCurrentUser_isCalledWithAuthenticatedUser(Role.ADMIN, false, true, Registration.GOOGLE); + } + private void whenGetCurrentUser_isCalledWithAuthenticatedUser( + @Nullable Role role, + boolean isUser, boolean isAdmin, + @NonNull Registration registration + ) throws Exception { // Given // When mockMvc .perform( MockMvcRequestBuilders .get("/api/users/me") - .with(SecurityTestTools.buildUser(role, "TestID", "TestLogin")) + .with(SecurityTestTools.buildUser(role, "TestID", registration, "TestLogin")) ) // Then .andExpect(status().isOk()) .andExpect(content().json(buildCurrentUserResponse( true, isUser, isAdmin, - "TestID", "TestLogin") - )) + "TestID", registration.id + "TestID", "TestLogin" + ))) ; } @@ -142,8 +160,14 @@ private void whenGetCurrentUser_isCalledWithAuthenticatedUser(Role role, boolean @NonNull private static StoredUserInfo createStoredUserInfo(Role role, String originalId, int index) { + return createStoredUserInfo(role, originalId, null, index); + } + + @NonNull + private static StoredUserInfo createStoredUserInfo(Role role, String originalId, Registration registration, int index) { + String registrationId = registration==null ? "registrationId" : registration.id; return new StoredUserInfo( - "registrationId" + originalId, role, "registrationId", originalId, + registrationId + originalId, role, registrationId, originalId, "login" + index, "name" + index, "location" + index, "url" + index, "avatarUrl" + index, "reason" + index ); @@ -170,28 +194,39 @@ private void addValue(List valueStrs, Object value, String valueName, St } private void fillStoredUserInfoRepository() { - storedUserInfoRepository.save(createStoredUserInfo(Role.ADMIN , "userId1", 1)); - storedUserInfoRepository.save(createStoredUserInfo(Role.USER , "userId2", 2)); - storedUserInfoRepository.save(createStoredUserInfo(Role.UNKNOWN_ACCOUNT, "userId3", 3)); + fillStoredUserInfoRepository(null); + } + private void fillStoredUserInfoRepository(Registration registration) { + storedUserInfoRepository.save(createStoredUserInfo(Role.ADMIN , "userId1", registration, 1)); + storedUserInfoRepository.save(createStoredUserInfo(Role.USER , "userId2", registration, 2)); + storedUserInfoRepository.save(createStoredUserInfo(Role.UNKNOWN_ACCOUNT, "userId3", registration, 3)); } private void assertStoredUserInfoRepositoryUnchanged() { - asserEntryEquals("registrationIduserId1", createStoredUserInfo(Role.ADMIN , "userId1", 1)); - asserEntryEquals("registrationIduserId2", createStoredUserInfo(Role.USER , "userId2", 2)); - asserEntryEquals("registrationIduserId3", createStoredUserInfo(Role.UNKNOWN_ACCOUNT, "userId3", 3)); + assertStoredUserInfoRepositoryUnchanged(null); + } + private void assertStoredUserInfoRepositoryUnchanged(Registration registration) { + String registrationId = registration==null ? "registrationId" : registration.id; + asserEntryEquals(registrationId+"userId1", createStoredUserInfo(Role.ADMIN , "userId1", registration, 1)); + asserEntryEquals(registrationId+"userId2", createStoredUserInfo(Role.USER , "userId2", registration, 2)); + asserEntryEquals(registrationId+"userId3", createStoredUserInfo(Role.UNKNOWN_ACCOUNT, "userId3", registration, 3)); } private void assertStoredUserInfoRepositoryHasOneChange(String originalId, StoredUserInfo expected) { - asserEntryEquals("registrationIduserId1", "userId1".equals(originalId) ? expected : createStoredUserInfo(Role.ADMIN , "userId1", 1)); - asserEntryEquals("registrationIduserId2", "userId2".equals(originalId) ? expected : createStoredUserInfo(Role.USER , "userId2", 2)); - asserEntryEquals("registrationIduserId3", "userId3".equals(originalId) ? expected : createStoredUserInfo(Role.UNKNOWN_ACCOUNT, "userId3", 3)); + assertStoredUserInfoRepositoryHasOneChange(originalId, expected, null); + } + private void assertStoredUserInfoRepositoryHasOneChange(String originalId, StoredUserInfo expected, Registration registration) { + String registrationId = registration==null ? "registrationId" : registration.id; + asserEntryEquals(registrationId+"userId1", "userId1".equals(originalId) ? expected : createStoredUserInfo(Role.ADMIN , "userId1", registration, 1)); + asserEntryEquals(registrationId+"userId2", "userId2".equals(originalId) ? expected : createStoredUserInfo(Role.USER , "userId2", registration, 2)); + asserEntryEquals(registrationId+"userId3", "userId3".equals(originalId) ? expected : createStoredUserInfo(Role.UNKNOWN_ACCOUNT, "userId3", registration, 3)); } private void asserEntryEquals(String id, StoredUserInfo expected) { Optional actualOpt = storedUserInfoRepository.findById(id); assertNotNull(actualOpt); if (expected==null) - assertTrue(actualOpt.isEmpty()); + assertTrue(actualOpt.isEmpty(), "Entry["+id+"] is empty"); else { - assertTrue(actualOpt.isPresent()); + assertTrue(actualOpt.isPresent(), "Entry["+id+"] is present"); assertEquals(expected, actualOpt.get()); } } @@ -263,7 +298,7 @@ void whenGetAllStoredUsers_isCalledByAdmin_returnsList() throws Exception { mockMvc .perform(MockMvcRequestBuilders .get("/api/users") - .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", Registration.GOOGLE, "login")) ) // Then @@ -289,7 +324,7 @@ private void whenGetAllStoredUsers_isCalledByNotAllowedUser_returns403Forbidden( // Given performRequest_return403Forbidden(MockMvcRequestBuilders .get("/api/users") - .with(SecurityTestTools.buildUser(role, userId, "registrationId" + userId, "login"))); + .with(SecurityTestTools.buildUser(role, userId, Registration.GOOGLE, "login"))); } @Test @DirtiesContext @@ -312,7 +347,7 @@ void whenUpdateStoredUser_isCalledNormal_returnsUpdatedData() throws Exception { mockMvc .perform(MockMvcRequestBuilders .put("/api/users/%s".formatted("registrationIduserId2")) - .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", Registration.GITHUB, "login")) .contentType(MediaType.APPLICATION_JSON) .content( buildResponse(createStoredUserInfo(Role.USER, "userId2", 7)) @@ -334,7 +369,7 @@ void whenUpdateStoredUser_isCalledNormal_returnsUpdatedData() throws Exception { void whenUpdateStoredUser_isCalledWithUnknownId_returns404NotFound() throws Exception { MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders .put("/api/users/%s".formatted("registrationIduserId7")) - .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", Registration.GOOGLE, "login")) .contentType(MediaType.APPLICATION_JSON) .content( buildResponse(createStoredUserInfo(Role.USER, "userId7", 7)) @@ -353,7 +388,7 @@ void whenUpdateStoredUser_isCalledByUSER_returns403Forbidden() throws Exception private void whenUpdateStoredUser_isCalledByNotAllowedUser_returns403Forbidden(Role role) throws Exception { performRequest_return403Forbidden( MockMvcRequestBuilders .put("/api/users/%s".formatted("registrationIduserId2")) - .with(SecurityTestTools.buildUser(role, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(role, "userId1", Registration.GOOGLE, "login")) .contentType(MediaType.APPLICATION_JSON) .content( buildResponse(createStoredUserInfo(Role.USER, "userId2", 7)) @@ -383,7 +418,7 @@ void whenUpdateStoredUser_isCalledWithDifferentIDs_returns400BadRequest() throws private void whenUpdateStoredUser_isCalledWithWrongIDs_returns400BadRequest(String idInPath, String idInData) throws Exception { performRequest_return400BadRequest( MockMvcRequestBuilders .put("/api/users/%s".formatted("registrationId" + idInPath)) - .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", Registration.GOOGLE, "login")) .contentType(MediaType.APPLICATION_JSON) .content( buildResponse(createStoredUserInfo(Role.USER, idInData, 7)) @@ -404,7 +439,7 @@ void whenDeleteStoredUser_isCalledNormal() throws Exception { mockMvc .perform(MockMvcRequestBuilders .delete("/api/users/%s".formatted("registrationIduserId3")) - .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", Registration.GOOGLE, "login")) ) // Then @@ -418,7 +453,7 @@ void whenDeleteStoredUser_isCalledNormal() throws Exception { void whenDeleteStoredUser_isCalledWithUnknownId_returns404NotFound() throws Exception { performRequest_return404NotFound( MockMvcRequestBuilders .delete("/api/users/%s".formatted("registrationIduserId7")) - .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", "registrationIduserId1", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "userId1", Registration.GOOGLE, "login")) ); } @@ -433,7 +468,7 @@ void whenDeleteStoredUser_isCalledByUnknownAccount_returns403Forbidden() throws private void whenDeleteStoredUser_isCalledByNotAllowedUser_returns403Forbidden(Role role, @NonNull String userId) throws Exception { performRequest_return403Forbidden( MockMvcRequestBuilders .delete("/api/users/%s".formatted("registrationIduserId2")) - .with(SecurityTestTools.buildUser(role, userId, "registrationId" + userId, "login")) + .with(SecurityTestTools.buildUser(role, userId, Registration.GOOGLE, "login")) ); } @@ -450,36 +485,37 @@ void whenDeleteStoredUser_isCalledByUnauthenticated_returns401Unauthorized() thr @Test @DirtiesContext void whenGetDenialReasonForCurrentUser_isCalledByADMIN_returnsString() throws Exception { - whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.ADMIN, "userId1", "reason1"); + whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.ADMIN, "userId1", "reason1", Registration.GOOGLE); } @Test @DirtiesContext void whenGetDenialReasonForCurrentUser_isCalledUSER_returnsString() throws Exception { - whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.USER, "userId2", "reason2"); + whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.USER, "userId2", "reason2", Registration.GITHUB); } @Test @DirtiesContext void whenGetDenialReasonForCurrentUser_isCalledUnknownAccount_returnsString() throws Exception { - whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.UNKNOWN_ACCOUNT, "userId3", "reason3"); + whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.UNKNOWN_ACCOUNT, "userId3", "reason3", Registration.GOOGLE); } @Test @DirtiesContext void whenGetDenialReasonForCurrentUser_isCalledWithUnknownUserId_returnsEmptyString() throws Exception { // --> no reason --> "Please wait, until ..." - whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.UNKNOWN_ACCOUNT, "userId4", ""); + whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role.UNKNOWN_ACCOUNT, "userId4", "", Registration.GOOGLE); } - private void whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role role, @NonNull String originalId, @NonNull String expectedReason) throws Exception { + private void whenGetDenialReasonForCurrentUser_isCalledNormal_returnsString(Role role, @NonNull String originalId, @NonNull String expectedReason, @NonNull Registration registration) + throws Exception { // Given - fillStoredUserInfoRepository(); + fillStoredUserInfoRepository(registration); // When mockMvc .perform(MockMvcRequestBuilders .get("/api/users/reason") - .with(SecurityTestTools.buildUser(role, originalId, "registrationId"+ originalId, "login")) + .with(SecurityTestTools.buildUser(role, originalId, registration, "login")) ) // Then .andExpect(status().isOk()) .andExpect(content().string(expectedReason)); - assertStoredUserInfoRepositoryUnchanged(); + assertStoredUserInfoRepositoryUnchanged(registration); } @Test @DirtiesContext From 79dc724a7893a10cd987090ab2bfb635484753b5 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:03:04 +0100 Subject: [PATCH 11/19] tests: updated ReactRoutingForwardingTest --- .../backend/ReactRoutingForwardingTest.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/ReactRoutingForwardingTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/ReactRoutingForwardingTest.java index 9be5a72..c5fe0e7 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/ReactRoutingForwardingTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/ReactRoutingForwardingTest.java @@ -1,7 +1,10 @@ package net.schwarzbaer.spring.promptoptimizer.backend; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.FileNotFoundException; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -15,10 +18,9 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.io.FileNotFoundException; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; @SpringBootTest @AutoConfigureMockMvc @@ -58,7 +60,7 @@ private void whenUrlIsCalled(String url, String testLabel) throws Exception { resultActions = mockMvc .perform(MockMvcRequestBuilders .get(url) - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "id", Registration.GITHUB, "login")) ); } catch (FileNotFoundException e) { assertTrue(true); From 430c1e19e12fb00bc28343f5388bb8cfa0446e23 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:13:51 +0100 Subject: [PATCH 12/19] tests: updated all basic ChatGpt tests --- .../ChatGptDisabledApiIntegrationTest.java | 20 ++++++++-------- .../chatgpt/ChatGptIntegrationTest.java | 20 ++++++++-------- .../backend/chatgpt/ChatGptTestTools.java | 13 +++++++---- .../backend/security/SecurityTestTools.java | 23 ++++--------------- 4 files changed, 35 insertions(+), 41 deletions(-) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptDisabledApiIntegrationTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptDisabledApiIntegrationTest.java index 2d2a722..5d05d7e 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptDisabledApiIntegrationTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptDisabledApiIntegrationTest.java @@ -1,8 +1,10 @@ package net.schwarzbaer.spring.promptoptimizer.backend.chatgpt; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; -import okhttp3.mockwebserver.MockWebServer; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -19,10 +21,10 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.io.IOException; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; +import okhttp3.mockwebserver.MockWebServer; @SpringBootTest @AutoConfigureMockMvc @@ -73,7 +75,7 @@ void whenAskChatGPT_withDisabledAPI_withUnknownAccount_returnsStatus403() throws // When mockMvc - .perform(ChatGptTestTools.buildAskRequest("TestPrompt", Role.UNKNOWN_ACCOUNT)) + .perform(ChatGptTestTools.buildAskRequest("TestPrompt", Role.UNKNOWN_ACCOUNT, Registration.GOOGLE)) // Then .andExpect(status().is(HttpStatus.FORBIDDEN.value())) @@ -87,7 +89,7 @@ void whenAskChatGPT_withDisabledAPI_withAllowedUser_returnsAnGeneratedAnswer(Rol // When mockMvc - .perform(ChatGptTestTools.buildAskRequest("TestPrompt", role)) + .perform(ChatGptTestTools.buildAskRequest("TestPrompt", role, Registration.GOOGLE)) // Then .andExpect(status().isOk()) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptIntegrationTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptIntegrationTest.java index 6ce956a..77fd968 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptIntegrationTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptIntegrationTest.java @@ -1,8 +1,10 @@ package net.schwarzbaer.spring.promptoptimizer.backend.chatgpt; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; -import okhttp3.mockwebserver.MockWebServer; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -19,10 +21,10 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.io.IOException; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; +import okhttp3.mockwebserver.MockWebServer; @SpringBootTest @AutoConfigureMockMvc @@ -73,7 +75,7 @@ void whenAskChatGPT_withUnknownAccount_returnsStatus403() throws Exception { // When mockMvc - .perform(ChatGptTestTools.buildAskRequest("TestPrompt", Role.UNKNOWN_ACCOUNT)) + .perform(ChatGptTestTools.buildAskRequest("TestPrompt", Role.UNKNOWN_ACCOUNT, Registration.GOOGLE)) // Then .andExpect(status().is(HttpStatus.FORBIDDEN.value())) @@ -90,7 +92,7 @@ void whenAskChatGPT_withAllowedUser_returnsAnswer(Role role) throws Exception { // When mockMvc - .perform(ChatGptTestTools.buildAskRequest("TestPrompt", role)) + .perform(ChatGptTestTools.buildAskRequest("TestPrompt", role, Registration.GOOGLE)) // Then .andExpect(status().isOk()) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptTestTools.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptTestTools.java index 5ba43c3..83e2fb7 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptTestTools.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/chatgpt/ChatGptTestTools.java @@ -1,19 +1,22 @@ package net.schwarzbaer.spring.promptoptimizer.backend.chatgpt; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; -import okhttp3.mockwebserver.MockResponse; import org.springframework.http.MediaType; +import org.springframework.lang.NonNull; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; +import okhttp3.mockwebserver.MockResponse; + public class ChatGptTestTools { - public static MockHttpServletRequestBuilder buildAskRequest(String prompt, Role role) { + public static MockHttpServletRequestBuilder buildAskRequest(String prompt, Role role, @NonNull Registration registration) { return MockMvcRequestBuilders .post("/api/ask") - .with(SecurityTestTools.buildUser(role)) + .with(SecurityTestTools.buildUser(role, registration)) .contentType(MediaType.APPLICATION_JSON) .content(""" diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java index 60b435d..ae39ad4 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java @@ -1,7 +1,8 @@ package net.schwarzbaer.spring.promptoptimizer.backend.security; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; + +import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; @@ -11,24 +12,10 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; -import java.util.stream.Stream; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService; public class SecurityTestTools { - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( - Role role - ) { - throw new UnsupportedOperationException(); - } - - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( - Role role, - String id, - String login - ) { - throw new UnsupportedOperationException(); - } public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( Role role, From bf05c82f7b6237327840e083f608578b7de7b40d Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Thu, 30 Nov 2023 18:39:43 +0100 Subject: [PATCH 13/19] tests: updated ScenarioIntegrationTest --- .../prompttests/ScenarioIntegrationTest.java | 490 +++++++++--------- 1 file changed, 233 insertions(+), 257 deletions(-) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/ScenarioIntegrationTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/ScenarioIntegrationTest.java index e7814f0..e77827f 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/ScenarioIntegrationTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/ScenarioIntegrationTest.java @@ -1,11 +1,17 @@ package net.schwarzbaer.spring.promptoptimizer.backend.prompttests; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.Scenario; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.TestRun; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.ScenarioRepository; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.TestRunRepository; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -21,17 +27,16 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.Scenario; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.TestRun; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.ScenarioRepository; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.TestRunRepository; +import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; @SpringBootTest @AutoConfigureMockMvc @@ -54,11 +59,19 @@ static void setUrlDynamically(DynamicPropertyRegistry reg) { reg.add("app.openai-api-url", ()->"dummy_url"); } - private void fillScenarioRepository() { - scenarioRepository.save(new Scenario("id1", "author1", "label1", 1)); - scenarioRepository.save(new Scenario("id2", "author2", "label2", 1)); - scenarioRepository.save(new Scenario("id3", "author2", "label3", 1)); - scenarioRepository.save(new Scenario("id4", "author2", "label4", 1)); + private void fillScenarioRepository () { + fillScenarioRepository(null); + } + private void fillScenarioRepository(Registration reg2) { + fillScenarioRepository("user1", null, "user2", reg2); + } + private void fillScenarioRepository(String user1, Registration reg1, String user2, Registration reg2) { + String regId1 = reg1==null ? "registrationId1" : reg1.id; + String regId2 = reg2==null ? "registrationId2" : reg2.id; + scenarioRepository.save(new Scenario("id1", regId1 + user1, "label1", 1)); + scenarioRepository.save(new Scenario("id2", regId2 + user2, "label2", 1)); + scenarioRepository.save(new Scenario("id3", regId2 + user2, "label3", 1)); + scenarioRepository.save(new Scenario("id4", regId2 + user2, "label4", 1)); } @NonNull @@ -73,43 +86,61 @@ private static TestRun createTestRun(String testRunId, String scenarioId) { ); } -// #################################################################################### -// getAllScenariosOfUser -// #################################################################################### - - @Test - @DirtiesContext - void whenGetAllScenariosOfUser_isCalledByUnauthorized_returnsStatus401Unauthorized() throws Exception { + private void performRequest_andGetStatusWithEmptyResponse( + MockHttpServletRequestBuilder request, + HttpStatus httpStatus + ) throws Exception { // Given fillScenarioRepository(); // When mockMvc - .perform(MockMvcRequestBuilders - .get("/api/scenario") - ) + .perform(request) // Then - .andExpect(status().is(HttpStatus.UNAUTHORIZED.value())) + .andExpect(status().is(httpStatus.value())) .andExpect(content().string("")); } - @Test - @DirtiesContext - void whenGetAllScenariosOfUser_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { + private void saveExampleScenario_performRequest_andGetStatus( + String storedScenarioId, String storedAuthorID, + MockHttpServletRequestBuilder request, + HttpStatus status + ) throws Exception { // Given - fillScenarioRepository(); + scenarioRepository.save(new Scenario(storedScenarioId, storedAuthorID, "label1", 1)); // When mockMvc - .perform(MockMvcRequestBuilders - .get("/api/scenario") - .with(SecurityTestTools.buildUser(Role.UNKNOWN_ACCOUNT, "id", "author2", "login")) - ) + .perform(request) // Then - .andExpect(status().is(HttpStatus.FORBIDDEN.value())) - .andExpect(content().string("")); + .andExpect(status().is(status.value())); + } + +// #################################################################################### +// getAllScenariosOfUser +// #################################################################################### + + @Test + @DirtiesContext + void whenGetAllScenariosOfUser_isCalledByUnauthorized_returnsStatus401Unauthorized() throws Exception { + performRequest_andGetStatusWithEmptyResponse( + MockMvcRequestBuilders + .get("/api/scenario"), + HttpStatus.UNAUTHORIZED + ); + } + + @Test + @DirtiesContext + void whenGetAllScenariosOfUser_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { + performRequest_andGetStatusWithEmptyResponse( + MockMvcRequestBuilders + .get("/api/scenario") + .with(SecurityTestTools.buildUser(Role.UNKNOWN_ACCOUNT, "user2", Registration.GITHUB, "login")), + HttpStatus.FORBIDDEN + ); } @ParameterizedTest @@ -117,24 +148,24 @@ void whenGetAllScenariosOfUser_isCalledByUnknownAccount_returnsStatus403Forbidde @ArgumentsSource(SecurityTestTools.UserAndAdminRoles.class) void whenGetAllScenariosOfUser_isCalledByAllowedUser_returnsList(Role role) throws Exception { // Given - fillScenarioRepository(); + fillScenarioRepository(Registration.GITHUB); // When mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario") - .with(SecurityTestTools.buildUser(role, "id", "author2", "login")) + .with(SecurityTestTools.buildUser(role, "user2", Registration.GITHUB, "login")) ) // Then .andExpect(status().isOk()) .andExpect(content().json(""" [ - { "id": "id2", "authorID": "author2", "label": "label2", "maxWantedWordCount": 1 }, - { "id": "id3", "authorID": "author2", "label": "label3", "maxWantedWordCount": 1 }, - { "id": "id4", "authorID": "author2", "label": "label4", "maxWantedWordCount": 1 } + { "id": "id2", "authorID": "%1$s", "label": "label2", "maxWantedWordCount": 1 }, + { "id": "id3", "authorID": "%1$s", "label": "label3", "maxWantedWordCount": 1 }, + { "id": "id4", "authorID": "%1$s", "label": "label4", "maxWantedWordCount": 1 } ] - """)); + """.formatted( Registration.GITHUB.id + "user2" ))); } // #################################################################################### @@ -144,60 +175,50 @@ void whenGetAllScenariosOfUser_isCalledByAllowedUser_returnsList(Role role) thro @Test @DirtiesContext void whenGetAllScenarios_isCalledByUnauthorized_returnsStatus401Unauthorized() throws Exception { - // Given - fillScenarioRepository(); - - // When - mockMvc - .perform(MockMvcRequestBuilders.get("/api/scenario/all")) - - // Then - .andExpect(status().is(HttpStatus.UNAUTHORIZED.value())) - .andExpect(content().string("")); + performRequest_andGetStatusWithEmptyResponse( + MockMvcRequestBuilders + .get("/api/scenario/all"), + HttpStatus.UNAUTHORIZED + ); } @ParameterizedTest @DirtiesContext @ArgumentsSource(SecurityTestTools.NotAdminRoles.class) void whenGetAllScenarios_isCalledByNotAllowedRole_returnsStatus403Forbidden(Role role) throws Exception { - // Given - fillScenarioRepository(); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .get("/api/scenario/all") - .with(SecurityTestTools.buildUser(role, "id", "dbId", "login")) - ) - - // Then - .andExpect(status().is(HttpStatus.FORBIDDEN.value())) - .andExpect(content().string("")); + performRequest_andGetStatusWithEmptyResponse( + MockMvcRequestBuilders + .get("/api/scenario/all") + .with(SecurityTestTools.buildUser(role, "user2", Registration.GOOGLE, "login")), + HttpStatus.FORBIDDEN + ); } @Test @DirtiesContext void whenGetAllScenarios_isCalledByAdmin_returnsList() throws Exception { // Given - fillScenarioRepository(); + Registration reg1 = Registration.GITHUB; + Registration reg2 = Registration.GOOGLE; + fillScenarioRepository("user1", reg1, "user2", reg2); // When mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/all") - .with(SecurityTestTools.buildUser(Role.ADMIN, "id", "dbId", "login")) + .with(SecurityTestTools.buildUser(Role.ADMIN, "user2", reg2, "login")) ) // Then .andExpect(status().isOk()) .andExpect(content().json(""" [ - { "id": "id1", "authorID": "author1", "label": "label1", "maxWantedWordCount": 1 }, - { "id": "id2", "authorID": "author2", "label": "label2", "maxWantedWordCount": 1 }, - { "id": "id3", "authorID": "author2", "label": "label3", "maxWantedWordCount": 1 }, - { "id": "id4", "authorID": "author2", "label": "label4", "maxWantedWordCount": 1 } + { "id": "id1", "authorID": "%1$s", "label": "label1", "maxWantedWordCount": 1 }, + { "id": "id2", "authorID": "%2$s", "label": "label2", "maxWantedWordCount": 1 }, + { "id": "id3", "authorID": "%2$s", "label": "label3", "maxWantedWordCount": 1 }, + { "id": "id4", "authorID": "%2$s", "label": "label4", "maxWantedWordCount": 1 } ] - """)); + """.formatted(reg1.id + "user1", reg2.id + "user2"))); } // #################################################################################### @@ -207,42 +228,30 @@ void whenGetAllScenarios_isCalledByAdmin_returnsList() throws Exception { @Test @DirtiesContext void whenAddScenarios_isCalledByUnauthorized_returnsStatus401Unauthorized() throws Exception { - // Given - - // When - mockMvc - .perform(MockMvcRequestBuilders - .post("/api/scenario") - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { "label": "labelXY" } - """) - ) - - // Then - .andExpect(status().is(HttpStatus.UNAUTHORIZED.value())) - .andExpect(content().string("")); + performRequest_andGetStatusWithEmptyResponse( + MockMvcRequestBuilders + .post("/api/scenario") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { "label": "labelXY" } + """), + HttpStatus.UNAUTHORIZED + ); } @Test @DirtiesContext void whenAddScenarios_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { - // Given - - // When - mockMvc - .perform(MockMvcRequestBuilders - .post("/api/scenario") - .with(SecurityTestTools.buildUser(Role.UNKNOWN_ACCOUNT, "id", "userXY", "login")) - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { "label": "labelXY" } - """) - ) - - // Then - .andExpect(status().is(HttpStatus.FORBIDDEN.value())) - .andExpect(content().string("")); + performRequest_andGetStatusWithEmptyResponse( + MockMvcRequestBuilders + .post("/api/scenario") + .with(SecurityTestTools.buildUser(Role.UNKNOWN_ACCOUNT, "userId", Registration.GOOGLE, "login")) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { "label": "labelXY" } + """), + HttpStatus.FORBIDDEN + ); } @ParameterizedTest @@ -255,7 +264,7 @@ void whenAddScenarios_isCalledByAllowedUser_returnsList(Role role) throws Except mockMvc .perform(MockMvcRequestBuilders .post("/api/scenario") - .with(SecurityTestTools.buildUser(role, "id", "userXY", "login")) + .with(SecurityTestTools.buildUser(role, "userId", Registration.GITHUB, "login")) .contentType(MediaType.APPLICATION_JSON) .content(""" { "label": "labelXY" } @@ -265,8 +274,8 @@ void whenAddScenarios_isCalledByAllowedUser_returnsList(Role role) throws Except // Then .andExpect(status().isOk()) .andExpect(content().json(""" - { "authorID": "userXY", "label": "labelXY" } - """)); + { "authorID": "%s", "label": "labelXY" } + """.formatted(Registration.GITHUB.id + "userId"))); } // #################################################################################### @@ -276,115 +285,114 @@ void whenAddScenarios_isCalledByAllowedUser_returnsList(Role role) throws Except @Test @DirtiesContext void whenUpdateScenario_getsPathIdDifferentToScenarioID_returnsStatus400BadRequest() throws Exception { whenUpdateScenario_getsWrongArguments_returnsStatus400BadRequest( - "id2","{ \"id\": \"id1\", \"authorID\": \"author1\", \"label\": \"labelNew\" }" + "id2", + "{ \"id\": \"id1\", \"authorID\": \"%s\", \"label\": \"labelNew\" }".formatted(Registration.GITHUB.id + "userId1"), + "userId1", Registration.GITHUB ); } - @Test @DirtiesContext void whenUpdateScenario_getsScenarioWithNoId_returnsStatus400BadRequest() throws Exception { whenUpdateScenario_getsWrongArguments_returnsStatus400BadRequest( - "id1","{ \"authorID\": \"author1\", \"label\": \"labelNew\" }" + "id1", + "{ \"authorID\": \"%s\", \"label\": \"labelNew\" }".formatted(Registration.GITHUB.id + "userId1"), + "userId1", Registration.GITHUB ); } - @Test @DirtiesContext void whenUpdateScenario_getsScenarioWithNoAuthorId_returnsStatus400BadRequest() throws Exception { whenUpdateScenario_getsWrongArguments_returnsStatus400BadRequest( - "id1","{ \"id\": \"id1\", \"label\": \"labelNew\" }" + "id1", + "{ \"id\": \"id1\", \"label\": \"labelNew\" }", + "userId1", Registration.GITHUB ); } - private void whenUpdateScenario_getsWrongArguments_returnsStatus400BadRequest( - String pathId, String requestBody + String pathId, String requestBody, @NonNull String userId, @NonNull Registration registration ) throws Exception { // Given + fillScenarioRepository(); // When mockMvc .perform(MockMvcRequestBuilders .put("/api/scenario/%s".formatted(pathId)) - .with(SecurityTestTools.buildUser(Role.USER, "userId1", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(requestBody) ) // Then - .andExpect(status().is(HttpStatus.BAD_REQUEST.value())); + .andExpect(status().isBadRequest()); } @Test @DirtiesContext void whenUpdateScenario_getsScenarioWithUnknownId_returnsStatus404NotFound() throws Exception { // Given - scenarioRepository.save(new Scenario("id2", "author1", "label1", 1)); + String userId = "userId1"; + Registration reg = Registration.GOOGLE; + String authorID = reg.id + userId; + + scenarioRepository.save(new Scenario("id2", authorID, "label1", 1)); // When mockMvc .perform(MockMvcRequestBuilders .put("/api/scenario/%s".formatted("id1")) - .with(SecurityTestTools.buildUser(Role.USER, "userId1", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, userId, reg, "login")) .contentType(MediaType.APPLICATION_JSON) - .content("{ \"id\": \"id1\", \"authorID\": \"author1\", \"label\": \"labelNew\" }") + .content("{ \"id\": \"id1\", \"authorID\": \"%s\", \"label\": \"labelNew\" }".formatted(authorID)) ) // Then - .andExpect(status().is(HttpStatus.NOT_FOUND.value())); + .andExpect(status().isNotFound()); } @Test @DirtiesContext void whenUpdateScenario_isCalledByUnauthorized_returnsStatus401Unauthorized() throws Exception { - // Given - scenarioRepository.save(new Scenario("id1", "author1", "labelOld", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .put("/api/scenario/%s".formatted("id1")) - .contentType(MediaType.APPLICATION_JSON) - .content(""" - { "id": "id1", "authorID": "%s", "label": "labelNew" } - """.formatted("author1")) - ) - - // Then - .andExpect(status().is(HttpStatus.UNAUTHORIZED.value())); + saveExampleScenario_performRequest_andGetStatus( + "id1", "author1", + MockMvcRequestBuilders + .put("/api/scenario/%s".formatted("id1")) + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { "id": "id1", "authorID": "%s", "label": "labelNew" } + """.formatted("author1")), + HttpStatus.UNAUTHORIZED + ); } @Test @DirtiesContext void whenUpdateScenario_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { whenUpdateScenario_isCalled_returnsStatus403Forbidden( - Role.UNKNOWN_ACCOUNT, "author1", "author1", "author1" - ); - } - @Test @DirtiesContext - void whenUpdateScenario_isCalledByNonAdmin_withNoDbId_returnsStatus403Forbidden() throws Exception { - whenUpdateScenario_isCalled_returnsStatus403Forbidden( - Role.USER, null, "author1", "author1" + Role.UNKNOWN_ACCOUNT, "userId1", "userId1", "userId1", Registration.GOOGLE ); } @Test @DirtiesContext - void whenUpdateScenario_isCalledByNonAdmin_withDbIdDifferentToGivenScenario_returnsStatus403Forbidden() throws Exception { + void whenUpdateScenario_isCalledByUser_withDbIdDifferentToGivenScenario_returnsStatus403Forbidden() throws Exception { whenUpdateScenario_isCalled_returnsStatus403Forbidden( - Role.USER, "author1", "author1", "author2" + Role.USER, "userId1", "userId2", "userId1", Registration.GOOGLE ); } @Test @DirtiesContext - void whenUpdateScenario_isCalledByNonAdmin_withDbIdDifferentToStoredScenario_returnsStatus403Forbidden() throws Exception { + void whenUpdateScenario_isCalledByUser_withDbIdDifferentToStoredScenario_returnsStatus403Forbidden() throws Exception { whenUpdateScenario_isCalled_returnsStatus403Forbidden( - Role.USER,"author1", "author2", "author1" + Role.USER, "userId2", "userId1", "userId1", Registration.GOOGLE ); } private void whenUpdateScenario_isCalled_returnsStatus403Forbidden( - Role role, String userDbId, @NonNull String authorOfStored, @NonNull String authorOfGiven + @NonNull Role role, @NonNull String userIdOfStored, @NonNull String userIdOfGiven, @NonNull String userId, @NonNull Registration registration ) throws Exception { // Given + String authorOfStored = registration.id + userIdOfStored; + String authorOfGiven = registration.id + userIdOfGiven; scenarioRepository.save(new Scenario("id1", authorOfStored, "labelOld", 1)); // When mockMvc .perform(MockMvcRequestBuilders .put("/api/scenario/%s".formatted("id1")) - .with(SecurityTestTools.buildUser(role, "userId1", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(""" { "id": "id1", "authorID": "%s", "label": "labelNew", "maxWantedWordCount": 1 } @@ -392,44 +400,42 @@ private void whenUpdateScenario_isCalled_returnsStatus403Forbidden( ) // Then - .andExpect(status().is(HttpStatus.FORBIDDEN.value())); + .andExpect(status().isForbidden()); } - @Test @DirtiesContext - void whenUpdateScenario_isCalledByAdmin_returnsUpdatedValue() throws Exception { - whenUpdateScenario_isCalledByAllowedUser_returnsUpdatedValue(Role.ADMIN, "authorAdmin", "author2"); + @Test @DirtiesContext void whenUpdateScenario_isCalledByAdmin_returnsUpdatedValue() throws Exception { + whenUpdateScenario_isCalledByAllowedUser_returnsUpdatedValue(Role.ADMIN, Registration.GITHUB.id + "userId2", "userId1", Registration.GITHUB); } - @Test @DirtiesContext - void whenUpdateScenario_isCalledByUser_returnsUpdatedValue() throws Exception { - whenUpdateScenario_isCalledByAllowedUser_returnsUpdatedValue(Role.USER, "author1", "author1"); + @Test @DirtiesContext void whenUpdateScenario_isCalledByUser_returnsUpdatedValue() throws Exception { + whenUpdateScenario_isCalledByAllowedUser_returnsUpdatedValue(Role.USER, Registration.GITHUB.id + "userId1", "userId1", Registration.GITHUB); } private void whenUpdateScenario_isCalledByAllowedUser_returnsUpdatedValue( - Role role, String userDbId, @NonNull String storedAuthorId + @NonNull Role role, @NonNull String storedAuthorID, @NonNull String userId, @NonNull Registration registration ) throws Exception { // Given - scenarioRepository.save(new Scenario("id1", storedAuthorId, "labelOld", 1)); + scenarioRepository.save(new Scenario("id1", storedAuthorID, "labelOld", 1)); // When mockMvc .perform(MockMvcRequestBuilders .put("/api/scenario/%s".formatted("id1")) - .with(SecurityTestTools.buildUser(role, "userId1", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(""" { "id": "id1", "authorID": "%s", "label": "labelNew", "maxWantedWordCount": 1 } - """.formatted(storedAuthorId)) + """.formatted(storedAuthorID)) ) // Then .andExpect(status().isOk()) .andExpect(content().json(""" { "id": "id1", "authorID": "%s", "label": "labelNew", "maxWantedWordCount": 1 } - """.formatted(storedAuthorId))); + """.formatted(storedAuthorID))); Optional actual = scenarioRepository.findById("id1"); assertNotNull(actual); assertTrue(actual.isPresent()); - Scenario expected = new Scenario("id1", storedAuthorId, "labelNew", 1); + Scenario expected = new Scenario("id1", storedAuthorID, "labelNew", 1); assertEquals(expected, actual.get()); } @@ -438,13 +444,13 @@ private void whenUpdateScenario_isCalledByAllowedUser_returnsUpdatedValue( // #################################################################################### @Test @DirtiesContext void whenDeleteScenario_isCalledByAdmin() throws Exception { - whenDeleteScenario_isCalledByAllowedUser(Role.ADMIN, "authorAdmin", "author2"); + whenDeleteScenario_isCalledByAllowedUser(Role.ADMIN, Registration.GITHUB.id + "userId2", "userId1", Registration.GITHUB); } @Test @DirtiesContext void whenDeleteScenario_isCalledByUser() throws Exception { - whenDeleteScenario_isCalledByAllowedUser(Role.USER, "author1", "author1"); + whenDeleteScenario_isCalledByAllowedUser(Role.USER, Registration.GITHUB.id + "userId1", "userId1", Registration.GITHUB); } private void whenDeleteScenario_isCalledByAllowedUser( - Role role, String userDbId, @NonNull String storedAuthorID + @NonNull Role role, @NonNull String storedAuthorID, @NonNull String userId, @NonNull Registration registration ) throws Exception { // Given scenarioRepository.save(new Scenario("id1", storedAuthorID, "label1", 1)); @@ -458,7 +464,7 @@ private void whenDeleteScenario_isCalledByAllowedUser( mockMvc .perform(MockMvcRequestBuilders .delete("/api/scenario/id1") - .with(SecurityTestTools.buildUser(role, "userId1", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) ) // Then @@ -475,60 +481,45 @@ private void whenDeleteScenario_isCalledByAllowedUser( @Test @DirtiesContext void whenDeleteScenario_isCalledWithUnknownId_returnsStatus404Notfound() throws Exception { - // Given - scenarioRepository.save(new Scenario("id2", "author1", "label1", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .delete("/api/scenario/id1") - .with(SecurityTestTools.buildUser(Role.USER, "userId1", "author1", "login")) - ) - - // Then - .andExpect(status().isNotFound()); + saveExampleScenario_performRequest_andGetStatus( + "id2", "author1", + MockMvcRequestBuilders + .delete("/api/scenario/id1") + .with(SecurityTestTools + .buildUser(Role.USER, "userId1", Registration.GOOGLE, "login") + ), + HttpStatus.NOT_FOUND + ); } @Test @DirtiesContext void whenDeleteScenario_isCalledUnauthorized_returnStatus401Unauthorized() throws Exception { - // Given - scenarioRepository.save(new Scenario("id1", "author1", "label1", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .delete("/api/scenario/id1") - ) - - // Then - .andExpect(status().isUnauthorized()); + saveExampleScenario_performRequest_andGetStatus( + "id1", "author1", + MockMvcRequestBuilders + .delete("/api/scenario/id1"), + HttpStatus.UNAUTHORIZED + ); } @Test @DirtiesContext void whenDeleteScenario_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { - whenDeleteScenario_isCalled_returnsStatus403Forbidden(Role.UNKNOWN_ACCOUNT, "author1", "author1"); - } - @Test @DirtiesContext void whenDeleteScenario_isCalledByUserWithNoDbIDs_returnsStatus403Forbidden() throws Exception { - whenDeleteScenario_isCalled_returnsStatus403Forbidden(Role.USER, null, "author2"); + whenDeleteScenario_isCalled_returnsStatus403Forbidden(Role.UNKNOWN_ACCOUNT, Registration.GITHUB.id + "userId1", "userId1", Registration.GITHUB); } @Test @DirtiesContext void whenDeleteScenario_isCalledWithDifferentAuthorIDs_returnsStatus403Forbidden() throws Exception { - whenDeleteScenario_isCalled_returnsStatus403Forbidden(Role.USER, "author2", "author1"); + whenDeleteScenario_isCalled_returnsStatus403Forbidden(Role.USER, Registration.GITHUB.id + "userId2", "userId1", Registration.GITHUB); } - private void whenDeleteScenario_isCalled_returnsStatus403Forbidden( - Role role, String userDbId, @NonNull String storedAuthorID + Role role, @NonNull String storedAuthorID, @NonNull String userId, @NonNull Registration registration ) throws Exception { - // Given - scenarioRepository.save(new Scenario("id1", storedAuthorID, "label1", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .delete("/api/scenario/id1") - .with(SecurityTestTools.buildUser(role, "userId1", userDbId, "login")) - ) - - // Then - .andExpect(status().isForbidden()); + saveExampleScenario_performRequest_andGetStatus( + "id1", storedAuthorID, + MockMvcRequestBuilders + .get("/api/scenario/id1") + .with(SecurityTestTools + .buildUser(role, userId, registration, "login") + ), + HttpStatus.FORBIDDEN + ); } // #################################################################################### @@ -536,14 +527,14 @@ private void whenDeleteScenario_isCalled_returnsStatus403Forbidden( // #################################################################################### @Test @DirtiesContext void whenGetScenarioById_isCalledByAdmin() throws Exception { - whenGetScenarioById_isCalledByAllowedUser(Role.ADMIN, "authorAdmin", "author2"); + whenGetScenarioById_isCalledByAllowedUser(Role.ADMIN, Registration.GITHUB.id + "userId2", "userId1", Registration.GITHUB); } @Test @DirtiesContext void whenGetScenarioById_isCalledByUser() throws Exception { - whenGetScenarioById_isCalledByAllowedUser(Role.USER, "author1", "author1"); + whenGetScenarioById_isCalledByAllowedUser(Role.USER, Registration.GITHUB.id + "userId1", "userId1", Registration.GITHUB); } private void whenGetScenarioById_isCalledByAllowedUser( - Role role, String userDbId, @NonNull String storedAuthorID + @NonNull Role role, @NonNull String storedAuthorID, @NonNull String userId, @NonNull Registration registration ) throws Exception { // Given scenarioRepository.save(new Scenario("id1", storedAuthorID, "label1", 1)); @@ -552,7 +543,7 @@ private void whenGetScenarioById_isCalledByAllowedUser( mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/id1") - .with(SecurityTestTools.buildUser(role, "userId1", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) ) // Then @@ -564,59 +555,44 @@ private void whenGetScenarioById_isCalledByAllowedUser( @Test @DirtiesContext void whenGetScenarioById_isCalledWithUnknownId_returnsStatus404NotFound() throws Exception { - // Given - scenarioRepository.save(new Scenario("id2", "author1", "label1", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .get("/api/scenario/id1") - .with(SecurityTestTools.buildUser(Role.USER, "userId1", "author1", "login")) - ) - - // Then - .andExpect(status().isNotFound()); + saveExampleScenario_performRequest_andGetStatus( + "id2", "author1", + MockMvcRequestBuilders + .get("/api/scenario/id1") + .with(SecurityTestTools + .buildUser(Role.USER, "userId1", Registration.GOOGLE, "login") + ), + HttpStatus.NOT_FOUND + ); } @Test @DirtiesContext void whenGetScenarioById_isCalledUnauthorized_returnsStatus401Unauthorized() throws Exception { - // Given - scenarioRepository.save(new Scenario("id1", "author1", "label1", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .get("/api/scenario/id1") - ) - - // Then - .andExpect(status().isUnauthorized()); + saveExampleScenario_performRequest_andGetStatus( + "id1", "author1", + MockMvcRequestBuilders + .get("/api/scenario/id1"), + HttpStatus.UNAUTHORIZED + ); } @Test @DirtiesContext void whenGetScenarioById_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { - whenGetScenarioById_isCalled_returnsStatus403Forbidden(Role.UNKNOWN_ACCOUNT, "author1", "author1"); - } - @Test @DirtiesContext void whenGetScenarioById_isCalledByUserWithNoDbIDs_returnsStatus403Forbidden() throws Exception { - whenGetScenarioById_isCalled_returnsStatus403Forbidden(Role.USER, null, "author2"); + whenGetScenarioById_isCalled_returnsStatus403Forbidden(Role.UNKNOWN_ACCOUNT, Registration.GITHUB.id + "userId1", "userId1", Registration.GITHUB); } @Test @DirtiesContext void whenGetScenarioById_isCalledWithDifferentAuthorIDs_returnsStatus403Forbidden() throws Exception { - whenGetScenarioById_isCalled_returnsStatus403Forbidden(Role.USER, "author2", "author1"); + whenGetScenarioById_isCalled_returnsStatus403Forbidden(Role.USER, Registration.GITHUB.id + "userId2", "userId1", Registration.GITHUB); } - private void whenGetScenarioById_isCalled_returnsStatus403Forbidden( - Role role, String userDbId, @NonNull String storedAuthorID + Role role, @NonNull String storedAuthorID, @NonNull String userId, @NonNull Registration registration ) throws Exception { - // Given - scenarioRepository.save(new Scenario("id1", storedAuthorID, "label1", 1)); - - // When - mockMvc - .perform(MockMvcRequestBuilders - .get("/api/scenario/id1") - .with(SecurityTestTools.buildUser(role, "userId1", userDbId, "login")) - ) - - // Then - .andExpect(status().isForbidden()); + saveExampleScenario_performRequest_andGetStatus( + "id1", storedAuthorID, + MockMvcRequestBuilders + .get("/api/scenario/id1") + .with(SecurityTestTools + .buildUser(role, userId, registration, "login") + ), + HttpStatus.FORBIDDEN + ); } } From 4b2adfb487f9ae3b1dbd599d872c7551c3cde007 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Fri, 1 Dec 2023 19:11:52 +0100 Subject: [PATCH 14/19] tests: updated TestRunIntegrationTest --- .../prompttests/TestRunIntegrationTest.java | 146 ++++++++++-------- .../backend/security/SecurityTestTools.java | 15 +- 2 files changed, 86 insertions(+), 75 deletions(-) diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/TestRunIntegrationTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/TestRunIntegrationTest.java index c9f1a5a..c6bf9f6 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/TestRunIntegrationTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/TestRunIntegrationTest.java @@ -1,14 +1,15 @@ package net.schwarzbaer.spring.promptoptimizer.backend.prompttests; -import net.schwarzbaer.spring.promptoptimizer.backend.chatgpt.ChatGptTestTools; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.Scenario; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.TestRun; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.ScenarioRepository; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.TestRunRepository; -import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.services.RunningTestRunsList; -import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; -import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; -import okhttp3.mockwebserver.MockWebServer; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.io.IOException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -26,13 +27,16 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import java.io.IOException; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import net.schwarzbaer.spring.promptoptimizer.backend.chatgpt.ChatGptTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.Scenario; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.models.TestRun; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.ScenarioRepository; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.repositories.TestRunRepository; +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.services.RunningTestRunsList; +import net.schwarzbaer.spring.promptoptimizer.backend.security.SecurityTestTools; +import net.schwarzbaer.spring.promptoptimizer.backend.security.models.Role; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; +import okhttp3.mockwebserver.MockWebServer; @SpringBootTest @AutoConfigureMockMvc @@ -116,6 +120,10 @@ private String createNewTestRunJSON(@Nullable String scenarioId, @NonNull String ); } + private static @NonNull String getId(@NonNull String userId, @NonNull Registration registration) { + return SecurityTestTools.getUserDbId(userId, registration); + } + // #################################################################################### // getTestRunsOfScenario // #################################################################################### @@ -138,16 +146,17 @@ void whenGetTestRunsOfScenario_isCalledByUnauthorized_returnsStatus401Unauthoriz @Test @DirtiesContext void whenGetTestRunsOfScenario_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { whenGetTestRunsOfScenario_isCalled_returnsStatus403Forbidden( - "author1", "author1", Role.UNKNOWN_ACCOUNT + getId("author1", Registration.GOOGLE), Role.UNKNOWN_ACCOUNT, "author1", Registration.GOOGLE ); } @Test @DirtiesContext void whenGetTestRunsOfScenario_isCalledDifferentAuthorIds_returnsStatus403Forbidden() throws Exception { whenGetTestRunsOfScenario_isCalled_returnsStatus403Forbidden( - "author3", "author2", Role.USER + getId("author3", Registration.GOOGLE), Role.USER, "author2", Registration.GOOGLE ); } - - private void whenGetTestRunsOfScenario_isCalled_returnsStatus403Forbidden(String storedAuthorID, String currentUserDbId, Role role) throws Exception { + private void whenGetTestRunsOfScenario_isCalled_returnsStatus403Forbidden( + @NonNull String storedAuthorID, @NonNull Role role, @NonNull String userId, @NonNull Registration registration + ) throws Exception { // Given scenarioRepository.save(new Scenario("scenarioId1", storedAuthorID, "label1", 1)); @@ -155,7 +164,7 @@ private void whenGetTestRunsOfScenario_isCalled_returnsStatus403Forbidden(String mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/%s/testrun".formatted("scenarioId1")) - .with(SecurityTestTools.buildUser(role, "id", currentUserDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) ) // Then @@ -164,16 +173,17 @@ private void whenGetTestRunsOfScenario_isCalled_returnsStatus403Forbidden(String @Test @DirtiesContext void whenGetTestRunsOfScenario_isCalledByAdmin_returnsList() throws Exception { whenGetTestRunsOfScenario_isCalledByAllowedUser_returnsList( - "authorOther", Role.ADMIN, "authorAdmin" + getId("authorOther", Registration.GITHUB), Role.ADMIN, "authorAdmin", Registration.GITHUB ); } @Test @DirtiesContext void whenGetTestRunsOfScenario_isCalledByUser_returnsList() throws Exception { whenGetTestRunsOfScenario_isCalledByAllowedUser_returnsList( - "authorUser", Role.USER, "authorUser" + getId("authorUser", Registration.GITHUB), Role.USER, "authorUser", Registration.GITHUB ); } - - private void whenGetTestRunsOfScenario_isCalledByAllowedUser_returnsList(String scenarioAuthorId, Role role, String userDbId) throws Exception { + private void whenGetTestRunsOfScenario_isCalledByAllowedUser_returnsList( + @NonNull String scenarioAuthorId, @NonNull Role role, @NonNull String userId, @NonNull Registration registration + ) throws Exception { // Given String scenarioId = "scenarioId1"; scenarioRepository.save(new Scenario(scenarioId, scenarioAuthorId, "label1", 1)); @@ -185,7 +195,7 @@ private void whenGetTestRunsOfScenario_isCalledByAllowedUser_returnsList(String mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/%s/testrun".formatted(scenarioId)) - .with(SecurityTestTools.buildUser(role, "id", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) ) // Then @@ -205,17 +215,16 @@ private void whenGetTestRunsOfScenario_isCalledByAllowedUser_returnsList(String @Test @DirtiesContext void whenGetTestRunsOfScenario_isCalledWithEmptyDB_returnsEmptyList() throws Exception { whenGetTestRunsOfScenario_returnsEmptyList(null, "scenarioId1"); } - private void whenGetTestRunsOfScenario_returnsEmptyList(String storedScenarioId, String requestedScenarioId) throws Exception { // Given if (storedScenarioId!=null) - scenarioRepository.save(new Scenario(storedScenarioId, "author1", "label1", 1)); + scenarioRepository.save(new Scenario(storedScenarioId, getId("author1", Registration.GITHUB), "label1", 1)); // When mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/%s/testrun".formatted(requestedScenarioId)) - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GITHUB, "login")) ) // Then @@ -228,13 +237,18 @@ private void whenGetTestRunsOfScenario_returnsEmptyList(String storedScenarioId, // #################################################################################### @Test @DirtiesContext void whenAddTestRun_isCalledByUser_returnsStoredTestRun() throws Exception { - whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun("authorUser", Role.USER, "authorUser"); + whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun( + getId("authorUser", Registration.GOOGLE), Role.USER, "authorUser", Registration.GOOGLE + ); } @Test @DirtiesContext void whenAddTestRun_isCalledByAdmin_returnsStoredTestRun() throws Exception { - whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun("authorOther", Role.ADMIN, "authorAdmin"); + whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun( + getId("authorOther", Registration.GOOGLE), Role.ADMIN, "authorAdmin", Registration.GOOGLE + ); } - - private void whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun(String storedAuthorID, Role role, String userDbId) throws Exception { + private void whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun( + @NonNull String storedAuthorID, @NonNull Role role, @NonNull String userId, @NonNull Registration registration + ) throws Exception { // Given scenarioRepository.save(new Scenario("scenarioId1", storedAuthorID, "label1", 1)); @@ -242,7 +256,7 @@ private void whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun(String st mockMvc .perform(MockMvcRequestBuilders .post("/api/scenario/scenarioId1/testrun") - .with(SecurityTestTools.buildUser(role, "id", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createTestRunJSON(null, "scenarioId1", true)) ) @@ -255,14 +269,14 @@ private void whenAddTestRun_isCalledByAllowedUser_returnsStoredTestRun(String st } @Test @DirtiesContext - void whenAddTestRun_isCalledWithUnknownScenarioId_returnsStoredTestRun() throws Exception { + void whenAddTestRun_isCalledWithUnknownScenarioId_returnsStatus404NotFound() throws Exception { // Given // When mockMvc .perform(MockMvcRequestBuilders .post("/api/scenario/scenarioId1/testrun") - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GOOGLE, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createTestRunJSON(null, "scenarioId1", true)) ) @@ -281,7 +295,6 @@ void whenAddTestRun_isCalledWithUnknownScenarioId_returnsStoredTestRun() throws "scenarioId3", null, "scenarioId2" ); } - private void whenAddTestRun_isCalled_returnsStatus400BadRequest(@NonNull String scenarioIdInPath, String testRunId, @NonNull String scenarioIdInTestRun) throws Exception { // Given @@ -289,7 +302,7 @@ private void whenAddTestRun_isCalled_returnsStatus400BadRequest(@NonNull String mockMvc .perform(MockMvcRequestBuilders .post("/api/scenario/%s/testrun".formatted(scenarioIdInPath)) - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GITHUB, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createTestRunJSON(testRunId, scenarioIdInTestRun, true)) ) @@ -315,16 +328,18 @@ void whenAddTestRun_isCalledUnauthorized_returnsStatus401Unauthorized() throws E } @Test @DirtiesContext void whenAddTestRun_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { - whenAddTestRun_returnsStatus403Forbidden("author1", Role.UNKNOWN_ACCOUNT, "author1"); + whenAddTestRun_returnsStatus403Forbidden( + getId("author1", Registration.GOOGLE), Role.UNKNOWN_ACCOUNT, "author1", Registration.GOOGLE + ); } @Test @DirtiesContext void whenAddTestRun_isCalledWithDifferentAuthorIds_returnsStatus403Forbidden() throws Exception { - whenAddTestRun_returnsStatus403Forbidden("authorA", Role.USER, "authorB"); - } - @Test @DirtiesContext void whenAddTestRun_isCalledWithUserWithoutUserDbId_returnsStatus403Forbidden() throws Exception { - whenAddTestRun_returnsStatus403Forbidden("author1", Role.USER, null); + whenAddTestRun_returnsStatus403Forbidden( + getId("authorA", Registration.GOOGLE), Role.USER, "authorB", Registration.GOOGLE + ); } - - private void whenAddTestRun_returnsStatus403Forbidden(String storedAuthorID, Role role, String userDbId) throws Exception { + private void whenAddTestRun_returnsStatus403Forbidden( + @NonNull String storedAuthorID, @NonNull Role role, @NonNull String userId, @NonNull Registration registration + ) throws Exception { // Given scenarioRepository.save(new Scenario("scenarioId1", storedAuthorID, "label1", 1)); @@ -332,7 +347,7 @@ private void whenAddTestRun_returnsStatus403Forbidden(String storedAuthorID, Rol mockMvc .perform(MockMvcRequestBuilders .post("/api/scenario/%s/testrun".formatted("scenarioId1")) - .with(SecurityTestTools.buildUser(role, "id", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createTestRunJSON(null, "scenarioId1", true)) ) @@ -353,7 +368,7 @@ void whenPerformTestRun_isCalledWithNoScenarioId_returnsStatus400BadRequest() th mockMvc .perform(MockMvcRequestBuilders .post("/api/testrun") - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GITHUB, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createNewTestRunJSON(null)) ) @@ -381,21 +396,17 @@ void whenPerformTestRun_isCalledByUnauthorized_returnsStatus401Unauthorized() th @Test @DirtiesContext void whenPerformTestRun_isCalledByUnknownAccount_returnsStatus403Forbidden() throws Exception { whenPerformTestRun_isCalledByNotAllowedUser_returnsStatus403Forbidden( - "author1", Role.UNKNOWN_ACCOUNT, "author1" + getId("author1", Registration.GOOGLE), Role.UNKNOWN_ACCOUNT, "author1", Registration.GOOGLE ); } @Test @DirtiesContext void whenPerformTestRun_isCalledDifferentAuthorIds_returnsStatus403Forbidden() throws Exception { whenPerformTestRun_isCalledByNotAllowedUser_returnsStatus403Forbidden( - "authorA", Role.USER, "authorB" - ); - } - @Test @DirtiesContext void whenPerformTestRun_isCalledNoUserDbId_returnsStatus403Forbidden() throws Exception { - whenPerformTestRun_isCalledByNotAllowedUser_returnsStatus403Forbidden( - "authorA", Role.USER, null + getId("authorA", Registration.GOOGLE), Role.USER, "authorB", Registration.GOOGLE ); } - - private void whenPerformTestRun_isCalledByNotAllowedUser_returnsStatus403Forbidden(String scenarioAuthorID, Role role, String userDbId) throws Exception { + private void whenPerformTestRun_isCalledByNotAllowedUser_returnsStatus403Forbidden( + @NonNull String scenarioAuthorID, @NonNull Role role, @NonNull String userId, @NonNull Registration registration + ) throws Exception { // Given scenarioRepository.save(new Scenario("scenarioId1", scenarioAuthorID, "label1", 1)); @@ -403,7 +414,7 @@ private void whenPerformTestRun_isCalledByNotAllowedUser_returnsStatus403Forbidd mockMvc .perform(MockMvcRequestBuilders .post("/api/testrun") - .with(SecurityTestTools.buildUser(role, "id", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createNewTestRunJSON("scenarioId1")) ) @@ -421,7 +432,7 @@ void whenPerformTestRun_isCalledWithUnknownScenarioID_returnsStatus404NotFound() mockMvc .perform(MockMvcRequestBuilders .post("/api/testrun") - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GITHUB, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createNewTestRunJSON("scenarioIdOther")) ) @@ -431,13 +442,18 @@ void whenPerformTestRun_isCalledWithUnknownScenarioID_returnsStatus404NotFound() } @Test @DirtiesContext void whenPerformTestRun_isCalledByUser() throws Exception { - whenPerformTestRun_isCalledByAllowedUser("author1", Role.USER, "author1"); + whenPerformTestRun_isCalledByAllowedUser( + getId("author1", Registration.GOOGLE), Role.USER, "author1", Registration.GOOGLE + ); } @Test @DirtiesContext void whenPerformTestRun_isCalledByAdmin() throws Exception { - whenPerformTestRun_isCalledByAllowedUser("authorOther", Role.ADMIN, "authorAdmin"); + whenPerformTestRun_isCalledByAllowedUser( + getId("authorOther", Registration.GOOGLE), Role.ADMIN, "authorAdmin", Registration.GOOGLE + ); } - - private void whenPerformTestRun_isCalledByAllowedUser(String scenarioAuthorID, Role role, String userDbId) throws Exception { + private void whenPerformTestRun_isCalledByAllowedUser( + @NonNull String scenarioAuthorID, @NonNull Role role, @NonNull String userId, @NonNull Registration registration + ) throws Exception { // Given scenarioRepository.save(new Scenario("scenarioId1", scenarioAuthorID, "label1", 1)); mockWebServer.enqueue( @@ -448,7 +464,7 @@ private void whenPerformTestRun_isCalledByAllowedUser(String scenarioAuthorID, R mockMvc .perform(MockMvcRequestBuilders .post("/api/testrun") - .with(SecurityTestTools.buildUser(role, "id", userDbId, "login")) + .with(SecurityTestTools.buildUser(role, userId, registration, "login")) .contentType(MediaType.APPLICATION_JSON) .content(createNewTestRunJSON("scenarioId1", "TestPrompt")) ) @@ -479,7 +495,7 @@ private void whenGetCurrentTestRunsOfScenario_returnsEmptyList(String scenarioId mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/%s/testrunstate".formatted(scenarioId)) - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GITHUB, "login")) ) // Then @@ -497,7 +513,7 @@ void whenGetCurrentTestRunsOfScenario_isCalled_returnsList() throws Exception { mockMvc .perform(MockMvcRequestBuilders .get("/api/scenario/%s/testrunstate".formatted("scenarioId1")) - .with(SecurityTestTools.buildUser(Role.USER, "id", "author1", "login")) + .with(SecurityTestTools.buildUser(Role.USER, "author1", Registration.GOOGLE, "login")) ) // Then diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java index ae39ad4..32cf629 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityTestTools.java @@ -17,15 +17,6 @@ public class SecurityTestTools { - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( - Role role, - String id, - String userDbId, - String login - ) { - throw new UnsupportedOperationException(); - } - public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor buildUser( @Nullable Role role, @NonNull UserAttributesService.Registration registration @@ -52,11 +43,15 @@ public static SecurityMockMvcRequestPostProcessors.OidcLoginRequestPostProcessor token.claim("email", login); break; } - token.claim(UserAttributesService.ATTR_USER_DB_ID, registration.id + id); + token.claim(UserAttributesService.ATTR_USER_DB_ID, getUserDbId(id, registration)); token.claim(UserAttributesService.ATTR_REGISTRATION_ID, registration.id); }); } + public static @NonNull String getUserDbId(@NonNull String userId, @NonNull UserAttributesService.Registration registration) { + return registration.id + userId; + } + public static class UserAndAdminRoles implements ArgumentsProvider { @Override public Stream provideArguments(ExtensionContext extensionContext) { From fa0c390f23ff27c6f10e2b3181bd3e942de78e6f Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Fri, 1 Dec 2023 19:16:41 +0100 Subject: [PATCH 15/19] tests: added test script --- backend/test-all-with-coverage.cmd | 1 + 1 file changed, 1 insertion(+) create mode 100644 backend/test-all-with-coverage.cmd diff --git a/backend/test-all-with-coverage.cmd b/backend/test-all-with-coverage.cmd new file mode 100644 index 0000000..169e53e --- /dev/null +++ b/backend/test-all-with-coverage.cmd @@ -0,0 +1 @@ +.\mvnw jacoco:prepare-agent test install jacoco:report \ No newline at end of file From 064d934c7bda20f521360832c69fb9bf920f4b9e Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Fri, 1 Dec 2023 19:45:25 +0100 Subject: [PATCH 16/19] tests: improved test coverage --- .../backend/security/SecurityConfig.java | 11 ++++--- .../services/RunningTestRunsListTest.java | 33 ++++++++++++++++--- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java index bfad21c..30d8c27 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/SecurityConfig.java @@ -88,7 +88,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @Bean public OAuth2UserService oauth2UserService(StoredUserInfoService storedUserInfoService, UserAttributesService userAttributesService) { DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); - return request -> configureUserData(storedUserInfoService, userAttributesService, delegate, request); + return request -> + configureUserData(storedUserInfoService, userAttributesService, delegate, request); } DefaultOAuth2User configureUserData( @@ -104,10 +105,10 @@ DefaultOAuth2User configureUserData( String registrationId = request.getClientRegistration().getRegistrationId(); String userDbId = registrationId + user.getName(); - System.out.println("User: ["+ registrationId +"] "+ user.getName()); - newAttributes.forEach((key, value) -> - System.out.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) - ); + // System.out.println("User: ["+ registrationId +"] "+ user.getName()); + // newAttributes.forEach((key, value) -> + // System.out.println(" ["+key+"]: "+value+ (value==null ? "" : " { Class:"+value.getClass().getName()+" }")) + // ); newAttributes.put(UserAttributesService.ATTR_USER_DB_ID, userDbId); newAttributes.put(UserAttributesService.ATTR_REGISTRATION_ID, registrationId); diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/services/RunningTestRunsListTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/services/RunningTestRunsListTest.java index cd9e910..50b685c 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/services/RunningTestRunsListTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/prompttests/services/RunningTestRunsListTest.java @@ -1,14 +1,18 @@ package net.schwarzbaer.spring.promptoptimizer.backend.prompttests.services; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.lang.NonNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.util.List; import java.util.Map; import java.util.Objects; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.lang.NonNull; + +import net.schwarzbaer.spring.promptoptimizer.backend.prompttests.services.RunningTestRunsList.ListEntry; class RunningTestRunsListTest { @@ -145,6 +149,27 @@ void whenRemoveEntry_isCalledWithLastEntry() { assertNull(entries); } + @Test + void whenRemoveEntry_isCalledWithUnknownScenarioId() { + // Given + ListEntry entry = + addEntry("scenarioId1", 2, 5, "prompt1a", "label1a"); + addEntry("scenarioId1", 3, 4, "prompt1b", "label1b"); + + // When + runningTestRunsList.removeEntry("scenarioId2", entry); + + // Then + List entries = runningTestRuns.get("scenarioId1"); + assertNotNull(entries); + assertEquals(2, entries.size()); + + RunningTestRunsList.ListEntry entry0 = entries.get(0); + RunningTestRunsList.ListEntry entry1 = entries.get(1); + assertEntryEquals(entry0,2, 5, "prompt1a", "label1a"); + assertEntryEquals(entry1,3, 4, "prompt1b", "label1b"); + } + @Test void whenRemoveEntry_isCalledWithUnknownEntry() { // Given From e18ee9467aa46b2c53bb40e226862e61e1eae0e1 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Sat, 2 Dec 2023 01:01:18 +0100 Subject: [PATCH 17/19] tests: added unit tests of UserAttributesService --- .../services/UserAttributesServiceTest.java | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java new file mode 100644 index 0000000..44da322 --- /dev/null +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java @@ -0,0 +1,197 @@ +package net.schwarzbaer.spring.promptoptimizer.backend.security.services; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.lang.NonNull; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; + +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Field; +import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; + +public class UserAttributesServiceTest { + + private UserAttributesService userAttributesService; + + @BeforeEach + void setup() { + userAttributesService = new UserAttributesService(); + } + +// ############################################################################################ +// void fixAttributesIfNeeded( Map attributes, String registrationId ) +// ############################################################################################ + + @Test + void whenFixAttributesIfNeeded_isCalledWithGoogleData() { + // Given + Map attributes = new HashMap<>(); + attributes.put("sub", "value"); + attributes.put("other", "value2"); + + // When + userAttributesService.fixAttributesIfNeeded(attributes, Registration.GOOGLE.id); + + // Then + Map expected = Map.of( + "sub", "value", + "original_Id", "value", + "other", "value2" + ); + assertEquals(expected, attributes); + } + + @Test + void whenFixAttributesIfNeeded_isCalledWithNonGoogleData() { + // Given + Map attributes = new HashMap<>(); + attributes.put("field1", "value1"); + attributes.put("field2", "value2"); + + // When + userAttributesService.fixAttributesIfNeeded(attributes, "other"); + + // Then + Map expected = Map.of( + "field1", "value1", + "field2", "value2" + ); + assertEquals(expected, attributes); + } + +// ############################################################################################ +// String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, @NonNull String field, String nullDefault ) +// ############################################################################################ + + @Test void whenGetAttribute_WithUserAndFieldName_isCalledWithUnkownField_returnsNullDefault() { + whenGetAttribute_WithUserAndFieldName_isCalled("field2", "nullDefault"); + } + @Test void whenGetAttribute_WithUserAndFieldName_isCalledNormal_returnsValue() { + whenGetAttribute_WithUserAndFieldName_isCalled("field1", "value1"); + } + private void whenGetAttribute_WithUserAndFieldName_isCalled( + @NonNull String requestedFieldName, @NonNull String expectedReturnValue + ) { + // Given + DefaultOAuth2User user = new DefaultOAuth2User( + List.of(), + Map.of( + "id", "userId", + "field1", "value1" + ), + "id" + ); + + // When + String actual = userAttributesService.getAttribute(user, requestedFieldName, "nullDefault"); + + // Then + assertEquals(expectedReturnValue, actual); + } + +// ############################################################################################ +// String getAttribute( @NonNull Map userAttributes, @NonNull String field, String nullDefault ) +// ############################################################################################ + + @Test void whenGetAttribute_WithAttributeMapAndFieldName_isCalledWithUnkownField_returnsNullDefault() { + whenGetAttribute_WithAttributeMapAndFieldName_isCalled("field2", "nullDefault"); + } + @Test void whenGetAttribute_WithAttributeMapAndFieldName_isCalledNormal_returnsValue() { + whenGetAttribute_WithAttributeMapAndFieldName_isCalled("field1", "value1"); + } + private void whenGetAttribute_WithAttributeMapAndFieldName_isCalled( + @NonNull String requestedFieldName, @NonNull String expectedReturnValue + ) { + // Given + Map attributes = Objects.requireNonNull( Map.of( + "id", "userId", + "field1", "value1" + ) ); + + // When + String actual = userAttributesService.getAttribute(attributes, requestedFieldName, "nullDefault"); + + // Then + assertEquals(expectedReturnValue, actual); + } + +// ############################################################################################ +// String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, String registrationId, Field field, String nullDefault ) +// ############################################################################################ + + @Test void whenGetAttribute_WithUserAndRegistrationAndField_isCalledNormal_returnsValue() { + whenGetAttribute_WithUserAndRegistrationAndField_isCalled( + Registration.GITHUB.id, Field.NAME, "value1" + ); + } + @Test void whenGetAttribute_WithUserAndRegistrationAndField_isCalledUnknownRegId_returnsNullDefault() { + whenGetAttribute_WithUserAndRegistrationAndField_isCalled( + "other", Field.NAME, "nullDefault" + ); + } + @Test void whenGetAttribute_WithUserAndRegistrationAndField_isCalledUnsetField_returnsNullDefault() { + whenGetAttribute_WithUserAndRegistrationAndField_isCalled( + Registration.GITHUB.id, Field.LOCATION, "nullDefault" + ); + } + private void whenGetAttribute_WithUserAndRegistrationAndField_isCalled( + String registrationId, Field field, String expectedReturnValue + ) { + // Given + DefaultOAuth2User user = new DefaultOAuth2User( + List.of(), + Map.of( + "id", "userId", + "name", "value1" + ), + "id" + ); + + // When + String actual = userAttributesService.getAttribute(user, registrationId, field, "nullDefault"); + + // Then + assertEquals(expectedReturnValue, actual); + } + +// ############################################################################################ +// String getAttribute( @NonNull Map userAttributes, String registrationId, Field field, String nullDefault ) +// ############################################################################################ + + @Test void whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalledNormal_returnsValue() { + whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalled( + Registration.GITHUB.id, Field.NAME, "value1" + ); + } + @Test void whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalledUnknownRegId_returnsNullDefault() { + whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalled( + "other", Field.NAME, "nullDefault" + ); + } + @Test void whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalledUnsetField_returnsNullDefault() { + whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalled( + Registration.GITHUB.id, Field.LOCATION, "nullDefault" + ); + } + private void whenGetAttribute_WithAttributeMapAndRegistrationAndField_isCalled( + String registrationId, Field field, String expectedReturnValue + ) { + // Given + Map attributes = Objects.requireNonNull( Map.of( + "id", "userId", + "name", "value1" + ) ); + + // When + String actual = userAttributesService.getAttribute(attributes, registrationId, field, "nullDefault"); + + // Then + assertEquals(expectedReturnValue, actual); + } +} From 0c331904e71a4a7de06d1a718ad491235689dc06 Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Sat, 2 Dec 2023 01:19:36 +0100 Subject: [PATCH 18/19] feat: raised version of Spring Boot to 3.2.0 --- backend/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/pom.xml b/backend/pom.xml index eabce6b..392252f 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.1.6 + 3.2.0 net.schwarzbaer.spring.promptoptimizer From 03f2219891bebcd23ebccecd623d68389a23027c Mon Sep 17 00:00:00 2001 From: Hendrik Scholtz <135076120+Hendrik2319@users.noreply.github.com> Date: Sat, 2 Dec 2023 01:42:45 +0100 Subject: [PATCH 19/19] fix: fixed some code smells --- .../services/UserAttributesService.java | 28 ++++++++++--------- .../services/UserAttributesServiceTest.java | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java index ce96bfc..8b72c15 100644 --- a/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java +++ b/backend/src/main/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesService.java @@ -44,21 +44,23 @@ private static Map> createConfig() { HashMap> newConfig = new HashMap<>(); EnumMap fields; - newConfig.put(Registration.GITHUB.id, fields = new EnumMap<>(Field.class)); + fields = new EnumMap<>(Field.class); fields.put( Field.ORIGINAL_ID, "id" ); fields.put( Field.LOGIN , "login" ); fields.put( Field.NAME , "name" ); fields.put( Field.LOCATION , "location" ); fields.put( Field.URL , "html_url" ); fields.put( Field.AVATAR_URL , "avatar_url" ); + newConfig.put(Registration.GITHUB.id, fields); - newConfig.put(Registration.GOOGLE.id, fields = new EnumMap<>(Field.class)); + fields = new EnumMap<>(Field.class); fields.put( Field.ORIGINAL_ID, "original_Id"); fields.put( Field.LOGIN , "email" ); fields.put( Field.NAME , "name" ); fields.put( Field.LOCATION , "locale" ); // fields.put( Field.URL , "html_url" ); fields.put( Field.AVATAR_URL , "picture" ); + newConfig.put(Registration.GOOGLE.id, fields); return newConfig; } @@ -81,22 +83,22 @@ public String getAttribute( @NonNull Map userAttributes, @NonNul return Objects.toString( userAttributes.get(field), nullDefault ); } - public String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, String registrationId, Field field, String nullDefault ) + public String getAttribute( @NonNull OAuth2AuthenticatedPrincipal user, String registrationId, Field field, String nullDefault ) { - return getAttribute( user, registrationId, field, nullDefault, this::getAttribute ); - } + return getAttribute( user, registrationId, field, nullDefault, this::getAttribute ); + } - public String getAttribute( @NonNull Map userAttributes, String registrationId, Field field, String nullDefault ) + public String getAttribute( @NonNull Map userAttributes, String registrationId, Field field, String nullDefault ) { - return getAttribute( userAttributes, registrationId, field, nullDefault, this::getAttribute ); - } + return getAttribute( userAttributes, registrationId, field, nullDefault, this::getAttribute ); + } - private interface GetAttributeFunction { - String getAttribute( @NonNull Source source, @NonNull String field, String nullDefault); + private interface GetAttributeFunction { + String getAttribute( @NonNull S source, @NonNull String field, String nullDefault); } - private String getAttribute( @NonNull Source source, String registrationId, Field field, String nullDefault, GetAttributeFunction getAttribute ) + private String getAttribute( @NonNull S source, String registrationId, Field field, String nullDefault, GetAttributeFunction getAttribute ) { Map attrNames = config.get(registrationId); if (attrNames==null) return nullDefault; @@ -104,6 +106,6 @@ private String getAttribute( @NonNull Source source, String registratio String attrName = attrNames.get(field); if (attrName==null) return nullDefault; - return getAttribute.getAttribute( source, attrName, nullDefault ); - } + return getAttribute.getAttribute( source, attrName, nullDefault ); + } } diff --git a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java index 44da322..18b4174 100644 --- a/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java +++ b/backend/src/test/java/net/schwarzbaer/spring/promptoptimizer/backend/security/services/UserAttributesServiceTest.java @@ -15,7 +15,7 @@ import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Field; import net.schwarzbaer.spring.promptoptimizer.backend.security.services.UserAttributesService.Registration; -public class UserAttributesServiceTest { +class UserAttributesServiceTest { private UserAttributesService userAttributesService;