From 1d098394ee74edf23beae79be468e5a637872fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Tue, 10 Sep 2024 13:39:01 +0200 Subject: [PATCH 1/9] feature/Always create a new term and conditions, never update existing one --- .../main/scala/code/api/util/NewStyle.scala | 2 +- .../code/model/dataAccess/AuthUser.scala | 8 +++--- .../scala/code/snippet/PrivacyPolicy.scala | 2 +- .../code/snippet/TermsAndConditions.scala | 2 +- .../scala/code/snippet/UserInvitation.scala | 8 +++--- .../src/main/scala/code/users/LiftUsers.scala | 6 ++-- .../main/scala/code/users/UserAgreement.scala | 28 +------------------ .../code/users/UserAgreementProvider.scala | 3 +- 8 files changed, 16 insertions(+), 43 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index ab5944ce17..ec0df9bf6f 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -852,7 +852,7 @@ object NewStyle extends MdcLoggable{ } } def getAgreementByUserId(userId: String, agreementType: String, callContext: Option[CallContext]): Future[Box[UserAgreement]] = { - Future(UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, agreementType)) + Future(UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(userId, agreementType)) } def getEntitlementsByBankId(bankId: String, callContext: Option[CallContext]): Future[List[Entitlement]] = { diff --git a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala index 4dc05128f0..09bda9b104 100644 --- a/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala +++ b/obp-api/src/main/scala/code/model/dataAccess/AuthUser.scala @@ -686,9 +686,9 @@ import net.liftweb.util.Helpers._ val privacyPolicyValue: String = getWebUiPropsValue("webui_privacy_policy", "") val termsAndConditionsValue: String = getWebUiPropsValue("webui_terms_and_conditions", "") // User Agreement table - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( theUser.user.foreign.map(_.userId).getOrElse(""), "privacy_conditions", privacyPolicyValue) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( theUser.user.foreign.map(_.userId).getOrElse(""), "terms_and_conditions", termsAndConditionsValue) if (!skipEmailValidation) { sendValidationEmail(theUser) @@ -1041,13 +1041,13 @@ def restoreSomeSessions(): Unit = { def redirectUri(user: Box[ResourceUser]): String = { val userId = user.map(_.userId).getOrElse("") val hashedAgreementTextOfUser = - UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, "terms_and_conditions") + UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(userId, "terms_and_conditions") .map(_.agreementHash).getOrElse(HashUtil.Sha256Hash("not set")) val agreementText = getWebUiPropsValue("webui_terms_and_conditions", "not set") val hashedAgreementText = HashUtil.Sha256Hash(agreementText) if(hashedAgreementTextOfUser == hashedAgreementText) { // Check terms and conditions val hashedAgreementTextOfUser = - UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(userId, "privacy_conditions") + UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(userId, "privacy_conditions") .map(_.agreementHash).getOrElse(HashUtil.Sha256Hash("not set")) val agreementText = getWebUiPropsValue("webui_privacy_policy", "not set") val hashedAgreementText = HashUtil.Sha256Hash(agreementText) diff --git a/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala b/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala index 1ff3fd0e71..b9549c9921 100644 --- a/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala +++ b/obp-api/src/main/scala/code/snippet/PrivacyPolicy.scala @@ -68,7 +68,7 @@ class PrivacyPolicy extends MdcLoggable { private def updateUserAgreement() = { if(AuthUser.currentUser.isDefined) { val agreementText = getWebUiPropsValue("webui_privacy_policy", "not set") - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( AuthUser.currentUser.flatMap(_.user.foreign.map(_.userId)).getOrElse(""), "privacy_conditions", agreementText) S.redirectTo("/") } diff --git a/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala b/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala index 87e6393636..e66692cc71 100644 --- a/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala +++ b/obp-api/src/main/scala/code/snippet/TermsAndConditions.scala @@ -69,7 +69,7 @@ class TermsAndConditions extends MdcLoggable { if(AuthUser.currentUser.isDefined) { val agreementText = getWebUiPropsValue("webui_terms_and_conditions", "not set") // val hashedAgreementText = HashUtil.Sha256Hash(agreementText) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( AuthUser.currentUser.flatMap(_.user.foreign.map(_.userId)).getOrElse(""), "terms_and_conditions", agreementText) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index 700b754fa8..fc43ee5a8d 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -121,13 +121,13 @@ class UserInvitation extends MdcLoggable { showError(msg) case _ => // User Agreement table - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "privacy_conditions", privacyConditionsValue) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "terms_and_conditions", termsAndConditionsValue) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "accept_marketing_info", marketingInfoCheckboxVar.is.toString) - UserAgreementProvider.userAgreementProvider.vend.createOrUpdateUserAgreement( + UserAgreementProvider.userAgreementProvider.vend.createUserAgreement( u.userId, "consent_for_collecting_personal_data", consentForCollectingCheckboxVar.is.toString) // Set the status of the user invitation to "FINISHED" UserInvitationProvider.userInvitationProvider.vend.updateStatusOfUserInvitation(userInvitation.map(_.userInvitationId).getOrElse(""), "FINISHED") diff --git a/obp-api/src/main/scala/code/users/LiftUsers.scala b/obp-api/src/main/scala/code/users/LiftUsers.scala index f5a21983d0..39ae2f690a 100644 --- a/obp-api/src/main/scala/code/users/LiftUsers.scala +++ b/obp-api/src/main/scala/code/users/LiftUsers.scala @@ -133,9 +133,9 @@ object LiftUsers extends Users with MdcLoggable{ } private def getUserAgreements(user: ResourceUser) = { - val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "accept_marketing_info") - val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "terms_and_conditions") - val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getUserAgreement(user.userId, "privacy_conditions") + val acceptMarketingInfo = UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(user.userId, "accept_marketing_info") + val termsAndConditions = UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(user.userId, "terms_and_conditions") + val privacyConditions = UserAgreementProvider.userAgreementProvider.vend.getLastUserAgreement(user.userId, "privacy_conditions") val agreements = acceptMarketingInfo.toList ::: termsAndConditions.toList ::: privacyConditions.toList agreements } diff --git a/obp-api/src/main/scala/code/users/UserAgreement.scala b/obp-api/src/main/scala/code/users/UserAgreement.scala index ac51bfd865..e4deda7818 100644 --- a/obp-api/src/main/scala/code/users/UserAgreement.scala +++ b/obp-api/src/main/scala/code/users/UserAgreement.scala @@ -10,32 +10,6 @@ import net.liftweb.mapper._ import net.liftweb.common.Box.tryo object MappedUserAgreementProvider extends UserAgreementProvider { - // TODO Change the function name - override def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = { - UserAgreement.find( - By(UserAgreement.UserId, userId), - By(UserAgreement.AgreementType, agreementType) - ) match { - // TODO We should be adding an additional record. Not changing existing one. - case Full(existingUser) => - Full( - existingUser - .AgreementType(agreementType) - .AgreementText(agreementText) - .saveMe() - ) - case Empty => - Full( - UserAgreement.create - .UserId(userId) - .AgreementType(agreementType) - .AgreementText(agreementText) - .Date(new Date) - .saveMe() - ) - case everythingElse => everythingElse - } - } override def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] = { Full( UserAgreement.create @@ -46,7 +20,7 @@ object MappedUserAgreementProvider extends UserAgreementProvider { .saveMe() ) } - override def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = { + override def getLastUserAgreement(userId: String, agreementType: String): Box[UserAgreement] = { UserAgreement.findAll( By(UserAgreement.UserId, userId), By(UserAgreement.AgreementType, agreementType) diff --git a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala index 3cade8c980..8381fdbe98 100644 --- a/obp-api/src/main/scala/code/users/UserAgreementProvider.scala +++ b/obp-api/src/main/scala/code/users/UserAgreementProvider.scala @@ -16,9 +16,8 @@ object UserAgreementProvider extends SimpleInjector { } trait UserAgreementProvider { - def createOrUpdateUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] def createUserAgreement(userId: String, agreementType: String, agreementText: String): Box[UserAgreement] - def getUserAgreement(userId: String, agreementType: String): Box[UserAgreement] + def getLastUserAgreement(userId: String, agreementType: String): Box[UserAgreement] } trait UserAgreementTrait { From d930479bffd1c67caba3ee2bbf27df5105f20985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 12 Sep 2024 12:18:23 +0200 Subject: [PATCH 2/9] docfix/Tweak user invitation endpoints --- obp-api/src/main/scala/code/api/util/NewStyle.scala | 2 +- obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index ec0df9bf6f..b73b9a2e43 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -833,7 +833,7 @@ object NewStyle extends MdcLoggable{ } def getUserInvitation(bankId: BankId, secretLink: Long, callContext: Option[CallContext]): OBPReturnType[UserInvitation] = Future { val response: Box[UserInvitation] = UserInvitationProvider.userInvitationProvider.vend.getUserInvitation(bankId, secretLink) - (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 400), callContext) + (unboxFullOrFail(response, callContext, s"$CannotGetUserInvitation", 404), callContext) } def getUserInvitations(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[List[UserInvitation]] = Future { val response = UserInvitationProvider.userInvitationProvider.vend.getUserInvitations(bankId) diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index 88841a027f..bfdc3711a5 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -3869,16 +3869,17 @@ trait APIMethods400 extends MdcLoggable { "POST", "/banks/BANK_ID/user-invitations", "Get User Invitation Information", - s"""Create User Invitation Information. + s"""Get User Invitation Information. | |${authenticationRequiredMessage(false)} |""", PostUserInvitationAnonymousJsonV400(secret_key = 5819479115482092878L), userInvitationJsonV400, List( - UserNotLoggedIn, $BankNotFound, UserCustomerLinksNotFoundForUser, + CannotGetUserInvitation, + CannotFindUserInvitation, UnknownError ), List(apiTagUserInvitation, apiTagKyc) From 386827e110fa9510f3ae6a22387cdeaf6e915108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 12 Sep 2024 12:36:26 +0200 Subject: [PATCH 3/9] test/Add basic user invitation tests --- .../code/api/v4_0_0/UserInvitationTest.scala | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala new file mode 100644 index 0000000000..39fc02e6cd --- /dev/null +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala @@ -0,0 +1,108 @@ +package code.api.v4_0_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole.{CanCreateUserInvitation, CanGetUserInvitation} +import code.api.util.ErrorMessages.{CannotGetUserInvitation, UserHasMissingRoles, UserNotLoggedIn} +import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag + +class UserInvitationTest extends V400ServerSetup { + /** + * Test tags + * Example: To run tests with tag "getPermissions": + * mvn test -D tagsToInclude + * + * This is made possible by the scalatest maven plugin + */ + object VersionOfApi extends Tag(ApiVersion.v4_0_0.toString) + object ApiEndpoint1 extends Tag(nameOf(Implementations4_0_0.createUserInvitation)) + object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getUserInvitationAnonymous)) + object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getUserInvitation)) + object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getUserInvitations)) + + + feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitation").POST + val postJson = SwaggerDefinitionsJSON.userInvitationPostJsonV400 + val response400 = makePostRequest(request400, write(postJson)) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint1 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitation").POST <@(user1) + val postJson = SwaggerDefinitionsJSON.userInvitationPostJsonV400 + val response400 = makePostRequest(request400, write(postJson)) + Then("error should be " + UserHasMissingRoles + CanCreateUserInvitation) + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should startWith(UserHasMissingRoles + CanCreateUserInvitation) + } + } + + + feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations").POST <@(user1) + val postJson = PostUserInvitationAnonymousJsonV400(secret_key = 0L) + val response400 = makePostRequest(request400, write(postJson)) + Then("error should be " + CannotGetUserInvitation) + response400.code should equal(404) + response400.body.extract[ErrorMessage].message should be(CannotGetUserInvitation) + } + } + + feature(s"test $ApiEndpoint3 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations" / "secret-link").GET + val response400 = makeGetRequest(request400) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint3 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint3, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations" / "secret-link").GET <@(user1) + val response400 = makeGetRequest(request400) + Then("error should be " + UserHasMissingRoles + CanGetUserInvitation) + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should startWith(UserHasMissingRoles + CanGetUserInvitation) + } + } + + feature(s"test $ApiEndpoint4 version $VersionOfApi - Unauthorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations").GET + val response400 = makeGetRequest(request400) + Then("We should get a 401") + response400.code should equal(401) + response400.body.extract[ErrorMessage].message should equal(UserNotLoggedIn) + } + } + feature(s"test $ApiEndpoint4 version $VersionOfApi - Authorized access") { + scenario("We will call the endpoint without user credentials", ApiEndpoint4, VersionOfApi) { + When("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations").GET <@(user1) + val response400 = makeGetRequest(request400) + Then("error should be " + UserHasMissingRoles + CanGetUserInvitation) + response400.code should equal(403) + response400.body.extract[ErrorMessage].message should startWith(UserHasMissingRoles + CanGetUserInvitation) + } + } + + +} From 598f8b314452a70ca8bd9ffdfe0541c8752d5ffb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Fri, 13 Sep 2024 09:58:11 +0200 Subject: [PATCH 4/9] test/Add basic user invitation tests 2 --- .../code/api/v4_0_0/UserInvitationTest.scala | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala index 39fc02e6cd..4aca1148e4 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala @@ -2,9 +2,11 @@ package code.api.v4_0_0 import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole import code.api.util.ApiRole.{CanCreateUserInvitation, CanGetUserInvitation} import code.api.util.ErrorMessages.{CannotGetUserInvitation, UserHasMissingRoles, UserNotLoggedIn} import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 +import code.entitlement.Entitlement import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.ApiVersion @@ -48,6 +50,29 @@ class UserInvitationTest extends V400ServerSetup { response400.body.extract[ErrorMessage].message should startWith(UserHasMissingRoles + CanCreateUserInvitation) } } + feature(s"test $ApiEndpoint1 and $ApiEndpoint4 version $VersionOfApi - Successful response") { + scenario("We will call the endpoint without user credentials", ApiEndpoint1, ApiEndpoint4, VersionOfApi) { + When("We add required entitlement") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, ApiRole.CanCreateUserInvitation.toString) + Then("We make a request v4.0.0") + val request400 = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitation").POST <@(user1) + val postJson = SwaggerDefinitionsJSON.userInvitationPostJsonV400 + val response400 = makePostRequest(request400, write(postJson)) + Then("We get successful response") + response400.code should equal(201) + val userInvitation = response400.body.extract[UserInvitationJsonV400] + + When("We add required entitlement") + Entitlement.entitlement.vend.addEntitlement(testBankId1.value, resourceUser1.userId, ApiRole.CanGetUserInvitation.toString) + Then(s"We make a request $ApiEndpoint4") + val request = (v4_0_0_Request / "banks" / testBankId1.value / "user-invitations").GET <@ (user1) + val response = makeGetRequest(request) + Then("We get successful response") + response.code should equal(200) + val userInvitations = response.body.extract[UserInvitationsJsonV400] + userInvitations.user_invitations.exists(i => i.email == userInvitation.email) + } + } feature(s"test $ApiEndpoint2 version $VersionOfApi - Authorized access") { From 784cb6aecd74a4cd368cfa11ce3167fcdaa223ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Mon, 16 Sep 2024 14:09:10 +0200 Subject: [PATCH 5/9] test/Add basic selenium tests regarding user invitation --- .../code/api/v4_0_0/UserInvitationTest.scala | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala index 4aca1148e4..38b9412250 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala @@ -7,11 +7,16 @@ import code.api.util.ApiRole.{CanCreateUserInvitation, CanGetUserInvitation} import code.api.util.ErrorMessages.{CannotGetUserInvitation, UserHasMissingRoles, UserNotLoggedIn} import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0 import code.entitlement.Entitlement +import code.users.{UserInvitation, UserInvitationProvider} +import code.util.Helper.MdcLoggable import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.ApiVersion +import net.liftweb.common.Box import net.liftweb.json.Serialization.write +import net.liftweb.util.Helpers.tryo import org.scalatest.Tag +import org.scalatestplus.selenium.HtmlUnit class UserInvitationTest extends V400ServerSetup { /** @@ -26,6 +31,26 @@ class UserInvitationTest extends V400ServerSetup { object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getUserInvitationAnonymous)) object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getUserInvitation)) object ApiEndpoint4 extends Tag(nameOf(Implementations4_0_0.getUserInvitations)) + + + case class UserInvitationAtBrowser() extends HtmlUnit with MdcLoggable { + implicit val driver = webDriver + + def checkPrepoulatedFields(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + tryo { + go.to(loginPage) + val firstNameOk = textField("firstName").value == userInvitation.firstName + val lastNameOk = textField("lastName").value == userInvitation.lastName + val emailOk = textField("devEmail").value == userInvitation.email + val companyNameOk = textField("companyName").value == userInvitation.company + val countryOk = textField("country").value == userInvitation.country + + close() + quit() + firstNameOk && lastNameOk && emailOk && companyNameOk && countryOk + } + } + } feature(s"test $ApiEndpoint1 version $VersionOfApi - Unauthorized access") { @@ -70,7 +95,17 @@ class UserInvitationTest extends V400ServerSetup { Then("We get successful response") response.code should equal(200) val userInvitations = response.body.extract[UserInvitationsJsonV400] - userInvitations.user_invitations.exists(i => i.email == userInvitation.email) + userInvitations.user_invitations.exists(i => i.email == userInvitation.email) should equal(true) + + + val invitation: UserInvitation = UserInvitationProvider.userInvitationProvider.vend.getUserInvitations(testBankId1) + .getOrElse(Nil) + .filter(i => i.email == userInvitation.email) + .head + + val b = UserInvitationAtBrowser() + val pageUrl = (baseRequest / "user-invitation" < Date: Tue, 17 Sep 2024 10:32:38 +0200 Subject: [PATCH 6/9] test/Add advanced selenium tests regarding user invitation --- obp-api/src/main/webapp/user-invitation.html | 8 +-- .../code/api/v4_0_0/UserInvitationTest.scala | 67 ++++++++++++++++++- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/obp-api/src/main/webapp/user-invitation.html b/obp-api/src/main/webapp/user-invitation.html index 55764e7378..c9ca142b76 100644 --- a/obp-api/src/main/webapp/user-invitation.html +++ b/obp-api/src/main/webapp/user-invitation.html @@ -74,8 +74,8 @@

Complete your user invitation

Privacy Policy
- - + +
@@ -83,8 +83,8 @@

Complete your user invitation

Terms and Conditions
- - + +

diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala index 38b9412250..8203a6f9bc 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala @@ -36,6 +36,11 @@ class UserInvitationTest extends V400ServerSetup { case class UserInvitationAtBrowser() extends HtmlUnit with MdcLoggable { implicit val driver = webDriver + def closeAndQuit(): Unit = { + close() + quit() + } + def checkPrepoulatedFields(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { tryo { go.to(loginPage) @@ -45,11 +50,64 @@ class UserInvitationTest extends V400ServerSetup { val companyNameOk = textField("companyName").value == userInvitation.company val countryOk = textField("country").value == userInvitation.country - close() - quit() firstNameOk && lastNameOk && emailOk && companyNameOk && countryOk } } + def checkSubmitButtonDisabled(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + tryo { + go.to(loginPage) + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + !button.isEnabled + } + } + def checkSubmitButtonDisabled2(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + tryo { + go.to(loginPage) + checkbox("user_invitation_privacy_checkbox").select + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + !button.isEnabled + } + } + def checkSubmitButtonDisabled3(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + tryo { + go.to(loginPage) + checkbox("user_invitation_terms_checkbox").select + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + !button.isEnabled + } + } + def checkSubmitButtonEnabled(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + tryo { + go.to(loginPage) + checkbox("consent_for_collecting_checkbox").select + checkbox("user_invitation_privacy_checkbox").select + checkbox("user_invitation_terms_checkbox").select + val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected + val privacyCheckbox = checkbox("user_invitation_privacy_checkbox").isSelected + val termsCheckboxValue = checkbox("user_invitation_terms_checkbox").isSelected + val button = driver.findElementById("submit-button") + + ((termsCheckboxValue && privacyCheckbox && consentForCollectingCheckbox) == button.isEnabled) && + button.isEnabled + } + } + + } @@ -106,6 +164,11 @@ class UserInvitationTest extends V400ServerSetup { val b = UserInvitationAtBrowser() val pageUrl = (baseRequest / "user-invitation" < Date: Tue, 17 Sep 2024 10:40:20 +0200 Subject: [PATCH 7/9] docfif/Tweak file name --- ...erInvitationTest.scala => UserInvitationApiAndGuiTest.scala} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename obp-api/src/test/scala/code/api/v4_0_0/{UserInvitationTest.scala => UserInvitationApiAndGuiTest.scala} (99%) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala similarity index 99% rename from obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala rename to obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala index 8203a6f9bc..ec41629517 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala @@ -18,7 +18,7 @@ import net.liftweb.util.Helpers.tryo import org.scalatest.Tag import org.scalatestplus.selenium.HtmlUnit -class UserInvitationTest extends V400ServerSetup { +class UserInvitationApiAndGuiTest extends V400ServerSetup { /** * Test tags * Example: To run tests with tag "getPermissions": From 8905117a07fd2a9e75dea90730d61d129384fba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Tue, 17 Sep 2024 12:49:25 +0200 Subject: [PATCH 8/9] test/Add advanced selenium tests regarding user invitation 2 --- .../scala/code/snippet/UserInvitation.scala | 6 +++--- .../v4_0_0/UserInvitationApiAndGuiTest.scala | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/snippet/UserInvitation.scala b/obp-api/src/main/scala/code/snippet/UserInvitation.scala index fc43ee5a8d..0833b5cad3 100644 --- a/obp-api/src/main/scala/code/snippet/UserInvitation.scala +++ b/obp-api/src/main/scala/code/snippet/UserInvitation.scala @@ -187,9 +187,9 @@ class UserInvitation extends MdcLoggable { "#companyName" #> SHtml.text(companyVar.is, companyVar(_)) & "#devEmail" #> SHtml.text(devEmailVar, devEmailVar(_)) & "#username" #> SHtml.text(usernameVar, usernameVar(_)) & - "#privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_)) & - "#terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_)) & - "#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_)) & + "#user_invitation_privacy_checkbox" #> SHtml.checkbox(privacyCheckboxVar, privacyCheckboxVar(_), "id" -> "user_invitation_privacy_checkbox") & + "#user_invitation_terms_checkbox" #> SHtml.checkbox(termsCheckboxVar, termsCheckboxVar(_), "id" -> "user_invitation_terms_checkbox") & + "#marketing_info_checkbox" #> SHtml.checkbox(marketingInfoCheckboxVar, marketingInfoCheckboxVar(_), "id" -> "marketing_info_checkbox") & "#consent_for_collecting_checkbox" #> SHtml.checkbox(consentForCollectingCheckboxVar, consentForCollectingCheckboxVar(_), "id" -> "consent_for_collecting_checkbox") & "#consent_for_collecting_mandatory" #> SHtml.checkbox(consentForCollectingMandatoryCheckboxVar, consentForCollectingMandatoryCheckboxVar(_), "id" -> "consent_for_collecting_mandatory", "hidden" -> "true") & "type=submit" #> SHtml.submit(s"$registrationConsumerButtonValue", () => submitButtonDefense) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala index ec41629517..891bf88690 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala @@ -106,6 +106,19 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { button.isEnabled } } + def submitForm(loginPage: String, userInvitation: UserInvitation): Box[Boolean]= { + tryo { + go.to(loginPage) + checkbox("consent_for_collecting_checkbox").select + checkbox("user_invitation_privacy_checkbox").select + checkbox("user_invitation_terms_checkbox").select + click on XPathQuery("""//input[@type='submit']""") + val newURL = currentUrl + // In case of successful submit the new URL looks like + // http://localhost:8016/user_mgt/reset_password/IXIU0TVDUCKOH3RWEJF0XSSHNKSEFTQT?action=set + newURL.contains("user_mgt/reset_password") + } + } } @@ -163,11 +176,17 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { val b = UserInvitationAtBrowser() val pageUrl = (baseRequest / "user-invitation" < Date: Wed, 18 Sep 2024 08:40:45 +0200 Subject: [PATCH 9/9] test/Add advanced selenium tests regarding user invitation GUI flow --- .../v4_0_0/UserInvitationApiAndGuiTest.scala | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala index 891bf88690..85003d8091 100644 --- a/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala +++ b/obp-api/src/test/scala/code/api/v4_0_0/UserInvitationApiAndGuiTest.scala @@ -53,7 +53,7 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { firstNameOk && lastNameOk && emailOk && companyNameOk && countryOk } } - def checkSubmitButtonDisabled(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + def checkSubmitButtonDisabled(loginPage: String): Box[Boolean] = { tryo { go.to(loginPage) val consentForCollectingCheckbox = checkbox("consent_for_collecting_checkbox").isSelected @@ -65,7 +65,7 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { !button.isEnabled } } - def checkSubmitButtonDisabled2(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + def checkSubmitButtonDisabled2(loginPage: String): Box[Boolean] = { tryo { go.to(loginPage) checkbox("user_invitation_privacy_checkbox").select @@ -78,7 +78,7 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { !button.isEnabled } } - def checkSubmitButtonDisabled3(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + def checkSubmitButtonDisabled3(loginPage: String): Box[Boolean] = { tryo { go.to(loginPage) checkbox("user_invitation_terms_checkbox").select @@ -91,7 +91,7 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { !button.isEnabled } } - def checkSubmitButtonEnabled(loginPage: String, userInvitation: UserInvitation): Box[Boolean] = { + def checkSubmitButtonEnabled(loginPage: String): Box[Boolean] = { tryo { go.to(loginPage) checkbox("consent_for_collecting_checkbox").select @@ -106,17 +106,36 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { button.isEnabled } } - def submitForm(loginPage: String, userInvitation: UserInvitation): Box[Boolean]= { + def submitForm(loginPage: String, invitation: UserInvitation): Box[Boolean]= { tryo { go.to(loginPage) checkbox("consent_for_collecting_checkbox").select checkbox("user_invitation_privacy_checkbox").select checkbox("user_invitation_terms_checkbox").select click on XPathQuery("""//input[@type='submit']""") - val newURL = currentUrl + val setPasswordUrl = currentUrl // In case of successful submit the new URL looks like // http://localhost:8016/user_mgt/reset_password/IXIU0TVDUCKOH3RWEJF0XSSHNKSEFTQT?action=set - newURL.contains("user_mgt/reset_password") + if(setPasswordUrl.contains("user_mgt/reset_password") && setPasswordUrl.contains("action=set")) { + // Set a new password + val password = "Mjsjssj2odjd#" + val passwordField = IdQuery("password").webElement + passwordField.clear() + passwordField.sendKeys(password) + // Repeat the password value + val repeatPasswordField = IdQuery("repeatpassword").webElement + repeatPasswordField.clear() + repeatPasswordField.sendKeys(password) + // Submit the form + click on XPathQuery("""//input[@type='submit']""") + // After the password is set the page is redirected to the home page i.e. http://localhost:8016/ + // and the user is logged in + val span = driver.findElementById("loggedIn-username") + loginPage.contains(currentUrl) && + span.getText == s"${invitation.firstName.toLowerCase()}.${invitation.lastName.toLowerCase()}" + } else { + false + } } } @@ -178,10 +197,10 @@ class UserInvitationApiAndGuiTest extends V400ServerSetup { val pageUrl = (baseRequest / "user-invitation" <