Merge remote-tracking branch 'Simon/develop' into develop

# Conflicts:
#	obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala
#	obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala
This commit is contained in:
hongwei 2022-09-29 09:30:20 +02:00
commit cb9a3fd1c3
8 changed files with 707 additions and 6 deletions

View File

@ -158,6 +158,12 @@ paginator.displayingrecords = Mostrando %s-%s de %s
open_bank_project_is = Open Bank Project es
and_commercial_licenses = TESOBE y licencias comerciales
#FAQS
what.is.the.correct.base.URL.for.this.instance = \u00bfCu\u00e1l es la URL base correcta para esta caja de arena (Sandbox)?
The.base.URL.is = La URL base es
Please.make.sure.you.are.using.this.in.all.your.API.calls = Por favor, aseg\u00farese de que utiliza esto en todas sus llamadas a la API
# Country names
country_1 = United States
country_2 = Afghanistan

View File

@ -4102,6 +4102,14 @@ object SwaggerDefinitionsJSON {
branch_id = branchIdExample.value,
account_routings = List(accountRoutingJsonV121)
)
val createAccountRequestJsonV500 = CreateAccountRequestJsonV500(
user_id = Some(userIdExample.value),
label = labelExample.value,
product_code = productCodeExample.value,
balance = Some(amountOfMoneyJsonV121),
branch_id = Some(branchIdExample.value),
account_routings = Some(List(accountRoutingJsonV121))
)
val settlementAccountRequestJson = SettlementAccountRequestJson(
user_id = userIdExample.value,
@ -4639,6 +4647,14 @@ object SwaggerDefinitionsJSON {
description = descriptionExample.value,
meta = metaJson,
)
val putProductJsonV500 = PutProductJsonV500(
parent_product_code = parentProductCodeExample.value,
name = productNameExample.value,
more_info_url = Some(moreInfoUrlExample.value),
terms_and_conditions_url = Some(termsAndConditionsUrlExample.value),
description = Some(descriptionExample.value),
meta = Some(metaJson)
)
val createMessageJsonV400 = CreateMessageJsonV400(
message = messageExample.value,

View File

@ -12,23 +12,25 @@ import code.api.v2_1_0.JSONFactory210
import code.api.v3_0_0.JSONFactory300
import code.api.v3_1_0._
import code.api.v4_0_0.JSONFactory400.createCustomersMinimalJson
import code.api.v4_0_0.{JSONFactory400, PutProductJsonV400}
import code.api.v5_0_0.JSONFactory500.createPhysicalCardJson
import code.bankconnectors.Connector
import code.consent.{ConsentRequests, Consents}
import code.entitlement.Entitlement
import code.model.dataAccess.BankAccountCreation
import code.transactionrequests.TransactionRequests.TransactionRequestTypes.{apply => _}
import code.util.Helper
import code.views.Views
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.ExecutionContext.Implicits.global
import com.openbankproject.commons.model.enums.StrongCustomerAuthentication
import com.openbankproject.commons.model.{AccountId, BankId, BankIdAccountId, CardAction, CardAttributeCommons, CardCollectionInfo, CardPostedInfo, CardReplacementInfo, CardReplacementReason, CreditLimit, CreditRating, CustomerFaceImage, PinResetInfo, PinResetReason, TransactionRequestType, UserAuthContextUpdateStatus, View, ViewId}
import com.openbankproject.commons.model.{AccountId, AccountRouting, BankId, BankIdAccountId, CardAction, CardAttributeCommons, CardCollectionInfo, CardPostedInfo, CardReplacementInfo, CardReplacementReason, CreditLimit, CreditRating, CustomerFaceImage, PinResetInfo, PinResetReason, ProductCode, TransactionRequestType, UserAuthContextUpdateStatus, View, ViewId}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.common.Full
import net.liftweb.common.{Empty, Full}
import net.liftweb.http.Req
import net.liftweb.http.rest.RestHelper
import net.liftweb.json
import net.liftweb.json.compactRender
import net.liftweb.json.{Extraction, compactRender, prettyRender}
import net.liftweb.util.Helpers.tryo
import net.liftweb.util.Props
@ -244,6 +246,144 @@ trait APIMethods500 {
}
}
}
resourceDocs += ResourceDoc(
createAccount,
implementedInApiVersion,
"createAccount",
"PUT",
"/banks/BANK_ID/accounts/ACCOUNT_ID",
"Create Account",
"""Create Account at bank specified by BANK_ID with Id specified by ACCOUNT_ID.
|
|The User can create an Account for themself - or - the User that has the USER_ID specified in the POST body.
|
|If the PUT body USER_ID *is* specified, the logged in user must have the Role canCreateAccount. Once created, the Account will be owned by the User specified by USER_ID.
|
|If the PUT body USER_ID is *not* specified, the account will be owned by the logged in User.
|
|The 'product_code' field SHOULD be a product_code from Product.
|If the 'product_code' matches a product_code from Product, account attributes will be created that match the Product Attributes.
|
|Note: The Amount MUST be zero.""".stripMargin,
createAccountRequestJsonV500,
createAccountResponseJsonV310,
List(
InvalidJsonFormat,
BankNotFound,
UserNotLoggedIn,
InvalidUserId,
InvalidAccountIdFormat,
InvalidBankIdFormat,
UserNotFoundById,
UserHasMissingRoles,
InvalidAccountBalanceAmount,
InvalidAccountInitialBalance,
InitialBalanceMustBeZero,
InvalidAccountBalanceCurrency,
AccountIdAlreadyExists,
UnknownError
),
List(apiTagAccount,apiTagOnboarding, apiTagNewStyle),
Some(List(canCreateAccount))
)
lazy val createAccount : OBPEndpoint = {
// Create a new account
case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: Nil JsonPut json -> _ => {
cc =>{
for {
(Full(u), callContext) <- authenticatedAccess(cc)
(account, callContext) <- Connector.connector.vend.checkBankAccountExists(bankId, accountId, callContext)
_ <- Helper.booleanToFuture(AccountIdAlreadyExists, cc=callContext){
account.isEmpty
}
failMsg = s"$InvalidJsonFormat The Json body should be the ${prettyRender(Extraction.decompose(createAccountRequestJsonV310))} "
createAccountJson <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[CreateAccountRequestJsonV500]
}
loggedInUserId = u.userId
userIdAccountOwner = createAccountJson.user_id match {
case Some(userId) => userId
case _ => loggedInUserId
}
_ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext){
isValidID(accountId.value)
}
_ <- Helper.booleanToFuture(InvalidBankIdFormat, cc=callContext){
isValidID(accountId.value)
}
(postedOrLoggedInUser,callContext) <- NewStyle.function.findByUserId(userIdAccountOwner, callContext)
// User can create account for self or an account for another user if they have CanCreateAccount role
_ <- Helper.booleanToFuture(InvalidAccountIdFormat, cc=callContext){
isValidID(accountId.value)
}
_ <- {userIdAccountOwner == loggedInUserId} match {
case true => Future.successful(Full(Unit))
case false =>
NewStyle.function.hasEntitlement(
bankId.value,
loggedInUserId,
canCreateAccount,
callContext,
s"${UserHasMissingRoles} $canCreateAccount or create account for self"
)
}
initialBalanceAsString = createAccountJson.balance.map(_.amount).getOrElse("0")
accountType = createAccountJson.product_code
accountLabel = createAccountJson.label
initialBalanceAsNumber <- NewStyle.function.tryons(InvalidAccountInitialBalance, 400, callContext) {
BigDecimal(initialBalanceAsString)
}
_ <- Helper.booleanToFuture(InitialBalanceMustBeZero, cc=callContext){0 == initialBalanceAsNumber}
_ <- Helper.booleanToFuture(InvalidISOCurrencyCode, cc=callContext){isValidCurrencyISOCode(createAccountJson.balance.map(_.currency).getOrElse("EUR"))}
currency = createAccountJson.balance.map(_.currency).getOrElse("EUR")
(_, callContext ) <- NewStyle.function.getBank(bankId, callContext)
_ <- Helper.booleanToFuture(s"$InvalidAccountRoutings Duplication detected in account routings, please specify only one value per routing scheme", 400, cc=callContext){
createAccountJson.account_routings.getOrElse(Nil).map(_.scheme).distinct.size == createAccountJson.account_routings.getOrElse(Nil).size
}
alreadyExistAccountRoutings <- Future.sequence(createAccountJson.account_routings.getOrElse(Nil).map(accountRouting =>
NewStyle.function.getAccountRouting(Some(bankId), accountRouting.scheme, accountRouting.address, callContext).map(_ => Some(accountRouting)).fallbackTo(Future.successful(None))
))
alreadyExistingAccountRouting = alreadyExistAccountRoutings.collect {
case Some(accountRouting) => s"bankId: $bankId, scheme: ${accountRouting.scheme}, address: ${accountRouting.address}"
}
_ <- Helper.booleanToFuture(s"$AccountRoutingAlreadyExist (${alreadyExistingAccountRouting.mkString("; ")})", cc=callContext) {
alreadyExistingAccountRouting.isEmpty
}
(bankAccount,callContext) <- NewStyle.function.createBankAccount(
bankId,
accountId,
accountType,
accountLabel,
currency,
initialBalanceAsNumber,
postedOrLoggedInUser.name,
createAccountJson.branch_id.getOrElse(""),
createAccountJson.account_routings.getOrElse(Nil).map(r => AccountRouting(r.scheme, r.address)),
callContext
)
(productAttributes, callContext) <- NewStyle.function.getProductAttributesByBankAndCode(bankId, ProductCode(accountType), callContext)
(accountAttributes, callContext) <- NewStyle.function.createAccountAttributes(
bankId,
accountId,
ProductCode(accountType),
productAttributes,
None,
callContext: Option[CallContext]
)
} yield {
//1 Create or Update the `Owner` for the new account
//2 Add permission to the user
//3 Set the user as the account holder
BankAccountCreation.setAccountHolderAndRefreshUserAccountAccess(bankId, accountId, postedOrLoggedInUser, callContext)
(JSONFactory310.createAccountJSON(userIdAccountOwner, bankAccount, accountAttributes), HttpCode.`201`(callContext))
}
}
}
}
staticResourceDocs += ResourceDoc(
@ -1021,6 +1161,86 @@ trait APIMethods500 {
}
}
staticResourceDocs += ResourceDoc(
createProduct,
implementedInApiVersion,
nameOf(createProduct),
"PUT",
"/banks/BANK_ID/products/PRODUCT_CODE",
"Create Product",
s"""Create or Update Product for the Bank.
|
|
|Typical Super Family values / Asset classes are:
|
|Debt
|Equity
|FX
|Commodity
|Derivative
|
|$productHiearchyAndCollectionNote
|
|
|${authenticationRequiredMessage(true) }
|
|
|""",
putProductJsonV500,
productJsonV400.copy(attributes = None, fees = None),
List(
$UserNotLoggedIn,
$BankNotFound,
UserHasMissingRoles,
UnknownError
),
List(apiTagProduct, apiTagNewStyle),
Some(List(canCreateProduct, canCreateProductAtAnyBank))
)
lazy val createProduct: OBPEndpoint = {
case "banks" :: BankId(bankId) :: "products" :: ProductCode(productCode) :: Nil JsonPut json -> _ => {
cc =>
for {
(Full(u), callContext) <- SS.user
_ <- NewStyle.function.hasAtLeastOneEntitlement(failMsg = createProductEntitlementsRequiredText)(bankId.value, u.userId, createProductEntitlements, callContext)
failMsg = s"$InvalidJsonFormat The Json body should be the $PutProductJsonV400 "
product <- NewStyle.function.tryons(failMsg, 400, callContext) {
json.extract[PutProductJsonV500]
}
parentProductCode <- product.parent_product_code.trim.nonEmpty match {
case false =>
Future(Empty)
case true =>
Future(Connector.connector.vend.getProduct(bankId, ProductCode(product.parent_product_code))) map {
getFullBoxOrFail(_, callContext, ParentProductNotFoundByProductCode + " {" + product.parent_product_code + "}", 400)
}
}
success <- Future(Connector.connector.vend.createOrUpdateProduct(
bankId = bankId.value,
code = productCode.value,
parentProductCode = parentProductCode.map(_.code.value).toOption,
name = product.name,
category = null,
family = null,
superFamily = null,
moreInfoUrl = product.more_info_url.getOrElse(""),
termsAndConditionsUrl = product.terms_and_conditions_url.getOrElse(""),
details = null,
description = product.description.getOrElse(""),
metaLicenceId = product.meta.map(_.license.id).getOrElse(""),
metaLicenceName = product.meta.map(_.license.name).getOrElse("")
)) map {
connectorEmptyResponse(_, callContext)
}
} yield {
(JSONFactory400.createProductJson(success), HttpCode.`201`(callContext))
}
}
}
staticResourceDocs += ResourceDoc(
addCardForBank,
implementedInApiVersion,

View File

@ -29,6 +29,7 @@ package code.api.v5_0_0
import java.util.Date
import code.api.util.APIUtil.{stringOptionOrNull, stringOrNull}
import code.api.v1_2_1.BankRoutingJsonV121
import code.api.v1_4_0.JSONFactory1_4_0.{CustomerFaceImageJson, MetaJsonV140}
import code.api.v1_3_0.JSONFactory1_3_0.{cardActionsToString, createAccountJson, createPinResetJson, createReplacementJson}
import code.api.v1_3_0.{PinResetJSON, ReplacementJSON}
import code.api.v1_4_0.JSONFactory1_4_0.CustomerFaceImageJson
@ -61,6 +62,15 @@ case class BankJson500(
attributes: Option[List[BankAttributeBankResponseJsonV400]]
)
case class CreateAccountRequestJsonV500(
user_id : Option[String],
label : String,
product_code : String,
balance : Option[AmountOfMoneyJsonV121],
branch_id : Option[String],
account_routings: Option[List[AccountRoutingJsonV121]]
)
case class PostCustomerJsonV500(
legal_name: String,
mobile_phone_number: String,
@ -81,6 +91,15 @@ case class PostCustomerJsonV500(
name_suffix: Option[String] = None
)
case class PutProductJsonV500(
parent_product_code: String,
name: String,
more_info_url: Option[String] = None,
terms_and_conditions_url: Option[String] = None,
description: Option[String] = None,
meta: Option[MetaJsonV140] = None,
)
case class UserAuthContextJsonV500(
user_auth_context_id: String,
user_id: String,

View File

@ -6,14 +6,14 @@
<hr/>
<h3>
<button aria-expanded="false" onclick="mouseClick(this)" onKeyDown="EnterKeyPressed(this)">
What is the correct base URL for this sandbox?
<lift:loc locid="what.is.the.correct.base.URL.for.this.instance">What is the correct base URL for this sandbox5?</lift:loc>
</button>
</h3>
<p id="main-faq-item0" class="collapse">
The base URL is<br/>
<lift:loc locid="The.base.URL.is">The base URL is</lift:loc><br/>
<a class="api-link" data-lift="WebUI.apiLink"
href="">http://apisandbox.openbankproject.com</a><br/>
Please make sure you are using this in all your API calls
<lift:loc locid="Please.make.sure.you.are.using.this.in.all.your.API.calls">Please make sure you are using this in all your API calls</lift:loc><br/>
</p>
<img src="/media/images/icons/chevron_down_thick.svg" alt="">
</div>

View File

@ -0,0 +1,244 @@
package code.api.v5_0_0
import java.util.UUID
import java.util.concurrent.TimeUnit
import code.api.Constant
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.util.APIUtil.OAuth._
import code.api.util.APIUtil.extractErrorMessageCode
import code.api.util.ApiRole
import code.api.util.ErrorMessages.{UserHasMissingRoles, UserNotLoggedIn}
import code.api.v2_0_0.BasicAccountJSON
import code.api.v2_0_0.OBPAPI2_0_0.Implementations2_0_0
import code.api.v3_0_0.CoreAccountsJsonV300
import code.api.v3_0_0.OBPAPI3_0_0.Implementations3_0_0
import code.api.v3_1_0.CreateAccountResponseJsonV310
import code.api.v4_0_0.{AccountsBalancesJsonV400, ModeratedCoreAccountJsonV400}
import code.api.v5_0_0.OBPAPI5_0_0.Implementations5_0_0
import code.entitlement.Entitlement
import code.setup.DefaultUsers
import com.github.dwickern.macros.NameOf.nameOf
import com.openbankproject.commons.model.enums.AccountRoutingScheme
import com.openbankproject.commons.model.{AccountRoutingJsonV121, AmountOfMoneyJsonV121, ErrorMessage}
import com.openbankproject.commons.util.ApiVersion
import net.liftweb.json.Serialization.write
import org.scalatest.Tag
import scala.util.Random
class AccountTest extends V500ServerSetup with DefaultUsers {
object VersionOfApi extends Tag(ApiVersion.v5_0_0.toString)
object ApiEndpoint2 extends Tag(nameOf(Implementations5_0_0.createAccount))
//We need this endpoint to test the result
object ApiEndpoint4 extends Tag(nameOf(Implementations3_0_0.corePrivateAccountsAllBanks))
object ApiEndpoint5 extends Tag(nameOf(Implementations2_0_0.getPrivateAccountsAtOneBank))
object ApiEndpoint6 extends Tag(nameOf(Implementations3_0_0.getPrivateAccountById))
lazy val testBankId = testBankId1
lazy val putCreateAccountJSONV310 = SwaggerDefinitionsJSON.createAccountRequestJsonV310.copy(user_id = resourceUser1.userId, balance = AmountOfMoneyJsonV121("EUR","0"))
lazy val putCreateAccountOtherUserJsonV310 = SwaggerDefinitionsJSON.createAccountRequestJsonV310
.copy(user_id = resourceUser2.userId, balance = AmountOfMoneyJsonV121("EUR","0"),
account_routings = List(AccountRoutingJsonV121(Random.nextString(10), Random.nextString(10))))
val userAccountId = UUID.randomUUID.toString
val user2AccountId = UUID.randomUUID.toString
feature(s"Create Account $VersionOfApi - Unauthorized access") {
scenario("We will call the endpoint without user credentials", ApiEndpoint2, VersionOfApi) {
When(s"We make a request $VersionOfApi")
val request310 = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / "ACCOUNT_ID" ).PUT
val response310 = makePutRequest(request310, write(putCreateAccountJSONV310))
Then("We should get a 401")
response310.code should equal(401)
And("error should be " + UserNotLoggedIn)
response310.body.extract[ErrorMessage].message should equal (UserNotLoggedIn)
}
}
feature(s"Create Account $VersionOfApi - Authorized access") {
scenario("We will call the endpoint with user credentials", ApiEndpoint2, VersionOfApi) {
When(s"We make a request $VersionOfApi")
Entitlement.entitlement.vend.addEntitlement(testBankId.value, resourceUser1.userId, ApiRole.canCreateAccount.toString())
val request = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / "TEST_ACCOUNT_ID" ).PUT <@(user1)
val response = makePutRequest(request, write(putCreateAccountJSONV310))
Then("We should get a 201")
response.code should equal(201)
//for create account endpoint, we need to wait for `setAccountHolderAndRefreshUserAccountAccess` method,
//it is an asynchronous process, need some time to be done.
TimeUnit.SECONDS.sleep(2)
val account = response.body.extract[CreateAccountResponseJsonV310]
account.product_code should be (putCreateAccountJSONV310.product_code)
account.`label` should be (putCreateAccountJSONV310.`label`)
account.balance.amount.toDouble should be (putCreateAccountJSONV310.balance.amount.toDouble)
account.balance.currency should be (putCreateAccountJSONV310.balance.currency)
account.branch_id should be (putCreateAccountJSONV310.branch_id)
account.user_id should be (putCreateAccountJSONV310.user_id)
account.label should be (putCreateAccountJSONV310.label)
account.account_routings should be (putCreateAccountJSONV310.account_routings)
//We need to waite some time for the account creation, because we introduce `AuthUser.refreshUser(user, callContext)`
//It may not finished when we call the get accounts directly.
TimeUnit.SECONDS.sleep(2)
Then(s"we call $ApiEndpoint4 to get the account back")
val requestApiEndpoint4 = (v5_0_0_Request / "my" / "accounts" ).PUT <@(user1)
val responseApiEndpoint4 = makeGetRequest(requestApiEndpoint4)
responseApiEndpoint4.code should equal(200)
val accounts = responseApiEndpoint4.body.extract[CoreAccountsJsonV300].accounts
accounts.map(_.id).toList.toString() contains(account.account_id) should be (true)
Then(s"we call $ApiEndpoint5 to get the account back")
val requestApiEndpoint5 = (v5_0_0_Request /"banks" / testBankId.value / "accounts").GET <@ (user1)
val responseApiEndpoint5 = makeGetRequest(requestApiEndpoint5)
Then("We should get a 200")
responseApiEndpoint5.code should equal(200)
responseApiEndpoint5.body.extract[List[BasicAccountJSON]].toList.toString() contains(account.account_id) should be (true)
val requestGetApiEndpoint3 = (v5_0_0_Request / "banks" / testBankId.value / "balances").GET <@ (user1)
val responseGetApiEndpoint3 = makeGetRequest(requestGetApiEndpoint3)
responseGetApiEndpoint3.code should equal(200)
responseGetApiEndpoint3.body.extract[AccountsBalancesJsonV400].accounts.map(_.account_id) contains(account.account_id) should be (true)
Then(s"We make a request $VersionOfApi but with other user")
val request500WithNewAccountId = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / "TEST_ACCOUNT_ID2" ).PUT <@(user2)
val responseWithNoRole = makePutRequest(request500WithNewAccountId, write(putCreateAccountOtherUserJsonV310))
Then("We should get a 403 and some error message")
responseWithNoRole.code should equal(403)
responseWithNoRole.body.toString contains(extractErrorMessageCode(UserHasMissingRoles)) should be (true)
Then("We grant the roles and test it again")
Entitlement.entitlement.vend.addEntitlement(testBankId.value, resourceUser2.userId, ApiRole.canCreateAccount.toString)
val responseWithOtherUserV500 = makePutRequest(request500WithNewAccountId, write(putCreateAccountOtherUserJsonV310))
val account2 = responseWithOtherUserV500.body.extract[CreateAccountResponseJsonV310]
account2.product_code should be (putCreateAccountOtherUserJsonV310.product_code)
account2.`label` should be (putCreateAccountOtherUserJsonV310.`label`)
account2.balance.amount.toDouble should be (putCreateAccountOtherUserJsonV310.balance.amount.toDouble)
account2.balance.currency should be (putCreateAccountOtherUserJsonV310.balance.currency)
account2.branch_id should be (putCreateAccountOtherUserJsonV310.branch_id)
account2.user_id should be (putCreateAccountOtherUserJsonV310.user_id)
account2.label should be (putCreateAccountOtherUserJsonV310.label)
account2.account_routings should be (putCreateAccountOtherUserJsonV310.account_routings)
}
scenario("Create new account will have system owner view, and other use also have the system owner view should not get the account back", ApiEndpoint2, VersionOfApi) {
When(s"We make a request $VersionOfApi")
Entitlement.entitlement.vend.addEntitlement(testBankId.value, resourceUser1.userId, ApiRole.canCreateAccount.toString)
val request500 = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / userAccountId ).PUT <@(user1)
val putCreateAccountJson = putCreateAccountJSONV310.copy(account_routings = List(AccountRoutingJsonV121("AccountNumber", "15649885656")))
val response500 = makePutRequest(request500, write(putCreateAccountJson))
Then("We should get a 201")
response500.code should equal(201)
//for create account endpoint, we need to wait for `setAccountHolderAndRefreshUserAccountAccess` method,
//it is an asynchronous process, need some time to be done.
TimeUnit.SECONDS.sleep(2)
val account = response500.body.extract[CreateAccountResponseJsonV310]
account.product_code should be (putCreateAccountJson.product_code)
account.`label` should be (putCreateAccountJson.`label`)
account.balance.amount.toDouble should be (putCreateAccountJson.balance.amount.toDouble)
account.balance.currency should be (putCreateAccountJson.balance.currency)
account.branch_id should be (putCreateAccountJson.branch_id)
account.user_id should be (putCreateAccountJson.user_id)
account.label should be (putCreateAccountJson.label)
account.account_routings should be (putCreateAccountJson.account_routings)
Then(s"we call $ApiEndpoint6 to get the account back")
val requestApiEndpoint6 = (v5_0_0_Request /"banks" / testBankId.value / "accounts" / userAccountId / Constant.SYSTEM_OWNER_VIEW_ID/ "account" ).GET <@(user1)
val responseApiEndpoint6 = makeGetRequest(requestApiEndpoint6)
responseApiEndpoint6.code should equal(200)
val accountEndpoint6 = responseApiEndpoint6.body.extract[ModeratedCoreAccountJsonV400]
accountEndpoint6.id should be (userAccountId)
accountEndpoint6.label should be (account.label)
Then(s"we prepare the user2 will create a new account ($ApiEndpoint2)and he will have system view, and to call get account ($ApiEndpoint6) and compare the result.")
Entitlement.entitlement.vend.addEntitlement(testBankId.value, resourceUser2.userId, ApiRole.canCreateAccount.toString)
val requestUser2_500 = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / user2AccountId ).PUT <@(user2)
val responseUser2_500 = makePutRequest(requestUser2_500, write(putCreateAccountJSONV310.copy(user_id = resourceUser2.userId, balance = AmountOfMoneyJsonV121("EUR","0"))))
Then("We should get a 201")
responseUser2_500.code should equal(201)
//for create account endpoint, we need to wait for `setAccountHolderAndRefreshUserAccountAccess` method,
//it is an asynchronous process, need some time to be done.
TimeUnit.SECONDS.sleep(2)
Then(s"we call $ApiEndpoint6 to get the account back by user2")
val requestApiUser2Endpoint6 = (v5_0_0_Request /"banks" / testBankId.value / "accounts" / userAccountId / Constant.SYSTEM_OWNER_VIEW_ID/ "account" ).GET <@(user2)
val responseApiUser2Endpoint6 = makeGetRequest(requestApiUser2Endpoint6)
//This mean, the user2 can not get access to user1's account!
responseApiUser2Endpoint6.code should not equal(200)
}
scenario("Create new account with an already existing routing scheme/address should not create the account", ApiEndpoint2, VersionOfApi) {
When(s"We make a request $VersionOfApi to create the first account")
Entitlement.entitlement.vend.addEntitlement(testBankId.value, resourceUser1.userId, ApiRole.canCreateAccount.toString)
val request310_1 = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / "TEST_ACCOUNT_ID_1" ).PUT <@(user1)
val response310_1 = makePutRequest(request310_1, write(putCreateAccountJSONV310))
Then("We should get a 201")
response310_1.code should equal(201)
//for create account endpoint, we need to wait for `setAccountHolderAndRefreshUserAccountAccess` method,
//it is an asynchronous process, need some time to be done.
TimeUnit.SECONDS.sleep(2)
val account = response310_1.body.extract[CreateAccountResponseJsonV310]
account.product_code should be (putCreateAccountJSONV310.product_code)
account.`label` should be (putCreateAccountJSONV310.`label`)
account.balance.amount.toDouble should be (putCreateAccountJSONV310.balance.amount.toDouble)
account.balance.currency should be (putCreateAccountJSONV310.balance.currency)
account.branch_id should be (putCreateAccountJSONV310.branch_id)
account.user_id should be (putCreateAccountJSONV310.user_id)
account.label should be (putCreateAccountJSONV310.label)
account.account_routings should be (putCreateAccountJSONV310.account_routings)
When(s"We make a request $VersionOfApi to create the second account with an already existing scheme/address")
val request310_2 = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / user2AccountId ).PUT <@(user1)
val response310_2 = makePutRequest(request310_2, write(putCreateAccountJSONV310))
Then("We should get a 400 in the createAccount response")
response310_2.code should equal(400)
response310_2.body.toString should include("OBP-30115: Account Routing already exist.")
Then(s"The second account should not be created")
val requestApiGetAccount = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / user2AccountId / Constant.SYSTEM_OWNER_VIEW_ID / "account" ).GET <@(user1)
val responseApiGetAccount = makeGetRequest(requestApiGetAccount)
And("We should get a 404 in the getAccount response")
responseApiGetAccount.code should equal(404)
}
scenario("Create new account with a duplication in routing scheme should not create the account", ApiEndpoint2, VersionOfApi) {
When(s"We make a request $VersionOfApi to create the account")
Entitlement.entitlement.vend.addEntitlement(testBankId.value, resourceUser1.userId, ApiRole.canCreateAccount.toString)
val request500 = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / userAccountId ).PUT <@(user1)
val putCreateAccountJsonWithRoutingSchemeDuplication = putCreateAccountJSONV310.copy(account_routings =
List(AccountRoutingJsonV121(AccountRoutingScheme.IBAN.toString, Random.nextString(10)),
AccountRoutingJsonV121(AccountRoutingScheme.IBAN.toString, Random.nextString(10))))
val response500 = makePutRequest(request500, write(putCreateAccountJsonWithRoutingSchemeDuplication))
Then("We should get a 400 in the createAccount response")
response500.code should equal(400)
response500.body.toString should include ("Duplication detected in account routings, please specify only one value per routing scheme")
Then(s"The account should not be created")
val requestApiGetAccount = (v5_0_0_Request / "banks" / testBankId.value / "accounts" / userAccountId / Constant.SYSTEM_OWNER_VIEW_ID / "account" ).GET <@(user1)
val responseApiGetAccount = makeGetRequest(requestApiGetAccount)
And("We should get a 404 in the getAccount response")
responseApiGetAccount.code should equal(404)
}
}
}

