From f5c7d7fc68a114e30ba2554ccaf38b770a6e7407 Mon Sep 17 00:00:00 2001 From: Riccardo Torsoli <122275960+nttdata-rtorsoli@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:25:43 +0100 Subject: [PATCH] PIN-4332 - Update of daily calls with the same value of the previous version is not allowed (#196) --- .../api/impl/PurposeApiServiceImpl.scala | 10 +++ .../api/impl/ResponseHandlers.scala | 1 + .../error/PurposeProcessErrors.scala | 3 +- .../PurposeApiServiceSpec.scala | 70 ++++++++++++++++--- 4 files changed, 72 insertions(+), 12 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..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 @@ -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 @@ -734,6 +735,15 @@ final case class PurposeApiServiceImpl( else Future.failed(PurposeNotInDraftState(purpose.id)) } + private def assertDailyCallsIsDifferentThanBefore(purpose: PersistentPurpose, dailyCalls: Int): Future[Unit] = { + 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.failed(UnchangedDailyCalls(purpose.id)) + case _ => Future.successful(()) + } + } + 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..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,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: 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 7a3dbabd..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,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 UnchangedDailyCalls(purposeId: UUID) + extends ComponentError("0028", s"Creation of new version without changing daily calls for purpose $purposeId") } diff --git a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala index d8cecb40..3332d6c4 100644 --- a/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala +++ b/src/test/scala/it/pagopa/interop/purposeprocess/PurposeApiServiceSpec.scala @@ -2156,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 = @@ -2180,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, @@ -2188,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 @@ -2216,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) @@ -2248,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" @@ -2260,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 = @@ -2272,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, @@ -2280,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 @@ -2306,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( @@ -2325,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()