diff --git a/lifecycle/src/main/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRules.scala b/lifecycle/src/main/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRules.scala index f6acfb14..49d43ed2 100644 --- a/lifecycle/src/main/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRules.scala +++ b/lifecycle/src/main/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRules.scala @@ -13,7 +13,7 @@ import it.pagopa.interop.tenantmanagement.model.tenant.{ PersistentCertifiedAttribute, PersistentDeclaredAttribute, PersistentVerifiedAttribute, - PersistentTenant, + PersistentTenantAttribute, PersistentTenantVerifier } @@ -29,10 +29,13 @@ object AttributesRules { consumerAttributes.filter(_.revocationTimestamp.isEmpty).map(_.id) ) - def certifiedAttributesSatisfied(descriptor: CatalogDescriptor, consumer: PersistentTenant): Boolean = + def certifiedAttributesSatisfied( + descriptor: CatalogDescriptor, + consumerAttributes: List[PersistentTenantAttribute] + ): Boolean = certifiedAttributesSatisfied( descriptor.attributes, - consumer.attributes.collect { case a: PersistentCertifiedAttribute => a } + consumerAttributes.collect { case a: PersistentCertifiedAttribute => a } ) def declaredAttributesSatisfied( @@ -41,10 +44,13 @@ object AttributesRules { ): Boolean = attributesSatisfied(eServiceAttributes.declared, consumerAttributes.filter(_.revocationTimestamp.isEmpty).map(_.id)) - def declaredAttributesSatisfied(descriptor: CatalogDescriptor, consumer: PersistentTenant): Boolean = + def declaredAttributesSatisfied( + descriptor: CatalogDescriptor, + consumerAttributes: List[PersistentTenantAttribute] + ): Boolean = declaredAttributesSatisfied( descriptor.attributes, - consumer.attributes.collect { case a: PersistentDeclaredAttribute => a } + consumerAttributes.collect { case a: PersistentDeclaredAttribute => a } ) def verifiedAttributesSatisfied( @@ -64,12 +70,12 @@ object AttributesRules { def verifiedAttributesSatisfied( agreement: PersistentAgreement, descriptor: CatalogDescriptor, - consumer: PersistentTenant + consumerAttributes: List[PersistentTenantAttribute] ): Boolean = verifiedAttributesSatisfied( agreement.producerId, descriptor.attributes, - consumer.attributes.collect { case a: PersistentVerifiedAttribute => a } + consumerAttributes.collect { case a: PersistentVerifiedAttribute => a } ) private def attributesSatisfied(requested: Seq[CatalogAttribute], assigned: Seq[UUID]): Boolean = { diff --git a/lifecycle/src/test/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRulesSpec.scala b/lifecycle/src/test/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRulesSpec.scala index d4a160a3..7ab9afb7 100644 --- a/lifecycle/src/test/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRulesSpec.scala +++ b/lifecycle/src/test/scala/it/pagopa/interop/agreementprocess/lifecycle/AttributesRulesSpec.scala @@ -32,7 +32,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe true + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe true } "return true if at least one attribute in every CatalogItem group attribute is satisfied" in { @@ -57,7 +57,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe true + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe true } "return true if CatalogItem single and group attributes are satisfied" in { @@ -79,7 +79,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe true + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe true } "return false if at least one CatalogItem single attribute is not satisfied" in { @@ -95,7 +95,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe false + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } "return false if at least one CatalogItem group attribute is not satisfied" in { @@ -116,7 +116,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe false + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } "return false if an CatalogItem single attribute is assigned but revoked" in { @@ -131,7 +131,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe false + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } "return false if the CatalogItem group attribute is assigned but revoked" in { @@ -146,7 +146,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - certifiedAttributesSatisfied(descriptor, consumer) shouldBe false + certifiedAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } } @@ -168,7 +168,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe true + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe true } "return true if at least one attribute in every CatalogItem group attribute is satisfied" in { @@ -193,7 +193,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe true + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe true } "return true if CatalogItem single and group attributes are satisfied" in { @@ -215,7 +215,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe true + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe true } "return false if at least one CatalogItem single attribute is not satisfied" in { @@ -231,7 +231,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe false + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } "return false if at least one CatalogItem group attribute is not satisfied" in { @@ -252,7 +252,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe false + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } "return false if an CatalogItem single attribute is assigned but revoked" in { @@ -267,7 +267,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe false + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } "return false if the CatalogItem group attribute is assigned but revoked" in { @@ -281,7 +281,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - declaredAttributesSatisfied(descriptor, consumer) shouldBe false + declaredAttributesSatisfied(descriptor, consumer.attributes) shouldBe false } } @@ -305,7 +305,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe true + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe true } "return true if at least one attribute in every CatalogItem group attribute is satisfied" in { @@ -332,7 +332,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe true + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe true } "return true if CatalogItem single and group attributes are satisfied" in { @@ -356,7 +356,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe true + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe true } "return false if at least one CatalogItem single attribute is not satisfied" in { @@ -377,7 +377,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if at least one CatalogItem group attribute is not satisfied" in { @@ -403,7 +403,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if an CatalogItem single attribute is assigned but not verified" in { @@ -420,7 +420,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if the CatalogItem group attribute is assigned but not verified" in { @@ -437,7 +437,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if a single attribute is verified but not by the PersistentAgreement producer" in { @@ -457,7 +457,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if a group attribute is verified but not by the PersistentAgreement producer" in { @@ -477,7 +477,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if at least one single attribute is expired" in { @@ -502,7 +502,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } "return false if at least one group attribute is expired" in { @@ -532,7 +532,7 @@ class AttributesRulesSpec extends AnyWordSpecLike { val descriptor: CatalogDescriptor = SpecData.descriptor.copy(attributes = descriptorAttr) val consumer: PersistentTenant = SpecData.tenant.copy(attributes = tenantAttr) - verifiedAttributesSatisfied(agreement, descriptor, consumer) shouldBe false + verifiedAttributesSatisfied(agreement, descriptor, consumer.attributes) shouldBe false } } } diff --git a/src/main/resources/interface-specification.yml b/src/main/resources/interface-specification.yml index f3eb4069..15e52615 100644 --- a/src/main/resources/interface-specification.yml +++ b/src/main/resources/interface-specification.yml @@ -775,21 +775,12 @@ paths: summary: Re-calculate agreements state description: Re-calculate state for all agreements that contain the given attribute operationId: computeAgreementsByAttribute - parameters: - - name: consumerId - in: path - description: The identifier of the consumer - required: true - schema: - type: string - format: uuid - - name: attributeId - in: path - description: The identifier of the attribute - required: true - schema: - type: string - format: uuid + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ComputeAgreementStatePayload' + required: true responses: '204': description: Bulk operation executed @@ -1016,6 +1007,135 @@ components: format: uuid required: - id + ComputeAgreementStatePayload: + type: object + properties: + attributeId: + type: string + format: uuid + consumer: + $ref: '#/components/schemas/CompactTenant' + required: + - attributeId + - consumer + CompactTenant: + description: Tenant model + type: object + properties: + id: + type: string + format: uuid + attributes: + type: array + items: + $ref: '#/components/schemas/TenantAttribute' + required: + - id + - attributes + TenantAttribute: + type: object + properties: + declared: + $ref: '#/components/schemas/DeclaredTenantAttribute' + certified: + $ref: '#/components/schemas/CertifiedTenantAttribute' + verified: + $ref: '#/components/schemas/VerifiedTenantAttribute' + DeclaredTenantAttribute: + type: object + properties: + id: + type: string + format: uuid + assignmentTimestamp: + type: string + format: date-time + revocationTimestamp: + type: string + format: date-time + required: + - id + - kind + - assignmentTimestamp + CertifiedTenantAttribute: + type: object + properties: + id: + type: string + format: uuid + assignmentTimestamp: + type: string + format: date-time + revocationTimestamp: + type: string + format: date-time + required: + - id + - kind + - assignmentTimestamp + VerifiedTenantAttribute: + type: object + properties: + id: + type: string + format: uuid + assignmentTimestamp: + type: string + format: date-time + verifiedBy: + type: array + items: + $ref: '#/components/schemas/TenantVerifier' + revokedBy: + type: array + items: + $ref: '#/components/schemas/TenantRevoker' + required: + - id + - kind + - assignmentTimestamp + - verifiedBy + - revokedBy + TenantVerifier: + type: object + properties: + id: + type: string + format: uuid + verificationDate: + type: string + format: date-time + expirationDate: + type: string + format: date-time + extensionDate: + type: string + format: date-time + required: + - id + - verificationDate + TenantRevoker: + type: object + properties: + id: + type: string + format: uuid + verificationDate: + type: string + format: date-time + expirationDate: + type: string + format: date-time + extensionDate: + type: string + format: date-time + revocationDate: + type: string + format: date-time + required: + - id + - verificationDate + - revocationDate Agreements: type: object properties: diff --git a/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiMarshallerImpl.scala b/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiMarshallerImpl.scala index b5b784aa..c975d6f7 100644 --- a/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiMarshallerImpl.scala +++ b/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiMarshallerImpl.scala @@ -37,4 +37,7 @@ case object AgreementApiMarshallerImpl extends AgreementApiMarshaller with Spray override implicit def toEntityMarshallerCompactEServices: ToEntityMarshaller[CompactEServices] = sprayJsonMarshaller[CompactEServices] + + override implicit def fromEntityUnmarshallerComputeAgreementStatePayload + : FromEntityUnmarshaller[ComputeAgreementStatePayload] = sprayJsonUnmarshaller[ComputeAgreementStatePayload] } diff --git a/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiServiceImpl.scala b/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiServiceImpl.scala index 41cac440..d07a24d3 100644 --- a/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiServiceImpl.scala +++ b/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/AgreementApiServiceImpl.scala @@ -46,9 +46,9 @@ import it.pagopa.interop.tenantmanagement.model.tenant.{ PersistentCertifiedAttribute, PersistentDeclaredAttribute, PersistentTenant, + PersistentTenantAttribute, PersistentVerifiedAttribute } - import java.time.OffsetDateTime import java.time.format.DateTimeFormatter import java.util.UUID @@ -457,10 +457,10 @@ final case class AgreementApiServiceImpl( } } - override def computeAgreementsByAttribute(consumerId: String, attributeId: String)(implicit - contexts: Seq[(String, String)] - ): Route = authorize(ADMIN_ROLE, INTERNAL_ROLE, M2M_ROLE) { - val operationLabel = s"Recalculating agreements status for attribute $attributeId" + override def computeAgreementsByAttribute( + payload: ComputeAgreementStatePayload + )(implicit contexts: Seq[(String, String)]): Route = authorize(ADMIN_ROLE, INTERNAL_ROLE, M2M_ROLE) { + val operationLabel = s"Recalculating agreements status for attribute ${payload.attributeId}" logger.info(operationLabel) val allowedStateTransitions: Map[AgreementManagement.AgreementState, List[AgreementManagement.AgreementState]] = @@ -521,13 +521,13 @@ final case class AgreementApiServiceImpl( ) } - def updateStates(consumer: PersistentTenant, eServices: Map[UUID, CatalogItem])( + def updateStates(consumerAttributes: List[PersistentTenantAttribute], eServices: Map[UUID, CatalogItem])( agreement: PersistentAgreement ): Future[Unit] = eServices .get(agreement.eserviceId) .flatMap(_.descriptors.find(_.id == agreement.descriptorId)) - .map(AgreementStateByAttributesFSM.nextState(agreement, _, consumer)) + .map(AgreementStateByAttributesFSM.nextState(agreement, _, consumerAttributes)) .fold { logger.error( s"Descriptor ${agreement.descriptorId} Service ${agreement.eserviceId} not found for agreement ${agreement.id}" @@ -550,21 +550,18 @@ final case class AgreementApiServiceImpl( } val result: Future[Unit] = for { - consumerUuid <- consumerId.toFutureUUID - attributeUuid <- attributeId.toFutureUUID - agreements <- agreementManagementService.getAgreements( - consumerId = consumerUuid.some, + agreements <- agreementManagementService.getAgreements( + consumerId = payload.consumer.id.some, states = updatableStates.map(_.toPersistent) ) - consumer <- tenantManagementService - .getTenantById(consumerUuid) + attributes <- payload.consumer.attributes.toList.traverse(PersistentTenantAttribute.fromAPI).toFuture uniqueEServiceIds = agreements.map(_.eserviceId).distinct // Not using Future.traverse to not overload our backend. Execution time is not critical for this job eServices <- uniqueEServiceIds.traverse(catalogManagementService.getEServiceById) - filteredEServices = eServices.filter(eServiceContainsAttribute(attributeUuid)) + filteredEServices = eServices.filter(eServiceContainsAttribute(payload.attributeId)) eServicesMap = filteredEServices.fproductLeft(_.id).toMap filteredAgreement = agreements.filter(a => filteredEServices.exists(_.id == a.eserviceId)) - _ <- filteredAgreement.traverse(updateStates(consumer, eServicesMap)) + _ <- filteredAgreement.traverse(updateStates(attributes, eServicesMap)) } yield () onComplete(result) { @@ -579,7 +576,7 @@ final case class AgreementApiServiceImpl( consumer: PersistentTenant, payload: AgreementSubmissionPayload )(implicit contexts: Seq[(String, String)]): Future[AgreementManagement.Agreement] = { - val nextStateByAttributes = AgreementStateByAttributesFSM.nextState(agreement, descriptor, consumer) + val nextStateByAttributes = AgreementStateByAttributesFSM.nextState(agreement, descriptor, consumer.attributes) val suspendedByPlatform = suspendedByPlatformFlag(nextStateByAttributes) val newState = agreementStateByFlags(nextStateByAttributes, None, None, suspendedByPlatform) @@ -706,7 +703,7 @@ final case class AgreementApiServiceImpl( consumer: PersistentTenant, requesterOrgId: UUID )(implicit contexts: Seq[(String, String)]): Future[AgreementManagement.Agreement] = { - val nextStateByAttributes = AgreementStateByAttributesFSM.nextState(agreement, descriptor, consumer) + val nextStateByAttributes = AgreementStateByAttributesFSM.nextState(agreement, descriptor, consumer.attributes) val suspendedByConsumer = suspendedByConsumerFlag(agreement, requesterOrgId, Active) val suspendedByProducer = suspendedByProducerFlag(agreement, requesterOrgId, Active) val suspendedByPlatform = suspendedByPlatformFlag(nextStateByAttributes) @@ -873,7 +870,7 @@ final case class AgreementApiServiceImpl( consumer: PersistentTenant, requesterOrgId: UUID )(implicit contexts: Seq[(String, String)]): Future[AgreementManagement.Agreement] = { - val nextStateByAttributes = AgreementStateByAttributesFSM.nextState(agreement, descriptor, consumer) + val nextStateByAttributes = AgreementStateByAttributesFSM.nextState(agreement, descriptor, consumer.attributes) val suspendedByConsumer = suspendedByConsumerFlag(agreement, requesterOrgId, Suspended) val suspendedByProducer = suspendedByProducerFlag(agreement, requesterOrgId, Suspended) val suspendedByPlatform = suspendedByPlatformFlag(nextStateByAttributes) @@ -1095,14 +1092,17 @@ final case class AgreementApiServiceImpl( private def validateCertifiedAttributes(descriptor: CatalogDescriptor, consumer: PersistentTenant): Future[Unit] = Future .failed(MissingCertifiedAttributesError(descriptor.id, consumer.id)) - .unlessA(certifiedAttributesSatisfied(descriptor, consumer)) + .unlessA(certifiedAttributesSatisfied(descriptor, consumer.attributes)) private def verifiedAndDeclareSatisfied( agreement: PersistentAgreement, newDescriptor: CatalogDescriptor, tenant: PersistentTenant ): Boolean = - verifiedAttributesSatisfied(agreement, newDescriptor, tenant) && declaredAttributesSatisfied(newDescriptor, tenant) + verifiedAttributesSatisfied(agreement, newDescriptor, tenant.attributes) && declaredAttributesSatisfied( + newDescriptor, + tenant.attributes + ) private def verifyConsumerDoesNotActivatePending( agreement: PersistentAgreement, diff --git a/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/package.scala b/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/package.scala index 474a0322..d08fd1a1 100644 --- a/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/package.scala +++ b/src/main/scala/it/pagopa/interop/agreementprocess/api/impl/package.scala @@ -28,11 +28,20 @@ package object impl extends SprayJsonSupport with DefaultJsonProtocol { implicit def compactOrganizationsFormat: RootJsonFormat[CompactOrganizations] = jsonFormat2(CompactOrganizations) implicit def agreementCompactEServiceFormat: RootJsonFormat[CompactEService] = jsonFormat2(CompactEService) implicit def agreementCompactEServicesFormat: RootJsonFormat[CompactEServices] = jsonFormat2(CompactEServices) - - implicit def mailAttachmentFormat: RootJsonFormat[MailAttachment] = jsonFormat3(MailAttachment) - - implicit def interopEnvelopFormat: RootJsonFormat[InteropEnvelope] = jsonFormat5(InteropEnvelope) - + implicit def TenantVerifierFormat: RootJsonFormat[TenantVerifier] = jsonFormat4(TenantVerifier) + implicit def TenantRevokerFormat: RootJsonFormat[TenantRevoker] = jsonFormat5(TenantRevoker) + implicit def DeclaredTenantAttributeFormat: RootJsonFormat[DeclaredTenantAttribute] = + jsonFormat3(DeclaredTenantAttribute) + implicit def CertifiedTenantAttributeFormat: RootJsonFormat[CertifiedTenantAttribute] = + jsonFormat3(CertifiedTenantAttribute) + implicit def VerifiedTenantAttributeFormat: RootJsonFormat[VerifiedTenantAttribute] = + jsonFormat4(VerifiedTenantAttribute) + implicit def tenantAttributeFormat: RootJsonFormat[TenantAttribute] = jsonFormat3(TenantAttribute) + implicit def compactTenantFormat: RootJsonFormat[CompactTenant] = jsonFormat2(CompactTenant) + implicit def mailAttachmentFormat: RootJsonFormat[MailAttachment] = jsonFormat3(MailAttachment) + implicit def interopEnvelopFormat: RootJsonFormat[InteropEnvelope] = jsonFormat5(InteropEnvelope) implicit def mailInfoFormat: RootJsonFormat[MailTemplate] = jsonFormat2(MailTemplate.apply) + implicit def computeAgreementPayloadFormat: RootJsonFormat[ComputeAgreementStatePayload] = + jsonFormat2(ComputeAgreementStatePayload) } diff --git a/src/main/scala/it/pagopa/interop/agreementprocess/common/Adapters.scala b/src/main/scala/it/pagopa/interop/agreementprocess/common/Adapters.scala index eb3f4386..cf2be5e7 100644 --- a/src/main/scala/it/pagopa/interop/agreementprocess/common/Adapters.scala +++ b/src/main/scala/it/pagopa/interop/agreementprocess/common/Adapters.scala @@ -2,9 +2,13 @@ package it.pagopa.interop.agreementprocess.common import cats.implicits._ import it.pagopa.interop.agreementmanagement.client.{model => AgreementManagement} -import it.pagopa.interop.agreementprocess.error.AgreementProcessErrors.AgreementNotInExpectedState +import it.pagopa.interop.agreementprocess.error.AgreementProcessErrors.{ + AgreementNotInExpectedState, + InvalidAttributeStructure +} import it.pagopa.interop.agreementprocess.model._ import it.pagopa.interop.agreementmanagement.model.agreement._ +import it.pagopa.interop.tenantmanagement.model.{tenant => TenantManagement} import java.util.UUID @@ -343,4 +347,53 @@ object Adapters { path = newPath ) } + + implicit class PersistentVerificationTenantVerifierObjectWrapper( + private val p: TenantManagement.PersistentTenantVerifier.type + ) extends AnyVal { + def fromAPI(p: TenantVerifier): TenantManagement.PersistentTenantVerifier = + TenantManagement.PersistentTenantVerifier( + id = p.id, + verificationDate = p.verificationDate, + expirationDate = p.expirationDate, + extensionDate = p.expirationDate + ) + } + + implicit class PersistentVerificationTenantRevokerObjectWrapper( + private val p: TenantManagement.PersistentTenantRevoker.type + ) extends AnyVal { + def fromAPI(p: TenantRevoker): TenantManagement.PersistentTenantRevoker = TenantManagement.PersistentTenantRevoker( + id = p.id, + verificationDate = p.verificationDate, + expirationDate = p.expirationDate, + extensionDate = p.expirationDate, + revocationDate = p.revocationDate + ) + } + + implicit class PersistentAttributesObjectWrapper(private val p: TenantManagement.PersistentTenantAttribute.type) + extends AnyVal { + def fromAPI(attribute: TenantAttribute): Either[Throwable, TenantManagement.PersistentTenantAttribute] = + attribute match { + case TenantAttribute(Some(declared), None, None) => + TenantManagement + .PersistentDeclaredAttribute(declared.id, declared.assignmentTimestamp, declared.revocationTimestamp) + .asRight + case TenantAttribute(None, Some(certified), None) => + TenantManagement + .PersistentCertifiedAttribute(certified.id, certified.assignmentTimestamp, certified.revocationTimestamp) + .asRight + case TenantAttribute(None, None, Some(verified)) => + TenantManagement + .PersistentVerifiedAttribute( + id = verified.id, + assignmentTimestamp = verified.assignmentTimestamp, + verifiedBy = verified.verifiedBy.toList.map(TenantManagement.PersistentTenantVerifier.fromAPI), + revokedBy = verified.revokedBy.toList.map(TenantManagement.PersistentTenantRevoker.fromAPI) + ) + .asRight + case _ => InvalidAttributeStructure.asLeft + } + } } diff --git a/src/main/scala/it/pagopa/interop/agreementprocess/error/AgreementProcessErrors.scala b/src/main/scala/it/pagopa/interop/agreementprocess/error/AgreementProcessErrors.scala index f9a5a980..a61ea643 100644 --- a/src/main/scala/it/pagopa/interop/agreementprocess/error/AgreementProcessErrors.scala +++ b/src/main/scala/it/pagopa/interop/agreementprocess/error/AgreementProcessErrors.scala @@ -98,4 +98,6 @@ object AgreementProcessErrors { final case class AttributeNotFound(attributeId: UUID) extends ComponentError("0022", s"Attribute ${attributeId.toString} not found") + + case object InvalidAttributeStructure extends ComponentError("0023", "Invalid Attribute Structure") } diff --git a/src/main/scala/it/pagopa/interop/agreementprocess/service/AgreementStateByAttributesFSM.scala b/src/main/scala/it/pagopa/interop/agreementprocess/service/AgreementStateByAttributesFSM.scala index 31401b84..276f1916 100644 --- a/src/main/scala/it/pagopa/interop/agreementprocess/service/AgreementStateByAttributesFSM.scala +++ b/src/main/scala/it/pagopa/interop/agreementprocess/service/AgreementStateByAttributesFSM.scala @@ -1,7 +1,7 @@ package it.pagopa.interop.agreementprocess.service import it.pagopa.interop.agreementprocess.lifecycle.AttributesRules._ -import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenant +import it.pagopa.interop.tenantmanagement.model.tenant.PersistentTenantAttribute import it.pagopa.interop.catalogmanagement.model.CatalogDescriptor import it.pagopa.interop.agreementmanagement.model.agreement.{ PersistentAgreement, @@ -21,31 +21,31 @@ object AgreementStateByAttributesFSM { def nextState( agreement: PersistentAgreement, descriptor: CatalogDescriptor, - consumer: PersistentTenant + consumerAttributes: List[PersistentTenantAttribute] ): PersistentAgreementState = agreement.state match { case Draft => // Skip attributes validation if consuming own EServices if (agreement.consumerId == agreement.producerId) Active - else if (!certifiedAttributesSatisfied(descriptor, consumer)) MissingCertifiedAttributes + else if (!certifiedAttributesSatisfied(descriptor, consumerAttributes)) MissingCertifiedAttributes else if ( - descriptor.agreementApprovalPolicy == Some(Automatic) && - declaredAttributesSatisfied(descriptor, consumer) && - verifiedAttributesSatisfied(agreement, descriptor, consumer) + descriptor.agreementApprovalPolicy.contains(Automatic) && + declaredAttributesSatisfied(descriptor, consumerAttributes) && + verifiedAttributesSatisfied(agreement, descriptor, consumerAttributes) ) Active - else if (declaredAttributesSatisfied(descriptor, consumer)) Pending + else if (declaredAttributesSatisfied(descriptor, consumerAttributes)) Pending else Draft case Pending => - if (!certifiedAttributesSatisfied(descriptor, consumer)) MissingCertifiedAttributes - else if (!declaredAttributesSatisfied(descriptor, consumer)) Draft - else if (!verifiedAttributesSatisfied(agreement, descriptor, consumer)) Pending + if (!certifiedAttributesSatisfied(descriptor, consumerAttributes)) MissingCertifiedAttributes + else if (!declaredAttributesSatisfied(descriptor, consumerAttributes)) Draft + else if (!verifiedAttributesSatisfied(agreement, descriptor, consumerAttributes)) Pending else Active case Active => if (agreement.consumerId == agreement.producerId) Active else if ( - certifiedAttributesSatisfied(descriptor, consumer) && - declaredAttributesSatisfied(descriptor, consumer) && - verifiedAttributesSatisfied(agreement, descriptor, consumer) + certifiedAttributesSatisfied(descriptor, consumerAttributes) && + declaredAttributesSatisfied(descriptor, consumerAttributes) && + verifiedAttributesSatisfied(agreement, descriptor, consumerAttributes) ) Active else @@ -53,16 +53,16 @@ object AgreementStateByAttributesFSM { case Suspended => if (agreement.consumerId == agreement.producerId) Active else if ( - certifiedAttributesSatisfied(descriptor, consumer) && - declaredAttributesSatisfied(descriptor, consumer) && - verifiedAttributesSatisfied(agreement, descriptor, consumer) + certifiedAttributesSatisfied(descriptor, consumerAttributes) && + declaredAttributesSatisfied(descriptor, consumerAttributes) && + verifiedAttributesSatisfied(agreement, descriptor, consumerAttributes) ) Active else Suspended case Archived => Archived case MissingCertifiedAttributes => - if (certifiedAttributesSatisfied(descriptor, consumer)) Draft + if (certifiedAttributesSatisfied(descriptor, consumerAttributes)) Draft else MissingCertifiedAttributes case Rejected => Rejected } diff --git a/src/test/scala/it/pagopa/interop/agreementprocess/AgreementApiAuthzSpec.scala b/src/test/scala/it/pagopa/interop/agreementprocess/AgreementApiAuthzSpec.scala index ca9bfbe9..b874b9cf 100644 --- a/src/test/scala/it/pagopa/interop/agreementprocess/AgreementApiAuthzSpec.scala +++ b/src/test/scala/it/pagopa/interop/agreementprocess/AgreementApiAuthzSpec.scala @@ -142,7 +142,9 @@ class AgreementApiAuthzSpec extends AnyWordSpecLike with MockFactory with AuthzS val endpoint = AuthorizedRoutes.endpoints("computeAgreementsByAttribute") validateAuthorization( endpoint, - { implicit c: Seq[(String, String)] => service.computeAgreementsByAttribute("fake", "fake") } + { implicit c: Seq[(String, String)] => + service.computeAgreementsByAttribute(SpecData.computeAgreementStatePayload) + } ) } diff --git a/src/test/scala/it/pagopa/interop/agreementprocess/AgreementStateByAttributesFSMSpec.scala b/src/test/scala/it/pagopa/interop/agreementprocess/AgreementStateByAttributesFSMSpec.scala index b294758c..3e841b33 100644 --- a/src/test/scala/it/pagopa/interop/agreementprocess/AgreementStateByAttributesFSMSpec.scala +++ b/src/test/scala/it/pagopa/interop/agreementprocess/AgreementStateByAttributesFSMSpec.scala @@ -15,8 +15,10 @@ import it.pagopa.interop.agreementmanagement.model.agreement.{ Rejected, Draft } + import org.scalatest.matchers.should.Matchers._ import org.scalatest.wordspec.AnyWordSpecLike + import java.util.UUID class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { @@ -32,8 +34,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val tenantAttr = List(tenantCertAttr, tenantDeclAttr, tenantVerAttr) val descriptor = SpecData.publishedDescriptor.copy(agreementApprovalPolicy = Automatic.some, attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Active + + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Active } "go to ACTIVE when Consumer and Producer are the same, even with unmet attributes" in { @@ -52,9 +54,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = DRAFT, producerId = producerId, consumerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr, id = producerId) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Active + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Active } "go to PENDING when Certified and Declared attributes are satisfied and Agreement Approval Policy is not AUTOMATIC" in { @@ -66,9 +67,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val descriptor = SpecData.publishedDescriptor.copy(agreementApprovalPolicy = Manual.some, attributes = descriptorAttr) val agreement: Agreement = SpecData.agreement.copy(state = DRAFT) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Pending + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Pending } "go to MISSING_CERTIFIED_ATTRIBUTES when Certified attributes are NOT satisfied" in { @@ -80,9 +80,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = DRAFT) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe MissingCertifiedAttributes + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe MissingCertifiedAttributes } } @@ -98,9 +97,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = PENDING, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Active + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Active } "stay in PENDING when Verified attributes are NOT satisfied" in { @@ -114,9 +112,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = PENDING) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Pending + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Pending } "go to DRAFT when Declared attributes are NOT satisfied" in { @@ -131,9 +128,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = PENDING, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Draft + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Draft } "go to MISSING_CERTIFIED_ATTRIBUTES when Certified attributes are NOT satisfied" in { @@ -148,9 +144,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = PENDING, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe MissingCertifiedAttributes + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe MissingCertifiedAttributes } } @@ -167,9 +162,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ACTIVE, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Suspended + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Suspended } "go to SUSPENDED when Declared attributes are NOT satisfied" in { @@ -184,9 +178,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ACTIVE, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Suspended + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Suspended } "go to SUSPENDED when Verified attributes are NOT satisfied" in { @@ -200,9 +193,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ACTIVE) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Suspended + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Suspended } "go to ACTIVE when Consumer and Producer are the same, even with unmet attributes" in { @@ -221,9 +213,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ACTIVE, producerId = producerId, consumerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr, id = producerId) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Active + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Active } } @@ -239,9 +230,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = SUSPENDED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Active + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Active } "stay in SUSPENDED when Certified attributes are NOT satisfied" in { @@ -256,9 +246,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = SUSPENDED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Suspended + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Suspended } "stay in SUSPENDED when Declared attributes are NOT satisfied" in { @@ -273,9 +262,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = SUSPENDED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Suspended + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Suspended } "stay in SUSPENDED when Verified attributes are NOT satisfied" in { @@ -289,9 +277,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = SUSPENDED) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Suspended + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Suspended } "go to ACTIVE when Consumer and Producer are the same, even with unmet attributes" in { @@ -310,9 +297,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = SUSPENDED, producerId = producerId, consumerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr, id = producerId) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Active + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Active } } @@ -328,9 +314,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ARCHIVED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Archived + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Archived } "stay in ARCHIVED when Certified attributes are NOT satisfied" in { @@ -345,9 +330,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ARCHIVED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Archived + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Archived } "stay in ARCHIVED when Declared attributes are NOT satisfied" in { @@ -362,9 +346,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ARCHIVED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Archived + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Archived } "stay in ARCHIVED when Verified attributes are NOT satisfied" in { @@ -378,9 +361,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = ARCHIVED) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Archived + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Archived } } @@ -392,9 +374,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = MISSING_CERTIFIED_ATTRIBUTES) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Draft + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Draft } "stay in MISSING_CERTIFIED_ATTRIBUTES when Certified attributes are NOT satisfied" in { @@ -409,9 +390,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = MISSING_CERTIFIED_ATTRIBUTES, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe MissingCertifiedAttributes + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe MissingCertifiedAttributes } "from REJECTED" should { @@ -426,9 +406,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = REJECTED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Rejected + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Rejected } "stay in REJECTED when Certified attributes are NOT satisfied" in { @@ -443,9 +422,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = REJECTED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Rejected + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Rejected } "stay in REJECTED when Declared attributes are NOT satisfied" in { @@ -460,9 +438,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = REJECTED, producerId = producerId) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Rejected + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Rejected } "stay in REJECTED when Verified attributes are NOT satisfied" in { @@ -476,9 +453,8 @@ class AgreementStateByAttributesFSMSpec extends AnyWordSpecLike { val agreement: Agreement = SpecData.agreement.copy(state = REJECTED) val descriptor = SpecData.descriptor.copy(attributes = descriptorAttr) - val consumer = SpecData.tenant.copy(attributes = tenantAttr) - nextState(agreement.toPersistent, descriptor, consumer) shouldBe Rejected + nextState(agreement.toPersistent, descriptor, tenantAttr) shouldBe Rejected } } } diff --git a/src/test/scala/it/pagopa/interop/agreementprocess/ComputeAgreementsStateSpec.scala b/src/test/scala/it/pagopa/interop/agreementprocess/ComputeAgreementsStateSpec.scala index e145fe0b..3441cd07 100644 --- a/src/test/scala/it/pagopa/interop/agreementprocess/ComputeAgreementsStateSpec.scala +++ b/src/test/scala/it/pagopa/interop/agreementprocess/ComputeAgreementsStateSpec.scala @@ -4,6 +4,7 @@ import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.testkit.ScalatestRouteTest import it.pagopa.interop.agreementprocess.common.Adapters._ import it.pagopa.interop.agreementmanagement.client.model.{AgreementState, UpdateAgreementSeed} +import it.pagopa.interop.agreementprocess.model.ComputeAgreementStatePayload import it.pagopa.interop.authorizationmanagement.client.model.ClientComponentState import it.pagopa.interop.commons.jwt.INTERNAL_ROLE import org.scalatest.matchers.should.Matchers._ @@ -16,10 +17,9 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc "Agreement State Compute" should { "succeed and update agreements whose EServices contain the attribute when Tenant gained the attribute" in { implicit val contexts: Seq[(String, String)] = contextWithRole(INTERNAL_ROLE) - val consumerId = UUID.randomUUID() - val (descriptorAttr, tenantAttr) = SpecData.matchingCertifiedAttributes - val attributeId = tenantAttr.id + val (descriptorAttr, tenantAttr) = SpecData.compactMatchingCertifiedAttributes + val attributeId = tenantAttr.certified.map(_.id).get val descriptor1 = SpecData.descriptor.copy(attributes = descriptorAttr) val descriptor2 = SpecData.descriptor @@ -27,7 +27,8 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc val eService1 = SpecData.eService.copy(id = UUID.randomUUID(), descriptors = descriptor1 :: Nil) val eService2 = SpecData.eService.copy(id = UUID.randomUUID(), descriptors = descriptor2 :: Nil) val eService3 = SpecData.eService.copy(id = UUID.randomUUID(), descriptors = descriptor3 :: Nil) - val tenant = SpecData.tenant.copy(attributes = List(tenantAttr)) + val tenant = SpecData.compactTenant.copy(attributes = List(tenantAttr)) + val payload = ComputeAgreementStatePayload(attributeId, tenant) val suspendedAgreement1 = SpecData.suspendedByPlatformAgreement.copy( @@ -103,7 +104,6 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc mockEServiceRetrieve(eService1.id, eService1) mockEServiceRetrieve(eService2.id, eService2) mockEServiceRetrieve(eService3.id, eService3) - mockTenantRetrieve(consumerId, tenant) mockAgreementUpdate(suspendedAgreement1.id, expectedSeed1, suspendedAgreement1) mockAgreementUpdate(missingCertAttrAgreement.id, expectedSeed2, missingCertAttrAgreement) @@ -116,14 +116,13 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc ClientComponentState.ACTIVE ) - Get() ~> service.computeAgreementsByAttribute(consumerId.toString, attributeId.toString) ~> check { + Get() ~> service.computeAgreementsByAttribute(payload) ~> check { status shouldEqual StatusCodes.NoContent } } "succeed and update agreements whose EServices contain the attribute when Tenant lost the attribute" in { implicit val contexts: Seq[(String, String)] = contextWithRole(INTERNAL_ROLE) - val consumerId = UUID.randomUUID() val attributeId = UUID.randomUUID() val descriptorAttr = SpecData.catalogCertifiedAttribute(attributeId) @@ -131,7 +130,8 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc val descriptor2 = SpecData.descriptor.copy(id = UUID.randomUUID()) val eService1 = SpecData.eService.copy(id = UUID.randomUUID(), descriptors = descriptor1 :: Nil) val eService2 = SpecData.eService.copy(id = UUID.randomUUID(), descriptors = descriptor2 :: Nil) - val tenant = SpecData.tenant + val tenant = SpecData.compactTenant + val payload = ComputeAgreementStatePayload(attributeId, tenant) val draftAgreement = SpecData.draftAgreement.copy(eserviceId = eService1.id, descriptorId = descriptor1.id) val pendingAgreement = SpecData.pendingAgreement.copy(eserviceId = eService1.id, descriptorId = descriptor1.id) @@ -144,7 +144,6 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc mockAgreementsRetrieve(agreements.map(_.toPersistent)) mockEServiceRetrieve(eService1.id, eService1) mockEServiceRetrieve(eService2.id, eService2) - mockTenantRetrieve(consumerId, tenant) mockAgreementUpdateIgnoreSeed(draftAgreement.id) mockAgreementUpdateIgnoreSeed(pendingAgreement.id) @@ -157,15 +156,13 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc ClientComponentState.INACTIVE ) - Get() ~> service.computeAgreementsByAttribute(consumerId.toString, attributeId.toString) ~> check { + Get() ~> service.computeAgreementsByAttribute(payload) ~> check { status shouldEqual StatusCodes.NoContent } } "succeed and do not update agreements if status has not changed" in { implicit val contexts: Seq[(String, String)] = contextWithRole(INTERNAL_ROLE) - val consumerId = UUID.randomUUID() - val attributeId = UUID.randomUUID() val descriptorAttr = SpecData.catalogCertifiedAttribute() val eServiceId1 = UUID.randomUUID() @@ -184,9 +181,8 @@ class ComputeAgreementsStateSpec extends AnyWordSpecLike with SpecHelper with Sc mockAgreementsRetrieve(agreements.map(_.toPersistent)) mockEServiceRetrieve(eServiceId1, eService1) mockEServiceRetrieve(eServiceId2, eService2) - mockTenantRetrieve(consumerId, SpecData.tenant) - Get() ~> service.computeAgreementsByAttribute(consumerId.toString, attributeId.toString) ~> check { + Get() ~> service.computeAgreementsByAttribute(SpecData.computeAgreementStatePayload) ~> check { status shouldEqual StatusCodes.NoContent } } diff --git a/src/test/scala/it/pagopa/interop/agreementprocess/SpecData.scala b/src/test/scala/it/pagopa/interop/agreementprocess/SpecData.scala index ff8f0e40..0f3da797 100644 --- a/src/test/scala/it/pagopa/interop/agreementprocess/SpecData.scala +++ b/src/test/scala/it/pagopa/interop/agreementprocess/SpecData.scala @@ -2,33 +2,42 @@ package it.pagopa.interop.agreementprocess import cats.syntax.all._ import it.pagopa.interop.agreementmanagement.client.model._ +import it.pagopa.interop.agreementprocess.model.{ + CertifiedTenantAttribute, + CompactTenant, + ComputeAgreementStatePayload, + DeclaredTenantAttribute, + TenantAttribute, + TenantVerifier, + VerifiedTenantAttribute +} import it.pagopa.interop.selfcare.userregistry.client.model.CertifiableFieldResourceOfstringEnums.Certification import it.pagopa.interop.selfcare.userregistry.client.model.{CertifiableFieldResourceOfstring, UserResource} import java.time.{OffsetDateTime, ZoneOffset} import java.util.UUID import it.pagopa.interop.catalogmanagement.model.{ - CatalogDescriptor, - Published => CatalogPublished, - Deprecated => CatalogDeprecated, - Archived => CatalogArchived, - Draft => CatalogDraft, Automatic, - CatalogItem, - Rest, + CatalogAttributeValue, CatalogAttributes, + CatalogDescriptor, + CatalogItem, GroupAttribute, + Rest, SingleAttribute, - CatalogAttributeValue + Archived => CatalogArchived, + Deprecated => CatalogDeprecated, + Draft => CatalogDraft, + Published => CatalogPublished } import it.pagopa.interop.tenantmanagement.model.tenant.{ - PersistentTenant, + PersistentCertifiedAttribute, + PersistentDeclaredAttribute, PersistentExternalId, + PersistentTenant, PersistentTenantKind, - PersistentDeclaredAttribute, - PersistentCertifiedAttribute, - PersistentVerifiedAttribute, - PersistentTenantVerifier + PersistentTenantVerifier, + PersistentVerifiedAttribute } import it.pagopa.interop.attributeregistrymanagement.model.persistence.attribute.{Certified, PersistentAttribute} @@ -87,6 +96,10 @@ object SpecData { createdAt = timestamp ) + def compactTenant: CompactTenant = CompactTenant(id = UUID.randomUUID(), attributes = Nil) + def computeAgreementStatePayload: ComputeAgreementStatePayload = + ComputeAgreementStatePayload(attributeId = UUID.randomUUID(), consumer = compactTenant) + def tenant: PersistentTenant = PersistentTenant( id = UUID.randomUUID(), selfcareId = Some(UUID.randomUUID().toString), @@ -123,9 +136,19 @@ object SpecData { def tenantCertifiedAttribute(id: UUID = UUID.randomUUID()): PersistentCertifiedAttribute = PersistentCertifiedAttribute(id = id, assignmentTimestamp = timestamp, revocationTimestamp = None) + def compactTenantCertifiedAttribute(id: UUID = UUID.randomUUID()): TenantAttribute = + TenantAttribute(certified = + Some(CertifiedTenantAttribute(id = id, assignmentTimestamp = timestamp, revocationTimestamp = None)) + ) + def tenantDeclaredAttribute(id: UUID = UUID.randomUUID()): PersistentDeclaredAttribute = PersistentDeclaredAttribute(id = id, assignmentTimestamp = timestamp, revocationTimestamp = None) + def compactTenantDeclaredAttribute(id: UUID = UUID.randomUUID()): TenantAttribute = + TenantAttribute(declared = + Some(DeclaredTenantAttribute(id = id, assignmentTimestamp = timestamp, revocationTimestamp = None)) + ) + def tenantVerifiedAttribute( id: UUID = UUID.randomUUID(), verifierId: UUID = UUID.randomUUID() @@ -144,6 +167,31 @@ object SpecData { revokedBy = Nil ) + def compactTenantVerifiedAttribute( + id: UUID = UUID.randomUUID(), + verifierId: UUID = UUID.randomUUID() + ): TenantAttribute = + TenantAttribute(verified = + Some( + VerifiedTenantAttribute( + id = id, + assignmentTimestamp = timestamp, + verifiedBy = Seq( + TenantVerifier(id = verifierId, verificationDate = timestamp, expirationDate = timestamp.plusYears(9).some) + ), + revokedBy = Nil + ) + ) + ) + + def compactMatchingCertifiedAttributes: (CatalogAttributes, TenantAttribute) = { + val attributeId = UUID.randomUUID() + val eServiceAttribute = catalogCertifiedAttribute(attributeId) + val tenantAttribute = compactTenantCertifiedAttribute(attributeId) + + (eServiceAttribute, tenantAttribute) + } + def matchingCertifiedAttributes: (CatalogAttributes, PersistentCertifiedAttribute) = { val attributeId = UUID.randomUUID() val eServiceAttribute = catalogCertifiedAttribute(attributeId) @@ -152,6 +200,14 @@ object SpecData { (eServiceAttribute, tenantAttribute) } + def compactMatchingDeclaredAttributes: (CatalogAttributes, TenantAttribute) = { + val attributeId = UUID.randomUUID() + val eServiceAttribute = catalogCertifiedAttribute(attributeId) + val tenantAttribute = compactTenantDeclaredAttribute(attributeId) + + (eServiceAttribute, tenantAttribute) + } + def matchingDeclaredAttributes: (CatalogAttributes, PersistentDeclaredAttribute) = { val attributeId = UUID.randomUUID() val eServiceAttribute = catalogDeclaredAttribute(attributeId) @@ -170,6 +226,14 @@ object SpecData { (eServiceAttribute, tenantAttribute) } + def compactMatchingVerifiedAttributes(verifierId: UUID = UUID.randomUUID()): (CatalogAttributes, TenantAttribute) = { + val attributeId = UUID.randomUUID() + val eServiceAttribute = catalogCertifiedAttribute(attributeId) + val tenantAttribute = compactTenantVerifiedAttribute(attributeId, verifierId) + + (eServiceAttribute, tenantAttribute) + } + def agreement: Agreement = Agreement( id = UUID.randomUUID(), eserviceId = UUID.randomUUID(),