View File

@ -0,0 +1,169 @@
/**
Open Bank Project - API
Copyright (C) 2011-2019, TESOBE GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Email: contact@tesobe.com
TESOBE GmbH
Osloerstrasse 16/17
Berlin 13359, Germany
This product includes software developed at
TESOBE (http://www.tesobe.com/)
*/
package code.api.v5_0_0
import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON
import code.api.util.ApiRole._
import code.api.util.APIUtil.OAuth._
import code.api.util.ErrorMessages._
import code.api.v4_0_0.OBPAPI4_0_0.Implementations4_0_0
import code.api.v5_0_0.OBPAPI5_0_0.Implementations5_0_0
import code.api.v4_0_0.{ProductJsonV400, ProductsJsonV400}
import code.entitlement.Entitlement
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
import scala.collection.immutable.Nil
class ProductTest extends V500ServerSetup {
override def beforeAll(): Unit = {
super.beforeAll()
}
override def afterAll(): Unit = {
super.afterAll()
}
/**
* 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.v5_0_0.toString)
object ApiEndpoint1 extends Tag(nameOf(Implementations5_0_0.createProduct))
object ApiEndpoint2 extends Tag(nameOf(Implementations4_0_0.getProduct))
object ApiEndpoint3 extends Tag(nameOf(Implementations4_0_0.getProducts))
lazy val testBankId = randomBankId
lazy val parentPutProductJsonV500: PutProductJsonV500 = SwaggerDefinitionsJSON.putProductJsonV500.copy(parent_product_code ="")
def createProduct(code: String, json: PutProductJsonV500): ProductJsonV400 = {
When("We try to create a product v4.0.0")
val request500 = (v5_0_0_Request / "banks" / testBankId / "products" / code).PUT <@ (user1)
val response500 = makePutRequest(request500, write(json))
Then("We should get a 201")
response500.code should equal(201)
val product = response500.body.extract[ProductJsonV400]
product.product_code shouldBe code
product.parent_product_code shouldBe json.parent_product_code
product.bank_id shouldBe testBankId
product.name shouldBe json.name
product.more_info_url shouldBe json.more_info_url.getOrElse("")
product.terms_and_conditions_url shouldBe json.terms_and_conditions_url.getOrElse("")
product.description shouldBe json.description.getOrElse("")
product
}
feature("Create Product v4.0.0") {
scenario("We will call the Add endpoint without a user credentials", ApiEndpoint1, VersionOfApi) {
When("We make a request v4.0.0")
val request400 = (v5_0_0_Request / "banks" / testBankId / "products" / "CODE").PUT
val response400 = makePutRequest(request400, write(parentPutProductJsonV500))
Then("We should get a 401")
response400.code should equal(401)
And("error should be " + UserNotLoggedIn)
response400.body.extract[ErrorMessage].message should equal (UserNotLoggedIn)
}
scenario("We will call the Add endpoint without a proper role", ApiEndpoint1, VersionOfApi) {
When("We make a request v4.0.0")
val request500 = (v5_0_0_Request / "banks" / testBankId / "products" / "CODE").PUT <@(user1)
val response500 = makePutRequest(request500, write(parentPutProductJsonV500))
Then("We should get a 403")
response500.code should equal(403)
val createProductEntitlements = canCreateProduct :: canCreateProductAtAnyBank :: Nil
val createProductEntitlementsRequiredText = UserHasMissingRoles + createProductEntitlements.mkString(" or ")
And("error should be " + createProductEntitlementsRequiredText)
response500.body.extract[ErrorMessage].message contains (createProductEntitlementsRequiredText) should be (true)
}
scenario("We will call the Add endpoint with user credentials and role", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(testBankId, resourceUser1.userId, CanCreateProduct.toString)
// Create an grandparent
val grandparent: ProductJsonV400 = createProduct(code = "GRANDPARENT_CODE", json = parentPutProductJsonV500)
// Create an parent
val product: ProductJsonV400 = createProduct(code = "PARENT_CODE", json = parentPutProductJsonV500.copy(parent_product_code = grandparent.product_code))
// Get
val requestGet400 = (v5_0_0_Request / "banks" / product.bank_id / "products" / product.product_code ).GET <@(user1)
val responseGet400 = makeGetRequest(requestGet400)
Then("We should get a 200")
responseGet400.code should equal(200)
val product1 = responseGet400.body.extract[ProductJsonV400]
// Create an child
val childPutProductJsonV400 = parentPutProductJsonV500.copy(parent_product_code = product.product_code)
createProduct(code = "PRODUCT_CODE", json = childPutProductJsonV400)
// Get
val requestGetAll400 = (v5_0_0_Request / "banks" / product.bank_id / "products").GET <@(user1)
val responseGetAll400 = makeGetRequest(requestGetAll400)
Then("We should get a 200")
responseGetAll400.code should equal(200)
val products: ProductsJsonV400 = responseGetAll400.body.extract[ProductsJsonV400]
products.products.size shouldBe 3
}
scenario("We will call the Add endpoint with user credentials and role and minimal PUT JSON", ApiEndpoint1, ApiEndpoint2, ApiEndpoint3, VersionOfApi) {
Entitlement.entitlement.vend.addEntitlement(testBankId, resourceUser1.userId, CanCreateProduct.toString)
// Create an grandparent
val grandparent: ProductJsonV400 = createProduct(
code = "GRANDPARENT_CODE",
json = PutProductJsonV500(
name = parentPutProductJsonV500.name,
parent_product_code = parentPutProductJsonV500.parent_product_code
)
)
// Create an parent
val product: ProductJsonV400 = createProduct(code = "PARENT_CODE", json = parentPutProductJsonV500.copy(parent_product_code = grandparent.product_code))
// Get
val requestGet400 = (v5_0_0_Request / "banks" / product.bank_id / "products" / product.product_code ).GET <@(user1)
val responseGet400 = makeGetRequest(requestGet400)
Then("We should get a 200")
responseGet400.code should equal(200)
val product1 = responseGet400.body.extract[ProductJsonV400]
// Create an child
val childPutProductJsonV400 = parentPutProductJsonV500.copy(parent_product_code = product.product_code)
createProduct(code = "PRODUCT_CODE", json = childPutProductJsonV400)
// Get
val requestGetAll400 = (v5_0_0_Request / "banks" / product.bank_id / "products").GET <@(user1)
val responseGetAll400 = makeGetRequest(requestGetAll400)
Then("We should get a 200")
responseGetAll400.code should equal(200)
val products: ProductsJsonV400 = responseGetAll400.body.extract[ProductsJsonV400]
products.products.size shouldBe 3
}
}
}

View File

@ -0,0 +1,27 @@
package code.api.v5_0_0
import code.api.v4_0_0.BanksJson400
import code.setup.{APIResponse, DefaultUsers, ServerSetupWithTestData}
import com.openbankproject.commons.util.ApiShortVersions
import dispatch.Req
import scala.util.Random.nextInt
trait V500ServerSetup extends ServerSetupWithTestData with DefaultUsers {
def v5_0_0_Request: Req = baseRequest / "obp" / "v5.0.0"
def dynamicEndpoint_Request: Req = baseRequest / "obp" / ApiShortVersions.`dynamic-endpoint`.toString
def dynamicEntity_Request: Req = baseRequest / "obp" / ApiShortVersions.`dynamic-entity`.toString
def randomBankId : String = {
def getBanksInfo : APIResponse = {
val request = v5_0_0_Request / "banks"
makeGetRequest(request)
}
val banksJson = getBanksInfo.body.extract[BanksJson400]
val randomPosition = nextInt(banksJson.banks.size)
val bank = banksJson.banks(randomPosition)
bank.id
}
}