diff --git a/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/PurposeApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/PurposeApiServiceImpl.scala index 061dabb..1882a8d 100644 --- a/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/PurposeApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/PurposeApiServiceImpl.scala @@ -106,10 +106,15 @@ final case class PurposeApiServiceImpl( tenantKind <- tenant.kind.toFuture(TenantKindNotFound(tenant.id)) } yield tenantKind - private def checkAgreements(eServiceId: UUID, consumerId: UUID, title: String): Future[Unit] = for { - agreements <- agreementManagementService.getAgreements(eServiceId, consumerId, OPERATIVE_AGREEMENT_STATES) - agreement <- agreements.headOption.toFuture(AgreementNotFound(eServiceId.toString, consumerId.toString)) - maybePurpose <- purposeManagementService + private def checkAgreements( + eServiceId: UUID, + consumerId: UUID, + title: String, + purposeId: Option[UUID] + ): Future[Unit] = for { + agreements <- agreementManagementService.getAgreements(eServiceId, consumerId, OPERATIVE_AGREEMENT_STATES) + agreement <- agreements.headOption.toFuture(AgreementNotFound(eServiceId.toString, consumerId.toString)) + result <- purposeManagementService .listPurposes( consumerId, title.some, @@ -122,9 +127,8 @@ final case class PurposeApiServiceImpl( limit = 1, exactMatchOnTitle = true ) - .map(_.results.headOption) - - _ <- maybePurpose.fold(Future.unit)(_ => Future.failed(DuplicatedPurposeName(title))) + purpose = purposeId.fold(result.results)(id => result.results.filterNot(_.id == id)) + _ <- purpose.headOption.map(_.title).fold(Future.unit)(_ => Future.failed(DuplicatedPurposeName(title))) } yield () override def createPurposeFromEService(seed: EServicePurposeSeed)(implicit @@ -146,7 +150,7 @@ final case class PurposeApiServiceImpl( _ <- checkFreeOfCharge(seed.isFreeOfCharge, seed.freeOfChargeReason) tenantKind <- getTenantKind(eService.producerId) purposeSeed = seed.toManagement(seed.eServiceId, riskAnalysis.riskAnalysisForm.toManagement(seed.riskAnalysisId)) - _ <- checkAgreements(seed.eServiceId, seed.consumerId, seed.title) + _ <- checkAgreements(seed.eServiceId, seed.consumerId, seed.title, None) purpose <- purposeManagementService.createPurpose(purposeSeed) isValidRiskAnalysisForm = isRiskAnalysisFormValid( riskAnalysisForm = purpose.riskAnalysisForm.map(_.toApi), @@ -171,7 +175,7 @@ final case class PurposeApiServiceImpl( _ <- checkFreeOfCharge(seed.isFreeOfCharge, seed.freeOfChargeReason) tenantKind <- getTenantKind(requesterOrgId) purposeSeed <- seed.toManagement(schemaOnlyValidation = true)(tenantKind).toFuture - _ <- checkAgreements(seed.eserviceId, seed.consumerId, seed.title) + _ <- checkAgreements(seed.eserviceId, seed.consumerId, seed.title, None) purpose <- purposeManagementService.createPurpose(purposeSeed) isValidRiskAnalysisForm = isRiskAnalysisFormValid( riskAnalysisForm = purpose.riskAnalysisForm.map(_.toApi), @@ -234,6 +238,7 @@ final case class PurposeApiServiceImpl( eService => if (eService.mode == Deliver) Future.unit else Future.failed(EServiceNotInDeliverMode(eService.id)), seed.isFreeOfCharge, seed.freeOfChargeReason, + seed.title, (_, tenantKind) => seed .toManagement(schemaOnlyValidation = true)(tenantKind) @@ -256,6 +261,7 @@ final case class PurposeApiServiceImpl( eService => if (eService.mode == Receive) Future.unit else Future.failed(EServiceNotInReceiveMode(eService.id)), seed.isFreeOfCharge, seed.freeOfChargeReason, + seed.title, (purpose, tenantKind) => seed .toManagement(schemaOnlyValidation = true, purpose.riskAnalysisForm.map(_.toApi))(tenantKind) @@ -270,11 +276,13 @@ final case class PurposeApiServiceImpl( eServiceModeCheck: CatalogItem => Future[Unit], isFreeOfCharge: Boolean, freeOfChargeReason: Option[String], + title: String, payload: (PersistentPurpose, PersistentTenantKind) => Future[PurposeManagementDependency.PurposeUpdateContent] )(implicit contexts: Seq[(String, String)]): Future[Purpose] = for { requesterOrgId <- getOrganizationIdFutureUUID(contexts) purposeUUID <- purposeId.toFutureUUID purpose <- purposeManagementService.getPurposeById(purposeUUID) + _ <- checkAgreements(purpose.eserviceId, purpose.consumerId, title, Some(purposeUUID)) _ <- assertOrganizationIsAConsumer(requesterOrgId, purpose.consumerId) _ <- assertPurposeIsInDraftState(purpose) eService <- catalogManagementService.getEServiceById(purpose.eserviceId) diff --git a/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/ResponseHandlers.scala b/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/ResponseHandlers.scala index edb5ccd..3868461 100644 --- a/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/ResponseHandlers.scala +++ b/src/main/scala/it/pagopa/interop/purposeprocess/api/impl/ResponseHandlers.scala @@ -96,8 +96,8 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotInDraftState) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) - - case Failure(ex) => internalServerError(ex, logMessage) + case Failure(ex: DuplicatedPurposeName) => conflict(ex, logMessage) + case Failure(ex) => internalServerError(ex, logMessage) } def updateReversePurposeResponse[T](logMessage: String)( @@ -114,8 +114,8 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotInDraftState) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) - - case Failure(ex) => internalServerError(ex, logMessage) + case Failure(ex: DuplicatedPurposeName) => conflict(ex, logMessage) + case Failure(ex) => internalServerError(ex, logMessage) } def getPurposeResponse[T](logMessage: String)( diff --git a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala index a7ef6b4..87b682b 100644 --- a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala +++ b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala @@ -1429,13 +1429,22 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate ) val purpose = - SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersion)) + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) implicit val context: Seq[(String, String)] = Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) + mockEServiceRetrieve(eserviceId, SpecData.eService.copy(id = eserviceId)) mockTenantRetrieve(consumerId, SpecData.tenant.copy(id = consumerId, kind = PersistentTenantKind.PRIVATE.some)) @@ -1446,6 +1455,47 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate status shouldEqual StatusCodes.OK } } + "fail if there is another purpose with the same title" in { + + val purposeId = UUID.randomUUID() + val eserviceId = UUID.randomUUID() + val consumerId = UUID.randomUUID() + val purposeUpdateContent = + PurposeUpdateContent( + title = "A title", + description = "A description", + isFreeOfCharge = false, + riskAnalysisForm = None, + dailyCalls = 100 + ) + + val purpose = + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) + + mockPurposeRetrieve(purposeId, purpose) + + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + val other = SpecData.purpose.copy( + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion), + title = "A title" + ) + mockListPurposesRetrieve(Seq(purpose, other)) + + Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { + status shouldEqual StatusCodes.Conflict + } + } "fail if case of eService with Receive mode" in { val purposeId = UUID.randomUUID() @@ -1464,11 +1514,19 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) val purpose = - SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersion)) + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) mockEServiceRetrieve(eserviceId, SpecData.eService.copy(id = eserviceId, mode = Receive)) mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { status shouldEqual StatusCodes.BadRequest val problem = responseAs[Problem] @@ -1493,11 +1551,19 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) val purpose = - SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersion)) + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) mockPurposeRetrieve(purposeId, purpose) mockEServiceRetrieve(eserviceId, SpecData.eService.copy(id = eserviceId)) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + mockListPurposesRetrieve(Seq(purpose)) + Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { status shouldEqual StatusCodes.BadRequest val problem = responseAs[Problem] @@ -1524,8 +1590,19 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate implicit val context: Seq[(String, String)] = Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) - mockPurposeRetrieve(purposeId, SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId)) + val purpose = + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) + + mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { status shouldEqual StatusCodes.Forbidden val problem = responseAs[Problem] @@ -1548,6 +1625,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate ) val purpose = SpecData.purpose.copy( + id = purposeId, eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersionNotInDraftState) @@ -1558,6 +1636,9 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { status shouldEqual StatusCodes.Forbidden val problem = responseAs[Problem] @@ -1583,7 +1664,12 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate ) val purpose = - SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersion)) + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) val seed = PurposeManagementDependency.PurposeUpdateContent( title = "A title", @@ -1606,6 +1692,9 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) mockEServiceRetrieve(eserviceId, SpecData.eService.copy(id = eserviceId, mode = Receive, producerId = producerId)) mockTenantRetrieve(producerId, SpecData.tenant.copy(id = producerId, kind = PersistentTenantKind.PRIVATE.some)) @@ -1616,6 +1705,46 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate status shouldEqual StatusCodes.OK } } + "fail if there is another purpose with the same title" in { + val purposeId = UUID.randomUUID() + val eserviceId = UUID.randomUUID() + val consumerId = UUID.randomUUID() + + val reversePurposeUpdateContent = + ReversePurposeUpdateContent( + title = "A title", + description = "A description", + isFreeOfCharge = false, + dailyCalls = 100 + ) + + val purpose = + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) + + mockPurposeRetrieve(purposeId, purpose) + + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + val other = SpecData.purpose.copy( + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion), + title = "A title" + ) + mockListPurposesRetrieve(Seq(purpose, other)) + + Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { + status shouldEqual StatusCodes.Conflict + } + } "fail if case of eService with Deliver mode" in { val purposeId = UUID.randomUUID() @@ -1633,11 +1762,19 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) val purpose = - SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersion)) + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) mockEServiceRetrieve(eserviceId, SpecData.eService.copy(id = eserviceId, mode = Deliver)) mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { status shouldEqual StatusCodes.BadRequest val problem = responseAs[Problem] @@ -1661,9 +1798,18 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) val purpose = - SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersion)) + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) mockPurposeRetrieve(purposeId, purpose) + + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) mockEServiceRetrieve(eserviceId, SpecData.eService.copy(id = eserviceId, mode = Receive)) Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { @@ -1691,7 +1837,19 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate implicit val context: Seq[(String, String)] = Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> requesterId.toString) - mockPurposeRetrieve(purposeId, SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId)) + val purpose = + SpecData.purpose.copy( + id = purposeId, + eserviceId = eserviceId, + consumerId = consumerId, + versions = Seq(SpecData.purposeVersion) + ) + + mockPurposeRetrieve(purposeId, purpose) + + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { status shouldEqual StatusCodes.Forbidden @@ -1714,6 +1872,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate ) val purpose = SpecData.purpose.copy( + id = purposeId, eserviceId = eserviceId, consumerId = consumerId, versions = Seq(SpecData.purposeVersionNotInDraftState) @@ -1724,6 +1883,9 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate mockPurposeRetrieve(purposeId, purpose) + mockAgreementsRetrieve(eserviceId, consumerId, Seq(AgreementActive)) + + mockListPurposesRetrieve(Seq(purpose)) Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { status shouldEqual StatusCodes.Forbidden val problem = responseAs[Problem]