diff --git a/build.sbt b/build.sbt index dd7b6b681..add70f9ea 100644 --- a/build.sbt +++ b/build.sbt @@ -17,11 +17,18 @@ ThisBuild / semanticdbVersion := "4.13.9" // Fix dependency conflicts ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-xml" % VersionScheme.Always +ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-java8-compat" % VersionScheme.Always lazy val liftVersion = "3.5.0" lazy val akkaVersion = "2.5.32" lazy val jettyVersion = "9.4.50.v20221201" lazy val avroVersion = "1.8.2" +lazy val pekkoVersion = "1.4.0" +lazy val pekkoHttpVersion = "1.3.0" +lazy val http4sVersion = "0.23.30" +lazy val catsEffectVersion = "3.5.7" +lazy val ip4sVersion = "3.7.0" +lazy val jakartaMailVersion = "2.0.1" lazy val commonSettings = Seq( resolvers ++= Seq( @@ -42,8 +49,8 @@ lazy val obpCommons = (project in file("obp-commons")) "net.liftweb" %% "lift-util" % liftVersion, "net.liftweb" %% "lift-mapper" % liftVersion, "org.scala-lang" % "scala-reflect" % "2.12.20", - "org.scalatest" %% "scalatest" % "3.2.15" % Test, - "org.scalactic" %% "scalactic" % "3.2.15", + "org.scalatest" %% "scalatest" % "3.0.9" % Test, + "org.scalactic" %% "scalactic" % "3.0.9", "net.liftweb" %% "lift-json" % liftVersion, "com.alibaba" % "transmittable-thread-local" % "2.11.5", "org.apache.commons" % "commons-lang3" % "3.12.0", @@ -95,6 +102,20 @@ lazy val obpApi = (project in file("obp-api")) "com.typesafe.akka" %% "akka-remote" % akkaVersion, "com.typesafe.akka" %% "akka-slf4j" % akkaVersion, "com.typesafe.akka" %% "akka-http-core" % "10.1.6", + + // Pekko (ActorSystem + Pekko HTTP used by OBP runtime components) + "org.apache.pekko" %% "pekko-actor" % pekkoVersion, + "org.apache.pekko" %% "pekko-remote" % pekkoVersion, + "org.apache.pekko" %% "pekko-slf4j" % pekkoVersion, + "org.apache.pekko" %% "pekko-stream" % pekkoVersion, + "org.apache.pekko" %% "pekko-http" % pekkoHttpVersion, + + // http4s (v7.0.0 experimental stack) + "org.typelevel" %% "cats-effect" % catsEffectVersion, + "com.comcast" %% "ip4s-core" % ip4sVersion, + "org.http4s" %% "http4s-core" % http4sVersion, + "org.http4s" %% "http4s-dsl" % http4sVersion, + "org.http4s" %% "http4s-ember-server" % http4sVersion, // Avro "com.sksamuel.avro4s" %% "avro4s-core" % avroVersion, @@ -164,6 +185,9 @@ lazy val obpApi = (project in file("obp-api")) // RabbitMQ "com.rabbitmq" % "amqp-client" % "5.22.0", "net.liftmodules" %% "amqp_3.1" % "1.5.0", + + // Blockchain (Ethereum raw transaction decoding) + "org.web3j" % "core" % "4.14.0", // Elasticsearch "org.elasticsearch" % "elasticsearch" % "8.14.0", @@ -175,6 +199,7 @@ lazy val obpApi = (project in file("obp-api")) // Utilities "cglib" % "cglib" % "3.3.0", "com.sun.activation" % "jakarta.activation" % "1.2.2", + "com.sun.mail" % "jakarta.mail" % jakartaMailVersion, "com.nulab-inc" % "zxcvbn" % "1.9.0", // Testing - temporarily disabled due to version incompatibility @@ -192,7 +217,7 @@ lazy val obpApi = (project in file("obp-api")) // Test dependencies "junit" % "junit" % "4.13.2" % Test, - "org.scalatest" %% "scalatest" % "3.2.15" % Test, + "org.scalatest" %% "scalatest" % "3.0.9" % Test, "org.seleniumhq.selenium" % "htmlunit-driver" % "2.36.0" % Test, "org.testcontainers" % "rabbitmq" % "1.20.3" % Test ) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala index 1a5d8bebc..bec0f2ade 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocs140.scala @@ -149,6 +149,7 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md object ResourceDocs600 extends OBPRestHelper with ResourceDocsAPIMethods with MdcLoggable { val version: ApiVersion = ApiVersion.v6_0_0 val versionStatus = ApiVersionStatus.BLEEDING_EDGE.toString + override def includeTechnologyInResponse: Boolean = true val routes: Seq[OBPEndpoint] = List( ImplementationsResourceDocs.getResourceDocsObpV400, ImplementationsResourceDocs.getResourceDocsSwagger, @@ -206,7 +207,7 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) resourceDocsJson.resource_docs case _ => contentParam match { @@ -247,4 +248,4 @@ object ResourceDocs300 extends OBPRestHelper with ResourceDocsAPIMethods with Md } } -} \ No newline at end of file +} diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala index b355e782e..648800604 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/ResourceDocsAPIMethods.scala @@ -65,6 +65,8 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth // We add previous APIMethods so we have access to the Resource Docs self: OBPRestHelper => + def includeTechnologyInResponse: Boolean = false + val ImplementationsResourceDocs = new Object() { val localResourceDocs = ArrayBuffer[ResourceDoc]() @@ -346,7 +348,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth // Filter val rdFiltered = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, resourceDocTags, partialFunctionNames) // Format the data as json - JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher, locale) + JSONFactory1_4_0.createResourceDocsJson(rdFiltered, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) } } @@ -500,7 +502,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth NewStyle.function.tryons(s"$UnknownError Can not prepare OBP resource docs.", 500, callContext) { val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) val resourceDocsJsonJValue = Full(resourceDocsJsonToJsonResponse(resourceDocsJson)) resourceDocsJsonJValue.map(successJsonResponse(_)) } @@ -709,7 +711,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) resourceDocsJson.resource_docs case _ => contentParam match { @@ -903,7 +905,7 @@ trait ResourceDocsAPIMethods extends MdcLoggable with APIMethods220 with APIMeth case _ if (apiCollectionIdParam.isDefined) => val operationIds = MappedApiCollectionEndpointsProvider.getApiCollectionEndpoints(apiCollectionIdParam.getOrElse("")).map(_.operationId).map(getObpFormatOperationId) val resourceDocs = ResourceDoc.getResourceDocs(operationIds) - val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale) + val resourceDocsJson = JSONFactory1_4_0.createResourceDocsJson(resourceDocs, isVersion4OrHigher, locale, includeTechnology = includeTechnologyInResponse) resourceDocsJson.resource_docs case _ => contentParam match { @@ -1257,4 +1259,3 @@ so the caller must specify any required filtering by catalog explicitly. } - 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 6e22b45cc..96fc6cee8 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 @@ -1796,7 +1796,8 @@ object SwaggerDefinitionsJSON { lazy val implementedByJson = ImplementedByJson( version = "1_4_0", - function = "getBranches" + function = "getBranches", + technology = None ) // Used to describe the OBP API calls for documentation and API discovery purposes lazy val canCreateCustomerSwagger = CanCreateCustomer() diff --git a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala index db6f7ee9f..f47ddbf0b 100644 --- a/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala +++ b/obp-api/src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala @@ -328,7 +328,8 @@ object JSONFactory1_4_0 extends MdcLoggable{ // Used to describe where an API call is implemented case class ImplementedByJson ( version : String, // Short hand for the version e.g. "1_4_0" means Implementations1_4_0 - function : String // The val / partial function that implements the call e.g. "getBranches" + function : String, // The val / partial function that implements the call e.g. "getBranches" + technology: Option[String] = None ) case class ResourceDocMeta( @@ -525,11 +526,12 @@ object JSONFactory1_4_0 extends MdcLoggable{ locale: Option[String],// this will be in the cacheKey resourceDocUpdatedTags: ResourceDoc, isVersion4OrHigher:Boolean,// this will be in the cacheKey + includeTechnology: Boolean, // this will be in the cacheKey urlParametersI18n:String , jsonRequestBodyFieldsI18n:String, jsonResponseBodyFieldsI18n:String ): ResourceDocJson = { - val cacheKey = LOCALISED_RESOURCE_DOC_PREFIX + s"operationId:${operationId}-locale:$locale- isVersion4OrHigher:$isVersion4OrHigher".intern() + val cacheKey = LOCALISED_RESOURCE_DOC_PREFIX + s"operationId:${operationId}-locale:$locale- isVersion4OrHigher:$isVersion4OrHigher- includeTechnology:$includeTechnology".intern() Caching.memoizeSyncWithImMemory(Some(cacheKey))(CREATE_LOCALISED_RESOURCE_DOC_JSON_TTL.seconds) { val fieldsDescription = if (resourceDocUpdatedTags.tags.toString.contains("Dynamic-Entity") @@ -564,6 +566,13 @@ object JSONFactory1_4_0 extends MdcLoggable{ val summary = resourceDocUpdatedTags.summary.replaceFirst("""\.(\s*)$""", "$1") // remove the ending dot in summary val translatedSummary = I18NUtil.ResourceDocTranslation.translate(I18NResourceDocField.SUMMARY, resourceDocUpdatedTags.operationId, locale, summary) + val technology = + if (includeTechnology) { + Some(if (resourceDocUpdatedTags.http4sPartialFunction.isDefined) "http4s" else "lift") + } else { + None + } + val resourceDoc = ResourceDocJson( operation_id = resourceDocUpdatedTags.operationId, request_verb = resourceDocUpdatedTags.requestVerb, @@ -575,7 +584,11 @@ object JSONFactory1_4_0 extends MdcLoggable{ example_request_body = resourceDocUpdatedTags.exampleRequestBody, success_response_body = resourceDocUpdatedTags.successResponseBody, error_response_bodies = resourceDocUpdatedTags.errorResponseBodies, - implemented_by = ImplementedByJson(resourceDocUpdatedTags.implementedInApiVersion.fullyQualifiedVersion, resourceDocUpdatedTags.partialFunctionName), // was resourceDocUpdatedTags.implementedInApiVersion.noV + implemented_by = ImplementedByJson( + version = resourceDocUpdatedTags.implementedInApiVersion.fullyQualifiedVersion, + function = resourceDocUpdatedTags.partialFunctionName, + technology = technology + ), // was resourceDocUpdatedTags.implementedInApiVersion.noV tags = resourceDocUpdatedTags.tags.map(i => i.tag), typed_request_body = createTypedBody(resourceDocUpdatedTags.exampleRequestBody), typed_success_response_body = createTypedBody(resourceDocUpdatedTags.successResponseBody), @@ -592,7 +605,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ }} - def createLocalisedResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean, locale: Option[String], urlParametersI18n:String ,jsonRequestBodyFieldsI18n:String, jsonResponseBodyFieldsI18n:String) : ResourceDocJson = { + def createLocalisedResourceDocJson(rd: ResourceDoc, isVersion4OrHigher:Boolean, locale: Option[String], includeTechnology: Boolean, urlParametersI18n:String ,jsonRequestBodyFieldsI18n:String, jsonResponseBodyFieldsI18n:String) : ResourceDocJson = { // We MUST recompute all resource doc values due to translation via Web UI props --> now need to wait $CREATE_LOCALISED_RESOURCE_DOC_JSON_TTL seconds val userDefinedEndpointTags = getAllEndpointTagsBox(rd.operationId).map(endpointTag =>ResourceDocTag(endpointTag.tagName)) val resourceDocWithUserDefinedEndpointTags: ResourceDoc = rd.copy(tags = userDefinedEndpointTags++ rd.tags) @@ -602,6 +615,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ locale: Option[String], resourceDocWithUserDefinedEndpointTags, isVersion4OrHigher: Boolean, + includeTechnology: Boolean, urlParametersI18n: String, jsonRequestBodyFieldsI18n: String, jsonResponseBodyFieldsI18n: String @@ -609,7 +623,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ } - def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean, locale: Option[String]) : ResourceDocsJson = { + def createResourceDocsJson(resourceDocList: List[ResourceDoc], isVersion4OrHigher:Boolean, locale: Option[String], includeTechnology: Boolean = false) : ResourceDocsJson = { val urlParametersI18n = I18NUtil.ResourceDocTranslation.translate( I18NResourceDocField.URL_PARAMETERS, "resourceDocUrlParametersString_i180n", @@ -632,11 +646,11 @@ object JSONFactory1_4_0 extends MdcLoggable{ if(isVersion4OrHigher){ ResourceDocsJson( - resourceDocList.map(createLocalisedResourceDocJson(_,isVersion4OrHigher, locale, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields)), + resourceDocList.map(createLocalisedResourceDocJson(_,isVersion4OrHigher, locale, includeTechnology, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields)), meta=Some(ResourceDocMeta(new Date(), resourceDocList.length)) ) } else { - ResourceDocsJson(resourceDocList.map(createLocalisedResourceDocJson(_,false, locale, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields))) + ResourceDocsJson(resourceDocList.map(createLocalisedResourceDocJson(_,false, locale, includeTechnology, urlParametersI18n, jsonRequestBodyFields, jsonResponseBodyFields))) } } diff --git a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala index 229c61027..70f9d8884 100644 --- a/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala +++ b/obp-api/src/main/scala/code/api/v7_0_0/Http4s700.scala @@ -219,7 +219,7 @@ object Http4s700 { requestedApiVersion <- Future(ApiVersionUtils.valueOf(requestedApiVersionString)) resourceDocs = ResourceDocs140.ImplementationsResourceDocs.getResourceDocsList(requestedApiVersion).getOrElse(Nil) filteredDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs, tags, functions) - } yield JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam) + } yield JSONFactory1_4_0.createResourceDocsJson(filteredDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true) } } diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala new file mode 100644 index 000000000..5b753dd2e --- /dev/null +++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala @@ -0,0 +1,46 @@ +package code.api.ResourceDocs1_4_0 + +import code.setup.{PropsReset, ServerSetup} +import net.liftweb.json.JsonAST.{JArray, JNothing, JNull, JString} + +class ResourceDocsTechnologyTest extends ServerSetup with PropsReset { + + feature("ResourceDocs implemented_by.technology") { + + scenario("v6.0.0 resource-docs should include implemented_by.technology") { + setPropsValues("resource_docs_requires_role" -> "false") + + val request = (baseRequest / "obp" / "v6.0.0" / "resource-docs" / "v6.0.0" / "obp").GET + val response = makeGetRequest(request) + + response.code should equal(200) + (response.body \ "resource_docs") match { + case JArray(docs) => + val technology = docs.head \ "implemented_by" \ "technology" + technology should equal(JString("lift")) + case _ => + fail("Expected resource_docs field to be an array") + } + } + + scenario("v5.0.0 resource-docs should not include implemented_by.technology") { + setPropsValues("resource_docs_requires_role" -> "false") + + val request = (baseRequest / "obp" / "v5.0.0" / "resource-docs" / "v5.0.0" / "obp").GET + val response = makeGetRequest(request) + + response.code should equal(200) + (response.body \ "resource_docs") match { + case JArray(docs) => + val technology = docs.head \ "implemented_by" \ "technology" + technology match { + case JNothing | JNull => succeed + case _ => fail("Expected implemented_by.technology to be absent for v5.0.0 resource-docs") + } + case _ => + fail("Expected resource_docs field to be an array") + } + } + } +} + diff --git a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala index 7b7bce74f..19534d7dd 100644 --- a/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala +++ b/obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTest.scala @@ -68,7 +68,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with def stringToNodeSeq(html : String) : NodeSeq = { val newHtmlString =scala.xml.XML.loadString("
" + html + "
").toString() //Note: `parse` method: We much enclose the div, otherwise only the first element is returned. - Html5.parse(newHtmlString).head + Html5.parse(newHtmlString).headOption.getOrElse(NodeSeq.Empty) } @@ -79,6 +79,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] responseGetObp.code should equal(200) + responseDocs.resource_docs.head.implemented_by.technology shouldBe Some("lift") //This should not throw any exceptions responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } @@ -97,6 +98,7 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] responseGetObp.code should equal(200) + responseDocs.resource_docs.head.implemented_by.technology shouldBe None //This should not throw any exceptions responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } diff --git a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala index c5be0d68c..62484f93a 100644 --- a/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala +++ b/obp-api/src/test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala @@ -5,9 +5,9 @@ import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.usersJsonV400 import java.util.Date import code.api.util.APIUtil.ResourceDoc import code.api.util.{APIUtil, ExampleValue} +import code.api.util.CustomJsonFormats import code.api.v1_4_0.JSONFactory1_4_0.ResourceDocJson import code.api.v3_0_0.OBPAPI3_0_0 -import code.setup.DefaultUsers import net.liftweb.json.Extraction.decompose import net.liftweb.json._ import org.everit.json.schema.loader.SchemaLoader @@ -44,7 +44,8 @@ case class AllCases( jvalues: List[JValue]= List(APIUtil.defaultJValue) ) -class JSONFactory1_4_0Test extends V140ServerSetup with DefaultUsers { +class JSONFactory1_4_0Test extends code.setup.ServerSetup { + override implicit val formats: Formats = CustomJsonFormats.formats feature("Test JSONFactory1_4_0") { scenario("prepareDescription should work well, extract the parameters from URL") { @@ -116,7 +117,7 @@ class JSONFactory1_4_0Test extends V140ServerSetup with DefaultUsers { scenario("createResourceDocJson should work well, no exception is good enough") { val resourceDoc: ResourceDoc = OBPAPI3_0_0.allResourceDocs(5) val result: ResourceDocJson = JSONFactory1_4_0.createLocalisedResourceDocJson(resourceDoc,false, None, - urlParameters, "JSON request body fields:", "JSON response body fields:") + includeTechnology = false, urlParameters, "JSON request body fields:", "JSON response body fields:") } scenario("createResourceDocsJson should work well, no exception is good enough") { @@ -124,6 +125,21 @@ class JSONFactory1_4_0Test extends V140ServerSetup with DefaultUsers { val result = JSONFactory1_4_0.createResourceDocsJson(resourceDoc.toList, false, None) } + scenario("Technology field should be None unless includeTechnology=true") { + val liftDoc: ResourceDoc = OBPAPI3_0_0.allResourceDocs(0) + val json1 = JSONFactory1_4_0.createLocalisedResourceDocJson(liftDoc, false, None, includeTechnology = false, urlParameters, "JSON request body fields:", "JSON response body fields:") + json1.implemented_by.technology shouldBe None + + val json2 = JSONFactory1_4_0.createLocalisedResourceDocJson(liftDoc, false, None, includeTechnology = true, urlParameters, "JSON request body fields:", "JSON response body fields:") + json2.implemented_by.technology shouldBe Some("lift") + } + + scenario("Technology field should be http4s when includeTechnology=true and doc is http4s") { + val http4sDoc: ResourceDoc = code.api.v7_0_0.Http4s700.resourceDocs.head + val json = JSONFactory1_4_0.createLocalisedResourceDocJson(http4sDoc, true, None, includeTechnology = true, urlParameters, "JSON request body fields:", "JSON response body fields:") + json.implemented_by.technology shouldBe Some("http4s") + } + scenario("createTypedBody should work well, no exception is good enough") { val inputCaseClass = AllCases() val result = JSONFactory1_4_0.createTypedBody(inputCaseClass) diff --git a/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala b/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala index c43070d73..9ef5bff8b 100644 --- a/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala +++ b/obp-api/src/test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala @@ -246,8 +246,19 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { json match { case JObject(fields) => toFieldMap(fields).get("resource_docs") match { - case Some(JArray(_)) => - succeed + case Some(JArray(resourceDocs)) => + resourceDocs.exists { + case JObject(rdFields) => + toFieldMap(rdFields).get("implemented_by") match { + case Some(JObject(implFields)) => + toFieldMap(implFields).get("technology") match { + case Some(JString(value)) => value == "http4s" + case _ => false + } + case _ => false + } + case _ => false + } shouldBe true case _ => fail("Expected resource_docs field to be an array") } diff --git a/obp-api/src/test/scala/code/setup/DefaultUsers.scala b/obp-api/src/test/scala/code/setup/DefaultUsers.scala index 4f148ff39..220e0f356 100644 --- a/obp-api/src/test/scala/code/setup/DefaultUsers.scala +++ b/obp-api/src/test/scala/code/setup/DefaultUsers.scala @@ -106,28 +106,79 @@ trait DefaultUsers { // Create resource user, need provider val defaultProvider = Constant.HostName + private def getOrCreateResourceUser( + provider: String, + providerId: String, + createdByConsentId: Option[String], + name: String, + email: String, + userId: Option[String], + company: String + ): ResourceUser = { + UserX.findByProviderId(provider = provider, idGivenByProvider = providerId) + .map(_.asInstanceOf[ResourceUser]) + .getOrElse { + try { + UserX + .createResourceUser( + provider = provider, + providerId = Some(providerId), + createdByConsentId = createdByConsentId, + name = Some(name), + email = Some(email), + userId = userId, + company = Some(company) + ) + .openOrThrowException(attemptedToOpenAnEmptyBox) + } catch { + case e: Throwable => + UserX.findByProviderId(provider = provider, idGivenByProvider = providerId) + .map(_.asInstanceOf[ResourceUser]) + .getOrElse(throw e) + } + } + } + // create some resource user for test purposes - lazy val resourceUser1 = UserX.findByProviderId(provider = defaultProvider, idGivenByProvider= resourceUser1Name).map(_.asInstanceOf[ResourceUser]) - .getOrElse(UserX.createResourceUser(provider = defaultProvider, providerId= Some(resourceUser1Name), createdByConsentId= None, name= Some(resourceUser1Name), - email= Some("resourceUser1@123.com"), userId= userId1, company = Some("Tesobe GmbH")) - .openOrThrowException(attemptedToOpenAnEmptyBox) - ) + lazy val resourceUser1 = getOrCreateResourceUser( + provider = defaultProvider, + providerId = resourceUser1Name, + createdByConsentId = None, + name = resourceUser1Name, + email = "resourceUser1@123.com", + userId = userId1, + company = "Tesobe GmbH" + ) - lazy val resourceUser2 = UserX.findByProviderId(provider = defaultProvider, idGivenByProvider= resourceUser2Name).map(_.asInstanceOf[ResourceUser]) - .getOrElse(UserX.createResourceUser(provider = defaultProvider, providerId= Some(resourceUser2Name), createdByConsentId= None, - name= Some(resourceUser2Name),email= Some("resourceUser2@123.com"), userId= userId2, company = Some("Tesobe GmbH")) - .openOrThrowException(attemptedToOpenAnEmptyBox) - ) + lazy val resourceUser2 = getOrCreateResourceUser( + provider = defaultProvider, + providerId = resourceUser2Name, + createdByConsentId = None, + name = resourceUser2Name, + email = "resourceUser2@123.com", + userId = userId2, + company = "Tesobe GmbH" + ) - lazy val resourceUser3 = UserX.findByProviderId(provider = defaultProvider, idGivenByProvider= resourceUser3Name).map(_.asInstanceOf[ResourceUser]) - .getOrElse(UserX.createResourceUser(provider = defaultProvider, providerId= Some(resourceUser3Name), createdByConsentId= None, - name= Some(resourceUser3Name),email= Some("resourceUser3@123.com"), userId= userId3, company = Some("Tesobe GmbH")) - .openOrThrowException(attemptedToOpenAnEmptyBox)) + lazy val resourceUser3 = getOrCreateResourceUser( + provider = defaultProvider, + providerId = resourceUser3Name, + createdByConsentId = None, + name = resourceUser3Name, + email = "resourceUser3@123.com", + userId = userId3, + company = "Tesobe GmbH" + ) - lazy val resourceUser4 = UserX.findByProviderId(provider = defaultProvider, idGivenByProvider= resourceUser4Name).map(_.asInstanceOf[ResourceUser]) - .getOrElse(UserX.createResourceUser(provider = GatewayLogin.gateway, providerId = Some(resourceUser4Name), createdByConsentId= Some("simonr"), name= Some(resourceUser4Name), - email= Some("resourceUser4@123.com"), userId=userId4, company = Some("Tesobe GmbH")) - .openOrThrowException(attemptedToOpenAnEmptyBox)) + lazy val resourceUser4 = getOrCreateResourceUser( + provider = GatewayLogin.gateway, + providerId = resourceUser4Name, + createdByConsentId = Some("simonr"), + name = resourceUser4Name, + email = "resourceUser4@123.com", + userId = userId4, + company = "Tesobe GmbH" + ) // create the tokens in database, we only need token-key and token-secretAllCases lazy val testToken1 = Tokens.tokens.vend.createToken( diff --git a/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala b/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala index 8e0964c98..d88905d18 100644 --- a/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala +++ b/obp-api/src/test/scala/code/setup/LocalMappedConnectorTestSetup.scala @@ -65,30 +65,56 @@ trait LocalMappedConnectorTestSetup extends TestConnectorSetupWithStandardPermis } override protected def createAccount(bankId: BankId, accountId : AccountId, currency : String) : BankAccount = { - BankAccountRouting.create - .BankId(bankId.value) - .AccountId(accountId.value) - .AccountRoutingScheme(AccountRoutingScheme.IBAN.toString) - .AccountRoutingAddress(iban4j.Iban.random().toString()) - .saveMe - BankAccountRouting.create - .BankId(bankId.value) - .AccountId(accountId.value) - .AccountRoutingScheme("AccountId") - .AccountRoutingAddress(accountId.value) - .saveMe - MappedBankAccount.create - .bank(bankId.value) - .theAccountId(accountId.value) - .accountCurrency(currency.toUpperCase) - .accountBalance(900000000) - .holder(randomString(4)) - .accountLastUpdate(now) - .accountName(randomString(4)) - .accountNumber(randomString(4)) - .accountLabel(randomString(4)) - .mBranchId(randomString(4)) - .saveMe + def getOrCreateRouting(scheme: String, address: String): Unit = { + val existing = BankAccountRouting.find( + By(BankAccountRouting.BankId, bankId.value), + By(BankAccountRouting.AccountId, accountId.value), + By(BankAccountRouting.AccountRoutingScheme, scheme) + ) + if (!existing.isDefined) { + try { + BankAccountRouting.create + .BankId(bankId.value) + .AccountId(accountId.value) + .AccountRoutingScheme(scheme) + .AccountRoutingAddress(address) + .saveMe + } catch { + case _: Throwable => + } + } + () + } + + getOrCreateRouting(AccountRoutingScheme.IBAN.toString, iban4j.Iban.random().toString()) + getOrCreateRouting("AccountId", accountId.value) + + val existingAccount = MappedBankAccount.find( + By(MappedBankAccount.bank, bankId.value), + By(MappedBankAccount.theAccountId, accountId.value) + ) + existingAccount.openOr { + try { + MappedBankAccount.create + .bank(bankId.value) + .theAccountId(accountId.value) + .accountCurrency(currency.toUpperCase) + .accountBalance(900000000) + .holder(randomString(4)) + .accountLastUpdate(now) + .accountName(randomString(4)) + .accountNumber(randomString(4)) + .accountLabel(randomString(4)) + .mBranchId(randomString(4)) + .saveMe + } catch { + case _: Throwable => + MappedBankAccount.find( + By(MappedBankAccount.bank, bankId.value), + By(MappedBankAccount.theAccountId, accountId.value) + ).openOrThrowException(attemptedToOpenAnEmptyBox) + } + } } override protected def updateAccountCurrency(bankId: BankId, accountId : AccountId, currency : String) : BankAccount = {