diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala index 839dead9..81898e90 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/api/impl/ProcessApiServiceImpl.scala @@ -35,10 +35,14 @@ import it.pagopa.interop.commons.utils.AkkaUtils._ import it.pagopa.interop.commons.utils.OpenapiUtils.parseArrayParameters import it.pagopa.interop.commons.utils.TypeConversions._ import it.pagopa.interop.commons.utils.errors.{ComponentError, GenericComponentErrors} +import it.pagopa.interop.catalogmanagement.model.CatalogItemMode +import it.pagopa.interop.agreementmanagement.model.agreement.{ + Active => AgreementActive, + Suspended => AgreementSuspended +} import java.util.UUID import scala.concurrent.{ExecutionContext, Future} -import it.pagopa.interop.catalogmanagement.model.CatalogItemMode final case class ProcessApiServiceImpl( catalogManagementService: CatalogManagementService, @@ -178,6 +182,7 @@ final case class ProcessApiServiceImpl( eServicesIds = eServicesIds, producersIds = Nil, consumersIds = Seq(organizationId), + descriptorsIds = Nil, states = agreementStates ) .map(_.map(_.eserviceId)) @@ -235,6 +240,32 @@ final case class ProcessApiServiceImpl( val operationLabel = s"Publishing descriptor $descriptorId for EService $eServiceId" logger.info(operationLabel) + def changeStateOfOldDescriptorOrCancelPublication( + eServiceId: UUID, + oldDescriptorId: UUID, + descriptorId: UUID, + validStates: Seq[PersistentAgreementState] + ): Future[Unit] = for { + validAgreements <- agreementManagementService.getAgreements( + List(eServiceId), + Nil, + Nil, + List(oldDescriptorId), + validStates + ) + _ <- validAgreements.headOption match { + case Some(_) => + deprecateDescriptor(oldDescriptorId.toString, eServiceId.toString).recoverWith(error => + resetDescriptorToDraft(eServiceId.toString, descriptorId.toString).flatMap(_ => Future.failed(error)) + ) + case None => + catalogManagementService.archiveDescriptor(eServiceId.toString, oldDescriptorId.toString) recoverWith ( + error => + resetDescriptorToDraft(eServiceId.toString, descriptorId.toString).flatMap(_ => Future.failed(error)) + ) + } + } yield () + def verifyRiskAnalysisForPublication(catalogItem: CatalogItem): Future[Unit] = catalogItem.mode match { case Deliver => Future.unit case Receive => @@ -265,10 +296,11 @@ final case class ProcessApiServiceImpl( _ <- catalogManagementService.publishDescriptor(eServiceId, descriptorId) _ <- currentActiveDescriptor .map(oldDescriptor => - deprecateDescriptorOrCancelPublication( - eServiceId = eServiceId, - descriptorIdToDeprecate = oldDescriptor.id.toString, - descriptorIdToCancel = descriptorId + changeStateOfOldDescriptorOrCancelPublication( + eServiceId = eServiceUuid, + oldDescriptorId = oldDescriptor.id, + descriptorId = descriptorUuid, + validStates = List(AgreementActive, AgreementSuspended) ) ) .sequence @@ -467,13 +499,6 @@ final case class ProcessApiServiceImpl( EServiceCannotBeUpdated(eService.id.toString) ) - private[this] def deprecateDescriptorOrCancelPublication( - eServiceId: String, - descriptorIdToDeprecate: String, - descriptorIdToCancel: String - )(implicit contexts: Seq[(String, String)]): Future[Unit] = deprecateDescriptor(descriptorIdToDeprecate, eServiceId) - .recoverWith(error => resetDescriptorToDraft(eServiceId, descriptorIdToCancel).flatMap(_ => Future.failed(error))) - private[this] def deprecateDescriptor(descriptorId: String, eServiceId: String)(implicit contexts: Seq[(String, String)] ): Future[Unit] = catalogManagementService diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelAgreementQueries.scala b/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelAgreementQueries.scala index 36ac8cc6..f0eea339 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelAgreementQueries.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/common/readmodel/ReadModelAgreementQueries.scala @@ -17,13 +17,14 @@ object ReadModelAgreementQueries extends ReadModelQuery { eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState], offset: Int, limit: Int )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] = { val query: Bson = - getAgreementsFilters(eServicesIds, consumersIds, producersIds, states) + getAgreementsFilters(eServicesIds, consumersIds, producersIds, descriptorsIds, states) for { agreements <- readModel.aggregate[PersistentAgreement]( @@ -40,20 +41,23 @@ object ReadModelAgreementQueries extends ReadModelQuery { eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState] ): Bson = { val statesFilter = listStatesFilter(states) - val eServicesIdsFilter = + val eServicesIdsFilter = mapToVarArgs(eServicesIds.map(id => Filters.eq("data.eserviceId", id.toString)))(Filters.or) - val consumersIdsFilter = + val consumersIdsFilter = mapToVarArgs(consumersIds.map(id => Filters.eq("data.consumerId", id.toString)))(Filters.or) - val producersIdsFilter = + val producersIdsFilter = mapToVarArgs(producersIds.map(id => Filters.eq("data.producerId", id.toString)))(Filters.or) + val descriptorsIdsFilter = + mapToVarArgs(descriptorsIds.map(id => Filters.eq("data.descriptorId", id.toString)))(Filters.or) mapToVarArgs( - eServicesIdsFilter.toList ++ consumersIdsFilter.toList ++ producersIdsFilter.toList ++ statesFilter.toList + eServicesIdsFilter.toList ++ consumersIdsFilter.toList ++ producersIdsFilter.toList ++ descriptorsIdsFilter.toList ++ statesFilter.toList )(Filters.and).getOrElse(Filters.empty()) } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/AgreementManagementService.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/AgreementManagementService.scala index d5466636..c4552445 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/AgreementManagementService.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/AgreementManagementService.scala @@ -11,6 +11,7 @@ trait AgreementManagementService { eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState] )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] } diff --git a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/AgreementManagementServiceImpl.scala b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/AgreementManagementServiceImpl.scala index b5e601cc..bf891027 100644 --- a/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/AgreementManagementServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/catalogprocess/service/impl/AgreementManagementServiceImpl.scala @@ -14,24 +14,35 @@ object AgreementManagementServiceImpl extends AgreementManagementService { eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState] )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] = - getAllAgreements(eServicesIds, consumersIds, producersIds, states) + getAllAgreements(eServicesIds, consumersIds, producersIds, descriptorsIds, states) private def getAgreements( eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState], offset: Int, limit: Int )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] = - ReadModelAgreementQueries.getAgreements(eServicesIds, consumersIds, producersIds, states, offset, limit) + ReadModelAgreementQueries.getAgreements( + eServicesIds, + consumersIds, + producersIds, + descriptorsIds, + states, + offset, + limit + ) private def getAllAgreements( eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState] )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] = { @@ -40,6 +51,7 @@ object AgreementManagementServiceImpl extends AgreementManagementService { eServicesIds = eServicesIds, consumersIds = consumersIds, producersIds = producersIds, + descriptorsIds = descriptorsIds, states = states, offset = offset, limit = 50 diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala index 3cc5fe01..70e77c8d 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/CatalogProcessSpec.scala @@ -2,7 +2,11 @@ package it.pagopa.interop.catalogprocess import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest -import it.pagopa.interop.agreementmanagement.model.agreement.{Active, PersistentAgreementState} +import it.pagopa.interop.agreementmanagement.model.agreement.{ + Active => AgreementActive, + Suspended => AgreementSuspended, + PersistentAgreementState +} import it.pagopa.interop.authorizationmanagement.client.{model => AuthorizationManagementDependency} import it.pagopa.interop.catalogmanagement.client.model.AgreementApprovalPolicy.AUTOMATIC import it.pagopa.interop.catalogmanagement.client.{model => CatalogManagementDependency} @@ -135,11 +139,11 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR val limit = 50 (mockAgreementManagementService - .getAgreements(_: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[PersistentAgreementState])( + .getAgreements(_: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[PersistentAgreementState])( _: ExecutionContext, _: ReadModelService )) - .expects(eServicesIds, Seq(requesterId), producersIds, Seq(Active), *, *) + .expects(eServicesIds, Seq(requesterId), producersIds, Nil, Seq(AgreementActive), *, *) .once() .returns(Future.successful(Seq(SpecData.agreement))) @@ -204,11 +208,11 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR val limit = 50 (mockAgreementManagementService - .getAgreements(_: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[PersistentAgreementState])( + .getAgreements(_: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[PersistentAgreementState])( _: ExecutionContext, _: ReadModelService )) - .expects(eServicesIds, Seq(requesterId), producersIds, Seq(Active), *, *) + .expects(eServicesIds, Seq(requesterId), producersIds, Nil, Seq(AgreementActive), *, *) .once() .returns(Future.successful(Seq.empty)) @@ -1409,6 +1413,173 @@ class CatalogProcessSpec extends SpecHelper with AnyWordSpecLike with ScalatestR status shouldEqual StatusCodes.NoContent } } + + "succeed if descriptor is Draft and archive the previous one" in { + val requesterId = UUID.randomUUID() + val descriptorId1 = UUID.randomUUID() + val descriptorId2 = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(SpecData.catalogItem.id, *, *) + .once() + .returns( + Future.successful( + SpecData.catalogItem.copy( + producerId = requesterId, + descriptors = Seq( + SpecData.catalogDescriptor + .copy( + id = descriptorId1, + state = Published, + interface = Option(SpecData.catalogDocument), + version = "2" + ), + SpecData.catalogDescriptor + .copy(id = descriptorId2, state = Draft, interface = Option(SpecData.catalogDocument)) + ) + ) + ) + ) + + (mockAgreementManagementService + .getAgreements(_: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[PersistentAgreementState])( + _: ExecutionContext, + _: ReadModelService + )) + .expects( + Seq(SpecData.catalogItem.id), + Nil, + Nil, + List(descriptorId1), + Seq(AgreementActive, AgreementSuspended), + *, + * + ) + .once() + .returns(Future.successful(Seq.empty)) + + (mockCatalogManagementService + .archiveDescriptor(_: String, _: String)(_: Seq[(String, String)])) + .expects(SpecData.catalogItem.id.toString, descriptorId1.toString, *) + .returning(Future.unit) + .once() + + (mockCatalogManagementService + .publishDescriptor(_: String, _: String)(_: Seq[(String, String)])) + .expects(SpecData.catalogItem.id.toString, descriptorId2.toString, *) + .returning(Future.unit) + .once() + + (mockAuthorizationManagementService + .updateStateOnClients( + _: UUID, + _: UUID, + _: AuthorizationManagementDependency.ClientComponentState, + _: Seq[String], + _: Int + )(_: Seq[(String, String)])) + .expects( + SpecData.catalogItem.id, + descriptorId2, + AuthorizationManagementDependency.ClientComponentState.ACTIVE, + SpecData.catalogDescriptor.audience, + SpecData.catalogDescriptor.voucherLifespan, + * + ) + .returning(Future.unit) + .once() + + Post() ~> service.publishDescriptor(SpecData.catalogItem.id.toString, descriptorId2.toString) ~> check { + status shouldEqual StatusCodes.NoContent + } + } + "succeed if descriptor is Draft and deprecate the previous one" in { + val requesterId = UUID.randomUUID() + val descriptorId1 = UUID.randomUUID() + val descriptorId2 = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) + + (mockCatalogManagementService + .getEServiceById(_: UUID)(_: ExecutionContext, _: ReadModelService)) + .expects(SpecData.catalogItem.id, *, *) + .once() + .returns( + Future.successful( + SpecData.catalogItem.copy( + producerId = requesterId, + descriptors = Seq( + SpecData.catalogDescriptor + .copy( + id = descriptorId1, + state = Published, + interface = Option(SpecData.catalogDocument), + version = "2" + ), + SpecData.catalogDescriptor + .copy(id = descriptorId2, state = Draft, interface = Option(SpecData.catalogDocument)) + ) + ) + ) + ) + + (mockAgreementManagementService + .getAgreements(_: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[UUID], _: Seq[PersistentAgreementState])( + _: ExecutionContext, + _: ReadModelService + )) + .expects( + Seq(SpecData.catalogItem.id), + Nil, + Nil, + List(descriptorId1), + Seq(AgreementActive, AgreementSuspended), + *, + * + ) + .once() + .returns(Future.successful(Seq(SpecData.agreement))) + + (mockCatalogManagementService + .deprecateDescriptor(_: String, _: String)(_: Seq[(String, String)])) + .expects(SpecData.catalogItem.id.toString, descriptorId1.toString, *) + .returning(Future.unit) + .once() + + (mockCatalogManagementService + .publishDescriptor(_: String, _: String)(_: Seq[(String, String)])) + .expects(SpecData.catalogItem.id.toString, descriptorId2.toString, *) + .returning(Future.unit) + .once() + + (mockAuthorizationManagementService + .updateStateOnClients( + _: UUID, + _: UUID, + _: AuthorizationManagementDependency.ClientComponentState, + _: Seq[String], + _: Int + )(_: Seq[(String, String)])) + .expects( + SpecData.catalogItem.id, + descriptorId2, + AuthorizationManagementDependency.ClientComponentState.ACTIVE, + SpecData.catalogDescriptor.audience, + SpecData.catalogDescriptor.voucherLifespan, + * + ) + .returning(Future.unit) + .once() + + Post() ~> service.publishDescriptor(SpecData.catalogItem.id.toString, descriptorId2.toString) ~> check { + status shouldEqual StatusCodes.NoContent + } + } "fail if descriptor has not interface" in { val requesterId = UUID.randomUUID() diff --git a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala index 711e8cf8..268ae9d1 100644 --- a/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala +++ b/src/test/scala/it/pagopa/interop/catalogprocess/util/FakeDependencies.scala @@ -335,6 +335,7 @@ object FakeDependencies { eServicesIds: Seq[UUID], consumersIds: Seq[UUID], producersIds: Seq[UUID], + descriptorsIds: Seq[UUID], states: Seq[PersistentAgreementState] )(implicit ec: ExecutionContext, readModel: ReadModelService): Future[Seq[PersistentAgreement]] = Future.successful(Seq.empty)