From 558ee1d404922b3b45d55055e4d96f15cd09d237 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 28 Jan 2026 14:01:29 +0100 Subject: [PATCH 1/6] feature/(resource-docs): include technology field in API documentation Add `technology` field to `implemented_by` JSON to indicate whether an endpoint is implemented using lift or http4s. The field is included only when `includeTechnologyInResponse` is true, which is set for API versions 6.0.0 and 7.0.0. This helps API consumers understand the underlying implementation technology. Update tests to verify technology field presence/absence based on API version. Also improve test setup robustness by making user and account creation idempotent, and update build dependencies to support http4s and pekko. --- build.sbt | 31 ++++++- .../ResourceDocs1_4_0/ResourceDocs140.scala | 5 +- .../ResourceDocsAPIMethods.scala | 11 +-- .../SwaggerDefinitionsJSON.scala | 3 +- .../code/api/v1_4_0/JSONFactory1_4_0.scala | 28 ++++-- .../scala/code/api/v7_0_0/Http4s700.scala | 2 +- .../ResourceDocsTechnologyTest.scala | 46 ++++++++++ .../ResourceDocs1_4_0/ResourceDocsTest.scala | 4 +- .../api/v1_4_0/JSONFactory1_4_0Test.scala | 22 ++++- .../code/api/v7_0_0/Http4s700RoutesTest.scala | 15 +++- .../test/scala/code/setup/DefaultUsers.scala | 87 +++++++++++++++---- .../setup/LocalMappedConnectorTestSetup.scala | 74 +++++++++++----- 12 files changed, 261 insertions(+), 67 deletions(-) create mode 100644 obp-api/src/test/scala/code/api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala diff --git a/build.sbt b/build.sbt index dd7b6b681d..add70f9eae 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 1a5d8bebc0..bec0f2adeb 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 b355e782ed..6488006040 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 6e22b45cca..96fc6cee83 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 db6f7ee9f0..f47ddbf0be 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 229c610276..70f9d8884e 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 0000000000..5b753dd2e3 --- /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 7b7bce74f9..19534d7dde 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 c5be0d68c7..62484f93a0 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 c43070d730..9ef5bff8bf 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 4f148ff39c..220e0f3567 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 8e0964c982..d88905d188 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 = { From 8e52e20c86bdd01b13dc0321fe19a25dfdaa0da7 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 28 Jan 2026 14:25:06 +0100 Subject: [PATCH 2/6] refactor/(api): use ApiShortVersions constant for v7.0.0 version Replace hardcoded "v7.0.0" string with ApiShortVersions.`v7.0.0`.toString in ResourceDocMiddleware and update test files accordingly to use the constant. This ensures consistency and easier maintenance when API version references need to be updated. --- .../util/http4s/ResourceDocMiddleware.scala | 3 +- .../http4s/Http4sCallContextBuilderTest.scala | 111 +++++++++--------- .../util/http4s/ResourceDocMatcherTest.scala | 93 ++++++++------- 3 files changed, 107 insertions(+), 100 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala b/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala index 78e946fb05..26fbaa18c1 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/ResourceDocMiddleware.scala @@ -10,6 +10,7 @@ import code.api.util.newstyle.ViewNewStyle import code.api.util.{APIUtil, ApiRole, CallContext, NewStyle} import code.util.Helper.MdcLoggable import com.openbankproject.commons.model._ +import com.openbankproject.commons.util.ApiShortVersions import com.github.dwickern.macros.NameOf.nameOf import net.liftweb.common.{Box, Empty, Full} import org.http4s._ @@ -86,7 +87,7 @@ object ResourceDocMiddleware extends MdcLoggable { def apply(resourceDocs: ArrayBuffer[ResourceDoc]): HttpRoutes[IO] => HttpRoutes[IO] = { routes => Kleisli[HttpF, Request[IO], Response[IO]] { req: Request[IO] => // Build initial CallContext from request - OptionT.liftF(Http4sCallContextBuilder.fromRequest(req, "v7.0.0")).flatMap { cc => + OptionT.liftF(Http4sCallContextBuilder.fromRequest(req, ApiShortVersions.`v7.0.0`.toString)).flatMap { cc => ResourceDocMatcher.findResourceDoc(req.method.name, req.uri.path, resourceDocs) match { case Some(resourceDoc) => val ccWithDoc = ResourceDocMatcher.attachToCallContext(cc, resourceDoc) diff --git a/obp-api/src/test/scala/code/api/util/http4s/Http4sCallContextBuilderTest.scala b/obp-api/src/test/scala/code/api/util/http4s/Http4sCallContextBuilderTest.scala index d6d22baee5..8c504a126a 100644 --- a/obp-api/src/test/scala/code/api/util/http4s/Http4sCallContextBuilderTest.scala +++ b/obp-api/src/test/scala/code/api/util/http4s/Http4sCallContextBuilderTest.scala @@ -2,6 +2,7 @@ package code.api.util.http4s import cats.effect.IO import cats.effect.unsafe.implicits.global +import com.openbankproject.commons.util.ApiShortVersions import net.liftweb.common.{Empty, Full} import org.http4s._ import org.http4s.dsl.io._ @@ -23,49 +24,51 @@ import org.scalatest.{FeatureSpec, GivenWhenThen, Matchers, Tag} class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenWhenThen { object Http4sCallContextBuilderTag extends Tag("Http4sCallContextBuilder") + private val v700 = ApiShortVersions.`v7.0.0`.toString + private val base = s"/obp/$v700" feature("Http4sCallContextBuilder - URL extraction") { scenario("Extract URL with path only", Http4sCallContextBuilderTag) { - Given("A request with path /obp/v7.0.0/banks") + Given(s"A request with path $base/banks") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("URL should match the request URI") - callContext.url should equal("/obp/v7.0.0/banks") + callContext.url should equal(s"$base/banks") } scenario("Extract URL with query parameters", Http4sCallContextBuilderTag) { Given("A request with query parameters") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks?limit=10&offset=0") + uri = Uri.unsafeFromString(s"$base/banks?limit=10&offset=0") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("URL should include query parameters") - callContext.url should equal("/obp/v7.0.0/banks?limit=10&offset=0") + callContext.url should equal(s"$base/banks?limit=10&offset=0") } scenario("Extract URL with path parameters", Http4sCallContextBuilderTag) { Given("A request with path parameters") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1") + uri = Uri.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("URL should include path parameters") - callContext.url should equal("/obp/v7.0.0/banks/gh.29.de/accounts/test1") + callContext.url should equal(s"$base/banks/gh.29.de/accounts/test1") } } @@ -75,7 +78,7 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A request with multiple headers") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("Content-Type"), "application/json"), Header.Raw(org.typelevel.ci.CIString("Accept"), "application/json"), @@ -83,7 +86,7 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Headers should be converted to HTTPParam list") callContext.requestHeaders should not be empty @@ -96,11 +99,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A request with no custom headers") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Headers list should be empty or contain only default headers") // http4s may add default headers, so we just check it's a list @@ -115,11 +118,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val jsonBody = """{"name": "Test Bank", "id": "test-bank-1"}""" val request = Request[IO]( method = Method.POST, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withEntity(jsonBody) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Body should be extracted as Some(string)") callContext.httpBody should be(Some(jsonBody)) @@ -129,11 +132,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A GET request with no body") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Body should be None") callContext.httpBody should be(None) @@ -144,11 +147,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val jsonBody = """{"name": "Updated Bank"}""" val request = Request[IO]( method = Method.PUT, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks/test-bank-1") + uri = Uri.unsafeFromString(s"$base/banks/test-bank-1") ).withEntity(jsonBody) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Body should be extracted") callContext.httpBody should be(Some(jsonBody)) @@ -162,13 +165,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val requestId = "test-correlation-id-12345" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("X-Request-ID"), requestId) ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Correlation ID should match the header value") callContext.correlationId should equal(requestId) @@ -178,11 +181,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A request without X-Request-ID header") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Correlation ID should be generated (UUID format)") callContext.correlationId should not be empty @@ -198,13 +201,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val clientIp = "192.168.1.100" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("X-Forwarded-For"), clientIp) ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("IP address should match the header value") callContext.ipAddress should equal(clientIp) @@ -215,13 +218,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val forwardedFor = "192.168.1.100, 10.0.0.1, 172.16.0.1" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("X-Forwarded-For"), forwardedFor) ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("IP address should be the first IP in the list") callContext.ipAddress should equal("192.168.1.100") @@ -231,11 +234,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A request without X-Forwarded-For or remote address") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("IP address should be empty string") callContext.ipAddress should equal("") @@ -249,13 +252,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val token = "eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.test" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("DirectLogin"), s"token=$token") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("DirectLogin params should contain token") callContext.directLoginParams should contain key "token" @@ -267,13 +270,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val token = "eyJhbGciOiJIUzI1NiJ9.eyIiOiIifQ.test" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("Authorization"), s"DirectLogin token=$token") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("DirectLogin params should contain token") callContext.directLoginParams should contain key "token" @@ -287,13 +290,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A request with DirectLogin username and password") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("DirectLogin"), """username="testuser", password="testpass", consumer_key="key123"""") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("DirectLogin params should contain all parameters") callContext.directLoginParams should contain key "username" @@ -309,13 +312,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val oauthHeader = """OAuth oauth_consumer_key="consumer123", oauth_token="token456", oauth_signature="sig789"""" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("Authorization"), oauthHeader) ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("OAuth params should be extracted") callContext.oAuthParams should contain key "oauth_consumer_key" @@ -334,13 +337,13 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val bearerToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test.signature" val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("Authorization"), s"Bearer $bearerToken") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Authorization header should be stored") callContext.authReqHeaderField should equal(Full(s"Bearer $bearerToken")) @@ -350,11 +353,11 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A request without Authorization header") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Auth header field should be Empty") callContext.authReqHeaderField should equal(Empty) @@ -373,40 +376,40 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW Given("A POST request") val request = Request[IO]( method = Method.POST, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("Verb should be POST") callContext.verb should equal("POST") } scenario("Set implementedInVersion from parameter", Http4sCallContextBuilderTag) { - Given("A request with API version v7.0.0") + Given(s"A request with API version $v700") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext with version parameter") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("implementedInVersion should match the parameter") - callContext.implementedInVersion should equal("v7.0.0") + callContext.implementedInVersion should equal(v700) } scenario("Set startTime to current date", Http4sCallContextBuilderTag) { Given("A request") val request = Request[IO]( method = Method.GET, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks") + uri = Uri.unsafeFromString(s"$base/banks") ) When("Building CallContext") val beforeTime = new java.util.Date() - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() val afterTime = new java.util.Date() Then("startTime should be set and within reasonable range") @@ -427,7 +430,7 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW val request = Request[IO]( method = Method.POST, - uri = Uri.unsafeFromString("/obp/v7.0.0/banks?limit=10") + uri = Uri.unsafeFromString(s"$base/banks?limit=10") ).withHeaders( Header.Raw(org.typelevel.ci.CIString("Content-Type"), "application/json"), Header.Raw(org.typelevel.ci.CIString("DirectLogin"), s"token=$token"), @@ -436,12 +439,12 @@ class Http4sCallContextBuilderTest extends FeatureSpec with Matchers with GivenW ).withEntity(jsonBody) When("Building CallContext") - val callContext = Http4sCallContextBuilder.fromRequest(request, "v7.0.0").unsafeRunSync() + val callContext = Http4sCallContextBuilder.fromRequest(request, v700).unsafeRunSync() Then("All fields should be populated correctly") - callContext.url should equal("/obp/v7.0.0/banks?limit=10") + callContext.url should equal(s"$base/banks?limit=10") callContext.verb should equal("POST") - callContext.implementedInVersion should equal("v7.0.0") + callContext.implementedInVersion should equal(v700) callContext.correlationId should equal(correlationId) callContext.ipAddress should equal(clientIp) callContext.httpBody should be(Some(jsonBody)) diff --git a/obp-api/src/test/scala/code/api/util/http4s/ResourceDocMatcherTest.scala b/obp-api/src/test/scala/code/api/util/http4s/ResourceDocMatcherTest.scala index a686295aa6..a2c7c52233 100644 --- a/obp-api/src/test/scala/code/api/util/http4s/ResourceDocMatcherTest.scala +++ b/obp-api/src/test/scala/code/api/util/http4s/ResourceDocMatcherTest.scala @@ -2,6 +2,7 @@ package code.api.util.http4s import code.api.util.APIUtil.ResourceDoc import code.api.util.ApiTag.ResourceDocTag +import com.openbankproject.commons.util.ApiShortVersions import com.openbankproject.commons.util.ApiVersion import net.liftweb.json.JsonAST.JObject import org.http4s._ @@ -25,6 +26,8 @@ import scala.collection.mutable.ArrayBuffer class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThen { object ResourceDocMatcherTag extends Tag("ResourceDocMatcher") + private val v700 = ApiShortVersions.`v7.0.0`.toString + private val base = s"/obp/$v700" // Helper to create minimal ResourceDoc for testing private def createResourceDoc( @@ -56,8 +59,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks", "getBanks") ) - When("Matching a GET request to /obp/v7.0.0/banks") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + When(s"Matching a GET request to $base/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -71,8 +74,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("POST", "/banks", "createBank") ) - When("Matching a POST request to /obp/v7.0.0/banks") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + When(s"Matching a POST request to $base/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val result = ResourceDocMatcher.findResourceDoc("POST", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -86,8 +89,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/management/metrics", "getMetrics") ) - When("Matching a GET request to /obp/v7.0.0/management/metrics") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/management/metrics") + When(s"Matching a GET request to $base/management/metrics") + val path = Uri.Path.unsafeFromString(s"$base/management/metrics") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -101,8 +104,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks", "getBanks") ) - When("Matching a POST request to /obp/v7.0.0/banks") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + When(s"Matching a POST request to $base/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val result = ResourceDocMatcher.findResourceDoc("POST", path, resourceDocs) Then("Should return None") @@ -115,8 +118,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks", "getBanks") ) - When("Matching a GET request to /obp/v7.0.0/accounts") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/accounts") + When(s"Matching a GET request to $base/accounts") + val path = Uri.Path.unsafeFromString(s"$base/accounts") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should return None") @@ -132,8 +135,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks/BANK_ID", "getBank") ) - When("Matching a GET request to /obp/v7.0.0/banks/gh.29.de") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de") + When(s"Matching a GET request to $base/banks/gh.29.de") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -147,8 +150,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks/BANK_ID/accounts", "getBankAccounts") ) - When("Matching a GET request to /obp/v7.0.0/banks/test-bank-1/accounts") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/test-bank-1/accounts") + When(s"Matching a GET request to $base/banks/test-bank-1/accounts") + val path = Uri.Path.unsafeFromString(s"$base/banks/test-bank-1/accounts") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -160,8 +163,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe Given("A matched ResourceDoc with BANK_ID") val resourceDoc = createResourceDoc("GET", "/banks/BANK_ID", "getBank") - When("Extracting path parameters from /obp/v7.0.0/banks/gh.29.de") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de") + When(s"Extracting path parameters from $base/banks/gh.29.de") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should extract BANK_ID value") @@ -178,8 +181,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID", "getBankAccount") ) - When("Matching a GET request to /obp/v7.0.0/banks/gh.29.de/accounts/test1") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1") + When(s"Matching a GET request to $base/banks/gh.29.de/accounts/test1") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -191,8 +194,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe Given("A matched ResourceDoc with BANK_ID and ACCOUNT_ID") val resourceDoc = createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID", "getBankAccount") - When("Extracting path parameters from /obp/v7.0.0/banks/gh.29.de/accounts/test1") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1") + When(s"Extracting path parameters from $base/banks/gh.29.de/accounts/test1") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should extract both BANK_ID and ACCOUNT_ID values") @@ -208,8 +211,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID/transactions", "getTransactions") ) - When("Matching a GET request to /obp/v7.0.0/banks/test-bank/accounts/acc-123/transactions") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/test-bank/accounts/acc-123/transactions") + When(s"Matching a GET request to $base/banks/test-bank/accounts/acc-123/transactions") + val path = Uri.Path.unsafeFromString(s"$base/banks/test-bank/accounts/acc-123/transactions") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -226,8 +229,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions", "getTransactionsForView") ) - When("Matching a GET request to /obp/v7.0.0/banks/gh.29.de/accounts/test1/owner/transactions") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1/owner/transactions") + When(s"Matching a GET request to $base/banks/gh.29.de/accounts/test1/owner/transactions") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1/owner/transactions") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -239,8 +242,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe Given("A matched ResourceDoc with BANK_ID, ACCOUNT_ID and VIEW_ID") val resourceDoc = createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transactions", "getTransactionsForView") - When("Extracting path parameters from /obp/v7.0.0/banks/gh.29.de/accounts/test1/owner/transactions") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1/owner/transactions") + When(s"Extracting path parameters from $base/banks/gh.29.de/accounts/test1/owner/transactions") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1/owner/transactions") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should extract all three parameter values") @@ -258,8 +261,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/account", "getAccountForView") ) - When("Matching a GET request to /obp/v7.0.0/banks/test-bank/accounts/acc-1/public/account") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/test-bank/accounts/acc-1/public/account") + When(s"Matching a GET request to $base/banks/test-bank/accounts/acc-1/public/account") + val path = Uri.Path.unsafeFromString(s"$base/banks/test-bank/accounts/acc-1/public/account") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -277,7 +280,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a GET request with counterparty ID") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1/owner/counterparties/ff010868-ac7d-4f96-9fc5-70dd5757e891") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1/owner/counterparties/ff010868-ac7d-4f96-9fc5-70dd5757e891") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -290,7 +293,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe val resourceDoc = createResourceDoc("GET", "/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/counterparties/COUNTERPARTY_ID", "getCounterparty") When("Extracting path parameters") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts/test1/owner/counterparties/ff010868-ac7d-4f96-9fc5-70dd5757e891") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts/test1/owner/counterparties/ff010868-ac7d-4f96-9fc5-70dd5757e891") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should extract all parameter values including COUNTERPARTY_ID") @@ -311,7 +314,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a DELETE request") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/management/counterparties/counterparty-123") + val path = Uri.Path.unsafeFromString(s"$base/management/counterparties/counterparty-123") val result = ResourceDocMatcher.findResourceDoc("DELETE", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -331,7 +334,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a request that doesn't match any ResourceDoc") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/accounts") + val path = Uri.Path.unsafeFromString(s"$base/accounts") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should return None") @@ -344,8 +347,8 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe createResourceDoc("GET", "/banks", "getBanks") ) - When("Matching a DELETE request to /obp/v7.0.0/banks") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + When(s"Matching a DELETE request to $base/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val result = ResourceDocMatcher.findResourceDoc("DELETE", path, resourceDocs) Then("Should return None") @@ -359,7 +362,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a request with different segment count") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should return None") @@ -373,7 +376,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a request with different literal segment") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/transactions") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/transactions") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should return None") @@ -388,7 +391,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe val resourceDoc = createResourceDoc("GET", "/banks", "getBanks") When("Extracting path parameters") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should return empty map") @@ -400,7 +403,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe val resourceDoc = createResourceDoc("GET", "/banks/BANK_ID", "getBank") When("Extracting path parameters with special characters") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de-test_bank") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de-test_bank") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should extract the full value including special characters") @@ -413,7 +416,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe val resourceDoc = createResourceDoc("GET", "/banks/BANK_ID", "getBank") When("Extracting parameters from path with different segment count") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/accounts") + val path = Uri.Path.unsafeFromString(s"$base/accounts") val params = ResourceDocMatcher.extractPathParams(path, resourceDoc) Then("Should return empty map due to segment count mismatch") @@ -458,9 +461,9 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe val resourceDoc = createResourceDoc("GET", "/banks", "getBanks") val originalContext = code.api.util.CallContext( correlationId = "test-correlation-id", - url = "/obp/v7.0.0/banks", + url = s"$base/banks", verb = "GET", - implementedInVersion = "v7.0.0" + implementedInVersion = v700 ) When("Attaching ResourceDoc to CallContext") @@ -486,7 +489,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a specific request") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks/gh.29.de/accounts") + val path = Uri.Path.unsafeFromString(s"$base/banks/gh.29.de/accounts") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should select the most specific matching ResourceDoc") @@ -502,7 +505,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching a request") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should return the first matching ResourceDoc") @@ -520,7 +523,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching with lowercase get") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/banks") + val path = Uri.Path.unsafeFromString(s"$base/banks") val result = ResourceDocMatcher.findResourceDoc("get", path, resourceDocs) Then("Should find the matching ResourceDoc") @@ -535,7 +538,7 @@ class ResourceDocMatcherTest extends FeatureSpec with Matchers with GivenWhenThe ) When("Matching with different case /Banks") - val path = Uri.Path.unsafeFromString("/obp/v7.0.0/Banks") + val path = Uri.Path.unsafeFromString(s"$base/Banks") val result = ResourceDocMatcher.findResourceDoc("GET", path, resourceDocs) Then("Should not match (case-sensitive)") From b29f21232e4cf70b5f0740e6a27bbb1f9132f6dd Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 28 Jan 2026 14:49:40 +0100 Subject: [PATCH 3/6] refactor/(api): use ApiVersion constants instead of hardcoded strings - Replace hardcoded API version strings with ApiVersion constants in JSON factories and API methods - Add new ApiVersion constants for Bahrain OBF and AU Open Banking - Update test to use ApiVersion constants for version strings - Ensure consistency and maintainability across API version references --- .../AUOpenBanking/v1_0_0/ApiCollector.scala | 4 +- .../api/BahrainOBF/v1_0_0/ApiCollector.scala | 4 +- .../SwaggerDefinitionsJSON.scala | 8 +- .../code/api/v4_0_0/JSONFactory4.0.0.scala | 3 +- .../scala/code/api/v6_0_0/APIMethods600.scala | 52 ++-- .../code/api/v6_0_0/JSONFactory6.0.0.scala | 5 +- .../ResourceDocsTechnologyTest.scala | 12 +- .../ResourceDocs1_4_0/ResourceDocsTest.scala | 231 ++++++++++-------- .../commons/util/ApiVersion.scala | 2 + 9 files changed, 175 insertions(+), 146 deletions(-) diff --git a/obp-api/src/main/scala/code/api/AUOpenBanking/v1_0_0/ApiCollector.scala b/obp-api/src/main/scala/code/api/AUOpenBanking/v1_0_0/ApiCollector.scala index d8fea1182f..12e75972be 100644 --- a/obp-api/src/main/scala/code/api/AUOpenBanking/v1_0_0/ApiCollector.scala +++ b/obp-api/src/main/scala/code/api/AUOpenBanking/v1_0_0/ApiCollector.scala @@ -35,7 +35,7 @@ import code.api.OBPRestHelper import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints} import code.api.util.ScannedApis import code.util.Helper.MdcLoggable -import com.openbankproject.commons.util.{ApiVersionStatus, ScannedApiVersion} +import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus} import scala.collection.mutable.ArrayBuffer @@ -47,7 +47,7 @@ This file defines which endpoints from all the versions are available in v1 */ object ApiCollector extends OBPRestHelper with MdcLoggable with ScannedApis { //please modify these three parameter if it is not correct. - override val apiVersion = ScannedApiVersion("cds-au", "AU", "v1.0.0") + override val apiVersion = ApiVersion.auOpenBankingV100 val versionStatus = ApiVersionStatus.DRAFT.toString private[this] val endpoints = diff --git a/obp-api/src/main/scala/code/api/BahrainOBF/v1_0_0/ApiCollector.scala b/obp-api/src/main/scala/code/api/BahrainOBF/v1_0_0/ApiCollector.scala index ff5422cff8..5c0148ce2d 100644 --- a/obp-api/src/main/scala/code/api/BahrainOBF/v1_0_0/ApiCollector.scala +++ b/obp-api/src/main/scala/code/api/BahrainOBF/v1_0_0/ApiCollector.scala @@ -35,7 +35,7 @@ import code.api.OBPRestHelper import code.api.util.APIUtil.{OBPEndpoint, ResourceDoc, getAllowedEndpoints} import code.api.util.{ScannedApis} import code.util.Helper.MdcLoggable -import com.openbankproject.commons.util.{ApiVersionStatus, ScannedApiVersion} +import com.openbankproject.commons.util.{ApiVersion, ApiVersionStatus} import scala.collection.mutable.ArrayBuffer @@ -45,7 +45,7 @@ import scala.collection.mutable.ArrayBuffer This file defines which endpoints from all the versions are available in v1 */ object ApiCollector extends OBPRestHelper with MdcLoggable with ScannedApis { - override val apiVersion = ScannedApiVersion("BAHRAIN-OBF", "BAHRAIN-OBF", "v1.0.0") + override val apiVersion = ApiVersion.bahrainObfV100 val versionStatus = ApiVersionStatus.DRAFT.toString private[this] val endpoints = 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 96fc6cee83..5e454e8a23 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 @@ -4019,7 +4019,7 @@ object SwaggerDefinitionsJSON { lazy val topApiJson = TopApiJson( count = 7076, Implemented_by_partial_function = "getBanks", - implemented_in_version = "v1.2.1" + implemented_in_version = ApiVersion.v1_2_1.toString ) lazy val topApisJson = TopApisJson(List(topApiJson)) @@ -4130,7 +4130,7 @@ object SwaggerDefinitionsJSON { lazy val callLimitPostJsonV600 = CallLimitPostJsonV600( from_date = DateWithDayExampleObject, to_date = DateWithDayExampleObject, - api_version = Some("v6.0.0"), + api_version = Some(ApiVersion.v6_0_0.toString), api_name = Some("getConsumerCallLimits"), bank_id = None, per_second_call_limit = "100", @@ -4145,7 +4145,7 @@ object SwaggerDefinitionsJSON { rate_limiting_id = "80e1e0b2-d8bf-4f85-a579-e69ef36e3305", from_date = DateWithDayExampleObject, to_date = DateWithDayExampleObject, - api_version = Some("v6.0.0"), + api_version = Some(ApiVersion.v6_0_0.toString), api_name = Some("getConsumerCallLimits"), bank_id = None, per_second_call_limit = "100", @@ -5129,7 +5129,7 @@ object SwaggerDefinitionsJSON { user_id = userIdExample.value, allowed_attempts =3, challenge_type = ChallengeType.OBP_TRANSACTION_REQUEST_CHALLENGE.toString, - link = "/obp/v4.0.0/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge" + link = s"/obp/${ApiVersion.v4_0_0}/banks/BANK_ID/accounts/ACCOUNT_ID/VIEW_ID/transaction-request-types/TRANSACTION_REQUEST_TYPE/transaction-requests/TRANSACTION_REQUEST_ID/challenge" ) lazy val transactionRequestWithChargeJSON400 = TransactionRequestWithChargeJSON400( id = "4050046c-63b3-4868-8a22-14b4181d33a6", diff --git a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala index 5ef812c7d4..721ebbca9a 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/JSONFactory4.0.0.scala @@ -1283,7 +1283,7 @@ object JSONFactory400 { ).mkString("") val otpViaApiPath = Constant.HostName + List( - "/obp/v4.0.0/banks/", + s"/obp/${ApiVersion.v4_0_0}/banks/", stringOrNull(tr.from.bank_id), "/accounts/", stringOrNull(tr.from.account_id), @@ -2072,4 +2072,3 @@ object JSONFactory400 { } } } - diff --git a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala index 1ffe22eefd..2e610543e5 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/APIMethods600.scala @@ -1800,18 +1800,18 @@ trait APIMethods600 { ListResult( "scanned_api_versions", List( - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v1.2.1", fully_qualified_version = "OBPv1.2.1", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v1.3.0", fully_qualified_version = "OBPv1.3.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v1.4.0", fully_qualified_version = "OBPv1.4.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v2.0.0", fully_qualified_version = "OBPv2.0.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v2.1.0", fully_qualified_version = "OBPv2.1.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v2.2.0", fully_qualified_version = "OBPv2.2.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v3.0.0", fully_qualified_version = "OBPv3.0.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v3.1.0", fully_qualified_version = "OBPv3.1.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v4.0.0", fully_qualified_version = "OBPv4.0.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v5.0.0", fully_qualified_version = "OBPv5.0.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v5.1.0", fully_qualified_version = "OBPv5.1.0", is_active = true), - ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = "v6.0.0", fully_qualified_version = "OBPv6.0.0", is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v1_2_1.toString, fully_qualified_version = ApiVersion.v1_2_1.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v1_3_0.toString, fully_qualified_version = ApiVersion.v1_3_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v1_4_0.toString, fully_qualified_version = ApiVersion.v1_4_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v2_0_0.toString, fully_qualified_version = ApiVersion.v2_0_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v2_1_0.toString, fully_qualified_version = ApiVersion.v2_1_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v2_2_0.toString, fully_qualified_version = ApiVersion.v2_2_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v3_0_0.toString, fully_qualified_version = ApiVersion.v3_0_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v3_1_0.toString, fully_qualified_version = ApiVersion.v3_1_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v4_0_0.toString, fully_qualified_version = ApiVersion.v4_0_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v5_0_0.toString, fully_qualified_version = ApiVersion.v5_0_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v5_1_0.toString, fully_qualified_version = ApiVersion.v5_1_0.fullyQualifiedVersion, is_active = true), + ScannedApiVersionJsonV600(url_prefix = "obp", api_standard = "OBP", api_short_version = ApiVersion.v6_0_0.toString, fully_qualified_version = ApiVersion.v6_0_0.fullyQualifiedVersion, is_active = true), ScannedApiVersionJsonV600(url_prefix = "berlin-group", api_standard = "BG", api_short_version = "v1.3", fully_qualified_version = "BGv1.3", is_active = false) ) ), @@ -4289,7 +4289,7 @@ trait APIMethods600 { lazy val getWebUiProp: OBPEndpoint = { case "webui-props" :: webUiPropName :: Nil JsonGet req => { cc => implicit val ec = EndpointContext(Some(cc)) - logger.info(s"========== GET /obp/v6.0.0/webui-props/$webUiPropName (SINGLE PROP) called ==========") + logger.info(s"========== GET /obp/${ApiVersion.v6_0_0}/webui-props/$webUiPropName (SINGLE PROP) called ==========") val active = ObpS.param("active").getOrElse("false") for { invalidMsg <- Future(s"""$InvalidFilterParameterFormat `active` must be a boolean, but current `active` value is: ${active} """) @@ -4391,7 +4391,7 @@ trait APIMethods600 { case "webui-props":: Nil JsonGet req => { cc => implicit val ec = EndpointContext(Some(cc)) val what = ObpS.param("what").getOrElse("active") - logger.info(s"========== GET /obp/v6.0.0/webui-props (ALL PROPS) called with what=$what ==========") + logger.info(s"========== GET /obp/${ApiVersion.v6_0_0}/webui-props (ALL PROPS) called with what=$what ==========") for { callContext <- Future.successful(cc.callContext) _ <- NewStyle.function.tryons(s"""$InvalidFilterParameterFormat `what` must be one of: active, database, config. Current value: $what""", 400, callContext) { @@ -4417,11 +4417,11 @@ trait APIMethods600 { explicitWebUiPropsWithSource ++ configPropsNotInDatabase } } yield { - logger.info(s"========== GET /obp/v6.0.0/webui-props returning ${result.size} records ==========") + logger.info(s"========== GET /obp/${ApiVersion.v6_0_0}/webui-props returning ${result.size} records ==========") result.foreach { prop => logger.info(s" - name: ${prop.name}, value: ${prop.value}, webUiPropsId: ${prop.webUiPropsId}") } - logger.info(s"========== END GET /obp/v6.0.0/webui-props ==========") + logger.info(s"========== END GET /obp/${ApiVersion.v6_0_0}/webui-props ==========") (ListResult("webui_props", result), HttpCode.`200`(callContext)) } } @@ -6983,11 +6983,11 @@ trait APIMethods600 { schema = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string"}, "language": {"type": "string"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject], _links = Some(DynamicEntityLinksJsonV600( related = List( - RelatedLinkJsonV600("list", "/obp/v6.0.0/my/customer_preferences", "GET"), - RelatedLinkJsonV600("create", "/obp/v6.0.0/my/customer_preferences", "POST"), - RelatedLinkJsonV600("read", "/obp/v6.0.0/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "GET"), - RelatedLinkJsonV600("update", "/obp/v6.0.0/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "PUT"), - RelatedLinkJsonV600("delete", "/obp/v6.0.0/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "DELETE") + RelatedLinkJsonV600("list", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences", "GET"), + RelatedLinkJsonV600("create", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences", "POST"), + RelatedLinkJsonV600("read", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "GET"), + RelatedLinkJsonV600("update", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "PUT"), + RelatedLinkJsonV600("delete", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "DELETE") ) )) ) @@ -7047,11 +7047,11 @@ trait APIMethods600 { schema = net.liftweb.json.parse("""{"description": "User preferences", "required": ["theme"], "properties": {"theme": {"type": "string"}, "language": {"type": "string"}}}""").asInstanceOf[net.liftweb.json.JsonAST.JObject], _links = Some(DynamicEntityLinksJsonV600( related = List( - RelatedLinkJsonV600("list", "/obp/v6.0.0/my/customer_preferences", "GET"), - RelatedLinkJsonV600("create", "/obp/v6.0.0/my/customer_preferences", "POST"), - RelatedLinkJsonV600("read", "/obp/v6.0.0/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "GET"), - RelatedLinkJsonV600("update", "/obp/v6.0.0/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "PUT"), - RelatedLinkJsonV600("delete", "/obp/v6.0.0/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "DELETE") + RelatedLinkJsonV600("list", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences", "GET"), + RelatedLinkJsonV600("create", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences", "POST"), + RelatedLinkJsonV600("read", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "GET"), + RelatedLinkJsonV600("update", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "PUT"), + RelatedLinkJsonV600("delete", s"/obp/${ApiVersion.v6_0_0}/my/customer_preferences/CUSTOMER_PREFERENCES_ID", "DELETE") ) )) ) diff --git a/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala b/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala index 97ac5265d9..921adfe760 100644 --- a/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala +++ b/obp-api/src/main/scala/code/api/v6_0_0/JSONFactory6.0.0.scala @@ -38,6 +38,7 @@ import com.openbankproject.commons.model.{ CustomerAttribute, _ } +import com.openbankproject.commons.util.ApiVersion import net.liftweb.common.Box import java.util.Date @@ -1430,8 +1431,8 @@ object JSONFactory600 extends CustomJsonFormats with MdcLoggable { val entityName = entity.entityName val idPlaceholder = StringHelpers.snakify(entityName + "Id").toUpperCase() val baseUrl = entity.bankId match { - case Some(bankId) => s"/obp/v6.0.0/banks/$bankId/my/$entityName" - case None => s"/obp/v6.0.0/my/$entityName" + case Some(bankId) => s"/obp/${ApiVersion.v6_0_0}/banks/$bankId/my/$entityName" + case None => s"/obp/${ApiVersion.v6_0_0}/my/$entityName" } val links = DynamicEntityLinksJsonV600( 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 index 5b753dd2e3..a701b3846d 100644 --- 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 @@ -1,16 +1,19 @@ package code.api.ResourceDocs1_4_0 import code.setup.{PropsReset, ServerSetup} +import com.openbankproject.commons.util.ApiVersion import net.liftweb.json.JsonAST.{JArray, JNothing, JNull, JString} class ResourceDocsTechnologyTest extends ServerSetup with PropsReset { + private val v600 = ApiVersion.v6_0_0.toString + private val v500 = ApiVersion.v5_0_0.toString feature("ResourceDocs implemented_by.technology") { - scenario("v6.0.0 resource-docs should include implemented_by.technology") { + scenario(s"$v600 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 request = (baseRequest / "obp" / v600 / "resource-docs" / v600 / "obp").GET val response = makeGetRequest(request) response.code should equal(200) @@ -23,10 +26,10 @@ class ResourceDocsTechnologyTest extends ServerSetup with PropsReset { } } - scenario("v5.0.0 resource-docs should not include implemented_by.technology") { + scenario(s"$v500 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 request = (baseRequest / "obp" / v500 / "resource-docs" / v500 / "obp").GET val response = makeGetRequest(request) response.code should equal(200) @@ -43,4 +46,3 @@ class ResourceDocsTechnologyTest extends ServerSetup with PropsReset { } } } - 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 19534d7dde..dd1cb7bd9c 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 @@ -21,6 +21,31 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with object VersionOfApi extends Tag(ApiVersion.v1_4_0.toString) object ApiEndpoint1 extends Tag(nameOf(ImplementationsResourceDocs.getResourceDocsObp)) object ApiEndpoint2 extends Tag(nameOf(ImplementationsResourceDocs.getBankLevelDynamicResourceDocsObp)) + + private val v600 = ApiVersion.v6_0_0.toString + private val fq600 = ApiVersion.v6_0_0.fullyQualifiedVersion + private val v510 = ApiVersion.v5_1_0.toString + private val fq510 = ApiVersion.v5_1_0.fullyQualifiedVersion + private val v500 = ApiVersion.v5_0_0.toString + private val fq500 = ApiVersion.v5_0_0.fullyQualifiedVersion + private val v400 = ApiVersion.v4_0_0.toString + private val fq400 = ApiVersion.v4_0_0.fullyQualifiedVersion + private val v310 = ApiVersion.v3_1_0.toString + private val fq310 = ApiVersion.v3_1_0.fullyQualifiedVersion + private val v300 = ApiVersion.v3_0_0.toString + private val fq300 = ApiVersion.v3_0_0.fullyQualifiedVersion + private val v220 = ApiVersion.v2_2_0.toString + private val fq220 = ApiVersion.v2_2_0.fullyQualifiedVersion + private val v210 = ApiVersion.v2_1_0.toString + private val fq210 = ApiVersion.v2_1_0.fullyQualifiedVersion + private val v200 = ApiVersion.v2_0_0.toString + private val fq200 = ApiVersion.v2_0_0.fullyQualifiedVersion + private val v140 = ApiVersion.v1_4_0.toString + private val fq140 = ApiVersion.v1_4_0.fullyQualifiedVersion + private val v130 = ApiVersion.v1_3_0.toString + private val fq130 = ApiVersion.v1_3_0.fullyQualifiedVersion + private val v121 = ApiVersion.v1_2_1.toString + private val fq121 = ApiVersion.v1_2_1.fullyQualifiedVersion override def beforeEach() = { super.beforeEach() @@ -73,8 +98,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with feature(s"test ${ApiEndpoint1.name} ") { - scenario(s"We will test ${ApiEndpoint1.name} Api -v6.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v600", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV6_0Request / "resource-docs" / v600 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -83,8 +108,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with //This should not throw any exceptions responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv6.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV6_0Request / "resource-docs" / "OBPv6.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq600", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV6_0Request / "resource-docs" / fq600 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -92,8 +117,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with //This should not throw any exceptions responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v5.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV5_0Request / "resource-docs" / "v5.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v500", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV5_0Request / "resource-docs" / v500 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -104,52 +129,52 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with } scenario("Test OpenAPI endpoint with valid parameters", ApiEndpoint1, VersionOfApi) { - val requestGetOpenAPI = (ResourceDocsV6_0Request / "resource-docs" / "v6.0.0" / "openapi").GET < stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv5.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV5_0Request / "resource-docs" / "OBPv5.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq500", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV5_0Request / "resource-docs" / fq500 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -168,8 +193,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v4.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v4.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v400", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v400 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -178,8 +203,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv4.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv4.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq400", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq400 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -188,8 +213,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v3.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v3.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v310", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v310 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -199,8 +224,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv3.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv3.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq310", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq310 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -209,8 +234,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v3.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v3.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v300", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v300 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -219,8 +244,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv3.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv3.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq300", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq300 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -229,8 +254,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v2.2.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.2.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v220", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v220 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -239,8 +264,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv2.2.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv2.2.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq220", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq220 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -249,8 +274,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v2.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v210", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v210 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -259,8 +284,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv2.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv2.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq210", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq210 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -269,8 +294,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v2.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v2.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v200", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v200 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -279,8 +304,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv2.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv2.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq200", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq200 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -289,16 +314,16 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v1.4.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.4.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v140", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v140 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") responseGetObp.body.extract[ResourceDocsJson] responseGetObp.code should equal(200) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv1.4.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv1.4.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq140", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq140 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -307,8 +332,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v1.3.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.3.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v130", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v130 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -317,8 +342,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv1.3.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv1.3.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq130", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq130 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -327,8 +352,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v1.2.1", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v1.2.1" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$v121", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v121 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -337,8 +362,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -OBPv1.2.1", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "OBPv1.2.1" / "obp").GET + scenario(s"We will test ${ApiEndpoint1.name} Api -$fq121", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / fq121 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -387,11 +412,11 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v4.0.0 - resource_docs_requires_role props", ApiEndpoint1, VersionOfApi) { + scenario(s"We will test ${ApiEndpoint1.name} Api -$v400 - resource_docs_requires_role props", ApiEndpoint1, VersionOfApi) { setPropsValues( "resource_docs_requires_role" -> "true", ) - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v4.0.0" / "obp").GET + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v400 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -399,11 +424,11 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseGetObp.toString contains(AuthenticatedUserIsRequired) should be (true) } - scenario(s"We will test ${ApiEndpoint1.name} Api -v4.0.0 - resource_docs_requires_role props- login in user", ApiEndpoint1, VersionOfApi) { + scenario(s"We will test ${ApiEndpoint1.name} Api -$v400 - resource_docs_requires_role props- login in user", ApiEndpoint1, VersionOfApi) { setPropsValues( "resource_docs_requires_role" -> "true", ) - val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / "v4.0.0" / "obp").GET <@ (user1) + val requestGetObp = (ResourceDocsV4_0Request / "resource-docs" / v400 / "obp").GET <@ (user1) val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -415,8 +440,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with } feature(s"test ${ApiEndpoint2.name} ") { - scenario(s"We will test ${ApiEndpoint2.name} Api -v6.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v6.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v600", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v600 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -424,8 +449,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with //This should not throw any exceptions responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv6.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv6.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq600", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq600 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -433,8 +458,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with //This should not throw any exceptions responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v5.0.0/v4.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v5.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v500/$v400", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v500 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -443,8 +468,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v4.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v4.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v400", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v400 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -453,8 +478,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv4.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv4.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq400", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq400 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -463,8 +488,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v3.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v3.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v310", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v310 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -474,8 +499,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv3.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv3.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq310", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq310 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -484,8 +509,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v3.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v3.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v300", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v300 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -494,8 +519,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv3.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv3.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq300", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq300 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -504,8 +529,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v2.2.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v2.2.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v220", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v220 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -514,8 +539,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv2.2.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv2.2.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq220", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq220 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -524,8 +549,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v2.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v2.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v210", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v210 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -534,8 +559,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv2.1.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv2.1.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq210", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq210 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -544,8 +569,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v2.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v2.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v200", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v200 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -554,8 +579,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv2.0.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv2.0.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq200", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq200 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -564,16 +589,16 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v1.4.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v1.4.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v140", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v140 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") responseGetObp.body.extract[ResourceDocsJson] responseGetObp.code should equal(200) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv1.4.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv1.4.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq140", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq140 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -582,8 +607,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v1.3.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v1.3.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v130", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v130 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -592,8 +617,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv1.3.0", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv1.3.0" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq130", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq130 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -602,8 +627,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v1.2.1", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v1.2.1" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$v121", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v121 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -612,8 +637,8 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -OBPv1.2.1", ApiEndpoint1, VersionOfApi) { - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "OBPv1.2.1" / "obp").GET + scenario(s"We will test ${ApiEndpoint2.name} Api -$fq121", ApiEndpoint1, VersionOfApi) { + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / fq121 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -662,11 +687,11 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseDocs.resource_docs.map(responseDoc => stringToNodeSeq(responseDoc.description)) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v4.0.0 - resource_docs_requires_role props", ApiEndpoint1, VersionOfApi) { + scenario(s"We will test ${ApiEndpoint2.name} Api -$v400 - resource_docs_requires_role props", ApiEndpoint1, VersionOfApi) { setPropsValues( "resource_docs_requires_role" -> "true", ) - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v4.0.0" / "obp").GET + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v400 / "obp").GET val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] @@ -674,11 +699,11 @@ class ResourceDocsTest extends ResourceDocsV140ServerSetup with PropsReset with responseGetObp.toString contains(AuthenticatedUserIsRequired) should be (true) } - scenario(s"We will test ${ApiEndpoint2.name} Api -v4.0.0 - resource_docs_requires_role props- login in user", ApiEndpoint1, VersionOfApi) { + scenario(s"We will test ${ApiEndpoint2.name} Api -$v400 - resource_docs_requires_role props- login in user", ApiEndpoint1, VersionOfApi) { setPropsValues( "resource_docs_requires_role" -> "true", ) - val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / "v4.0.0" / "obp").GET <@ (user1) + val requestGetObp = (ResourceDocsV1_4Request /"banks"/ testBankId1.value/ "resource-docs" / v400 / "obp").GET <@ (user1) val responseGetObp = makeGetRequest(requestGetObp) And("We should get 200 and the response can be extract to case classes") val responseDocs = responseGetObp.body.extract[ResourceDocsJson] diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala index 6173ec700c..e0fad93ce5 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/ApiVersion.scala @@ -143,6 +143,8 @@ object ApiVersion { val berlinGroupV13 = ScannedApiVersion("berlin-group", "BG", "v1.3") val mxofV100 = ScannedApiVersion("mxof", "MXOF", "v1.0.0") val cnbv9 = ScannedApiVersion("CNBV9", "CNBV9", "v1.0.0") + val bahrainObfV100 = ScannedApiVersion("BAHRAIN-OBF", "BAHRAIN-OBF", "v1.0.0") + val auOpenBankingV100 = ScannedApiVersion("cds-au", "AU", "v1.0.0") /** * the ApiPathZero value must be got by obp-api project, so here is a workaround, let obp-api project modify this value From 54a1552643c62b1d0befe862cb559dde4ea5a5c5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 28 Jan 2026 15:23:44 +0100 Subject: [PATCH 4/6] test/ add tests for http4s-only resource docs and version validation Add integration tests to verify that the /resource-docs endpoint returns only http4s technology endpoints and rejects requests for non-v7 API versions. This ensures proper filtering and version handling in the http4s routes. --- .../scala/code/api/v7_0_0/Http4s700.scala | 47 +++++++----- .../code/api/v7_0_0/Http4s700RoutesTest.scala | 75 +++++++++++++++++++ 2 files changed, 102 insertions(+), 20 deletions(-) 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 70f9d8884e..e812a4f9e7 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 @@ -9,7 +9,7 @@ import code.api.util.APIUtil.{EmptyBody, _} import code.api.util.ApiRole.canGetCardsForBank import code.api.util.ApiTag._ import code.api.util.ErrorMessages._ -import code.api.util.http4s.{Http4sRequestAttributes, ResourceDocMiddleware} +import code.api.util.http4s.{ErrorResponseConverter, Http4sRequestAttributes, ResourceDocMiddleware} import code.api.util.http4s.Http4sRequestAttributes.{RequestOps, EndpointHelpers} import code.api.util.{ApiVersionUtils, CallContext, CustomJsonFormats, NewStyle} import code.api.v1_3_0.JSONFactory1_3_0 @@ -26,6 +26,7 @@ import org.http4s.dsl.io._ import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import scala.language.{higherKinds, implicitConversions} +import scala.util.{Failure, Success, Try} object Http4s700 { @@ -201,25 +202,31 @@ object Http4s700 { val getResourceDocsObpV700: HttpRoutes[IO] = HttpRoutes.of[IO] { case req @ GET -> `prefixPath` / "resource-docs" / requestedApiVersionString / "obp" => - EndpointHelpers.executeAndRespond(req) { _ => - val queryParams = req.uri.query.multiParams - val tags = queryParams - .get("tags") - .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).map(ResourceDocTag(_)).toList) - val functions = queryParams - .get("functions") - .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).toList) - val localeParam = queryParams - .get("locale") - .flatMap(_.headOption) - .orElse(queryParams.get("language").flatMap(_.headOption)) - .map(_.trim) - .filter(_.nonEmpty) - for { - 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, includeTechnology = true) + implicit val cc: CallContext = req.callContext + val queryParams = req.uri.query.multiParams + val tags = queryParams + .get("tags") + .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).map(ResourceDocTag(_)).toList) + val functions = queryParams + .get("functions") + .map(_.flatMap(_.split(",").toList).map(_.trim).filter(_.nonEmpty).toList) + val localeParam = queryParams + .get("locale") + .flatMap(_.headOption) + .orElse(queryParams.get("language").flatMap(_.headOption)) + .map(_.trim) + .filter(_.nonEmpty) + + Try(ApiVersionUtils.valueOf(requestedApiVersionString)) match { + case Success(requestedApiVersion) if requestedApiVersion == ApiVersion.v7_0_0 => + val http4sOnlyDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs.toList, tags, functions) + EndpointHelpers.executeAndRespond(req) { _ => + Future.successful(JSONFactory1_4_0.createResourceDocsJson(http4sOnlyDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true)) + } + case Success(_) => + ErrorResponseConverter.createErrorResponse(400, s"API Version not supported by this server: $requestedApiVersionString", cc) + case Failure(_) => + ErrorResponseConverter.createErrorResponse(400, s"Invalid API Version: $requestedApiVersionString", cc) } } 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 9ef5bff8bf..8b0f445c09 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 @@ -267,6 +267,81 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { } } + scenario("Return only http4s technology endpoints", Http4s700RoutesTag) { + Given("GET /obp/v7.0.0/resource-docs/v7.0.0/obp request") + setPropsValues("resource_docs_requires_role" -> "false") + val request = Request[IO]( + method = Method.GET, + uri = Uri.unsafeFromString("/obp/v7.0.0/resource-docs/v7.0.0/obp") + ) + + When("Running through wrapped routes") + val (status, json) = runAndParseJson(request) + + Then("Response is 200 OK and includes no lift endpoints") + status shouldBe Status.Ok + json match { + case JObject(fields) => + toFieldMap(fields).get("resource_docs") match { + 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 + 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 == "lift" + case _ => false + } + case _ => false + } + case _ => false + } shouldBe false + case _ => + fail("Expected resource_docs field to be an array") + } + case _ => + fail("Expected JSON object for resource-docs endpoint") + } + } + + scenario("Reject requesting non-v7 API version docs", Http4s700RoutesTag) { + Given("GET /obp/v7.0.0/resource-docs/v6.0.0/obp request") + setPropsValues("resource_docs_requires_role" -> "false") + val request = Request[IO]( + method = Method.GET, + uri = Uri.unsafeFromString("/obp/v7.0.0/resource-docs/v6.0.0/obp") + ) + + When("Running through wrapped routes") + val (status, json) = runAndParseJson(request) + + Then("Response is 400 Bad Request") + status.code shouldBe 400 + json match { + case JObject(fields) => + toFieldMap(fields).get("message") match { + case Some(JString(message)) => + message should include("API Version not supported") + case _ => + fail("Expected message field as JSON string for invalid-version response") + } + case _ => + fail("Expected JSON object for invalid-version response") + } + } + scenario("Reject unauthenticated access when resource docs role is required", Http4s700RoutesTag) { Given("GET /obp/v7.0.0/resource-docs/v7.0.0/obp request without auth headers and role required") setPropsValues("resource_docs_requires_role" -> "true") From 747d761c9b3b3c790ce9d0610060121cbb2a2538 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 28 Jan 2026 16:00:57 +0100 Subject: [PATCH 5/6] feature/(http4s): handle errors in executeAndRespond and improve error parsing Refactor executeAndRespond to properly handle exceptions from Future and convert them to HTTP responses using ErrorResponseConverter. This ensures consistent error handling across HTTP4S endpoints. Simplify error response creation by parsing APIFailureNewStyle exceptions from JSON message instead of direct type matching, making error handling more robust. Update API version validation in Http4s700 to use NewStyle.function.tryons and Helper.booleanToFuture for consistent error handling patterns. Adjust test to use proper error message constant for invalid API version. --- .../util/http4s/ErrorResponseConverter.scala | 48 ++++++------------- .../code/api/util/http4s/Http4sSupport.scala | 11 +++-- .../scala/code/api/v7_0_0/Http4s700.scala | 28 +++++++---- .../code/api/v7_0_0/Http4s700RoutesTest.scala | 5 +- 4 files changed, 44 insertions(+), 48 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala b/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala index 856b0f1ee7..25c2d78291 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/ErrorResponseConverter.scala @@ -1,10 +1,9 @@ package code.api.util.http4s import cats.effect._ -import code.api.APIFailureNewStyle import code.api.util.ErrorMessages._ import code.api.util.CallContext -import net.liftweb.common.{Failure => LiftFailure} +import net.liftweb.json.{JInt, JString, parseOpt} import net.liftweb.json.compactRender import net.liftweb.json.JsonDSL._ import org.http4s._ @@ -30,6 +29,8 @@ object ErrorResponseConverter { implicit val formats: Formats = CustomJsonFormats.formats private val jsonContentType: `Content-Type` = `Content-Type`(MediaType.application.json) + private val internalFieldsFailCode = "failCode" + private val internalFieldsFailMsg = "failMsg" /** * OBP standard error response format. @@ -51,39 +52,20 @@ object ErrorResponseConverter { * Convert any error to http4s Response[IO]. */ def toHttp4sResponse(error: Throwable, callContext: CallContext): IO[Response[IO]] = { - error match { - case e: APIFailureNewStyle => apiFailureToResponse(e, callContext) - case _ => unknownErrorToResponse(error, callContext) + parseApiFailureFromExceptionMessage(error).map { failure => + createErrorResponse(failure.code, failure.message, callContext) + }.getOrElse { + unknownErrorToResponse(error, callContext) } } - /** - * Convert APIFailureNewStyle to http4s Response. - * Uses failCode as HTTP status and failMsg as error message. - */ - def apiFailureToResponse(failure: APIFailureNewStyle, callContext: CallContext): IO[Response[IO]] = { - val errorJson = OBPErrorResponse(failure.failCode, failure.failMsg) - val status = org.http4s.Status.fromInt(failure.failCode).getOrElse(org.http4s.Status.BadRequest) - IO.pure( - Response[IO](status) - .withEntity(toJsonString(errorJson)) - .withContentType(jsonContentType) - .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) - ) - } - - /** - * Convert Lift Box Failure to http4s Response. - * Returns 400 Bad Request with failure message. - */ - def boxFailureToResponse(failure: LiftFailure, callContext: CallContext): IO[Response[IO]] = { - val errorJson = OBPErrorResponse(400, failure.msg) - IO.pure( - Response[IO](org.http4s.Status.BadRequest) - .withEntity(toJsonString(errorJson)) - .withContentType(jsonContentType) - .putHeaders(org.http4s.Header.Raw(CIString("Correlation-Id"), callContext.correlationId)) - ) + private def parseApiFailureFromExceptionMessage(error: Throwable): Option[OBPErrorResponse] = { + Option(error.getMessage).flatMap(parseOpt).flatMap { json => + (json \ internalFieldsFailCode, json \ internalFieldsFailMsg) match { + case (JInt(code), JString(message)) => Some(OBPErrorResponse(code.toInt, message)) + case _ => None + } + } } /** @@ -91,7 +73,7 @@ object ErrorResponseConverter { * Returns 500 Internal Server Error. */ def unknownErrorToResponse(e: Throwable, callContext: CallContext): IO[Response[IO]] = { - val errorJson = OBPErrorResponse(500, s"$UnknownError: ${e.getMessage}") + val errorJson = OBPErrorResponse(500, UnknownError) IO.pure( Response[IO](org.http4s.Status.InternalServerError) .withEntity(toJsonString(errorJson)) diff --git a/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala b/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala index f231ba002c..1f95980fcd 100644 --- a/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala +++ b/obp-api/src/main/scala/code/api/util/http4s/Http4sSupport.scala @@ -106,9 +106,14 @@ object Http4sRequestAttributes { def executeAndRespond[A](req: Request[IO])(f: CallContext => Future[A])(implicit formats: Formats): IO[Response[IO]] = { implicit val cc: CallContext = req.callContext for { - result <- IO.fromFuture(IO(f(cc))) - jsonString = prettyRender(Extraction.decompose(result)) - response <- Ok(jsonString) + attempted <- IO.fromFuture(IO(f(cc))).attempt + response <- attempted match { + case Right(result) => + val jsonString = prettyRender(Extraction.decompose(result)) + Ok(jsonString) + case Left(error) => + ErrorResponseConverter.toHttp4sResponse(error, cc) + } } yield response } 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 e812a4f9e7..6d28b18a18 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 @@ -26,7 +26,7 @@ import org.http4s.dsl.io._ import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future import scala.language.{higherKinds, implicitConversions} -import scala.util.{Failure, Success, Try} +import code.util.Helper object Http4s700 { @@ -217,16 +217,24 @@ object Http4s700 { .map(_.trim) .filter(_.nonEmpty) - Try(ApiVersionUtils.valueOf(requestedApiVersionString)) match { - case Success(requestedApiVersion) if requestedApiVersion == ApiVersion.v7_0_0 => - val http4sOnlyDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs.toList, tags, functions) - EndpointHelpers.executeAndRespond(req) { _ => - Future.successful(JSONFactory1_4_0.createResourceDocsJson(http4sOnlyDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true)) + EndpointHelpers.executeAndRespond(req) { _ => + for { + requestedApiVersion <- NewStyle.function.tryons( + failMsg = s"$InvalidApiVersionString Current value: $requestedApiVersionString", + failCode = 400, + callContext = Some(cc) + ) { + ApiVersionUtils.valueOf(requestedApiVersionString) + } + _ <- Helper.booleanToFuture( + failMsg = s"$InvalidApiVersionString This server supports only ${ApiVersion.v7_0_0}. Current value: $requestedApiVersionString", + failCode = 400, + cc = Some(cc) + ) { + requestedApiVersion == ApiVersion.v7_0_0 } - case Success(_) => - ErrorResponseConverter.createErrorResponse(400, s"API Version not supported by this server: $requestedApiVersionString", cc) - case Failure(_) => - ErrorResponseConverter.createErrorResponse(400, s"Invalid API Version: $requestedApiVersionString", cc) + http4sOnlyDocs = ResourceDocsAPIMethodsUtil.filterResourceDocs(resourceDocs.toList, tags, functions) + } yield JSONFactory1_4_0.createResourceDocsJson(http4sOnlyDocs, isVersion4OrHigher = true, localeParam, includeTechnology = true) } } 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 8b0f445c09..3f8e80795a 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 @@ -3,7 +3,7 @@ package code.api.v7_0_0 import cats.effect.IO import cats.effect.unsafe.implicits.global import code.api.util.ApiRole.{canGetCardsForBank, canReadResourceDoc} -import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, BankNotFound, UserHasMissingRoles} +import code.api.util.ErrorMessages.{AuthenticatedUserIsRequired, BankNotFound, InvalidApiVersionString, UserHasMissingRoles} import code.setup.ServerSetupWithTestData import net.liftweb.json.JValue import net.liftweb.json.JsonAST.{JArray, JField, JObject, JString} @@ -333,7 +333,8 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { case JObject(fields) => toFieldMap(fields).get("message") match { case Some(JString(message)) => - message should include("API Version not supported") + message should include(InvalidApiVersionString) + message should include("v6.0.0") case _ => fail("Expected message field as JSON string for invalid-version response") } From 30f83680a6274b8c8d9205a51a49b8c803fd22ea Mon Sep 17 00:00:00 2001 From: hongwei Date: Thu, 29 Jan 2026 14:49:48 +0100 Subject: [PATCH 6/6] refactor/(api): replace hardcoded technology strings with constants Use TECHNOLOGY_LIFTWEB and TECHNOLOGY_HTTP4S constants from Constant object instead of inline string literals "lift" and "http4s" across codebase. This improves maintainability and reduces risk of typos. --- obp-api/src/main/scala/code/api/constant/constant.scala | 5 +++++ .../src/main/scala/code/api/v1_4_0/JSONFactory1_4_0.scala | 3 ++- .../api/ResourceDocs1_4_0/ResourceDocsTechnologyTest.scala | 3 ++- .../code/api/ResourceDocs1_4_0/ResourceDocsTest.scala | 3 ++- .../test/scala/code/api/v1_4_0/JSONFactory1_4_0Test.scala | 6 +++--- .../test/scala/code/api/v7_0_0/Http4s700RoutesTest.scala | 7 ++++--- 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/obp-api/src/main/scala/code/api/constant/constant.scala b/obp-api/src/main/scala/code/api/constant/constant.scala index 9816ad4a29..0f7167cbe1 100644 --- a/obp-api/src/main/scala/code/api/constant/constant.scala +++ b/obp-api/src/main/scala/code/api/constant/constant.scala @@ -654,6 +654,11 @@ object Constant extends MdcLoggable { CAN_GRANT_ACCESS_TO_VIEWS, CAN_REVOKE_ACCESS_TO_VIEWS, ) + + + final val TECHNOLOGY_LIFTWEB = "liftweb" + final val TECHNOLOGY_HTTP4S = "http4s" + } 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 f47ddbf0be..1608f3e98e 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 @@ -3,6 +3,7 @@ package code.api.v1_4_0 import code.api.Constant.{CREATE_LOCALISED_RESOURCE_DOC_JSON_TTL, LOCALISED_RESOURCE_DOC_PREFIX} import code.api.berlin.group.v1_3.JvalueCaseClass import code.api.cache.Caching +import code.api.Constant import java.util.Date import code.api.util.APIUtil.{EmptyBody, PrimaryDataBody, ResourceDoc} import code.api.util.ApiTag.ResourceDocTag @@ -568,7 +569,7 @@ object JSONFactory1_4_0 extends MdcLoggable{ val technology = if (includeTechnology) { - Some(if (resourceDocUpdatedTags.http4sPartialFunction.isDefined) "http4s" else "lift") + Some(if (resourceDocUpdatedTags.http4sPartialFunction.isDefined) Constant.TECHNOLOGY_HTTP4S else Constant.TECHNOLOGY_LIFTWEB) } else { None } 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 index a701b3846d..bec8e32ecf 100644 --- 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 @@ -1,5 +1,6 @@ package code.api.ResourceDocs1_4_0 +import code.api.Constant import code.setup.{PropsReset, ServerSetup} import com.openbankproject.commons.util.ApiVersion import net.liftweb.json.JsonAST.{JArray, JNothing, JNull, JString} @@ -20,7 +21,7 @@ class ResourceDocsTechnologyTest extends ServerSetup with PropsReset { (response.body \ "resource_docs") match { case JArray(docs) => val technology = docs.head \ "implemented_by" \ "technology" - technology should equal(JString("lift")) + technology should equal(JString(Constant.TECHNOLOGY_LIFTWEB)) 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 dd1cb7bd9c..8135580af2 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 @@ -1,5 +1,6 @@ package code.api.ResourceDocs1_4_0 +import code.api.Constant import code.api.ResourceDocs1_4_0.ResourceDocs140.ImplementationsResourceDocs import code.api.berlin.group.ConstantsBG import code.api.util.APIUtil.OAuth._ @@ -104,7 +105,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") + responseDocs.resource_docs.head.implemented_by.technology shouldBe Some(Constant.TECHNOLOGY_LIFTWEB) //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 62484f93a0..38304e9208 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 @@ -1,7 +1,7 @@ package code.api.v1_4_0 import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.usersJsonV400 - +import code.api.Constant import java.util.Date import code.api.util.APIUtil.ResourceDoc import code.api.util.{APIUtil, ExampleValue} @@ -131,13 +131,13 @@ class JSONFactory1_4_0Test extends code.setup.ServerSetup { 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") + json2.implemented_by.technology shouldBe Some(Constant.TECHNOLOGY_LIFTWEB) } 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") + json.implemented_by.technology shouldBe Some(Constant.TECHNOLOGY_HTTP4S) } scenario("createTypedBody should work well, no exception is good enough") { 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 3f8e80795a..56cf330d57 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 @@ -1,5 +1,6 @@ package code.api.v7_0_0 +import code.api.Constant import cats.effect.IO import cats.effect.unsafe.implicits.global import code.api.util.ApiRole.{canGetCardsForBank, canReadResourceDoc} @@ -252,7 +253,7 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { toFieldMap(rdFields).get("implemented_by") match { case Some(JObject(implFields)) => toFieldMap(implFields).get("technology") match { - case Some(JString(value)) => value == "http4s" + case Some(JString(value)) => value == Constant.TECHNOLOGY_HTTP4S case _ => false } case _ => false @@ -289,7 +290,7 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { toFieldMap(rdFields).get("implemented_by") match { case Some(JObject(implFields)) => toFieldMap(implFields).get("technology") match { - case Some(JString(value)) => value == "http4s" + case Some(JString(value)) => value == Constant.TECHNOLOGY_HTTP4S case _ => false } case _ => false @@ -301,7 +302,7 @@ class Http4s700RoutesTest extends ServerSetupWithTestData { toFieldMap(rdFields).get("implemented_by") match { case Some(JObject(implFields)) => toFieldMap(implFields).get("technology") match { - case Some(JString(value)) => value == "lift" + case Some(JString(value)) => value == Constant.TECHNOLOGY_LIFTWEB case _ => false } case _ => false