From 669d09af7cee0653d33469d0db019b526383f0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Mili=C4=87?= Date: Thu, 22 Sep 2022 13:18:27 +0200 Subject: [PATCH] feature/Add endpoint createProduct v5.0.0 --- .../SwaggerDefinitionsJSON.scala | 8 + .../scala/code/api/v5_0_0/APIMethods500.scala | 85 ++++++++- .../code/api/v5_0_0/JSONFactory5.0.0.scala | 11 +- .../scala/code/api/v5_0_0/ProductTest.scala | 169 ++++++++++++++++++ .../code/api/v5_0_0/V500ServerSetup.scala | 27 +++ 5 files changed, 297 insertions(+), 3 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/v5_0_0/ProductTest.scala create mode 100644 obp-api/src/test/scala/code/api/v5_0_0/V500ServerSetup.scala diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index e2db4eeb5..114df800d 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -4623,6 +4623,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, diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index a842c9883..18296a01d 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -12,6 +12,7 @@ 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.bankconnectors.Connector import code.consent.{ConsentRequests, Consents} import code.entitlement.Entitlement @@ -21,9 +22,9 @@ 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.{BankId, CreditLimit, CreditRating, CustomerFaceImage, UserAuthContextUpdateStatus} +import com.openbankproject.commons.model.{BankId, CreditLimit, CreditRating, CustomerFaceImage, ProductCode, UserAuthContextUpdateStatus} 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 @@ -1019,6 +1020,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)) + } + } + } + + + } } diff --git a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala index ea8b8f4ef..0596e1e1e 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/JSONFactory5.0.0.scala @@ -30,7 +30,7 @@ import java.util.Date import code.api.util.APIUtil.stringOrNull import code.api.v1_2_1.BankRoutingJsonV121 -import code.api.v1_4_0.JSONFactory1_4_0.CustomerFaceImageJson +import code.api.v1_4_0.JSONFactory1_4_0.{CustomerFaceImageJson, MetaJsonV140} import code.api.v2_1_0.CustomerCreditRatingJSON import code.api.v3_1_0.PostConsentEntitlementJsonV310 import code.api.v4_0_0.BankAttributeBankResponseJsonV400 @@ -80,6 +80,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, diff --git a/obp-api/src/test/scala/code/api/v5_0_0/ProductTest.scala b/obp-api/src/test/scala/code/api/v5_0_0/ProductTest.scala new file mode 100644 index 000000000..324d9793a --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_0_0/ProductTest.scala @@ -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 . + +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 + } + } + + +} diff --git a/obp-api/src/test/scala/code/api/v5_0_0/V500ServerSetup.scala b/obp-api/src/test/scala/code/api/v5_0_0/V500ServerSetup.scala new file mode 100644 index 000000000..890112dbd --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_0_0/V500ServerSetup.scala @@ -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 + } + +} \ No newline at end of file