From 13dbed36fa2e35ea7f587936043424b182c4c927 Mon Sep 17 00:00:00 2001 From: Pavel Salamon Date: Tue, 1 Oct 2024 14:20:04 +0200 Subject: [PATCH] health endpoint --- .../absa/atum/server/api/http/Endpoints.scala | 6 +- .../co/absa/atum/server/api/http/Routes.scala | 5 +- .../atum/server/model/StatusResponse.scala | 35 ++++++++++++ .../api/http/HealthEndpointUnitTests.scala | 57 +++++++++++++++++++ 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 server/src/main/scala/za/co/absa/atum/server/model/StatusResponse.scala create mode 100644 server/src/test/scala/za/co/absa/atum/server/api/http/HealthEndpointUnitTests.scala diff --git a/server/src/main/scala/za/co/absa/atum/server/api/http/Endpoints.scala b/server/src/main/scala/za/co/absa/atum/server/api/http/Endpoints.scala index 66a4efad..c524d482 100644 --- a/server/src/main/scala/za/co/absa/atum/server/api/http/Endpoints.scala +++ b/server/src/main/scala/za/co/absa/atum/server/api/http/Endpoints.scala @@ -21,7 +21,7 @@ import sttp.tapir.generic.auto.schemaForCaseClass import sttp.tapir.json.circe.jsonBody import sttp.tapir.ztapir._ import za.co.absa.atum.model.dto._ -import za.co.absa.atum.server.model.ErrorResponse +import za.co.absa.atum.server.model.{ErrorResponse, StatusResponse} import za.co.absa.atum.server.model.SuccessResponse._ import sttp.tapir.{PublicEndpoint, Validator, endpoint} import za.co.absa.atum.server.api.http.ApiPaths.{Health, ZioMetrics, _} @@ -177,7 +177,7 @@ trait Endpoints extends BaseEndpoints { endpoint.get.in(ZioMetrics).out(stringBody) } - protected val healthEndpoint: PublicEndpoint[Unit, Unit, Unit, Any] = - endpoint.get.in(Health) + protected val healthEndpoint: PublicEndpoint[Unit, Unit, StatusResponse, Any] = + endpoint.get.in(Health).out(jsonBody[StatusResponse].example(StatusResponse.up)) } diff --git a/server/src/main/scala/za/co/absa/atum/server/api/http/Routes.scala b/server/src/main/scala/za/co/absa/atum/server/api/http/Routes.scala index 825e3ce1..f8095ec4 100644 --- a/server/src/main/scala/za/co/absa/atum/server/api/http/Routes.scala +++ b/server/src/main/scala/za/co/absa/atum/server/api/http/Routes.scala @@ -27,7 +27,7 @@ import sttp.tapir.ztapir._ import za.co.absa.atum.model.dto.{AdditionalDataDTO, AdditionalDataPatchDTO, CheckpointV2DTO, PartitioningWithIdDTO} import za.co.absa.atum.server.api.controller.{CheckpointController, FlowController, PartitioningController} import za.co.absa.atum.server.config.{HttpMonitoringConfig, JvmMonitoringConfig} -import za.co.absa.atum.server.model.ErrorResponse +import za.co.absa.atum.server.model.{ErrorResponse, StatusResponse} import za.co.absa.atum.server.model.SuccessResponse._ import zio._ import zio.interop.catz._ @@ -103,7 +103,7 @@ trait Routes extends Endpoints with ServerOptions { PartitioningController.getFlowPartitionings(flowId, limit, offset) } ), - createServerEndpoint(healthEndpoint, (_: Unit) => ZIO.unit) + createServerEndpoint(healthEndpoint, (_: Unit) => ZIO.succeed(StatusResponse.up)) ) ZHttp4sServerInterpreter[HttpEnv.Env](http4sServerOptions(metricsInterceptorOption)).from(endpoints).toRoutes } @@ -125,6 +125,7 @@ trait Routes extends Endpoints with ServerOptions { // getPartitioningMeasuresEndpointV2, // getFlowPartitioningsEndpointV2, // getPartitioningMainFlowEndpointV2 + healthEndpoint ) ZHttp4sServerInterpreter[HttpEnv.Env](http4sServerOptions(None)) .from(SwaggerInterpreter().fromEndpoints[HttpEnv.F](endpoints, "Atum API", "1.0")) diff --git a/server/src/main/scala/za/co/absa/atum/server/model/StatusResponse.scala b/server/src/main/scala/za/co/absa/atum/server/model/StatusResponse.scala new file mode 100644 index 00000000..1950bb1a --- /dev/null +++ b/server/src/main/scala/za/co/absa/atum/server/model/StatusResponse.scala @@ -0,0 +1,35 @@ +/* + * Copyright 2021 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.atum.server.model + +case class StatusResponse(status: String, message: String) + +object StatusResponse { + + import io.circe.generic.semiauto._ + + implicit val encoder: io.circe.Encoder[StatusResponse] = deriveEncoder + implicit val decoder: io.circe.Decoder[StatusResponse] = deriveDecoder + + def up: StatusResponse = { + StatusResponse( + status = "UP", + message = "Atum server is up and running" + ) + } + +} diff --git a/server/src/test/scala/za/co/absa/atum/server/api/http/HealthEndpointUnitTests.scala b/server/src/test/scala/za/co/absa/atum/server/api/http/HealthEndpointUnitTests.scala new file mode 100644 index 00000000..77ed5657 --- /dev/null +++ b/server/src/test/scala/za/co/absa/atum/server/api/http/HealthEndpointUnitTests.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2021 ABSA Group Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package za.co.absa.atum.server.api.http + +import sttp.client3.circe.asJson +import sttp.client3.testing.SttpBackendStub +import sttp.client3.{UriContext, basicRequest} +import sttp.model.StatusCode +import sttp.tapir.server.stub.TapirStubInterpreter +import sttp.tapir.ztapir.{RIOMonadError, RichZEndpoint} +import za.co.absa.atum.server.model.StatusResponse +import zio.test.Assertion.equalTo +import zio.test.{Spec, TestEnvironment, ZIOSpecDefault, assertZIO} +import zio.{Scope, ZIO} + +object HealthEndpointUnitTests extends ZIOSpecDefault with Endpoints { + + private val healthServerEndpoint = healthEndpoint.zServerLogic((_: Unit) => ZIO.succeed(StatusResponse.up)) + + override def spec: Spec[TestEnvironment with Scope, Any] = { + val backendStub = TapirStubInterpreter(SttpBackendStub.apply(new RIOMonadError[Any])) + .whenServerEndpoint(healthServerEndpoint) + .thenRunLogic() + .backend() + + suite("HealthEndpointSuite")( + test("Returns expected StatusResponse") { + val request = basicRequest + .get(uri"https://test.com/health") + .response(asJson[StatusResponse]) + + val response = request.send(backendStub) + + val body = response.map(_.body) + val statusCode = response.map(_.code) + + assertZIO(body <&> statusCode)( + equalTo(Right(StatusResponse.up), StatusCode.Ok) + ) + } + ) + } +}