From 54a1552643c62b1d0befe862cb559dde4ea5a5c5 Mon Sep 17 00:00:00 2001 From: hongwei Date: Wed, 28 Jan 2026 15:23:44 +0100 Subject: [PATCH] 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 70f9d8884..e812a4f9e 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 9ef5bff8b..8b0f445c0 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")