From ab21e8c954ec7e523ac66691f93579fac3977955 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Tue, 9 Jan 2024 12:32:33 +0100 Subject: [PATCH 1/3] BKE - Update of daily calls with the same value of the previous version is not allowed --- .../api/impl/PurposeApiServiceImpl.scala | 41 ++++++++----- .../api/impl/ResponseHandlers.scala | 2 + .../error/PurposeProcessErrors.scala | 3 +- .../PurposeApiServiceSpec.scala | 58 +++++++++++++++++++ 4 files changed, 88 insertions(+), 16 deletions(-) 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 5c8d72bf..e8a67bd7 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 @@ -230,6 +230,7 @@ final case class PurposeApiServiceImpl( eService => if (eService.mode == Deliver) Future.unit else Future.failed(EServiceNotInDeliverMode(eService.id)), seed.isFreeOfCharge, seed.freeOfChargeReason, + seed.dailyCalls, (_, tenantKind) => seed .toManagement(schemaOnlyValidation = true)(tenantKind) @@ -252,6 +253,7 @@ final case class PurposeApiServiceImpl( eService => if (eService.mode == Receive) Future.unit else Future.failed(EServiceNotInReceiveMode(eService.id)), seed.isFreeOfCharge, seed.freeOfChargeReason, + seed.dailyCalls, (purpose, tenantKind) => seed .toManagement(schemaOnlyValidation = true, purpose.riskAnalysisForm.map(_.toApi))(tenantKind) @@ -266,6 +268,7 @@ final case class PurposeApiServiceImpl( eServiceModeCheck: CatalogItem => Future[Unit], isFreeOfCharge: Boolean, freeOfChargeReason: Option[String], + dailyCalls: Int, payload: (PersistentPurpose, PersistentTenantKind) => Future[PurposeManagementDependency.PurposeUpdateContent] )(implicit contexts: Seq[(String, String)]): Future[Purpose] = for { requesterOrgId <- getOrganizationIdFutureUUID(contexts) @@ -273,6 +276,7 @@ final case class PurposeApiServiceImpl( purpose <- purposeManagementService.getPurposeById(purposeUUID) _ <- assertOrganizationIsAConsumer(requesterOrgId, purpose.consumerId) _ <- assertPurposeIsInDraftState(purpose) + _ <- assertDailyCallsIsDifferentThanBefore(purpose, dailyCalls) eService <- catalogManagementService.getEServiceById(purpose.eserviceId) _ <- eServiceModeCheck(eService) _ <- checkFreeOfCharge(isFreeOfCharge, freeOfChargeReason) @@ -615,21 +619,6 @@ final case class PurposeApiServiceImpl( dailyCalls = dailyCalls ) - def getDailyCalls(versions: Seq[PersistentPurposeVersion]): Int = { - - val ordering: Ordering[OffsetDateTime] = Ordering(Ordering.by[OffsetDateTime, Long](_.toEpochSecond).reverse) - - val latestNoWaiting: Option[Int] = versions - .filterNot(_.state == WaitingForApproval) - .sortBy(_.createdAt)(ordering) - .map(_.dailyCalls) - .headOption - - val latestAll: Option[Int] = versions.sortBy(_.createdAt)(ordering).map(_.dailyCalls).headOption - - latestNoWaiting.getOrElse(latestAll.getOrElse(0)) - } - val result: Future[Purpose] = for { organizationId <- getOrganizationIdFutureUUID(contexts) tenant <- tenantManagementService.getTenantById(organizationId) @@ -648,6 +637,21 @@ final case class PurposeApiServiceImpl( onComplete(result) { clonePurposeResponse[Purpose](operationLabel)(clonePurpose200) } } + private def getDailyCalls(versions: Seq[PersistentPurposeVersion]): Int = { + + val ordering: Ordering[OffsetDateTime] = Ordering(Ordering.by[OffsetDateTime, Long](_.toEpochSecond).reverse) + + val latestNoWaiting: Option[Int] = versions + .filterNot(_.state == WaitingForApproval) + .sortBy(_.createdAt)(ordering) + .map(_.dailyCalls) + .headOption + + val latestAll: Option[Int] = versions.sortBy(_.createdAt)(ordering).map(_.dailyCalls).headOption + + latestNoWaiting.getOrElse(latestAll.getOrElse(0)) + } + override def retrieveLatestRiskAnalysisConfiguration(tenantKind: Option[String])(implicit contexts: Seq[(String, String)], toEntityMarshallerRiskAnalysisFormConfigResponse: ToEntityMarshaller[RiskAnalysisFormConfigResponse], @@ -734,6 +738,13 @@ final case class PurposeApiServiceImpl( else Future.failed(PurposeNotInDraftState(purpose.id)) } + private def assertDailyCallsIsDifferentThanBefore(purpose: PersistentPurpose, dailyCalls: Int): Future[Unit] = { + val previousDailyCalls = getDailyCalls(purpose.versions) + if (previousDailyCalls != dailyCalls) + Future.successful(()) + else Future.failed(DailyCallsEqualThanBefore(purpose.id)) + } + private def isRiskAnalysisFormValid( riskAnalysisForm: Option[RiskAnalysisForm], schemaOnlyValidation: Boolean = false 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 50e295c9..131bd587 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 @@ -92,6 +92,7 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: EServiceNotInDeliverMode) => badRequest(ex, logMessage) case Failure(ex: TenantNotFound) => badRequest(ex, logMessage) case Failure(ex: AgreementNotFound) => badRequest(ex, logMessage) + case Failure(ex: DailyCallsEqualThanBefore) => badRequest(ex, logMessage) case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotInDraftState) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) @@ -110,6 +111,7 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) case Failure(ex: TenantNotFound) => badRequest(ex, logMessage) case Failure(ex: AgreementNotFound) => badRequest(ex, logMessage) + case Failure(ex: DailyCallsEqualThanBefore) => badRequest(ex, logMessage) case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotInDraftState) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) diff --git a/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala b/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala index 7a3dbabd..6197d321 100644 --- a/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala @@ -91,5 +91,6 @@ object PurposeProcessErrors { extends ComponentError("0026", s"EService ${eServiceId.toString} has not Receive mode") final case class RiskAnalysisNotFound(eServiceId: UUID, riskAnalysisId: UUID) extends ComponentError("0027", s"EService $eServiceId does not contain Risk Analysis $riskAnalysisId") - + final case class DailyCallsEqualThanBefore(purposeId: UUID) + extends ComponentError("0028", s"Purpose $purposeId has the same dailyCalls") } diff --git a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala index d8cecb40..0a62ede9 100644 --- a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala +++ b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala @@ -1431,6 +1431,35 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate status shouldEqual StatusCodes.OK } } + "fail if daily calls attribute has the same value of previous version" 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 = 1000 + ) + + val purpose = + SpecData.purpose.copy(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) + + Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "012-0028" + } + } "fail if case of eService with Receive mode" in { val purposeId = UUID.randomUUID() @@ -1601,6 +1630,35 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate status shouldEqual StatusCodes.OK } } + "fail if daily calls attribute has the same value of previous version" 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 = 1000 + ) + + val purpose = + SpecData.purpose.copy(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) + + Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "012-0028" + } + } "fail if case of eService with Deliver mode" in { val purposeId = UUID.randomUUID() From b604ec35f0701704f439168a5a871b75fcf04a03 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 10 Jan 2024 10:48:27 +0100 Subject: [PATCH 2/3] PIN-4332 Resolved PR issues --- .../api/impl/PurposeApiServiceImpl.scala | 45 +++--- .../api/impl/ResponseHandlers.scala | 3 +- .../PurposeApiServiceSpec.scala | 128 ++++++++---------- 3 files changed, 82 insertions(+), 94 deletions(-) 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 e8a67bd7..4434857c 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 @@ -211,6 +211,7 @@ final case class PurposeApiServiceImpl( purposeUUID <- purposeId.toFutureUUID purpose <- purposeManagementService.getPurposeById(purposeUUID) _ <- assertOrganizationIsAConsumer(organizationId, purpose.consumerId) + _ <- assertDailyCallsIsDifferentThanBefore(purpose, seed.dailyCalls) version <- purposeManagementService.createPurposeVersion(purposeUUID, seed.toManagement) published <- publish(organizationId, purpose, version.toPersistent) } yield published.toApi @@ -230,7 +231,6 @@ final case class PurposeApiServiceImpl( eService => if (eService.mode == Deliver) Future.unit else Future.failed(EServiceNotInDeliverMode(eService.id)), seed.isFreeOfCharge, seed.freeOfChargeReason, - seed.dailyCalls, (_, tenantKind) => seed .toManagement(schemaOnlyValidation = true)(tenantKind) @@ -253,7 +253,6 @@ final case class PurposeApiServiceImpl( eService => if (eService.mode == Receive) Future.unit else Future.failed(EServiceNotInReceiveMode(eService.id)), seed.isFreeOfCharge, seed.freeOfChargeReason, - seed.dailyCalls, (purpose, tenantKind) => seed .toManagement(schemaOnlyValidation = true, purpose.riskAnalysisForm.map(_.toApi))(tenantKind) @@ -268,7 +267,6 @@ final case class PurposeApiServiceImpl( eServiceModeCheck: CatalogItem => Future[Unit], isFreeOfCharge: Boolean, freeOfChargeReason: Option[String], - dailyCalls: Int, payload: (PersistentPurpose, PersistentTenantKind) => Future[PurposeManagementDependency.PurposeUpdateContent] )(implicit contexts: Seq[(String, String)]): Future[Purpose] = for { requesterOrgId <- getOrganizationIdFutureUUID(contexts) @@ -276,7 +274,6 @@ final case class PurposeApiServiceImpl( purpose <- purposeManagementService.getPurposeById(purposeUUID) _ <- assertOrganizationIsAConsumer(requesterOrgId, purpose.consumerId) _ <- assertPurposeIsInDraftState(purpose) - _ <- assertDailyCallsIsDifferentThanBefore(purpose, dailyCalls) eService <- catalogManagementService.getEServiceById(purpose.eserviceId) _ <- eServiceModeCheck(eService) _ <- checkFreeOfCharge(isFreeOfCharge, freeOfChargeReason) @@ -619,6 +616,21 @@ final case class PurposeApiServiceImpl( dailyCalls = dailyCalls ) + def getDailyCalls(versions: Seq[PersistentPurposeVersion]): Int = { + + val ordering: Ordering[OffsetDateTime] = Ordering(Ordering.by[OffsetDateTime, Long](_.toEpochSecond).reverse) + + val latestNoWaiting: Option[Int] = versions + .filterNot(_.state == WaitingForApproval) + .sortBy(_.createdAt)(ordering) + .map(_.dailyCalls) + .headOption + + val latestAll: Option[Int] = versions.sortBy(_.createdAt)(ordering).map(_.dailyCalls).headOption + + latestNoWaiting.getOrElse(latestAll.getOrElse(0)) + } + val result: Future[Purpose] = for { organizationId <- getOrganizationIdFutureUUID(contexts) tenant <- tenantManagementService.getTenantById(organizationId) @@ -637,21 +649,6 @@ final case class PurposeApiServiceImpl( onComplete(result) { clonePurposeResponse[Purpose](operationLabel)(clonePurpose200) } } - private def getDailyCalls(versions: Seq[PersistentPurposeVersion]): Int = { - - val ordering: Ordering[OffsetDateTime] = Ordering(Ordering.by[OffsetDateTime, Long](_.toEpochSecond).reverse) - - val latestNoWaiting: Option[Int] = versions - .filterNot(_.state == WaitingForApproval) - .sortBy(_.createdAt)(ordering) - .map(_.dailyCalls) - .headOption - - val latestAll: Option[Int] = versions.sortBy(_.createdAt)(ordering).map(_.dailyCalls).headOption - - latestNoWaiting.getOrElse(latestAll.getOrElse(0)) - } - override def retrieveLatestRiskAnalysisConfiguration(tenantKind: Option[String])(implicit contexts: Seq[(String, String)], toEntityMarshallerRiskAnalysisFormConfigResponse: ToEntityMarshaller[RiskAnalysisFormConfigResponse], @@ -739,10 +736,12 @@ final case class PurposeApiServiceImpl( } private def assertDailyCallsIsDifferentThanBefore(purpose: PersistentPurpose, dailyCalls: Int): Future[Unit] = { - val previousDailyCalls = getDailyCalls(purpose.versions) - if (previousDailyCalls != dailyCalls) - Future.successful(()) - else Future.failed(DailyCallsEqualThanBefore(purpose.id)) + val ordering: Ordering[OffsetDateTime] = Ordering(Ordering.by[OffsetDateTime, Long](_.toEpochSecond).reverse) + val previousDailyCalls = purpose.versions.sortBy(_.createdAt)(ordering).map(_.dailyCalls).headOption + previousDailyCalls match { + case Some(x) if x != dailyCalls => Future.successful(()) + case _ => Future.failed(DailyCallsEqualThanBefore(purpose.id)) + } } private def isRiskAnalysisFormValid( 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 131bd587..888a13ee 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 @@ -75,6 +75,7 @@ object ResponseHandlers extends AkkaResponses { )(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route = result match { case Success(s) => success(s) + case Failure(ex: DailyCallsEqualThanBefore) => badRequest(ex, logMessage) case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) case Failure(ex: PurposeVersionConflict) => conflict(ex, logMessage) @@ -92,7 +93,6 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: EServiceNotInDeliverMode) => badRequest(ex, logMessage) case Failure(ex: TenantNotFound) => badRequest(ex, logMessage) case Failure(ex: AgreementNotFound) => badRequest(ex, logMessage) - case Failure(ex: DailyCallsEqualThanBefore) => badRequest(ex, logMessage) case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotInDraftState) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) @@ -111,7 +111,6 @@ object ResponseHandlers extends AkkaResponses { case Failure(ex: EServiceNotInReceiveMode) => badRequest(ex, logMessage) case Failure(ex: TenantNotFound) => badRequest(ex, logMessage) case Failure(ex: AgreementNotFound) => badRequest(ex, logMessage) - case Failure(ex: DailyCallsEqualThanBefore) => badRequest(ex, logMessage) case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotInDraftState) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) diff --git a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala index 0a62ede9..3332d6c4 100644 --- a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala +++ b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala @@ -1431,35 +1431,6 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate status shouldEqual StatusCodes.OK } } - "fail if daily calls attribute has the same value of previous version" 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 = 1000 - ) - - val purpose = - SpecData.purpose.copy(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) - - Post() ~> service.updatePurpose(purposeId.toString, purposeUpdateContent) ~> check { - status shouldEqual StatusCodes.BadRequest - val problem = responseAs[Problem] - problem.status shouldBe StatusCodes.BadRequest.intValue - problem.errors.head.code shouldBe "012-0028" - } - } "fail if case of eService with Receive mode" in { val purposeId = UUID.randomUUID() @@ -1630,35 +1601,6 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate status shouldEqual StatusCodes.OK } } - "fail if daily calls attribute has the same value of previous version" 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 = 1000 - ) - - val purpose = - SpecData.purpose.copy(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) - - Post() ~> service.updateReversePurpose(purposeId.toString, reversePurposeUpdateContent) ~> check { - status shouldEqual StatusCodes.BadRequest - val problem = responseAs[Problem] - problem.status shouldBe StatusCodes.BadRequest.intValue - problem.errors.head.code shouldBe "012-0028" - } - } "fail if case of eService with Deliver mode" in { val purposeId = UUID.randomUUID() @@ -2214,19 +2156,20 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate implicit val context: Seq[(String, String)] = Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) - val version2_1 = SpecData.purposeVersion.copy(id = UUID.randomUUID(), state = Active, dailyCalls = 1000) + val version2_1 = SpecData.purposeVersion.copy(id = UUID.randomUUID(), state = Active, dailyCalls = 1002) val purpose2 = SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(version2_1)) val path: String = "/here/there/foo/bar.pdf" val document: PersistentPurposeVersionDocument = PersistentPurposeVersionDocument(documentId, "application/pdf", path, SpecData.timestamp) - val version1_1 = SpecData.purposeVersion.copy(id = purposeVersionId1, state = Active) + val version1_1 = SpecData.purposeVersion.copy(id = purposeVersionId1, state = Active, dailyCalls = 1000) val version1_2 = SpecData.purposeVersion.copy( id = purposeVersionId2, state = WaitingForApproval, riskAnalysis = Some(document), - firstActivationAt = Some(SpecData.timestamp) + firstActivationAt = Some(SpecData.timestamp), + dailyCalls = 1001 ) val purpose = @@ -2238,7 +2181,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate ) val purposes = Seq(purpose, purpose2) - val seed: PurposeVersionSeed = PurposeVersionSeed(dailyCalls = 1000) + val seed: PurposeVersionSeed = PurposeVersionSeed(dailyCalls = 2000) val purposeVersion = PersistentPurposeVersion( id = purposeVersionId3, @@ -2246,7 +2189,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate createdAt = SpecData.timestamp, updatedAt = None, expectedApprovalDate = None, - dailyCalls = seed.dailyCalls, + dailyCalls = 2000, riskAnalysis = Some(document), firstActivationAt = Some(SpecData.timestamp), suspendedAt = None @@ -2274,7 +2217,8 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate riskAnalysis = Some( PurposeManagementDependency.PurposeVersionDocument(documentId, "application/pdf", path, SpecData.timestamp) ), - firstActivationAt = Some(SpecData.timestamp) + firstActivationAt = Some(SpecData.timestamp), + dailyCalls = 2000 ) mockVersionFirstActivation(purposeId, purposeVersionId3, eService.producerId, purpose.consumerId, updatedVersion) @@ -2306,7 +2250,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate implicit val context: Seq[(String, String)] = Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) - val version2_1 = SpecData.purposeVersion.copy(id = UUID.randomUUID(), state = Active, dailyCalls = 1000) + val version2_1 = SpecData.purposeVersion.copy(id = UUID.randomUUID(), state = Active, dailyCalls = 1002) val purpose2 = SpecData.purpose.copy(eserviceId = eserviceId, consumerId = consumerId, versions = Seq(version2_1)) val path: String = "/here/there/foo/bar.pdf" @@ -2318,7 +2262,8 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate id = purposeVersionId2, state = WaitingForApproval, riskAnalysis = Some(document), - firstActivationAt = Some(SpecData.timestamp) + firstActivationAt = Some(SpecData.timestamp), + dailyCalls = 1001 ) val purpose = @@ -2330,7 +2275,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate ) val purposes = Seq(purpose, purpose2) - val seed: PurposeVersionSeed = PurposeVersionSeed(dailyCalls = 1000) + val seed: PurposeVersionSeed = PurposeVersionSeed(dailyCalls = 2000) val purposeVersion = PersistentPurposeVersion( id = purposeVersionId3, @@ -2338,7 +2283,7 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate createdAt = SpecData.timestamp, updatedAt = None, expectedApprovalDate = None, - dailyCalls = seed.dailyCalls, + dailyCalls = 2000, riskAnalysis = None, firstActivationAt = Some(SpecData.timestamp), suspendedAt = None @@ -2364,7 +2309,8 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate id = purposeVersionId3, state = PurposeManagementDependency.PurposeVersionState.WAITING_FOR_APPROVAL, riskAnalysis = None, - firstActivationAt = Some(SpecData.timestamp) + firstActivationAt = Some(SpecData.timestamp), + dailyCalls = 2000 ) val payload = PurposeManagementDependency.StateChangeDetails( @@ -2383,7 +2329,51 @@ class PurposeApiServiceSpec extends AnyWordSpecLike with SpecHelper with Scalate responseAs[PurposeVersion] shouldEqual expected } } + "fail in case of version with the same dailyCalls than previous version" in { + + val consumerId = UUID.randomUUID() + val documentId = UUID.randomUUID() + val purposeId = UUID.randomUUID() + val purposeVersionId1 = UUID.randomUUID() + val purposeVersionId2 = UUID.randomUUID() + val eserviceId = UUID.randomUUID() + + implicit val context: Seq[(String, String)] = + Seq("bearer" -> bearerToken, USER_ROLES -> "admin", ORGANIZATION_ID_CLAIM -> consumerId.toString) + + val path: String = "/here/there/foo/bar.pdf" + val document: PersistentPurposeVersionDocument = + PersistentPurposeVersionDocument(documentId, "application/pdf", path, SpecData.timestamp) + val version1_1 = SpecData.purposeVersion.copy(id = purposeVersionId1, state = Draft, dailyCalls = 1000) + val version1_2 = SpecData.purposeVersion.copy( + id = purposeVersionId2, + state = WaitingForApproval, + riskAnalysis = Some(document), + firstActivationAt = None, + createdAt = SpecData.timestamp.plusDays(1), + dailyCalls = 1001 + ) + + val purpose = + SpecData.purpose.copy( + id = purposeId, + versions = Seq(version1_1, version1_2), + consumerId = consumerId, + eserviceId = eserviceId + ) + + val seed: PurposeVersionSeed = PurposeVersionSeed(dailyCalls = 1001) + + mockPurposeRetrieve(purposeId, purpose) + + Get() ~> service.createPurposeVersion(purposeId.toString, seed) ~> check { + status shouldEqual StatusCodes.BadRequest + val problem = responseAs[Problem] + problem.status shouldBe StatusCodes.BadRequest.intValue + problem.errors.head.code shouldBe "012-0028" + } + } "fail if Purpose does not exist" in { val purposeId = UUID.randomUUID() From dbc9ce6760ea86c73eca76d16007375aabb17303 Mon Sep 17 00:00:00 2001 From: nttdata-rtorsoli Date: Wed, 10 Jan 2024 13:40:15 +0100 Subject: [PATCH 3/3] PIN-4332 Resolved PR issues --- .../purposeprocess/api/impl/PurposeApiServiceImpl.scala | 4 ++-- .../interop/purposeprocess/api/impl/ResponseHandlers.scala | 2 +- .../interop/purposeprocess/error/PurposeProcessErrors.scala | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) 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 4434857c..09809fdb 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 @@ -739,8 +739,8 @@ final case class PurposeApiServiceImpl( val ordering: Ordering[OffsetDateTime] = Ordering(Ordering.by[OffsetDateTime, Long](_.toEpochSecond).reverse) val previousDailyCalls = purpose.versions.sortBy(_.createdAt)(ordering).map(_.dailyCalls).headOption previousDailyCalls match { - case Some(x) if x != dailyCalls => Future.successful(()) - case _ => Future.failed(DailyCallsEqualThanBefore(purpose.id)) + case Some(x) if x == dailyCalls => Future.failed(UnchangedDailyCalls(purpose.id)) + case _ => Future.successful(()) } } 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 888a13ee..f0396f90 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 @@ -75,7 +75,7 @@ object ResponseHandlers extends AkkaResponses { )(result: Try[T])(implicit contexts: Seq[(String, String)], logger: LoggerTakingImplicit[ContextFieldsToLog]): Route = result match { case Success(s) => success(s) - case Failure(ex: DailyCallsEqualThanBefore) => badRequest(ex, logMessage) + case Failure(ex: UnchangedDailyCalls) => badRequest(ex, logMessage) case Failure(ex: OrganizationIsNotTheConsumer) => forbidden(ex, logMessage) case Failure(ex: PurposeNotFound) => notFound(ex, logMessage) case Failure(ex: PurposeVersionConflict) => conflict(ex, logMessage) diff --git a/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala b/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala index 6197d321..60313e68 100644 --- a/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/purposeprocess/error/PurposeProcessErrors.scala @@ -91,6 +91,6 @@ object PurposeProcessErrors { extends ComponentError("0026", s"EService ${eServiceId.toString} has not Receive mode") final case class RiskAnalysisNotFound(eServiceId: UUID, riskAnalysisId: UUID) extends ComponentError("0027", s"EService $eServiceId does not contain Risk Analysis $riskAnalysisId") - final case class DailyCallsEqualThanBefore(purposeId: UUID) - extends ComponentError("0028", s"Purpose $purposeId has the same dailyCalls") + final case class UnchangedDailyCalls(purposeId: UUID) + extends ComponentError("0028", s"Creation of new version without changing daily calls for purpose $purposeId") }