diff --git a/packages/agreement-outbound-writer/package.json b/packages/agreement-outbound-writer/package.json index d0bf7bbb3c..cfd8df6915 100644 --- a/packages/agreement-outbound-writer/package.json +++ b/packages/agreement-outbound-writer/package.json @@ -29,7 +29,7 @@ "vitest": "1.6.0" }, "dependencies": { - "@pagopa/interop-outbound-models": "1.0.10", + "@pagopa/interop-outbound-models": "1.0.11a", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", @@ -40,4 +40,4 @@ "ts-pattern": "5.2.0", "zod": "3.23.8" } -} \ No newline at end of file +} diff --git a/packages/agreement-process/src/model/domain/agreement-validators.ts b/packages/agreement-process/src/model/domain/agreement-validators.ts index d579d85227..3da8cb0966 100644 --- a/packages/agreement-process/src/model/domain/agreement-validators.ts +++ b/packages/agreement-process/src/model/domain/agreement-validators.ts @@ -156,6 +156,64 @@ export const assertRequesterIsConsumerOrProducer = ( } }; +export const assertRequesterIsConsumerOrProducerOrDelegate = async ( + agreement: Agreement, + authData: AuthData, + readModelService: ReadModelService +): Promise => { + try { + assertRequesterIsConsumer(agreement, authData); + } catch (error) { + try { + assertRequesterIsProducer(agreement, authData); + } catch (error) { + const delegation = await readModelService.getDelegationByDelegateId( + authData.organizationId + ); + assertRequesterIsDelegate(delegation?.data.delegateId, authData); + } + } +}; + +export const assertRequesterIsProducerOrDelegate = ( + agreement: Agreement, + delegateIdActiveDelegation: TenantId | undefined, + authData: AuthData +): void => { + if (delegateIdActiveDelegation) { + assertRequesterIsDelegate(delegateIdActiveDelegation, authData); + } else { + assertRequesterIsProducer(agreement, authData); + } +}; + +export const assertRequesterIsDelegate = ( + delegateId: TenantId | undefined, + authData: AuthData +): void => { + if (authData.organizationId !== delegateId) { + throw operationNotAllowed(authData.organizationId); + } +}; + +export const assertRequesterCanActivate = ( + agreement: Agreement, + delegateIdActiveDelegation: TenantId | undefined, + authData: AuthData +): void => { + try { + assertRequesterIsConsumer(agreement, authData); + } catch (e) { + if (delegateIdActiveDelegation) { + assertRequesterIsDelegate(delegateIdActiveDelegation, authData); + } else { + assertRequesterIsProducer(agreement, authData); + } + } +}; + +export const assertRequesterCanSuspend = assertRequesterCanActivate; + export const assertSubmittableState = ( state: AgreementState, agreementId: AgreementId @@ -207,8 +265,15 @@ const validateLatestDescriptor = ( descriptorId: DescriptorId, allowedStates: DescriptorState[] ): Descriptor => { + const activeDescriptorStates: DescriptorState[] = [ + descriptorState.archived, + descriptorState.deprecated, + descriptorState.published, + descriptorState.suspended, + ]; + const recentActiveDescriptors = eservice.descriptors - .filter((d) => d.state !== descriptorState.draft) + .filter((d) => activeDescriptorStates.includes(d.state)) .sort((a, b) => Number(b.version) - Number(a.version)); if ( diff --git a/packages/agreement-process/src/services/agreementActivationProcessor.ts b/packages/agreement-process/src/services/agreementActivationProcessor.ts index c1ae2129ed..ad59eb486f 100644 --- a/packages/agreement-process/src/services/agreementActivationProcessor.ts +++ b/packages/agreement-process/src/services/agreementActivationProcessor.ts @@ -9,6 +9,7 @@ import { Descriptor, EService, Tenant, + TenantId, UserId, agreementState, genericError, @@ -47,6 +48,7 @@ export function createActivationUpdateAgreementSeed({ suspendedByConsumer, suspendedByProducer, suspendedByPlatform, + delegateId, }: { isFirstActivation: boolean; newState: AgreementState; @@ -58,8 +60,9 @@ export function createActivationUpdateAgreementSeed({ suspendedByConsumer: boolean | undefined; suspendedByProducer: boolean | undefined; suspendedByPlatform: boolean | undefined; + delegateId?: TenantId | undefined; }): UpdateAgreementSeed { - const stamp = createStamp(authData.userId); + const stamp = createStamp(authData.userId, delegateId); return isFirstActivation ? { @@ -113,7 +116,8 @@ export async function createActivationEvent( suspendedByPlatformChanged: boolean, agreementEventStoreVersion: number, authData: AuthData, - correlationId: CorrelationId + correlationId: CorrelationId, + delegateId?: TenantId ): Promise>> { if (isFirstActivation) { // Pending >>> Active @@ -150,30 +154,41 @@ export async function createActivationEvent( we also create the corresponding suspension/unsuspension by platform event. */ - return match([authData.organizationId, updatedAgreement.state]) - .with([updatedAgreement.producerId, agreementState.active], () => [ - toCreateEventAgreementUnsuspendedByProducer( - updatedAgreement, - agreementEventStoreVersion, - correlationId - ), - ]) - .with([updatedAgreement.producerId, agreementState.suspended], () => [ - toCreateEventAgreementUnsuspendedByProducer( - { - ...updatedAgreement, - suspendedByPlatform: originalSuspendedByPlatform, - }, - agreementEventStoreVersion, - correlationId - ), - ...maybeCreateSuspensionByPlatformEvents( - updatedAgreement, - suspendedByPlatformChanged, - agreementEventStoreVersion + 1, - correlationId - ), - ]) + return match<[TenantId | undefined, AgreementState]>([ + authData.organizationId, + updatedAgreement.state, + ]) + .with( + [updatedAgreement.producerId, agreementState.active], + [delegateId, agreementState.active], + () => [ + toCreateEventAgreementUnsuspendedByProducer( + updatedAgreement, + agreementEventStoreVersion, + correlationId + ), + ] + ) + .with( + [updatedAgreement.producerId, agreementState.suspended], + [delegateId, agreementState.suspended], + () => [ + toCreateEventAgreementUnsuspendedByProducer( + { + ...updatedAgreement, + suspendedByPlatform: originalSuspendedByPlatform, + }, + agreementEventStoreVersion, + correlationId + ), + ...maybeCreateSuspensionByPlatformEvents( + updatedAgreement, + suspendedByPlatformChanged, + agreementEventStoreVersion + 1, + correlationId + ), + ] + ) .with([updatedAgreement.consumerId, agreementState.active], () => [ toCreateEventAgreementUnsuspendedByConsumer( updatedAgreement, diff --git a/packages/agreement-process/src/services/agreementService.ts b/packages/agreement-process/src/services/agreementService.ts index 8702803a06..be60549b8c 100644 --- a/packages/agreement-process/src/services/agreementService.ts +++ b/packages/agreement-process/src/services/agreementService.ts @@ -37,6 +37,7 @@ import { unsafeBrandId, CompactTenant, CorrelationId, + Delegation, } from "pagopa-interop-models"; import { declaredAttributesSatisfied, @@ -87,9 +88,11 @@ import { assertActivableState, assertCanWorkOnConsumerDocuments, assertExpectedState, + assertRequesterCanActivate, + assertRequesterCanSuspend, assertRequesterIsConsumer, - assertRequesterIsConsumerOrProducer, - assertRequesterIsProducer, + assertRequesterIsConsumerOrProducerOrDelegate, + assertRequesterIsProducerOrDelegate, assertSubmittableState, failOnActivationFailure, matchingCertifiedAttributes, @@ -172,6 +175,18 @@ export const retrieveTenant = async ( return tenant; }; +export const retrieveDelegationByDelegateId = async ( + delegateId: TenantId, + readModelService: ReadModelService +): Promise | undefined> => + await readModelService.getDelegationByDelegateId(delegateId); + +export const retrieveActiveDelegationByEserviceId = async ( + eserviceId: EServiceId, + readModelService: ReadModelService +): Promise | undefined> => + await readModelService.getActiveDelegationByEserviceId(eserviceId); + const retrieveDescriptor = ( descriptorId: DescriptorId, eservice: EService @@ -401,10 +416,17 @@ export function agreementServiceBuilder( readModelService ); + const activeDelegation = await retrieveActiveDelegationByEserviceId( + agreement.data.eserviceId, + readModelService + ); + const delegateId = activeDelegation?.data.delegateId; + const nextStateByAttributes = nextStateByAttributesFSM( agreement.data, descriptor, - consumer + consumer, + delegateId ); const suspendedByPlatform = suspendedByPlatformFlag( @@ -757,7 +779,12 @@ export function agreementServiceBuilder( `Retrieving consumer document ${documentId} from agreement ${agreementId}` ); const agreement = await retrieveAgreement(agreementId, readModelService); - assertRequesterIsConsumerOrProducer(agreement.data, authData); + + await assertRequesterIsConsumerOrProducerOrDelegate( + agreement.data, + authData, + readModelService + ); return retrieveAgreementDocument(agreement.data, documentId); }, @@ -768,8 +795,14 @@ export function agreementServiceBuilder( logger.info(`Suspending agreement ${agreementId}`); const agreement = await retrieveAgreement(agreementId, readModelService); + const activeDelegation = await retrieveActiveDelegationByEserviceId( + agreement.data.eserviceId, + readModelService + ); + + const delegateId = activeDelegation?.data.delegateId; - assertRequesterIsConsumerOrProducer(agreement.data, authData); + assertRequesterCanSuspend(agreement.data, delegateId, authData); assertExpectedState( agreementId, @@ -797,6 +830,7 @@ export function agreementServiceBuilder( authData, descriptor, consumer, + delegateId, }); await repository.createEvent( @@ -804,7 +838,8 @@ export function agreementServiceBuilder( authData.organizationId, correlationId, updatedAgreement, - agreement + agreement, + delegateId ) ); @@ -873,8 +908,17 @@ export function agreementServiceBuilder( agreementId, readModelService ); + const activeDelegation = await retrieveActiveDelegationByEserviceId( + agreementToBeRejected.data.eserviceId, + readModelService + ); + const delegateId = activeDelegation?.data.delegateId; - assertRequesterIsProducer(agreementToBeRejected.data, authData); + assertRequesterIsProducerOrDelegate( + agreementToBeRejected.data, + delegateId, + authData + ); assertExpectedState( agreementId, @@ -913,7 +957,7 @@ export function agreementServiceBuilder( suspendedByPlatform: undefined, stamps: { ...agreementToBeRejected.data.stamps, - rejection: createStamp(authData.userId), + rejection: createStamp(authData.userId, delegateId), }, }; @@ -943,8 +987,15 @@ export function agreementServiceBuilder( ); const agreement = await retrieveAgreement(agreementId, readModelService); + const activeDelegation = await retrieveActiveDelegationByEserviceId( + agreement.data.eserviceId, + readModelService + ); + + const delegateId = activeDelegation?.data.delegateId; + + assertRequesterCanActivate(agreement.data, delegateId, authData); - assertRequesterIsConsumerOrProducer(agreement.data, authData); verifyConsumerDoesNotActivatePending(agreement.data, authData); assertActivableState(agreement.data); @@ -1036,6 +1087,7 @@ export function agreementServiceBuilder( suspendedByConsumer, suspendedByProducer, suspendedByPlatform, + delegateId, }); const updatedAgreementWithoutContract: Agreement = { @@ -1063,7 +1115,8 @@ export function agreementServiceBuilder( suspendedByPlatformChanged, agreement.metadata.version, authData, - correlationId + correlationId, + delegateId ); const archiveEvents = await archiveRelatedToAgreements( diff --git a/packages/agreement-process/src/services/agreementStampUtils.ts b/packages/agreement-process/src/services/agreementStampUtils.ts index 793d89ac0b..7333613f76 100644 --- a/packages/agreement-process/src/services/agreementStampUtils.ts +++ b/packages/agreement-process/src/services/agreementStampUtils.ts @@ -9,8 +9,12 @@ import { } from "pagopa-interop-models"; import { P, match } from "ts-pattern"; -export const createStamp = (userId: UserId): AgreementStamp => ({ +export const createStamp = ( + userId: UserId, + delegateId?: TenantId | undefined +): AgreementStamp => ({ who: unsafeBrandId(userId), + delegateId, when: new Date(), }); @@ -29,9 +33,17 @@ export const suspendedByProducerStamp = ( agreement: Agreement, requesterOrgId: TenantId, destinationState: AgreementState, - stamp: AgreementStamp + stamp: AgreementStamp, + delegateId?: TenantId | undefined ): AgreementStamp | undefined => - match([requesterOrgId, destinationState]) - .with([agreement.producerId, agreementState.suspended], () => stamp) + match<[TenantId | undefined, AgreementState]>([ + requesterOrgId, + destinationState, + ]) + .with( + [agreement.producerId, agreementState.suspended], + [delegateId, agreementState.suspended], + () => stamp + ) .with([agreement.producerId, P.any], () => undefined) .otherwise(() => agreement.stamps.suspensionByProducer); diff --git a/packages/agreement-process/src/services/agreementStateProcessor.ts b/packages/agreement-process/src/services/agreementStateProcessor.ts index d9e72b76cc..a64ae37e80 100644 --- a/packages/agreement-process/src/services/agreementStateProcessor.ts +++ b/packages/agreement-process/src/services/agreementStateProcessor.ts @@ -12,6 +12,7 @@ import { agreementState, CompactTenant, CorrelationId, + TenantId, } from "pagopa-interop-models"; import { P, match } from "ts-pattern"; import { @@ -41,9 +42,13 @@ const { const nextStateFromDraft = ( agreement: Agreement, descriptor: Descriptor, - tenant: Tenant | CompactTenant + tenant: Tenant | CompactTenant, + delegateId: TenantId | undefined ): AgreementState => { - if (agreement.consumerId === agreement.producerId) { + if ( + agreement.consumerId === agreement.producerId || + (delegateId && delegateId === agreement.consumerId) + ) { return active; } if (!certifiedAttributesSatisfied(descriptor.attributes, tenant.attributes)) { @@ -93,9 +98,13 @@ const nextStateFromPending = ( const nextStateFromActiveOrSuspended = ( agreement: Agreement, descriptor: Descriptor, - tenant: Tenant | CompactTenant + tenant: Tenant | CompactTenant, + delegateId: TenantId | undefined ): AgreementState => { - if (agreement.consumerId === agreement.producerId) { + if ( + agreement.consumerId === agreement.producerId || + (delegateId && agreement.consumerId === delegateId) + ) { return active; } if ( @@ -125,17 +134,18 @@ const nextStateFromMissingCertifiedAttributes = ( export const nextStateByAttributesFSM = ( agreement: Agreement, descriptor: Descriptor, - tenant: Tenant | CompactTenant + tenant: Tenant | CompactTenant, + delegateId?: TenantId | undefined ): AgreementState => match(agreement.state) .with(agreementState.draft, () => - nextStateFromDraft(agreement, descriptor, tenant) + nextStateFromDraft(agreement, descriptor, tenant, delegateId) ) .with(agreementState.pending, () => nextStateFromPending(agreement, descriptor, tenant) ) .with(agreementState.active, agreementState.suspended, () => - nextStateFromActiveOrSuspended(agreement, descriptor, tenant) + nextStateFromActiveOrSuspended(agreement, descriptor, tenant, delegateId) ) .with(agreementState.archived, () => archived) .with(agreementState.missingCertifiedAttributes, () => @@ -182,9 +192,10 @@ export const suspendedByConsumerFlag = ( export const suspendedByProducerFlag = ( agreement: Agreement, requesterOrgId: Tenant["id"], - targetDestinationState: AgreementState + targetDestinationState: AgreementState, + delegateId?: TenantId | undefined ): boolean | undefined => - requesterOrgId === agreement.producerId + requesterOrgId === agreement.producerId || requesterOrgId === delegateId ? targetDestinationState === agreementState.suspended : agreement.suspendedByProducer; diff --git a/packages/agreement-process/src/services/agreementSuspensionProcessor.ts b/packages/agreement-process/src/services/agreementSuspensionProcessor.ts index b7a1550f89..dbc9f71f7f 100644 --- a/packages/agreement-process/src/services/agreementSuspensionProcessor.ts +++ b/packages/agreement-process/src/services/agreementSuspensionProcessor.ts @@ -32,11 +32,13 @@ export function createSuspensionUpdatedAgreement({ authData, descriptor, consumer, + delegateId, }: { agreement: Agreement; authData: AuthData; descriptor: Descriptor; consumer: Tenant; + delegateId: TenantId | undefined; }): Agreement { /* nextAttributesState VS targetDestinationState -- targetDestinationState is the state where the caller wants to go (suspended, in this case) @@ -46,7 +48,8 @@ export function createSuspensionUpdatedAgreement({ const nextStateByAttributes = nextStateByAttributesFSM( agreement, descriptor, - consumer + consumer, + delegateId ); const suspendedByConsumer = suspendedByConsumerFlag( @@ -57,7 +60,8 @@ export function createSuspensionUpdatedAgreement({ const suspendedByProducer = suspendedByProducerFlag( agreement, authData.organizationId, - targetDestinationState + targetDestinationState, + delegateId ); const newState = agreementStateByFlags( @@ -67,13 +71,14 @@ export function createSuspensionUpdatedAgreement({ agreement.suspendedByPlatform ); - const stamp = createStamp(authData.userId); + const stamp = createStamp(authData.userId, delegateId); const suspensionByProducerStamp = suspendedByProducerStamp( agreement, authData.organizationId, agreementState.suspended, - stamp + stamp, + delegateId ); const suspensionByConsumerStamp = suspendedByConsumerStamp( @@ -105,18 +110,20 @@ export function createAgreementSuspendedEvent( organizationId: TenantId, correlationId: CorrelationId, updatedAgreement: Agreement, - agreement: WithMetadata + agreement: WithMetadata, + delegateId: TenantId | undefined ): CreateEvent { const isProducer = organizationId === agreement.data.producerId; const isConsumer = organizationId === agreement.data.consumerId; + const isDelegate = delegateId === organizationId; - if (!isProducer && !isConsumer) { + if (!isProducer && !isConsumer && !isDelegate) { throw genericError( - "Agreement can only be suspended by the consumer or producer." + "Agreement can only be suspended by the consumer or producer/delegate." ); } - return isProducer + return isProducer || isDelegate ? toCreateEventAgreementSuspendedByProducer( updatedAgreement, agreement.metadata.version, diff --git a/packages/agreement-process/src/services/readModelService.ts b/packages/agreement-process/src/services/readModelService.ts index 23b7975c30..7f25cf62bf 100644 --- a/packages/agreement-process/src/services/readModelService.ts +++ b/packages/agreement-process/src/services/readModelService.ts @@ -25,6 +25,12 @@ import { AttributeReadmodel, TenantId, genericInternalError, + DelegationState, + Delegation, + delegationState, + AgreementReadModel, + DescriptorReadModel, + EServiceReadModel, } from "pagopa-interop-models"; import { P, match } from "ts-pattern"; import { z } from "zod"; @@ -289,106 +295,135 @@ async function searchTenantsByName( } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function readModelServiceBuilder( - readModelRepository: ReadModelRepository -) { - const agreements = readModelRepository.agreements; - const eservices = readModelRepository.eservices; - const tenants = readModelRepository.tenants; - const attributes = readModelRepository.attributes; - return { - async getAgreements( - filters: AgreementQueryFilters, - limit: number, - offset: number - ): Promise> { - const aggregationPipeline = [ - getAgreementsFilters(filters), +function getDelegateAgreementsFilters(producerIds: TenantId[] | undefined) { + return producerIds && producerIds.length > 0 + ? [ { $lookup: { - from: "eservices", + from: "delegations", localField: "data.eserviceId", - foreignField: "data.id", - as: "eservices", + foreignField: "data.eserviceId", + as: "delegations", }, }, { - $unwind: "$eservices", + $unwind: { + path: "$delegations", + preserveNullAndEmptyArrays: true, + }, }, - ...(filters.showOnlyUpgradeable - ? [ + { + $match: { + $or: [ { - $addFields: { - currentDescriptor: { - $filter: { - input: "$eservices.data.descriptors", - as: "descr", - cond: { - $eq: ["$$descr.id", "$data.descriptorId"], - }, - }, + $and: [ + { + "delegations.data.state": agreementState.active, }, - }, - }, - { - $unwind: "$currentDescriptor", - }, - { - $addFields: { - upgradableDescriptor: { - $filter: { - input: "$eservices.data.descriptors", - as: "upgradable", - cond: { - $and: [ - { - $gt: [ - "$$upgradable.publishedAt", - "$currentDescriptor.publishedAt", - ], - }, - { - $in: [ - "$$upgradable.state", - [ - descriptorState.published, - descriptorState.suspended, - ], - ], - }, - ], - }, + { + "delegations.data.delegateId": { + $in: producerIds, }, }, - }, + ], }, { - $match: { - upgradableDescriptor: { $ne: [] }, + "data.producerId": { + $in: producerIds, }, }, - ] - : []), - { - $project: { - data: 1, - eservices: 1, - lowerName: { $toLower: "$eservices.data.name" }, + ], }, }, - { - $sort: { lowerName: 1 }, - }, - ]; + ] + : []; +} - const data = await agreements +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function readModelServiceBuilder( + readModelRepository: ReadModelRepository +) { + const agreements = readModelRepository.agreements; + const eservices = readModelRepository.eservices; + const tenants = readModelRepository.tenants; + const attributes = readModelRepository.attributes; + const delegations = readModelRepository.delegations; + return { + async getAgreements( + filters: AgreementQueryFilters, + limit: number, + offset: number + ): Promise> { + const { producerId, ...filtersWithoutProducerId } = filters; + const producerIds = producerId + ? Array.isArray(producerId) + ? producerId + : [producerId] + : []; + + const agreementsData = await agreements .aggregate( - [...aggregationPipeline, { $skip: offset }, { $limit: limit }], - { allowDiskUse: true } + [ + getAgreementsFilters(filtersWithoutProducerId), + ...getDelegateAgreementsFilters(producerIds), + ], + { + allowDiskUse: true, + } ) .toArray(); - const result = z.array(Agreement).safeParse(data.map((d) => d.data)); + const eserviceIds = agreementsData.map( + (agreement) => agreement.data.eserviceId + ); + const eservicesData = await eservices + .find({ "data.id": { $in: eserviceIds } }) + .toArray(); + + const eservicesMap = new Map( + eservicesData.map((eservice) => [eservice.data.id, eservice.data]) + ); + + const combinedData: Array<{ + agreement: AgreementReadModel; + eservice: EServiceReadModel; + }> = agreementsData.flatMap((agreement) => { + const eservice = eservicesMap.get(agreement.data.eserviceId); + return eservice ? [{ agreement: agreement.data, eservice }] : []; + }); + + const filteredData = filters.showOnlyUpgradeable + ? combinedData.filter((cb) => { + const currentDescriptor = cb.eservice.descriptors.find( + (descr) => descr.id === cb.agreement.descriptorId + ); + const upgradableDescriptor = cb.eservice.descriptors.filter( + (upgradable: DescriptorReadModel) => { + // Since the dates are optional, if they are undefined they are set to a very old date + const currentPublishedAt = + currentDescriptor?.publishedAt ?? new Date(0); + const upgradablePublishedAt = + upgradable.publishedAt ?? new Date(0); + return ( + upgradablePublishedAt > currentPublishedAt && + (upgradable.state === descriptorState.published || + upgradable.state === descriptorState.suspended) + ); + } + ); + return upgradableDescriptor.length > 0; + }) + : combinedData; + + const data = filteredData + .slice(offset, offset + limit) + .sort((a, b) => + a.eservice.name + .toLowerCase() + .localeCompare(b.eservice.name.toLowerCase()) + ); + + const result = z.array(Agreement).safeParse(data.map((d) => d.agreement)); if (!result.success) { throw genericInternalError( `Unable to parse agreements items: result ${JSON.stringify( @@ -399,10 +434,7 @@ export function readModelServiceBuilder( return { results: result.data, - totalCount: await ReadModelRepository.getTotalCount( - agreements, - aggregationPipeline - ), + totalCount: filteredData.length, }; }, async getAgreementById( @@ -505,18 +537,34 @@ export function readModelServiceBuilder( ...(filters.consumerIds.length === 0 ? undefined : { "data.consumerId": { $in: filters.consumerIds } }), - ...(filters.producerIds.length === 0 - ? undefined - : { "data.producerId": { $in: filters.producerIds } }), ...(filters.agreeementStates.length === 0 ? undefined : { "data.state": { $in: filters.agreeementStates } }), }; - const agreementEservicesIds = await agreements.distinct( - "data.eserviceId", - agreementFilter - ); + const agreementAggregationPipeline = [ + ...getDelegateAgreementsFilters(filters.producerIds), + { + $match: agreementFilter, + }, + { + $group: { + _id: "$data.eserviceId", + }, + }, + { + $project: { + _id: 0, + eserviceId: "$_id", + }, + }, + ]; + + const agreementData = await agreements + .aggregate([...agreementAggregationPipeline], { allowDiskUse: true }) + .toArray(); + + const agreementEservicesIds = agreementData.map((d) => d.eserviceId); const aggregationPipeline = [ { @@ -562,6 +610,59 @@ export function readModelServiceBuilder( ), }; }, + async getDelegationByDelegateId( + delegateId: TenantId, + state: DelegationState = delegationState.active + ): Promise | undefined> { + const data = await delegations.findOne( + { "data.delegateId": delegateId, "data.state": state }, + { projection: { data: true, metadata: true } } + ); + + if (!data) { + return undefined; + } + const result = z + .object({ + data: Delegation, + metadata: z.object({ version: z.number() }), + }) + .safeParse(data); + if (!result.success) { + throw genericInternalError( + `Unable to parse delegation item: result ${JSON.stringify( + result + )} - data ${JSON.stringify(data)} ` + ); + } + return result.data; + }, + async getActiveDelegationByEserviceId( + eserviceId: EServiceId + ): Promise | undefined> { + const data = await delegations.findOne( + { "data.eserviceId": eserviceId, "data.state": delegationState.active }, + { projection: { data: true, metadata: true } } + ); + + if (!data) { + return undefined; + } + const result = z + .object({ + data: Delegation, + metadata: z.object({ version: z.number() }), + }) + .safeParse(data); + if (!result.success) { + throw genericInternalError( + `Unable to parse delegation item: result ${JSON.stringify( + result + )} - data ${JSON.stringify(data)} ` + ); + } + return result.data; + }, }; } diff --git a/packages/agreement-process/test/activateAgreement.test.ts b/packages/agreement-process/test/activateAgreement.test.ts index 847e129636..e2be54c2c1 100644 --- a/packages/agreement-process/test/activateAgreement.test.ts +++ b/packages/agreement-process/test/activateAgreement.test.ts @@ -13,6 +13,7 @@ import { getMockAttribute, getMockCertifiedTenantAttribute, getMockDeclaredTenantAttribute, + getMockDelegation, getMockDescriptorPublished, getMockEService, getMockEServiceAttribute, @@ -43,6 +44,8 @@ import { UserId, VerifiedTenantAttribute, agreementState, + delegationKind, + delegationState, descriptorState, fromAgreementV2, generateId, @@ -73,6 +76,7 @@ import { config } from "../src/config/config.js"; import { addOneAgreement, addOneAttribute, + addOneDelegation, addOneEService, addOneTenant, agreementService, @@ -593,6 +597,126 @@ describe("activate agreement", () => { }) ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); }); + + it("should succed when the requester is the Delegate and first activation", async () => { + const producer = getMockTenant(); + const consumer = getMockTenant(); + const authData = getRandomAuthData(); + const esevice = { + ...getMockEService(), + producerId: producer.id, + consumerId: consumer.id, + descriptors: [getMockDescriptorPublished()], + }; + const agreement: Agreement = { + ...getMockAgreement(esevice.id), + state: agreementState.pending, + descriptorId: esevice.descriptors[0].id, + producerId: producer.id, + consumerId: consumer.id, + suspendedByConsumer: false, + suspendedByProducer: false, + suspendedByPlatform: false, + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: agreement.eserviceId, + delegateId: authData.organizationId, + state: delegationState.active, + }); + + await addOneTenant(consumer); + await addOneTenant(producer); + await addOneEService(esevice); + await addOneAgreement(agreement); + await addOneDelegation(delegation); + + const actualAgreement = await agreementService.activateAgreement( + agreement.id, + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); + + const expectedAgreement = { + ...agreement, + state: agreementState.active, + contract: actualAgreement.contract, + certifiedAttributes: actualAgreement.certifiedAttributes, + declaredAttributes: actualAgreement.declaredAttributes, + verifiedAttributes: actualAgreement.verifiedAttributes, + stamps: { + ...agreement.stamps, + activation: { + who: authData.userId, + when: expect.any(Date), + delegateId: delegation.delegateId, + }, + }, + }; + + expect(actualAgreement).toEqual(expectedAgreement); + }); + + it("should succed when the requester is the Delegate and from Suspended", async () => { + const producer = getMockTenant(); + const consumer = getMockTenant(); + const authData = getRandomAuthData(); + const esevice = { + ...getMockEService(), + producerId: producer.id, + consumerId: consumer.id, + descriptors: [getMockDescriptorPublished()], + }; + const agreement: Agreement = { + ...getMockAgreement(esevice.id), + state: agreementState.suspended, + descriptorId: esevice.descriptors[0].id, + producerId: producer.id, + consumerId: consumer.id, + suspendedAt: new Date(), + suspendedByConsumer: false, + suspendedByProducer: false, + suspendedByPlatform: false, + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: agreement.eserviceId, + delegateId: authData.organizationId, + state: delegationState.active, + }); + + await addOneTenant(consumer); + await addOneTenant(producer); + await addOneEService(esevice); + await addOneAgreement(agreement); + await addOneDelegation(delegation); + + const actualAgreement = await agreementService.activateAgreement( + agreement.id, + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); + + const expectedAgreement = { + ...agreement, + state: agreementState.active, + contract: actualAgreement.contract, + certifiedAttributes: actualAgreement.certifiedAttributes, + declaredAttributes: actualAgreement.declaredAttributes, + verifiedAttributes: actualAgreement.verifiedAttributes, + suspendedAt: undefined, + }; + + expect(actualAgreement).toEqual(expectedAgreement); + }); }); describe("Agreement Suspended", () => { @@ -1460,6 +1584,30 @@ describe("activate agreement", () => { ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); }); + it("should throw an operationNotAllowed error when the requester is the Producer but it is not the delegate", async () => { + const authData = getRandomAuthData(); + const agreement: Agreement = { + ...getMockAgreement(), + state: agreementState.pending, + producerId: authData.organizationId, + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: agreement.eserviceId, + state: delegationState.active, + }); + await addOneAgreement(agreement); + await addOneDelegation(delegation); + await expect( + agreementService.activateAgreement(agreement.id, { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); + }); + it.each( Object.values(agreementState).filter( (state) => !agreementActivableStates.includes(state) diff --git a/packages/agreement-process/test/agreementConsumerDocuments.test.ts b/packages/agreement-process/test/agreementConsumerDocuments.test.ts index 55591e916a..c5c113a077 100644 --- a/packages/agreement-process/test/agreementConsumerDocuments.test.ts +++ b/packages/agreement-process/test/agreementConsumerDocuments.test.ts @@ -4,6 +4,8 @@ import { fileManagerDeleteError, genericLogger } from "pagopa-interop-commons"; import { decodeProtobufPayload, getMockAgreement, + getMockDelegation, + getMockEService, getRandomAuthData, randomArrayItem, } from "pagopa-interop-commons-test/index.js"; @@ -17,6 +19,8 @@ import { EServiceId, TenantId, agreementState, + delegationKind, + delegationState, generateId, toAgreementV2, } from "pagopa-interop-models"; @@ -32,6 +36,8 @@ import { import { config } from "../src/config/config.js"; import { addOneAgreement, + addOneDelegation, + addOneEService, agreementService, fileManager, getMockConsumerDocument, @@ -73,6 +79,38 @@ describe("agreement consumer document", () => { expect(result).toEqual(agreement1.consumerDocuments[0]); }); + it("should succed when the requester is the delegate", async () => { + const eservice = getMockEService(); + const agreement = { + ...getMockAgreement(eservice.id), + consumerDocuments: [generateMock(AgreementDocument)], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: eservice.producerId, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneAgreement(agreement); + await addOneDelegation(delegation); + + const authData = getRandomAuthData(eservice.producerId); + const result = await agreementService.getAgreementConsumerDocument( + agreement.id, + agreement.consumerDocuments[0].id, + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); + + expect(result).toEqual(agreement.consumerDocuments[0]); + }); + it("should throw an agreementNotFound error when the agreement does not exist", async () => { const agreementId = generateId(); const authData = getRandomAuthData(agreement1.consumerId); diff --git a/packages/agreement-process/test/archiveAgreement.test.ts b/packages/agreement-process/test/archiveAgreement.test.ts index b62a85f4cb..faa70aa28b 100644 --- a/packages/agreement-process/test/archiveAgreement.test.ts +++ b/packages/agreement-process/test/archiveAgreement.test.ts @@ -99,7 +99,7 @@ describe("archive agreement", () => { toAgreementV2(expectedAgreemenentArchived) ); - expect(actualAgreement).toMatchObject(toAgreementV2(returnedAgreement)); + expect(actualAgreement).toEqual(toAgreementV2(returnedAgreement)); vi.useRealTimers(); }); diff --git a/packages/agreement-process/test/createAgreement.test.ts b/packages/agreement-process/test/createAgreement.test.ts index 05d8d75151..49f100d1ba 100644 --- a/packages/agreement-process/test/createAgreement.test.ts +++ b/packages/agreement-process/test/createAgreement.test.ts @@ -417,7 +417,9 @@ describe("create agreement", () => { const authData = getRandomAuthData(); const eserviceId = generateId(); const notDraftDescriptorStates = Object.values(descriptorState).filter( - (state) => state !== descriptorState.draft + (state) => + state !== descriptorState.draft && + state !== descriptorState.waitingForApproval ); const descriptor0: Descriptor = { @@ -469,7 +471,8 @@ describe("create agreement", () => { Object.values(descriptorState).filter( (state) => state !== descriptorState.published && - state !== descriptorState.draft + state !== descriptorState.draft && + state !== descriptorState.waitingForApproval ) ), }; diff --git a/packages/agreement-process/test/getAgreementEservices.test.ts b/packages/agreement-process/test/getAgreementEservices.test.ts index 9ba4e0bc9f..5208f1af06 100644 --- a/packages/agreement-process/test/getAgreementEservices.test.ts +++ b/packages/agreement-process/test/getAgreementEservices.test.ts @@ -25,6 +25,7 @@ describe("get agreement eservices", () => { let eservice1: EService; let eservice2: EService; let eservice3: EService; + let eservice4: EService; let tenant1: Tenant; let tenant2: Tenant; @@ -52,6 +53,10 @@ describe("get agreement eservices", () => { ...getMockEService(generateId(), tenant3.id), name: "EService 3 FooBar", }; + eservice4 = { + ...getMockEService(generateId(), tenant3.id), + name: "EService 4 FooBar", + }; await addOneTenant(tenant1); await addOneTenant(tenant2); @@ -59,6 +64,7 @@ describe("get agreement eservices", () => { await addOneEService(eservice1); await addOneEService(eservice2); await addOneEService(eservice3); + await addOneEService(eservice4); const agreement1 = { ...getMockAgreement(eservice1.id), @@ -79,10 +85,17 @@ describe("get agreement eservices", () => { consumerId: tenant1.id, state: agreementState.pending, }; + const agreement4 = { + ...getMockAgreement(eservice4.id), + producerId: eservice4.producerId, + consumerId: tenant3.id, + state: agreementState.draft, + }; await addOneAgreement(agreement1); await addOneAgreement(agreement2); await addOneAgreement(agreement3); + await addOneAgreement(agreement4); }); it("should get all agreement eservices", async () => { @@ -99,9 +112,9 @@ describe("get agreement eservices", () => { ); expect(eservices).toEqual({ - totalCount: 3, + totalCount: 4, results: expect.arrayContaining( - [eservice1, eservice2, eservice3].map(toCompactEService) + [eservice1, eservice2, eservice3, eservice4].map(toCompactEService) ), }); }); @@ -120,9 +133,9 @@ describe("get agreement eservices", () => { ); expect(eservices).toEqual({ - totalCount: 2, + totalCount: 3, results: expect.arrayContaining( - [eservice1, eservice3].map(toCompactEService) + [eservice1, eservice3, eservice4].map(toCompactEService) ), }); }); @@ -141,9 +154,9 @@ describe("get agreement eservices", () => { ); expect(eservices).toEqual({ - totalCount: 2, + totalCount: 3, results: expect.arrayContaining( - [eservice1, eservice2].map(toCompactEService) + [eservice1, eservice2, eservice4].map(toCompactEService) ), }); }); @@ -223,7 +236,7 @@ describe("get agreement eservices", () => { ); expect(eservices).toEqual({ - totalCount: 1, + totalCount: 2, results: expect.arrayContaining([eservice3].map(toCompactEService)), }); }); @@ -261,7 +274,7 @@ describe("get agreement eservices", () => { ); expect(eservices).toEqual({ - totalCount: 3, + totalCount: 4, results: expect.arrayContaining( [eservice1, eservice2].map(toCompactEService) ), @@ -282,7 +295,7 @@ describe("get agreement eservices", () => { ); expect(eservices).toEqual({ - totalCount: 3, + totalCount: 4, results: expect.arrayContaining( [eservice2, eservice3].map(toCompactEService) ), @@ -307,4 +320,24 @@ describe("get agreement eservices", () => { results: [], }); }); + + it("should get agreement eservice for a delegated eservice with filters: producerId", async () => { + const agreements1 = await agreementService.getAgreementEServices( + { + producerIds: [eservice4.producerId], + eserviceName: undefined, + consumerIds: [], + agreeementStates: [], + }, + 10, + 0, + genericLogger + ); + expect(agreements1).toEqual({ + totalCount: 2, + results: expect.arrayContaining( + [eservice3, eservice4].map(toCompactEService) + ), + }); + }); }); diff --git a/packages/agreement-process/test/getAgreements.test.ts b/packages/agreement-process/test/getAgreements.test.ts index dd30dac922..ad65d13b21 100644 --- a/packages/agreement-process/test/getAgreements.test.ts +++ b/packages/agreement-process/test/getAgreements.test.ts @@ -4,6 +4,7 @@ import { getMockDescriptorPublished, getMockEService, getMockAgreement, + getMockDelegation, } from "pagopa-interop-commons-test"; import { genericLogger } from "pagopa-interop-commons"; import { @@ -17,6 +18,7 @@ import { EServiceId, agreementState, TenantId, + delegationKind, } from "pagopa-interop-models"; import { describe, beforeEach, it, expect } from "vitest"; import { @@ -24,6 +26,7 @@ import { addOneEService, addOneAgreement, agreementService, + addOneDelegation, } from "./utils.js"; describe("get agreements", () => { @@ -38,6 +41,7 @@ describe("get agreements", () => { let eservice1: EService; let eservice2: EService; let eservice3: EService; + let eservice4: EService; let attribute1: AgreementAttribute; let attribute2: AgreementAttribute; let attribute3: AgreementAttribute; @@ -48,6 +52,7 @@ describe("get agreements", () => { let agreement4: Agreement; let agreement5: Agreement; let agreement6: Agreement; + let agreement7: Agreement; beforeEach(async () => { tenant1 = getMockTenant(); @@ -92,6 +97,10 @@ describe("get agreements", () => { ...getMockEService(generateId(), tenant3.id, [descriptor5]), name: "EService3", // Adding name because results are sorted by esevice name }; + eservice4 = { + ...getMockEService(generateId(), tenant3.id, [descriptor5]), + name: "EService4", // Adding name because results are sorted by esevice name + }; await addOneTenant(tenant1); await addOneTenant(tenant2); @@ -99,6 +108,7 @@ describe("get agreements", () => { await addOneEService(eservice1); await addOneEService(eservice2); await addOneEService(eservice3); + await addOneEService(eservice4); attribute1 = { id: generateId() }; attribute2 = { id: generateId() }; @@ -149,12 +159,27 @@ describe("get agreements", () => { producerId: eservice3.producerId, }; + agreement7 = { + ...getMockAgreement(eservice4.id, tenant1.id, agreementState.draft), + descriptorId: eservice4.descriptors[0].id, + producerId: eservice4.producerId, + }; + await addOneAgreement(agreement1); await addOneAgreement(agreement2); await addOneAgreement(agreement3); await addOneAgreement(agreement4); await addOneAgreement(agreement5); await addOneAgreement(agreement6); + await addOneAgreement(agreement7); + + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: eservice4.producerId, + eserviceId: eservice4.id, + state: agreementState.active, + }); + await addOneDelegation(delegation1); }); it("should get all agreements if no filters are provided", async () => { @@ -165,7 +190,7 @@ describe("get agreements", () => { genericLogger ); expect(allAgreements).toEqual({ - totalCount: 6, + totalCount: 7, results: expect.arrayContaining([ agreement1, agreement2, @@ -173,6 +198,7 @@ describe("get agreements", () => { agreement4, agreement5, agreement6, + agreement7, ]), }); }); @@ -221,8 +247,13 @@ describe("get agreements", () => { genericLogger ); expect(agreements1).toEqual({ - totalCount: 3, - results: expect.arrayContaining([agreement1, agreement3, agreement5]), + totalCount: 4, + results: expect.arrayContaining([ + agreement1, + agreement3, + agreement5, + agreement7, + ]), }); const agreements2 = await agreementService.getAgreements( @@ -234,13 +265,14 @@ describe("get agreements", () => { genericLogger ); expect(agreements2).toEqual({ - totalCount: 5, + totalCount: 6, results: expect.arrayContaining([ agreement1, agreement2, agreement3, agreement4, agreement5, + agreement7, ]), }); }); @@ -301,12 +333,13 @@ describe("get agreements", () => { genericLogger ); expect(agreements2).toEqual({ - totalCount: 4, + totalCount: 5, results: expect.arrayContaining([ agreement1, agreement3, agreement5, agreement6, + agreement7, ]), }); }); @@ -496,6 +529,7 @@ describe("get agreements", () => { results: expect.arrayContaining([agreement2, agreement3]), }); }); + it("should get no agreements in case no filters match", async () => { const agreements = await agreementService.getAgreements( { @@ -511,4 +545,19 @@ describe("get agreements", () => { results: [], }); }); + + it("should get agreements for a delegated eservice with filters: producerId", async () => { + const agreements = await agreementService.getAgreements( + { + producerId: eservice4.producerId, + }, + 10, + 0, + genericLogger + ); + expect(agreements).toEqual({ + totalCount: 3, + results: expect.arrayContaining([agreement5, agreement6, agreement7]), + }); + }); }); diff --git a/packages/agreement-process/test/rejectAgreement.test.ts b/packages/agreement-process/test/rejectAgreement.test.ts index 7e09c0ede2..c9ac9da123 100644 --- a/packages/agreement-process/test/rejectAgreement.test.ts +++ b/packages/agreement-process/test/rejectAgreement.test.ts @@ -6,6 +6,7 @@ import { getMockAgreement, getMockCertifiedTenantAttribute, getMockDeclaredTenantAttribute, + getMockDelegation, getMockDescriptorPublished, getMockEService, getMockEServiceAttribute, @@ -26,6 +27,8 @@ import { TenantId, VerifiedTenantAttribute, agreementState, + delegationKind, + delegationState, generateId, toAgreementV2, } from "pagopa-interop-models"; @@ -41,6 +44,7 @@ import { } from "../src/model/domain/errors.js"; import { addOneAgreement, + addOneDelegation, addOneEService, addOneTenant, agreementService, @@ -48,181 +52,211 @@ import { } from "./utils.js"; describe("reject agreement", () => { - it("should succeed when requester is Producer and the Agreement is in a rejectable state", async () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date()); - - const producerId = generateId(); - const tenantCertifiedAttribute: CertifiedTenantAttribute = { - ...getMockCertifiedTenantAttribute(), - revocationTimestamp: undefined, - }; - const revokedTenantCertifiedAttribute: CertifiedTenantAttribute = { - ...getMockCertifiedTenantAttribute(), - revocationTimestamp: new Date(), - }; - - const tenantDeclaredAttribute: DeclaredTenantAttribute = { - ...getMockDeclaredTenantAttribute(), - revocationTimestamp: undefined, - }; - const revokedTenantDeclaredAttribute: DeclaredTenantAttribute = { - ...getMockDeclaredTenantAttribute(), - revocationTimestamp: new Date(), - }; + it.each([ + { + desc: "Producer", + type: "producer", + }, + { + desc: "Delegate of an active delegation", + type: "delegate", + }, + ])( + "should succeed when requester is $desc and the Agreement is in a rejectable state", + async ({ type }) => { + vi.useFakeTimers(); + vi.setSystemTime(new Date()); - const tenantVerifiedAttribute: VerifiedTenantAttribute = { - ...getMockVerifiedTenantAttribute(), - verifiedBy: [ - { - id: producerId, - verificationDate: new Date(), - extensionDate: new Date(new Date().getTime() + 3600 * 1000), - }, - ], - }; + const producerId = generateId(); + const tenantCertifiedAttribute: CertifiedTenantAttribute = { + ...getMockCertifiedTenantAttribute(), + revocationTimestamp: undefined, + }; + const revokedTenantCertifiedAttribute: CertifiedTenantAttribute = { + ...getMockCertifiedTenantAttribute(), + revocationTimestamp: new Date(), + }; - const tenantVerifiedAttributeByAnotherProducer: VerifiedTenantAttribute = { - ...getMockVerifiedTenantAttribute(), - verifiedBy: [ - { id: generateId(), verificationDate: new Date() }, - ], - }; + const tenantDeclaredAttribute: DeclaredTenantAttribute = { + ...getMockDeclaredTenantAttribute(), + revocationTimestamp: undefined, + }; + const revokedTenantDeclaredAttribute: DeclaredTenantAttribute = { + ...getMockDeclaredTenantAttribute(), + revocationTimestamp: new Date(), + }; - const tenantVerfiedAttributeWithExpiredExtension: VerifiedTenantAttribute = - { + const tenantVerifiedAttribute: VerifiedTenantAttribute = { ...getMockVerifiedTenantAttribute(), verifiedBy: [ { id: producerId, verificationDate: new Date(), - extensionDate: new Date(), + extensionDate: new Date(new Date().getTime() + 3600 * 1000), }, ], }; - const consumer: Tenant = { - ...getMockTenant(), - attributes: [ - tenantCertifiedAttribute, - revokedTenantCertifiedAttribute, - tenantDeclaredAttribute, - revokedTenantDeclaredAttribute, - tenantVerifiedAttribute, - tenantVerifiedAttributeByAnotherProducer, - tenantVerfiedAttributeWithExpiredExtension, - // Adding some attributes not matching with descriptor attributes - // to test that they are not kept in the agreement - getMockVerifiedTenantAttribute(), - getMockCertifiedTenantAttribute(), - getMockDeclaredTenantAttribute(), - ], - }; - const descriptor: Descriptor = { - ...getMockDescriptorPublished(), - attributes: { - // I add also some attributes not matching with tenant attributes - // to test that they are not kept in the agreement - certified: [ - [ - getMockEServiceAttribute(tenantCertifiedAttribute.id), - getMockEServiceAttribute(revokedTenantCertifiedAttribute.id), - getMockEServiceAttribute(), + const tenantVerifiedAttributeByAnotherProducer: VerifiedTenantAttribute = + { + ...getMockVerifiedTenantAttribute(), + verifiedBy: [ + { id: generateId(), verificationDate: new Date() }, ], - ], - verified: [ - [ - getMockEServiceAttribute(tenantVerifiedAttribute.id), - getMockEServiceAttribute( - tenantVerifiedAttributeByAnotherProducer.id - ), - getMockEServiceAttribute( - tenantVerfiedAttributeWithExpiredExtension.id - ), - getMockEServiceAttribute(), + }; + + const tenantVerfiedAttributeWithExpiredExtension: VerifiedTenantAttribute = + { + ...getMockVerifiedTenantAttribute(), + verifiedBy: [ + { + id: producerId, + verificationDate: new Date(), + extensionDate: new Date(), + }, ], + }; + + const consumer: Tenant = { + ...getMockTenant(), + attributes: [ + tenantCertifiedAttribute, + revokedTenantCertifiedAttribute, + tenantDeclaredAttribute, + revokedTenantDeclaredAttribute, + tenantVerifiedAttribute, + tenantVerifiedAttributeByAnotherProducer, + tenantVerfiedAttributeWithExpiredExtension, + // Adding some attributes not matching with descriptor attributes + // to test that they are not kept in the agreement + getMockVerifiedTenantAttribute(), + getMockCertifiedTenantAttribute(), + getMockDeclaredTenantAttribute(), ], - declared: [ - [ - getMockEServiceAttribute(tenantDeclaredAttribute.id), - getMockEServiceAttribute(revokedTenantDeclaredAttribute.id), - getMockEServiceAttribute(), + }; + const descriptor: Descriptor = { + ...getMockDescriptorPublished(), + attributes: { + // I add also some attributes not matching with tenant attributes + // to test that they are not kept in the agreement + certified: [ + [ + getMockEServiceAttribute(tenantCertifiedAttribute.id), + getMockEServiceAttribute(revokedTenantCertifiedAttribute.id), + getMockEServiceAttribute(), + ], ], - ], - }, - }; - const eservice: EService = { - ...getMockEService(), - producerId, - descriptors: [descriptor], - }; + verified: [ + [ + getMockEServiceAttribute(tenantVerifiedAttribute.id), + getMockEServiceAttribute( + tenantVerifiedAttributeByAnotherProducer.id + ), + getMockEServiceAttribute( + tenantVerfiedAttributeWithExpiredExtension.id + ), + getMockEServiceAttribute(), + ], + ], + declared: [ + [ + getMockEServiceAttribute(tenantDeclaredAttribute.id), + getMockEServiceAttribute(revokedTenantDeclaredAttribute.id), + getMockEServiceAttribute(), + ], + ], + }, + }; + const eservice: EService = { + ...getMockEService(), + producerId, + descriptors: [descriptor], + }; - const agreement = { - ...getMockAgreement(), - eserviceId: eservice.id, - producerId: eservice.producerId, - descriptorId: descriptor.id, - consumerId: consumer.id, - state: randomArrayItem(agreementRejectableStates), - }; - await addOneTenant(consumer); - await addOneEService(eservice); - await addOneAgreement(agreement); + const agreement = { + ...getMockAgreement(), + eserviceId: eservice.id, + producerId: eservice.producerId, + descriptorId: descriptor.id, + consumerId: consumer.id, + state: randomArrayItem(agreementRejectableStates), + }; + await addOneTenant(consumer); + await addOneEService(eservice); + await addOneAgreement(agreement); - const authData = getRandomAuthData(agreement.producerId); - const returnedAgreement = await agreementService.rejectAgreement( - agreement.id, - "Rejected by producer due to test reasons", - { - authData, - serviceName: "", - correlationId: generateId(), - logger: genericLogger, + const authData = + type === "producer" + ? getRandomAuthData(agreement.producerId) + : getRandomAuthData(); + + if (type === "delegate") { + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: authData.organizationId, + eserviceId: eservice.id, + state: delegationState.active, + }); + await addOneDelegation(delegation); } - ); - const agreementEvent = await readLastAgreementEvent(agreement.id); + const returnedAgreement = await agreementService.rejectAgreement( + agreement.id, + "Rejected by producer due to test reasons", + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); + + const agreementEvent = await readLastAgreementEvent(agreement.id); - expect(agreementEvent).toMatchObject({ - type: "AgreementRejected", - event_version: 2, - version: "1", - stream_id: agreement.id, - }); + expect(agreementEvent).toMatchObject({ + type: "AgreementRejected", + event_version: 2, + version: "1", + stream_id: agreement.id, + }); - const actualAgreementRejected = decodeProtobufPayload({ - messageType: AgreementRejectedV2, - payload: agreementEvent.data, - }).agreement; + const actualAgreementRejected = decodeProtobufPayload({ + messageType: AgreementRejectedV2, + payload: agreementEvent.data, + }).agreement; - /* We must delete some properties because the rejection + /* We must delete some properties because the rejection sets them to undefined thus and the protobuf serialization strips them from the payload */ - delete agreement.suspendedByConsumer; - delete agreement.suspendedByProducer; - delete agreement.suspendedByPlatform; - const expectedAgreemenentRejected: Agreement = { - ...agreement, - state: agreementState.rejected, - rejectionReason: "Rejected by producer due to test reasons", - // Keeps only not revoked attributes that are matching in descriptor and tenant - verifiedAttributes: [{ id: tenantVerifiedAttribute.id }], - declaredAttributes: [{ id: tenantDeclaredAttribute.id }], - certifiedAttributes: [{ id: tenantCertifiedAttribute.id }], - stamps: { - ...agreement.stamps, - rejection: { - who: authData.userId, - when: new Date(), + delete agreement.suspendedByConsumer; + delete agreement.suspendedByProducer; + delete agreement.suspendedByPlatform; + const expectedAgreemenentRejected: Agreement = { + ...agreement, + state: agreementState.rejected, + rejectionReason: "Rejected by producer due to test reasons", + // Keeps only not revoked attributes that are matching in descriptor and tenant + verifiedAttributes: [{ id: tenantVerifiedAttribute.id }], + declaredAttributes: [{ id: tenantDeclaredAttribute.id }], + certifiedAttributes: [{ id: tenantCertifiedAttribute.id }], + stamps: { + ...agreement.stamps, + rejection: { + who: authData.userId, + when: new Date(), + ...(type === "delegate" + ? { delegateId: authData.organizationId } + : {}), + }, }, - }, - }; - expect(actualAgreementRejected).toMatchObject( - toAgreementV2(expectedAgreemenentRejected) - ); - expect(actualAgreementRejected).toEqual(toAgreementV2(returnedAgreement)); - vi.useRealTimers(); - }); + }; + expect(actualAgreementRejected).toMatchObject( + toAgreementV2(expectedAgreemenentRejected) + ); + expect(actualAgreementRejected).toEqual(toAgreementV2(returnedAgreement)); + vi.useRealTimers(); + } + ); it("should throw an agreementNotFound error when the agreement does not exist", async () => { await addOneAgreement(getMockAgreement()); @@ -378,4 +412,89 @@ describe("reject agreement", () => { descriptorNotFound(eservice.id, agreement.descriptorId) ); }); + + it("should throw operationNotAllowed when the requester is the producer and there is an active delegation", async () => { + const eservice: EService = { + ...getMockEService(), + descriptors: [getMockDescriptorPublished()], + }; + const consumer = getMockTenant(); + const delegate = getMockTenant(); + const agreement = { + ...getMockAgreement(), + state: randomArrayItem(agreementRejectableStates), + eserviceId: eservice.id, + producerId: eservice.producerId, + consumerId: consumer.id, + descriptorId: eservice.descriptors[0].id, + }; + const authData = getRandomAuthData(agreement.producerId); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: delegate.id, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneAgreement(agreement); + await addOneEService(eservice); + await addOneTenant(consumer); + await addOneTenant(delegate); + await addOneDelegation(delegation); + + await expect( + agreementService.rejectAgreement( + agreement.id, + "Rejected by producer due to test reasons", + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); + }); + + it("should throw a operationNotAllowed error when the requester is the delegate but the delegation in not active", async () => { + const eservice: EService = { + ...getMockEService(), + descriptors: [getMockDescriptorPublished()], + }; + const consumer = getMockTenant(); + const agreement = { + ...getMockAgreement(), + state: randomArrayItem(agreementRejectableStates), + eserviceId: eservice.id, + producerId: eservice.producerId, + consumerId: consumer.id, + descriptorId: eservice.descriptors[0].id, + }; + const authData = getRandomAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: authData.organizationId, + eserviceId: eservice.id, + state: delegationState.waitingForApproval, + }); + + await addOneAgreement(agreement); + await addOneEService(eservice); + await addOneTenant(consumer); + await addOneDelegation(delegation); + + await expect( + agreementService.rejectAgreement( + agreement.id, + "Rejected by producer due to test reasons", + + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); + }); }); diff --git a/packages/agreement-process/test/submitAgreement.test.ts b/packages/agreement-process/test/submitAgreement.test.ts index d62635ff8b..1bbd0eff98 100644 --- a/packages/agreement-process/test/submitAgreement.test.ts +++ b/packages/agreement-process/test/submitAgreement.test.ts @@ -462,6 +462,55 @@ describe("submit agreement", () => { ).rejects.toThrowError(notLatestEServiceDescriptor(agreement.descriptorId)); }); + it("should throw a notLatestEServiceDescriptor error when eservice has only a waiting for approval descriptor (no active descriptors)", async () => { + const producer = getMockTenant(); + const consumer = { + ...getMockTenant(), + mails: [ + { + id: generateId(), + kind: tenantMailKind.ContactEmail, + address: "avalidemailaddressfortenant@testingagreement.com", + createdAt: new Date(), + }, + ], + }; + + const descriptor = { + ...getMockDescriptor(), + state: descriptorState.waitingForApproval, + }; + const eservice = getMockEService(generateId(), producer.id, [ + descriptor, + ]); + + const agreement = { + ...getMockAgreement(eservice.id, consumer.id), + producerId: producer.id, + descriptorId: eservice.descriptors[0].id, + }; + + await addOneEService(eservice); + await addOneTenant(consumer); + await addOneTenant(producer); + await addOneAgreement(agreement); + + const authData = getRandomAuthData(consumer.id); + + await expect( + agreementService.submitAgreement( + agreement.id, + { consumerNotes: "This is a test" }, + { + authData, + correlationId: generateId(), + serviceName: "AgreementServiceTest", + logger: genericLogger, + } + ) + ).rejects.toThrowError(notLatestEServiceDescriptor(agreement.descriptorId)); + }); + it("should throw a notLatestEServiceDescriptor error when the agreement descriptor does not exist in the eservice descriptors", async () => { const producer = getMockTenant(); const consumer = { @@ -531,7 +580,9 @@ describe("submit agreement", () => { id: descriptorId, state: randomArrayItem( Object.values(descriptorState).filter( - (state: DescriptorState) => state !== descriptorState.draft + (state: DescriptorState) => + state !== descriptorState.draft && + state !== descriptorState.waitingForApproval ) ), version: "1", @@ -541,7 +592,9 @@ describe("submit agreement", () => { ...getMockDescriptor(), state: randomArrayItem( Object.values(descriptorState).filter( - (state: DescriptorState) => state !== descriptorState.draft + (state: DescriptorState) => + state !== descriptorState.draft && + state !== descriptorState.waitingForApproval ) ), version: "2", @@ -601,7 +654,9 @@ describe("submit agreement", () => { state: randomArrayItem( Object.values(descriptorState).filter( (state: DescriptorState) => - !allowedStatus.includes(state) && state !== descriptorState.draft + !allowedStatus.includes(state) && + state !== descriptorState.draft && + state !== descriptorState.waitingForApproval ) ), }; diff --git a/packages/agreement-process/test/suspendAgreement.test.ts b/packages/agreement-process/test/suspendAgreement.test.ts index b946783481..4516dedbff 100644 --- a/packages/agreement-process/test/suspendAgreement.test.ts +++ b/packages/agreement-process/test/suspendAgreement.test.ts @@ -10,6 +10,7 @@ import { getMockAgreementAttribute, getMockCertifiedTenantAttribute, getMockDeclaredTenantAttribute, + getMockDelegation, getMockDescriptorPublished, getMockEService, getMockEServiceAttribute, @@ -30,6 +31,8 @@ import { Tenant, TenantId, agreementState, + delegationKind, + delegationState, generateId, toAgreementV2, } from "pagopa-interop-models"; @@ -46,6 +49,7 @@ import { import { createStamp } from "../src/services/agreementStampUtils.js"; import { addOneAgreement, + addOneDelegation, addOneEService, addOneTenant, agreementService, @@ -181,12 +185,10 @@ describe("suspend agreement", () => { ...expectedStamps, }, }; - expect(actualAgreementSuspended).toMatchObject( + expect(actualAgreementSuspended).toEqual( toAgreementV2(expectedAgreementSuspended) ); - expect(actualAgreementSuspended).toMatchObject( - toAgreementV2(returnedAgreement) - ); + expect(actualAgreementSuspended).toEqual(toAgreementV2(returnedAgreement)); }); it("should succeed when requester is Consumer or Producer, Agreement producer and consumer are the same, and the Agreement is in an suspendable state", async () => { @@ -285,12 +287,10 @@ describe("suspend agreement", () => { }, }, }; - expect(actualAgreementSuspended).toMatchObject( + expect(actualAgreementSuspended).toEqual( toAgreementV2(expectedAgreementSuspended) ); - expect(actualAgreementSuspended).toMatchObject( - toAgreementV2(returnedAgreement) - ); + expect(actualAgreementSuspended).toEqual(toAgreementV2(returnedAgreement)); }); it("should preserve the suspension flags and the stamps that it does not update", async () => { @@ -393,14 +393,87 @@ describe("suspend agreement", () => { ...expectedStamps, }, }; - expect(actualAgreementSuspended).toMatchObject( + expect(actualAgreementSuspended).toEqual( toAgreementV2(expectedAgreementSuspended) ); - expect(actualAgreementSuspended).toMatchObject( - toAgreementV2(returnedAgreement) - ); + expect(actualAgreementSuspended).toEqual(toAgreementV2(returnedAgreement)); }); + it.each(agreementSuspendableStates)( + "should succeed if the requester is the delegate and the agreement is in state %s", + async (state) => { + const consumer: Tenant = { + ...getMockTenant(), + attributes: [ + getMockCertifiedTenantAttribute(), + getMockDeclaredTenantAttribute(), + getMockVerifiedTenantAttribute(), + ], + }; + + const descriptor = { + ...getMockDescriptorPublished(), + attributes: { + certified: [[getMockEServiceAttribute(consumer.attributes[0].id)]], + declared: [[getMockEServiceAttribute(consumer.attributes[1].id)]], + verified: [[getMockEServiceAttribute(consumer.attributes[2].id)]], + }, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + }; + const agreement = { + ...getMockAgreement(), + state, + eserviceId: eservice.id, + producerId: eservice.producerId, + consumerId: consumer.id, + descriptorId: descriptor.id, + suspendedByConsumer: false, + suspendedByProducer: false, + suspendedByPlatform: false, + }; + const authData = getRandomAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: authData.organizationId, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneAgreement(agreement); + await addOneEService(eservice); + await addOneTenant(consumer); + await addOneDelegation(delegation); + + const expectedAgreement = { + ...agreement, + state: agreementState.suspended, + suspendedByProducer: true, + stamps: { + ...agreement.stamps, + suspensionByProducer: { + delegateId: authData.organizationId, + who: authData.userId, + when: new Date(), + }, + }, + }; + + const actualAgreement = await agreementService.suspendAgreement( + agreement.id, + { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); + expect(actualAgreement).toEqual(expectedAgreement); + } + ); + it("should throw an agreementNotFound error when the agreement does not exist", async () => { await addOneAgreement(getMockAgreement()); const authData = getRandomAuthData(); @@ -530,4 +603,80 @@ describe("suspend agreement", () => { descriptorNotFound(eservice.id, agreement.descriptorId) ); }); + + it("should throw a operationNotAllowed error when the requester is the producer but not the delegate", async () => { + const eservice: EService = { + ...getMockEService(), + descriptors: [getMockDescriptorPublished()], + }; + const consumer = getMockTenant(); + const delegate = getMockTenant(); + const agreement = { + ...getMockAgreement(), + state: randomArrayItem(agreementSuspendableStates), + eserviceId: eservice.id, + producerId: eservice.producerId, + consumerId: consumer.id, + descriptorId: eservice.descriptors[0].id, + }; + const authData = getRandomAuthData(agreement.producerId); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: delegate.id, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneAgreement(agreement); + await addOneEService(eservice); + await addOneTenant(consumer); + await addOneTenant(delegate); + await addOneDelegation(delegation); + + await expect( + agreementService.suspendAgreement(agreement.id, { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); + }); + + it("should throw a operationNotAllowed error when the requester is the delegate but the delegation in not active", async () => { + const eservice: EService = { + ...getMockEService(), + descriptors: [getMockDescriptorPublished()], + }; + const consumer = getMockTenant(); + const agreement = { + ...getMockAgreement(), + state: randomArrayItem(agreementSuspendableStates), + eserviceId: eservice.id, + producerId: eservice.producerId, + consumerId: consumer.id, + descriptorId: eservice.descriptors[0].id, + }; + const authData = getRandomAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: authData.organizationId, + eserviceId: eservice.id, + state: delegationState.waitingForApproval, + }); + + await addOneAgreement(agreement); + await addOneEService(eservice); + await addOneTenant(consumer); + await addOneDelegation(delegation); + + await expect( + agreementService.suspendAgreement(agreement.id, { + authData, + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError(operationNotAllowed(authData.organizationId)); + }); }); diff --git a/packages/agreement-process/test/utils.ts b/packages/agreement-process/test/utils.ts index 8c072afb69..6689ff93e8 100644 --- a/packages/agreement-process/test/utils.ts +++ b/packages/agreement-process/test/utils.ts @@ -27,6 +27,7 @@ import { Attribute, toReadModelAttribute, TenantId, + Delegation, } from "pagopa-interop-models"; import { agreementApi, @@ -68,6 +69,7 @@ export const agreements = readModelRepository.agreements; export const eservices = readModelRepository.eservices; export const tenants = readModelRepository.tenants; export const attributes = readModelRepository.attributes; +export const delegations = readModelRepository.delegations; export const readModelService = readModelServiceBuilder(readModelRepository); @@ -116,6 +118,13 @@ export const addOneTenant = async (tenant: Tenant): Promise => { export const addOneAttribute = async (attribute: Attribute): Promise => { await writeInReadmodel(toReadModelAttribute(attribute), attributes); }; + +export const addOneDelegation = async ( + delegation: Delegation +): Promise => { + await writeInReadmodel(delegation, delegations); +}; + export const readLastAgreementEvent = async ( agreementId: AgreementId ): Promise> => diff --git a/packages/api-clients/open-api/apiGatewayApi.yml b/packages/api-clients/open-api/apiGatewayApi.yml index 299022dad9..f81280800b 100644 --- a/packages/api-clients/open-api/apiGatewayApi.yml +++ b/packages/api-clients/open-api/apiGatewayApi.yml @@ -2687,6 +2687,7 @@ components: - DEPRECATED - SUSPENDED - ARCHIVED + - WAITING_FOR_APPROVAL EServiceDescriptors: description: eservice descriptors list model type: object diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index 0cd2c52c31..66c5692def 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -3885,6 +3885,217 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + /eservices/{eServiceId}/descriptors/{descriptorId}/approve: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + security: + - bearerAuth: [] + tags: + - eservices + summary: approve a delegated new e-service version + operationId: approveDelegatedEServiceDescriptor + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + - name: descriptorId + in: path + description: the descriptor id + required: true + schema: + type: string + format: uuid + responses: + "204": + description: New delegated e-service version approved + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/CreatedResource" + "403": + description: Forbidden + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: EService or Descriptor not found + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "400": + description: Bad request + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + /eservices/{eServiceId}/descriptors/{descriptorId}/reject: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + security: + - bearerAuth: [] + tags: + - eservices + summary: reject a delegated new e-service version + operationId: rejectDelegatedEServiceDescriptor + requestBody: + description: Payload containing the reason of the rejection + content: + application/json: + schema: + $ref: "#/components/schemas/RejectDelegatedEServiceDescriptorSeed" + required: true + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + - name: descriptorId + in: path + description: the descriptor id + required: true + schema: + type: string + format: uuid + responses: + "204": + description: New delegated e-service version rejected + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/CreatedResource" + "403": + description: Forbidden + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: EService or Descriptor not found + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "400": + description: Bad request + headers: + "X-Rate-Limit-Limit": + schema: + type: integer + description: Max allowed requests within time interval + "X-Rate-Limit-Remaining": + schema: + type: integer + description: Remaining requests within time interval + "X-Rate-Limit-Interval": + schema: + type: integer + description: Time interval in milliseconds. Allowed requests will be constantly replenished during the interval. At the end of the interval the max allowed requests will be available + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /export/eservices/{eserviceId}/descriptors/{descriptorId}: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -4194,6 +4405,11 @@ paths: format: uuid default: [] explode: false + - in: query + name: delegated + description: if true only delegated e-services will be returned, if false only non-delegated e-services will be returned, if not present all e-services will be returned + schema: + type: boolean - in: query name: offset required: true @@ -8251,6 +8467,15 @@ paths: name: name schema: type: string + - in: query + name: features + description: comma separated feature types to filter the teanants with + schema: + type: array + items: + $ref: "#/components/schemas/TenantFeatureType" + default: [] + explode: false - in: query name: limit required: true @@ -13064,6 +13289,14 @@ components: type: string required: - description + RejectDelegatedEServiceDescriptorSeed: + type: object + additionalProperties: false + properties: + rejectionReason: + type: string + required: + - rejectionReason CatalogEServiceDescriptor: type: object additionalProperties: false @@ -14507,6 +14740,7 @@ components: - DEPRECATED - SUSPENDED - ARCHIVED + - WAITING_FOR_APPROVAL EServiceTechnology: type: string description: EService Descriptor State @@ -14858,6 +15092,11 @@ components: required: - results - pagination + TenantFeatureType: + type: string + enum: + - PERSISTENT_CERTIFIER + - DELEGATED_PRODUCER TenantFeature: oneOf: - type: object diff --git a/packages/api-clients/open-api/catalogApi.yml b/packages/api-clients/open-api/catalogApi.yml index ddd8cbc3a0..0c52acbd08 100644 --- a/packages/api-clients/open-api/catalogApi.yml +++ b/packages/api-clients/open-api/catalogApi.yml @@ -95,6 +95,11 @@ paths: description: mode schema: $ref: "#/components/schemas/EServiceMode" + - in: query + name: delegated + description: if true only delegated e-services will be returned, if false only non-delegated e-services will be returned, if not present all e-services will be returned + schema: + type: boolean - in: query name: offset required: true @@ -1069,6 +1074,105 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + /eservices/:eServiceId/descriptors/:descriptorId/approve: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + security: + - bearerAuth: [] + tags: + - process + summary: approve a delegated new e-service version + operationId: approveDelegatedEServiceDescriptor + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + - name: descriptorId + in: path + description: the descriptor id + required: true + schema: + type: string + format: uuid + responses: + "204": + description: New delegated e-service version approved + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: EService not found + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "400": + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + /eservices/:eServiceId/descriptors/:descriptorId/reject: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + security: + - bearerAuth: [] + tags: + - process + summary: reject a delegated new e-service version + operationId: rejectDelegatedEServiceDescriptor + requestBody: + description: Payload containing the reason of the rejection + content: + application/json: + schema: + $ref: "#/components/schemas/RejectDelegatedEServiceDescriptorSeed" + required: true + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + - name: descriptorId + in: path + description: the descriptor id + required: true + schema: + type: string + format: uuid + responses: + "204": + description: New delegated e-service version rejected + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: EService not found + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "400": + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /status: get: security: [] @@ -1740,6 +1844,7 @@ components: - DEPRECATED - SUSPENDED - ARCHIVED + - WAITING_FOR_APPROVAL EServiceDocumentKind: type: string description: EService Document Kind @@ -1757,6 +1862,14 @@ components: - ARCHIVED - MISSING_CERTIFIED_ATTRIBUTES - REJECTED + RejectDelegatedEServiceDescriptorSeed: + type: object + additionalProperties: false + properties: + rejectionReason: + type: string + required: + - rejectionReason Problem: properties: type: diff --git a/packages/api-clients/open-api/tenantApi.yml b/packages/api-clients/open-api/tenantApi.yml index b82f6cf08c..fd6205c2cf 100644 --- a/packages/api-clients/open-api/tenantApi.yml +++ b/packages/api-clients/open-api/tenantApi.yml @@ -348,6 +348,15 @@ paths: name: name schema: type: string + - in: query + name: features + description: comma separated feature types to filter the teanants with + schema: + type: array + items: + $ref: "#/components/schemas/TenantFeatureType" + default: [] + explode: false - in: query name: offset required: true @@ -1181,6 +1190,11 @@ components: required: - results - totalCount + TenantFeatureType: + type: string + enum: + - PERSISTENT_CERTIFIER + - DELEGATED_PRODUCER TenantFeature: oneOf: - type: object diff --git a/packages/api-clients/template-bff.hbs b/packages/api-clients/template-bff.hbs index a194e18ce6..a58ba30936 100644 --- a/packages/api-clients/template-bff.hbs +++ b/packages/api-clients/template-bff.hbs @@ -50,6 +50,8 @@ export const {{@key}}Endpoints = makeApi([ schema: z.union([z.string().optional().transform(v => v ? v.split(",") : undefined ).pipe(z.array(PurposeVersionState).optional().default([])), z.array(PurposeVersionState).optional().default([])]) {{else if (and (eq type "Query") (eq schema "z.array(DelegationState).optional().default([])"))}} schema: z.union([z.string().optional().transform(v => v ? v.split(",") : undefined ).pipe(z.array(DelegationState).optional().default([])), z.array(DelegationState).optional().default([])]) + {{else if (and (eq type "Query") (eq schema "z.array(TenantFeatureType).optional().default([])"))}} + schema: z.union([z.string().optional().transform(v => v ? v.split(",") : undefined ).pipe(z.array(TenantFeatureType).optional().default([])), z.array(TenantFeatureType).optional().default([])]) {{else}} schema: {{{schema}}}, {{/if}} diff --git a/packages/api-gateway/.env b/packages/api-gateway/.env index 0154687f2f..d0459ca36e 100644 --- a/packages/api-gateway/.env +++ b/packages/api-gateway/.env @@ -20,6 +20,7 @@ TENANT_PROCESS_URL="http://localhost:3500" PURPOSE_PROCESS_URL="http://localhost:3400" ATTRIBUTE_REGISTRY_PROCESS_URL="http://localhost:3200" AUTHORIZATION_PROCESS_URL="http://localhost:3300" +DELEGATION_PROCESS_URL="http://localhost:3800" NOTIFIER_URL="http://localhost:9999" READMODEL_DB_HOST=localhost diff --git a/packages/api-gateway/src/clients/clientsProvider.ts b/packages/api-gateway/src/clients/clientsProvider.ts index 02f7bc1f14..83642aab7d 100644 --- a/packages/api-gateway/src/clients/clientsProvider.ts +++ b/packages/api-gateway/src/clients/clientsProvider.ts @@ -6,6 +6,7 @@ import { attributeRegistryApi, notifierApi, authorizationApi, + delegationApi, } from "pagopa-interop-api-clients"; import { config } from "../config/config.js"; @@ -38,6 +39,10 @@ export type AuthorizationProcessClient = { client: ReturnType; }; +export type DelegationProcessClient = ReturnType< + typeof delegationApi.createDelegationApiClient +>; + export type PagoPAInteropBeClients = { catalogProcessClient: CatalogProcessClient; agreementProcessClient: AgreementProcessClient; @@ -46,6 +51,7 @@ export type PagoPAInteropBeClients = { attributeProcessClient: AttributeProcessClient; notifierEventsClient: NotifierEventsClient; authorizationProcessClient: AuthorizationProcessClient; + delegationProcessClient: DelegationProcessClient; }; export function getInteropBeClients(): PagoPAInteropBeClients { @@ -72,5 +78,8 @@ export function getInteropBeClients(): PagoPAInteropBeClients { config.authorizationProcessUrl ), }, + delegationProcessClient: delegationApi.createDelegationApiClient( + config.delegationProcessUrl + ), }; } diff --git a/packages/api-gateway/src/config/config.ts b/packages/api-gateway/src/config/config.ts index 28884e4f04..c65d3e4c78 100644 --- a/packages/api-gateway/src/config/config.ts +++ b/packages/api-gateway/src/config/config.ts @@ -72,6 +72,17 @@ export type AuthorizationProcessServerConfig = z.infer< typeof AuthorizationProcessServerConfig >; +export const DelegationProcessServerConfig = z + .object({ + DELEGATION_PROCESS_URL: APIEndpoint, + }) + .transform((c) => ({ + delegationProcessUrl: c.DELEGATION_PROCESS_URL, + })); +export type DelegationProcessServerConfig = z.infer< + typeof DelegationProcessServerConfig +>; + export const NotifierServerConfig = z .object({ NOTIFIER_URL: APIEndpoint, @@ -97,6 +108,7 @@ const ApiGatewayConfig = CommonHTTPServiceConfig.and(RedisRateLimiterConfig) .and(TenantProcessServerConfig) .and(PurposeProcessServerConfig) .and(AuthorizationProcessServerConfig) + .and(DelegationProcessServerConfig) .and(AttributeRegistryProcessServerConfig) .and(NotifierServerConfig) .and(ReadModelDbConfig); diff --git a/packages/api-gateway/src/models/errors.ts b/packages/api-gateway/src/models/errors.ts index 70fd862e6e..920b641af7 100644 --- a/packages/api-gateway/src/models/errors.ts +++ b/packages/api-gateway/src/models/errors.ts @@ -28,6 +28,7 @@ export const errorCodes = { tenantAttributeNotFound: "0016", attributeByCodeNotFound: "0017", certifiedAttributeAlreadyAssigned: "0018", + multipleActiveDelegationForEservice: "0019", }; export type ErrorCodes = keyof typeof errorCodes; @@ -268,3 +269,13 @@ export function certifiedAttributeAlreadyAssigned( title: "Certified attribute already assigned", }); } + +export function multipleActiveDelegationsForEservice( + eserviceId: catalogApi.EService["id"] +): ApiError { + return new ApiError({ + detail: `Unexpected multiple Active delegation for EService ${eserviceId}`, + code: "multipleActiveDelegationForEservice", + title: "Multiple active delegation found", + }); +} diff --git a/packages/api-gateway/src/routers/apiGatewayRouter.ts b/packages/api-gateway/src/routers/apiGatewayRouter.ts index 0d2973e620..a49151afbb 100644 --- a/packages/api-gateway/src/routers/apiGatewayRouter.ts +++ b/packages/api-gateway/src/routers/apiGatewayRouter.ts @@ -49,6 +49,7 @@ const apiGatewayRouter = ( attributeProcessClient, notifierEventsClient, authorizationProcessClient, + delegationProcessClient, }: PagoPAInteropBeClients ): ZodiosRouter => { const { M2M_ROLE } = userRoles; @@ -71,7 +72,8 @@ const apiGatewayRouter = ( const purposeService = purposeServiceBuilder( purposeProcessClient, catalogProcessClient, - agreementProcessClient + agreementProcessClient, + delegationProcessClient ); const tenantService = tenantServiceBuilder( diff --git a/packages/api-gateway/src/services/purposeService.ts b/packages/api-gateway/src/services/purposeService.ts index 646687db34..11e08bbd01 100644 --- a/packages/api-gateway/src/services/purposeService.ts +++ b/packages/api-gateway/src/services/purposeService.ts @@ -1,8 +1,14 @@ import { getAllFromPaginated, WithLogger } from "pagopa-interop-commons"; -import { apiGatewayApi, purposeApi } from "pagopa-interop-api-clients"; +import { + apiGatewayApi, + purposeApi, + catalogApi, + delegationApi, +} from "pagopa-interop-api-clients"; import { AgreementProcessClient, CatalogProcessClient, + DelegationProcessClient, PurposeProcessClient, } from "../clients/clientsProvider.js"; import { ApiGatewayAppContext } from "../utilities/context.js"; @@ -14,6 +20,8 @@ import { clientStatusCodeToError } from "../clients/catchClientError.js"; import { purposeNotFound } from "../models/errors.js"; import { assertIsEserviceProducer, + assertIsEserviceProducerDelegate, + assertOnlyOneActiveDelegationForEserviceExists, assertOnlyOneAgreementForEserviceAndConsumerExists, } from "./validators.js"; import { getAllAgreements } from "./agreementService.js"; @@ -61,11 +69,34 @@ const retrievePurpose = async ( }); }); +const retrieveActiveDelegationByEServiceId = async ( + delegationProcessClient: DelegationProcessClient, + headers: ApiGatewayAppContext["headers"], + eserviceId: catalogApi.EService["id"], + kind: delegationApi.DelegationKind +): Promise => { + const result = await delegationProcessClient.getDelegations({ + headers, + queries: { + eserviceIds: [eserviceId], + kind, + delegationStates: [delegationApi.DelegationState.Values.ACTIVE], + limit: 1, + offset: 0, + }, + }); + + assertOnlyOneActiveDelegationForEserviceExists(result, eserviceId); + + return result.results.at(0); +}; + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function purposeServiceBuilder( purposeProcessClient: PurposeProcessClient, catalogProcessClient: CatalogProcessClient, - agreementProcessClient: AgreementProcessClient + agreementProcessClient: AgreementProcessClient, + delegationProcessClient: DelegationProcessClient ) { return { getPurpose: async ( @@ -92,7 +123,18 @@ export function purposeServiceBuilder( }, }); - assertIsEserviceProducer(eservice, organizationId); + try { + assertIsEserviceProducer(eservice, organizationId); + } catch { + const delegation = await retrieveActiveDelegationByEServiceId( + delegationProcessClient, + headers, + eservice.id, + delegationApi.DelegationKind.Values.DELEGATED_PRODUCER + ); + + assertIsEserviceProducerDelegate(delegation, organizationId); + } } return toApiGatewayPurpose(purpose, logger); diff --git a/packages/api-gateway/src/services/validators.ts b/packages/api-gateway/src/services/validators.ts index e77291a705..fc2fc320b5 100644 --- a/packages/api-gateway/src/services/validators.ts +++ b/packages/api-gateway/src/services/validators.ts @@ -3,6 +3,7 @@ import { apiGatewayApi, attributeRegistryApi, catalogApi, + delegationApi, purposeApi, } from "pagopa-interop-api-clients"; import { operationForbidden, TenantId } from "pagopa-interop-models"; @@ -15,6 +16,7 @@ import { missingAvailableDescriptor, multipleAgreementForEserviceAndConsumer, unexpectedDescriptorState, + multipleActiveDelegationsForEservice, } from "../models/errors.js"; import { NonDraftCatalogApiDescriptor } from "../api/catalogApiConverter.js"; @@ -93,3 +95,21 @@ export function assertRegistryAttributeExists( throw attributeNotFoundInRegistry(attributeId); } } + +export function assertIsEserviceProducerDelegate( + delegation: delegationApi.Delegation | undefined, + organizationId: TenantId +): void { + if (!delegation || delegation.delegateId !== organizationId) { + throw operationForbidden; + } +} + +export function assertOnlyOneActiveDelegationForEserviceExists( + delegations: delegationApi.Delegations, + eserviceId: apiGatewayApi.EService["id"] +): void { + if (delegations.totalCount > 1) { + throw multipleActiveDelegationsForEservice(eserviceId); + } +} diff --git a/packages/authorization-updater/src/index.ts b/packages/authorization-updater/src/index.ts index 0c88769cc6..cec6573999 100644 --- a/packages/authorization-updater/src/index.ts +++ b/packages/authorization-updater/src/index.ts @@ -67,7 +67,8 @@ export async function sendCatalogAuthUpdate( { type: P.union( "EServiceDescriptorPublished", - "EServiceDescriptorActivated" + "EServiceDescriptorActivated", + "EServiceDescriptorDelegatorApproved" ), }, async (msg) => { @@ -134,7 +135,9 @@ export async function sendCatalogAuthUpdate( "EServiceRiskAnalysisAdded", "EServiceRiskAnalysisUpdated", "EServiceRiskAnalysisDeleted", - "EServiceDescriptionUpdated" + "EServiceDescriptionUpdated", + "EServiceDescriptorDelegateSubmitted", + "EServiceDescriptorDelegatorRejected" ), }, () => { diff --git a/packages/backend-for-frontend/src/routers/catalogRouter.ts b/packages/backend-for-frontend/src/routers/catalogRouter.ts index 9a79390e1b..a0779aaaa7 100644 --- a/packages/backend-for-frontend/src/routers/catalogRouter.ts +++ b/packages/backend-for-frontend/src/routers/catalogRouter.ts @@ -79,6 +79,7 @@ const catalogRouter = ( const response = await catalogService.getProducerEServices( req.query.q, req.query.consumersIds, + req.query.delegated, req.query.offset, req.query.limit, ctx @@ -745,7 +746,54 @@ const catalogRouter = ( ); return res.status(errorRes.status).send(errorRes); } - }); + }) + .post( + "/eservices/:eServiceId/descriptors/:descriptorId/approve", + async (req, res) => { + const ctx = fromBffAppContext(req.ctx, req.headers); + try { + await catalogService.approveDelegatedEServiceDescriptor( + unsafeBrandId(req.params.eServiceId), + unsafeBrandId(req.params.descriptorId), + ctx + ); + return res.status(204).send(); + } catch (error) { + const errorRes = makeApiProblem( + error, + emptyErrorMapper, + ctx.logger, + ctx.correlationId, + `Error approving eService ${req.params.eServiceId} version ${req.params.descriptorId}` + ); + return res.status(errorRes.status).send(errorRes); + } + } + ) + .post( + "/eservices/:eServiceId/descriptors/:descriptorId/reject", + async (req, res) => { + const ctx = fromBffAppContext(req.ctx, req.headers); + try { + await catalogService.rejectDelegatedEServiceDescriptor( + unsafeBrandId(req.params.eServiceId), + unsafeBrandId(req.params.descriptorId), + req.body, + ctx + ); + return res.status(204).send(); + } catch (error) { + const errorRes = makeApiProblem( + error, + emptyErrorMapper, + ctx.logger, + ctx.correlationId, + `Error rejecting eService ${req.params.eServiceId} version ${req.params.descriptorId}` + ); + return res.status(errorRes.status).send(errorRes); + } + } + ); return catalogRouter; }; diff --git a/packages/backend-for-frontend/src/routers/tenantRouter.ts b/packages/backend-for-frontend/src/routers/tenantRouter.ts index 5f93f610e5..981ae53bc8 100644 --- a/packages/backend-for-frontend/src/routers/tenantRouter.ts +++ b/packages/backend-for-frontend/src/routers/tenantRouter.ts @@ -381,31 +381,28 @@ const tenantRouter = ( return res.status(errorRes.status).send(errorRes); } }) - .get( - "/tenants", - - async (req, res) => { - const ctx = fromBffAppContext(req.ctx, req.headers); + .get("/tenants", async (req, res) => { + const ctx = fromBffAppContext(req.ctx, req.headers); - try { - const result = await tenantService.getTenants( - req.query.name, - req.query.limit, - ctx - ); - return res.status(200).send(bffApi.Tenants.parse(result)); - } catch (error) { - const errorRes = makeApiProblem( - error, - emptyErrorMapper, - ctx.logger, - ctx.correlationId, - `Error retrieving tenants` - ); - return res.status(errorRes.status).send(errorRes); - } + try { + const result = await tenantService.getTenants( + req.query.name, + req.query.features, + req.query.limit, + ctx + ); + return res.status(200).send(bffApi.Tenants.parse(result)); + } catch (error) { + const errorRes = makeApiProblem( + error, + emptyErrorMapper, + ctx.logger, + ctx.correlationId, + `Error retrieving tenants` + ); + return res.status(errorRes.status).send(errorRes); } - ) + }) .post("/tenants/delegatedProducer", async (req, res) => { const ctx = fromBffAppContext(req.ctx, req.headers); const tenantId = ctx.authData.organizationId; diff --git a/packages/backend-for-frontend/src/services/catalogService.ts b/packages/backend-for-frontend/src/services/catalogService.ts index 468a8d66dd..8cec70eb07 100644 --- a/packages/backend-for-frontend/src/services/catalogService.ts +++ b/packages/backend-for-frontend/src/services/catalogService.ts @@ -429,6 +429,7 @@ export function catalogServiceBuilder( getProducerEServices: async ( eserviceName: string | undefined, consumersIds: string[], + delegated: boolean | undefined, offset: number, limit: number, { headers, authData, logger }: WithLogger @@ -454,6 +455,7 @@ export function catalogServiceBuilder( queries: { name: eserviceName, producersIds: producerId, + delegated, offset, limit, }, @@ -479,6 +481,7 @@ export function catalogServiceBuilder( name: eserviceName, eservicesIds: eserviceIds, producersIds: producerId, + delegated, offset, limit, }, @@ -1213,5 +1216,34 @@ export function catalogServiceBuilder( descriptorId: eservice.descriptors[0].id, }; }, + approveDelegatedEServiceDescriptor: async ( + eServiceId: EServiceId, + descriptorId: EServiceId, + { headers, logger }: WithLogger + ): Promise => { + logger.info(`Approving e-service ${eServiceId} version ${descriptorId}`); + await catalogProcessClient.approveDelegatedEServiceDescriptor(undefined, { + headers, + params: { + eServiceId, + descriptorId, + }, + }); + }, + rejectDelegatedEServiceDescriptor: async ( + eServiceId: EServiceId, + descriptorId: EServiceId, + body: catalogApi.RejectDelegatedEServiceDescriptorSeed, + { headers, logger }: WithLogger + ): Promise => { + logger.info(`Rejecting e-service ${eServiceId} version ${descriptorId}`); + await catalogProcessClient.rejectDelegatedEServiceDescriptor(body, { + headers, + params: { + eServiceId, + descriptorId, + }, + }); + }, }; } diff --git a/packages/backend-for-frontend/src/services/tenantService.ts b/packages/backend-for-frontend/src/services/tenantService.ts index 82dac3d5f6..2e1914bbb6 100644 --- a/packages/backend-for-frontend/src/services/tenantService.ts +++ b/packages/backend-for-frontend/src/services/tenantService.ts @@ -107,6 +107,7 @@ export function tenantServiceBuilder( }, async getTenants( name: string | undefined, + features: tenantApi.TenantFeatureType[] | undefined, limit: number, { logger, headers, correlationId }: WithLogger ): Promise { @@ -115,6 +116,7 @@ export function tenantServiceBuilder( const pagedResults = await tenantProcessClient.tenant.getTenants({ queries: { name, + features, limit, offset, }, diff --git a/packages/catalog-outbound-writer/package.json b/packages/catalog-outbound-writer/package.json index 5e81c6a5db..25ff33e1b5 100644 --- a/packages/catalog-outbound-writer/package.json +++ b/packages/catalog-outbound-writer/package.json @@ -29,7 +29,7 @@ "vitest": "1.6.0" }, "dependencies": { - "@pagopa/interop-outbound-models": "1.0.10", + "@pagopa/interop-outbound-models": "1.0.11a", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", @@ -40,4 +40,4 @@ "ts-pattern": "5.2.0", "zod": "3.23.8" } -} \ No newline at end of file +} diff --git a/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts b/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts index 9b282048ef..62d5d9be92 100644 --- a/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts +++ b/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts @@ -98,6 +98,9 @@ export function toOutboundEventV2( { type: "EServiceDescriptorPublished" }, { type: "EServiceDescriptorSuspended" }, { type: "EServiceDraftDescriptorDeleted" }, + { type: "EServiceDescriptorDelegateSubmitted" }, + { type: "EServiceDescriptorDelegatorApproved" }, + { type: "EServiceDescriptorDelegatorRejected" }, (msg) => ({ event_version: msg.event_version, type: msg.type, diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts index 441744646e..e8a9044cc2 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV1.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV1.ts @@ -139,8 +139,11 @@ export async function handleMessageV1( dynamoDBClient ); }) - .with(descriptorState.draft, descriptorState.deprecated, () => - Promise.resolve() + .with( + descriptorState.draft, + descriptorState.deprecated, + descriptorState.waitingForApproval, + () => Promise.resolve() ) .exhaustive(); }) diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index 7043f7889e..7ced11e8ee 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -30,93 +30,97 @@ export async function handleMessageV2( dynamoDBClient: DynamoDBClient ): Promise { await match(message) - .with({ type: "EServiceDescriptorPublished" }, async (msg) => { - const { eservice, descriptor } = parseEServiceAndDescriptor( - msg.data.eservice, - unsafeBrandId(msg.data.descriptorId), - message.type - ); - const previousDescriptor = eservice.descriptors.find( - (d) => d.version === (Number(descriptor.version) - 1).toString() - ); - - // flow for current descriptor - const processCurrentDescriptor = async (): Promise => { - const primaryKeyCurrent = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: descriptor.id, - }); - const existingCatalogEntryCurrent = await readCatalogEntry( - primaryKeyCurrent, - dynamoDBClient + .with( + { type: "EServiceDescriptorPublished" }, + { type: "EServiceDescriptorDelegatorApproved" }, + async (msg) => { + const { eservice, descriptor } = parseEServiceAndDescriptor( + msg.data.eservice, + unsafeBrandId(msg.data.descriptorId), + message.type ); - if (existingCatalogEntryCurrent) { - if (existingCatalogEntryCurrent.version > msg.version) { - // Stops processing if the message is older than the catalog entry - return Promise.resolve(); - } else { - await updateDescriptorStateInPlatformStatesEntry( - dynamoDBClient, - primaryKeyCurrent, - descriptorStateToItemState(descriptor.state), - msg.version - ); - } - } else { - const catalogEntry: PlatformStatesCatalogEntry = { - PK: primaryKeyCurrent, - state: descriptorStateToItemState(descriptor.state), - descriptorAudience: descriptor.audience, - descriptorVoucherLifespan: descriptor.voucherLifespan, - version: msg.version, - updatedAt: new Date().toISOString(), - }; - - await writeCatalogEntry(catalogEntry, dynamoDBClient); - } - - // token-generation-states - const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ - eserviceId: eservice.id, - descriptorId: descriptor.id, - }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId, - descriptorStateToItemState(descriptor.state), - dynamoDBClient + const previousDescriptor = eservice.descriptors.find( + (d) => d.version === (Number(descriptor.version) - 1).toString() ); - }; - await processCurrentDescriptor(); + // flow for current descriptor + const processCurrentDescriptor = async (): Promise => { + const primaryKeyCurrent = makePlatformStatesEServiceDescriptorPK({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + const existingCatalogEntryCurrent = await readCatalogEntry( + primaryKeyCurrent, + dynamoDBClient + ); + if (existingCatalogEntryCurrent) { + if (existingCatalogEntryCurrent.version > msg.version) { + // Stops processing if the message is older than the catalog entry + return Promise.resolve(); + } else { + await updateDescriptorStateInPlatformStatesEntry( + dynamoDBClient, + primaryKeyCurrent, + descriptorStateToItemState(descriptor.state), + msg.version + ); + } + } else { + const catalogEntry: PlatformStatesCatalogEntry = { + PK: primaryKeyCurrent, + state: descriptorStateToItemState(descriptor.state), + descriptorAudience: descriptor.audience, + descriptorVoucherLifespan: descriptor.voucherLifespan, + version: msg.version, + updatedAt: new Date().toISOString(), + }; - // flow for previous descriptor + await writeCatalogEntry(catalogEntry, dynamoDBClient); + } - if ( - !previousDescriptor || - previousDescriptor.state !== descriptorState.archived - ) { - return Promise.resolve(); - } else { - const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ - eserviceId: eservice.id, - descriptorId: previousDescriptor.id, - }); + // token-generation-states + const eserviceId_descriptorId = makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: descriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId, + descriptorStateToItemState(descriptor.state), + dynamoDBClient + ); + }; - await deleteCatalogEntry(primaryKeyPrevious, dynamoDBClient); + await processCurrentDescriptor(); - // token-generation-states - const eserviceId_descriptorId_previous = - makeGSIPKEServiceIdDescriptorId({ + // flow for previous descriptor + + if ( + !previousDescriptor || + previousDescriptor.state !== descriptorState.archived + ) { + return Promise.resolve(); + } else { + const primaryKeyPrevious = makePlatformStatesEServiceDescriptorPK({ eserviceId: eservice.id, descriptorId: previousDescriptor.id, }); - await updateDescriptorStateInTokenGenerationStatesTable( - eserviceId_descriptorId_previous, - descriptorStateToItemState(previousDescriptor.state), - dynamoDBClient - ); + + await deleteCatalogEntry(primaryKeyPrevious, dynamoDBClient); + + // token-generation-states + const eserviceId_descriptorId_previous = + makeGSIPKEServiceIdDescriptorId({ + eserviceId: eservice.id, + descriptorId: previousDescriptor.id, + }); + await updateDescriptorStateInTokenGenerationStatesTable( + eserviceId_descriptorId_previous, + descriptorStateToItemState(previousDescriptor.state), + dynamoDBClient + ); + } } - }) + ) .with( { type: "EServiceDescriptorActivated" }, { type: "EServiceDescriptorSuspended" }, @@ -236,6 +240,8 @@ export async function handleMessageV2( { type: "EServiceRiskAnalysisUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceDescriptorDelegatorRejected" }, + { type: "EServiceDescriptorDelegateSubmitted" }, () => Promise.resolve() ) .exhaustive(); diff --git a/packages/catalog-process/src/model/domain/apiConverter.ts b/packages/catalog-process/src/model/domain/apiConverter.ts index 70e0d2a969..d4c43a9183 100644 --- a/packages/catalog-process/src/model/domain/apiConverter.ts +++ b/packages/catalog-process/src/model/domain/apiConverter.ts @@ -43,6 +43,7 @@ export function descriptorStateToApiEServiceDescriptorState( .with(descriptorState.suspended, () => "SUSPENDED") .with(descriptorState.deprecated, () => "DEPRECATED") .with(descriptorState.archived, () => "ARCHIVED") + .with(descriptorState.waitingForApproval, () => "WAITING_FOR_APPROVAL") .exhaustive(); } @@ -55,6 +56,7 @@ export function apiDescriptorStateToDescriptorState( .with("SUSPENDED", () => descriptorState.suspended) .with("DEPRECATED", () => descriptorState.deprecated) .with("ARCHIVED", () => descriptorState.archived) + .with("WAITING_FOR_APPROVAL", () => descriptorState.waitingForApproval) .exhaustive(); } diff --git a/packages/catalog-process/src/model/domain/errors.ts b/packages/catalog-process/src/model/domain/errors.ts index a183c27716..d1bb66462d 100644 --- a/packages/catalog-process/src/model/domain/errors.ts +++ b/packages/catalog-process/src/model/domain/errors.ts @@ -1,5 +1,6 @@ import { ApiError, + DelegationId, DescriptorId, EServiceDocumentId, EServiceId, @@ -33,6 +34,7 @@ export const errorCodes = { riskAnalysisDuplicated: "0021", eserviceWithoutValidDescriptors: "0022", audienceCannotBeEmpty: "0023", + eserviceWithActiveOrPendingDelegation: "0024", }; export type ErrorCodes = keyof typeof errorCodes; @@ -271,3 +273,14 @@ export function audienceCannotBeEmpty( title: "Audience cannot be empty", }); } + +export function eserviceWithActiveOrPendingDelegation( + eserviceId: EServiceId, + delegationId: DelegationId +): ApiError { + return new ApiError({ + detail: `E-service ${eserviceId} can't be deleted with an active or pending delegation ${delegationId}`, + code: "eserviceWithActiveOrPendingDelegation", + title: "E-service with active or pending delegation", + }); +} diff --git a/packages/catalog-process/src/model/domain/models.ts b/packages/catalog-process/src/model/domain/models.ts index 3e87abb3fe..6e4b215458 100644 --- a/packages/catalog-process/src/model/domain/models.ts +++ b/packages/catalog-process/src/model/domain/models.ts @@ -21,6 +21,7 @@ export type ApiGetEServicesFilters = { agreementStates: AgreementState[]; name?: string; mode?: EServiceMode; + delegated?: boolean; }; export type EServiceDocument = { diff --git a/packages/catalog-process/src/model/domain/toEvent.ts b/packages/catalog-process/src/model/domain/toEvent.ts index e9db5c3e79..62dc47aa92 100644 --- a/packages/catalog-process/src/model/domain/toEvent.ts +++ b/packages/catalog-process/src/model/domain/toEvent.ts @@ -479,3 +479,60 @@ export const toCreateEventEServiceDescriptionUpdated = ( }, correlationId, }); + +export const toCreateEventEServiceDescriptorDelegateSubmitted = ( + version: number, + descriptorId: DescriptorId, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceDescriptorDelegateSubmitted", + event_version: 2, + data: { + descriptorId, + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); + +export const toCreateEventEServiceDescriptorDelegatorApproved = ( + version: number, + descriptorId: DescriptorId, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceDescriptorDelegatorApproved", + event_version: 2, + data: { + descriptorId, + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); + +export const toCreateEventEServiceDescriptorDelegatorRejected = ( + version: number, + descriptorId: DescriptorId, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceDescriptorDelegatorRejected", + event_version: 2, + data: { + descriptorId, + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); diff --git a/packages/catalog-process/src/routers/EServiceRouter.ts b/packages/catalog-process/src/routers/EServiceRouter.ts index addca3936e..015b266b1e 100644 --- a/packages/catalog-process/src/routers/EServiceRouter.ts +++ b/packages/catalog-process/src/routers/EServiceRouter.ts @@ -54,6 +54,8 @@ import { updateEServiceDescriptionErrorMapper, updateEServiceErrorMapper, updateRiskAnalysisErrorMapper, + approveDelegatedEServiceDescriptorErrorMapper, + rejectDelegatedEServiceDescriptorErrorMapper, } from "../utilities/errorMappers.js"; const readModelService = readModelServiceBuilder( @@ -110,6 +112,7 @@ const eservicesRouter = ( states, agreementStates, mode, + delegated, offset, limit, } = req.query; @@ -126,6 +129,7 @@ const eservicesRouter = ( ), name, mode: mode ? apiEServiceModeToEServiceMode(mode) : undefined, + delegated, }, offset, limit, @@ -761,6 +765,55 @@ const eservicesRouter = ( return res.status(errorRes.status).send(errorRes); } } + ) + .post( + "/eservices/:eServiceId/descriptors/:descriptorId/approve", + authorizationMiddleware([ADMIN_ROLE]), + async (req, res) => { + const ctx = fromAppContext(req.ctx); + + try { + await catalogService.approveDelegatedEServiceDescriptor( + unsafeBrandId(req.params.eServiceId), + unsafeBrandId(req.params.descriptorId), + ctx + ); + return res.status(204).send(); + } catch (error) { + const errorRes = makeApiProblem( + error, + approveDelegatedEServiceDescriptorErrorMapper, + ctx.logger, + ctx.correlationId + ); + return res.status(errorRes.status).send(errorRes); + } + } + ) + .post( + "/eservices/:eServiceId/descriptors/:descriptorId/reject", + authorizationMiddleware([ADMIN_ROLE, API_ROLE]), + async (req, res) => { + const ctx = fromAppContext(req.ctx); + + try { + await catalogService.rejectDelegatedEServiceDescriptor( + unsafeBrandId(req.params.eServiceId), + unsafeBrandId(req.params.descriptorId), + req.body, + ctx + ); + return res.status(204).send(); + } catch (error) { + const errorRes = makeApiProblem( + error, + rejectDelegatedEServiceDescriptorErrorMapper, + ctx.logger, + ctx.correlationId + ); + return res.status(errorRes.status).send(errorRes); + } + } ); return eservicesRouter; }; diff --git a/packages/catalog-process/src/services/catalogService.ts b/packages/catalog-process/src/services/catalogService.ts index e5ecf618e1..a38933d69f 100644 --- a/packages/catalog-process/src/services/catalogService.ts +++ b/packages/catalog-process/src/services/catalogService.ts @@ -35,6 +35,9 @@ import { RiskAnalysis, RiskAnalysisId, eserviceMode, + delegationState, + operationForbidden, + DescriptorRejectionReason, } from "pagopa-interop-models"; import { catalogApi } from "pagopa-interop-api-clients"; import { match } from "ts-pattern"; @@ -52,6 +55,9 @@ import { toCreateEventEServiceDescriptorActivated, toCreateEventEServiceDescriptorAdded, toCreateEventEServiceDescriptorArchived, + toCreateEventEServiceDescriptorDelegateSubmitted, + toCreateEventEServiceDescriptorDelegatorApproved, + toCreateEventEServiceDescriptorDelegatorRejected, toCreateEventEServiceDescriptorPublished, toCreateEventEServiceDescriptorQuotasUpdated, toCreateEventEServiceDescriptorSuspended, @@ -88,15 +94,18 @@ import { eserviceWithoutValidDescriptors, audienceCannotBeEmpty, } from "../model/domain/errors.js"; +import { RejectDelegatedEServiceDescriptorSeed } from "../../../api-clients/dist/catalogApi.js"; import { ReadModelService } from "./readModelService.js"; import { - assertRequesterAllowed, + assertRequesterIsDelegateOrProducer, assertIsDraftEservice, assertIsReceiveEservice, assertTenantKindExists, validateRiskAnalysisSchemaOrThrow, assertHasNoDraftDescriptor, assertRiskAnalysisIsValidForPublication, + assertRequesterIsProducer, + assertNoExistingDelegationInActiveOrPendingState, } from "./validators.js"; const retrieveEService = async ( @@ -172,11 +181,15 @@ const updateDescriptorState = ( const descriptorStateChange = [descriptor.state, newState]; return match(descriptorStateChange) - .with([descriptorState.draft, descriptorState.published], () => ({ - ...descriptor, - state: newState, - publishedAt: new Date(), - })) + .with( + [descriptorState.draft, descriptorState.published], + [descriptorState.waitingForApproval, descriptorState.published], + () => ({ + ...descriptor, + state: newState, + publishedAt: new Date(), + }) + ) .with([descriptorState.published, descriptorState.suspended], () => ({ ...descriptor, state: newState, @@ -333,7 +346,11 @@ export function catalogServiceBuilder( logger.info(`Retrieving EService ${eserviceId}`); const eservice = await retrieveEService(eserviceId, readModelService); - return applyVisibilityToEService(eservice.data, authData); + return await applyVisibilityToEService( + eservice.data, + authData, + readModelService + ); }, async getEServices( @@ -353,8 +370,10 @@ export function catalogServiceBuilder( limit ); - const eservicesToReturn = eservicesList.results.map((eservice) => - applyVisibilityToEService(eservice, authData) + const eservicesToReturn = await Promise.all( + eservicesList.results.map((eservice) => + applyVisibilityToEService(eservice, authData, readModelService) + ) ); return { @@ -395,9 +414,10 @@ export function catalogServiceBuilder( const eservice = await retrieveEService(eserviceId, readModelService); const descriptor = retrieveDescriptor(descriptorId, eservice); const document = retrieveDocument(eserviceId, descriptor, documentId); - const checkedEService = applyVisibilityToEService( + const checkedEService = await applyVisibilityToEService( eservice.data, - authData + authData, + readModelService ); if (!checkedEService.descriptors.find((d) => d.id === descriptorId)) { throw eServiceDocumentNotFound(eserviceId, descriptorId, documentId); @@ -502,7 +522,12 @@ export function catalogServiceBuilder( logger.info(`Updating EService ${eserviceId}`); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); assertIsDraftEservice(eservice.data); @@ -510,7 +535,7 @@ export function catalogServiceBuilder( const eserviceWithSameName = await readModelService.getEServiceByNameAndProducerId({ name: eserviceSeed.name, - producerId: authData.organizationId, + producerId: eservice.data.producerId, }); if (eserviceWithSameName !== undefined) { throw eServiceDuplicate(eserviceSeed.name); @@ -547,7 +572,6 @@ export function catalogServiceBuilder( description: eserviceSeed.description, name: eserviceSeed.name, technology: updatedTechnology, - producerId: authData.organizationId, mode: updatedMode, riskAnalysis: checkedRiskAnalysis, descriptors: interfaceHasToBeDeleted @@ -578,10 +602,14 @@ export function catalogServiceBuilder( logger.info(`Deleting EService ${eserviceId}`); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); - + assertRequesterIsProducer(eservice.data.producerId, authData); assertIsDraftEservice(eservice.data); + await assertNoExistingDelegationInActiveOrPendingState( + eserviceId, + readModelService + ); + if (eservice.data.descriptors.length === 0) { const eserviceDeletionEvent = toCreateEventEServiceDeleted( eservice.metadata.version, @@ -634,7 +662,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); @@ -724,7 +757,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); @@ -795,7 +833,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); @@ -879,7 +922,12 @@ export function catalogServiceBuilder( logger.info(`Creating Descriptor for EService ${eserviceId}`); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); assertHasNoDraftDescriptor(eservice.data); const newVersion = nextDescriptorVersion(eservice.data); @@ -990,7 +1038,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); @@ -1041,7 +1094,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); @@ -1100,7 +1158,19 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + + const delegation = await readModelService.getLatestDelegation({ + eserviceId, + states: [delegationState.active], + }); + + if (delegation) { + if (authData.organizationId !== delegation.delegateId) { + throw operationForbidden; + } + } else { + assertRequesterIsProducer(eservice.data.producerId, authData); + } const descriptor = retrieveDescriptor(descriptorId, eservice); if (descriptor.state !== descriptorState.draft) { @@ -1124,71 +1194,37 @@ export function catalogServiceBuilder( throw audienceCannotBeEmpty(descriptor.id); } - const currentActiveDescriptor = eservice.data.descriptors.find( - (d: Descriptor) => d.state === descriptorState.published - ); - - const publishedDescriptor = updateDescriptorState( - descriptor, - descriptorState.published - ); - - const eserviceWithPublishedDescriptor = replaceDescriptor( - eservice.data, - publishedDescriptor - ); - - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type - const event = async () => { - if (currentActiveDescriptor !== undefined) { - const agreements = await readModelService.listAgreements({ - eservicesIds: [eserviceId], - consumersIds: [], - producersIds: [], - states: [agreementState.active, agreementState.suspended], - limit: 1, - descriptorId: currentActiveDescriptor.id, - }); - if (agreements.length === 0) { - const eserviceWithArchivedAndPublishedDescriptors = - replaceDescriptor( - eserviceWithPublishedDescriptor, - archiveDescriptor(eserviceId, currentActiveDescriptor, logger) - ); - - return toCreateEventEServiceDescriptorPublished( - eserviceId, - eservice.metadata.version, - descriptorId, - eserviceWithArchivedAndPublishedDescriptors, - correlationId - ); - } else { - const eserviceWithDeprecatedAndPublishedDescriptors = - replaceDescriptor( - eserviceWithPublishedDescriptor, - deprecateDescriptor(eserviceId, currentActiveDescriptor, logger) - ); + if (delegation) { + const eserviceWithWaitingForApprovalDescriptor = replaceDescriptor( + eservice.data, + updateDescriptorState(descriptor, descriptorState.waitingForApproval) + ); + await repository.createEvent( + toCreateEventEServiceDescriptorDelegateSubmitted( + eservice.metadata.version, + descriptor.id, + eserviceWithWaitingForApprovalDescriptor, + correlationId + ) + ); + } else { + const updatedEService = await processDescriptorPublication( + eservice.data, + descriptor, + readModelService, + logger + ); - return toCreateEventEServiceDescriptorPublished( - eserviceId, - eservice.metadata.version, - descriptorId, - eserviceWithDeprecatedAndPublishedDescriptors, - correlationId - ); - } - } else { - return toCreateEventEServiceDescriptorPublished( + await repository.createEvent( + toCreateEventEServiceDescriptorPublished( eserviceId, eservice.metadata.version, descriptorId, - eserviceWithPublishedDescriptor, + updatedEService, correlationId - ); - } - }; - await repository.createEvent(await event()); + ) + ); + } }, async suspendDescriptor( @@ -1201,7 +1237,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); if ( @@ -1238,7 +1279,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); if (descriptor.state !== descriptorState.suspended) { @@ -1307,7 +1353,11 @@ export function catalogServiceBuilder( const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + assertRequesterIsProducer(eservice.data.producerId, authData); + await assertNoExistingDelegationInActiveOrPendingState( + eservice.data.id, + readModelService + ); const clonedEServiceName = `${ eservice.data.name @@ -1316,7 +1366,7 @@ export function catalogServiceBuilder( if ( await readModelService.getEServiceByNameAndProducerId({ name: clonedEServiceName, - producerId: authData.organizationId, + producerId: eservice.data.producerId, }) ) { throw eServiceDuplicate(clonedEServiceName); @@ -1421,7 +1471,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); const updatedDescriptor = updateDescriptorState( @@ -1452,7 +1507,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const descriptor = retrieveDescriptor(descriptorId, eservice); @@ -1500,12 +1560,17 @@ export function catalogServiceBuilder( const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); assertIsDraftEservice(eservice.data); assertIsReceiveEservice(eservice.data); const tenant = await retrieveTenant( - authData.organizationId, + eservice.data.producerId, readModelService ); assertTenantKindExists(tenant); @@ -1560,12 +1625,17 @@ export function catalogServiceBuilder( const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); assertIsDraftEservice(eservice.data); assertIsReceiveEservice(eservice.data); const tenant = await retrieveTenant( - authData.organizationId, + eservice.data.producerId, readModelService ); assertTenantKindExists(tenant); @@ -1626,7 +1696,12 @@ export function catalogServiceBuilder( ); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); assertIsDraftEservice(eservice.data); assertIsReceiveEservice(eservice.data); @@ -1657,11 +1732,17 @@ export function catalogServiceBuilder( logger.info(`Updating EService ${eserviceId} description`); const eservice = await retrieveEService(eserviceId, readModelService); - assertRequesterAllowed(eservice.data.producerId, authData); + await assertRequesterIsDelegateOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); const hasValidDescriptor = eservice.data.descriptors.some( (descriptor) => descriptor.state !== descriptorState.draft && + descriptor.state !== descriptorState.waitingForApproval && descriptor.state !== descriptorState.archived ); if (!hasValidDescriptor) { @@ -1682,30 +1763,129 @@ export function catalogServiceBuilder( ); return updatedEservice; }, + async approveDelegatedEServiceDescriptor( + eserviceId: EServiceId, + descriptorId: DescriptorId, + { authData, correlationId, logger }: WithLogger + ): Promise { + logger.info(`Approving EService ${eserviceId} version ${descriptorId}`); + const eservice = await retrieveEService(eserviceId, readModelService); + + assertRequesterIsProducer(eservice.data.producerId, authData); + + const descriptor = retrieveDescriptor(descriptorId, eservice); + + if (descriptor.state !== descriptorState.waitingForApproval) { + throw notValidDescriptor(descriptor.id, descriptor.state.toString()); + } + + const updatedEService = await processDescriptorPublication( + eservice.data, + descriptor, + readModelService, + logger + ); + + await repository.createEvent( + toCreateEventEServiceDescriptorDelegatorApproved( + eservice.metadata.version, + descriptor.id, + updatedEService, + correlationId + ) + ); + }, + async rejectDelegatedEServiceDescriptor( + eserviceId: EServiceId, + descriptorId: DescriptorId, + body: RejectDelegatedEServiceDescriptorSeed, + { authData, correlationId, logger }: WithLogger + ): Promise { + logger.info(`Rejecting EService ${eserviceId} version ${descriptorId}`); + const eservice = await retrieveEService(eserviceId, readModelService); + + assertRequesterIsProducer(eservice.data.producerId, authData); + + const descriptor = retrieveDescriptor(descriptorId, eservice); + + if (descriptor.state !== descriptorState.waitingForApproval) { + throw notValidDescriptor(descriptor.id, descriptor.state.toString()); + } + + const newRejectionReason: DescriptorRejectionReason = { + rejectionReason: body.rejectionReason, + rejectedAt: new Date(), + }; + + const updatedDescriptor = updateDescriptorState( + { + ...descriptor, + rejectionReasons: [ + ...(descriptor.rejectionReasons ?? []), + newRejectionReason, + ], + }, + descriptorState.draft + ); + + const updatedEService = replaceDescriptor( + eservice.data, + updatedDescriptor + ); + + await repository.createEvent( + toCreateEventEServiceDescriptorDelegatorRejected( + eservice.metadata.version, + descriptor.id, + updatedEService, + correlationId + ) + ); + }, }; } -const isUserAllowedToSeeDraft = ( +async function isUserAllowedToSeeDraft( + eservice: EService, authData: AuthData, - producerId: TenantId -): boolean => - hasPermission( - [userRoles.ADMIN_ROLE, userRoles.API_ROLE, userRoles.SUPPORT_ROLE], - authData - ) && authData.organizationId === producerId; - -const applyVisibilityToEService = ( + readModelService: ReadModelService +): Promise { + if ( + !hasPermission( + [userRoles.ADMIN_ROLE, userRoles.API_ROLE, userRoles.SUPPORT_ROLE], + authData + ) + ) { + return false; + } + + if (authData.organizationId === eservice.producerId) { + return true; + } + + const activeDelegation = await readModelService.getLatestDelegation({ + eserviceId: eservice.id, + delegateId: authData.organizationId, + states: [delegationState.active], + }); + + return activeDelegation !== undefined; +} + +async function applyVisibilityToEService( eservice: EService, - authData: AuthData -): EService => { - if (isUserAllowedToSeeDraft(authData, eservice.producerId)) { + authData: AuthData, + readModelService: ReadModelService +): Promise { + if (await isUserAllowedToSeeDraft(eservice, authData, readModelService)) { return eservice; } if ( eservice.descriptors.length === 0 || (eservice.descriptors.length === 1 && - eservice.descriptors[0].state === descriptorState.draft) + (eservice.descriptors[0].state === descriptorState.draft || + eservice.descriptors[0].state === descriptorState.waitingForApproval)) ) { throw eServiceNotFound(eservice.id); } @@ -1713,10 +1893,12 @@ const applyVisibilityToEService = ( return { ...eservice, descriptors: eservice.descriptors.filter( - (d) => d.state !== descriptorState.draft + (d) => + d.state !== descriptorState.draft && + d.state !== descriptorState.waitingForApproval ), }; -}; +} const deleteDescriptorInterfaceAndDocs = async ( descriptor: Descriptor, @@ -1735,4 +1917,45 @@ const deleteDescriptorInterfaceAndDocs = async ( await Promise.all(deleteDescriptorDocs); }; +const processDescriptorPublication = async ( + eservice: EService, + descriptor: Descriptor, + readModelService: ReadModelService, + logger: Logger +): Promise => { + const currentActiveDescriptor = eservice.descriptors.find( + (d: Descriptor) => d.state === descriptorState.published + ); + + const publishedDescriptor = updateDescriptorState( + descriptor, + descriptorState.published + ); + + const eserviceWithPublishedDescriptor = replaceDescriptor( + eservice, + publishedDescriptor + ); + + if (!currentActiveDescriptor) { + return eserviceWithPublishedDescriptor; + } + + const currentEServiceAgreements = await readModelService.listAgreements({ + eservicesIds: [eservice.id], + consumersIds: [], + producersIds: [], + states: [agreementState.active, agreementState.suspended], + limit: 1, + descriptorId: currentActiveDescriptor.id, + }); + + return replaceDescriptor( + eserviceWithPublishedDescriptor, + currentEServiceAgreements.length === 0 + ? archiveDescriptor(eservice.id, currentActiveDescriptor, logger) + : deprecateDescriptor(eservice.id, currentActiveDescriptor, logger) + ); +}; + export type CatalogService = ReturnType; diff --git a/packages/catalog-process/src/services/readModelService.ts b/packages/catalog-process/src/services/readModelService.ts index b7f6e02bcb..4b0e9a8105 100644 --- a/packages/catalog-process/src/services/readModelService.ts +++ b/packages/catalog-process/src/services/readModelService.ts @@ -27,6 +27,9 @@ import { EServiceReadModel, TenantReadModel, genericInternalError, + Delegation, + DelegationState, + delegationState, } from "pagopa-interop-models"; import { match } from "ts-pattern"; import { z } from "zod"; @@ -99,6 +102,7 @@ export function readModelServiceBuilder( const agreements = readModelRepository.agreements; const attributes = readModelRepository.attributes; const tenants = readModelRepository.tenants; + const delegations = readModelRepository.delegations; return { async getEServices( @@ -115,6 +119,7 @@ export function readModelServiceBuilder( name, attributesIds, mode, + delegated, } = filters; const ids = await match(agreementStates.length) .with(0, () => eservicesIds) @@ -147,10 +152,32 @@ export function readModelServiceBuilder( "data.id": { $in: ids }, }); - const producersIdsFilter: ReadModelFilter = - ReadModelRepository.arrayToFilter(producersIds, { - "data.producerId": { $in: producersIds }, - }); + const producersIdsFilter = ReadModelRepository.arrayToFilter( + producersIds, + { + $or: [ + { "data.producerId": { $in: producersIds } }, + { + "delegation.data.delegateId": { $in: producersIds }, + "delegation.data.state": { $eq: delegationState.active }, + }, + ], + } + ); + + const delegationLookup = + producersIds.length > 0 || delegated !== undefined + ? [ + { + $lookup: { + from: "delegations", + localField: "data.id", + foreignField: "data.eserviceId", + as: "delegation", + }, + }, + ] + : []; const descriptorsStateFilter: ReadModelFilter = ReadModelRepository.arrayToFilter(states, { @@ -201,7 +228,12 @@ export function readModelServiceBuilder( { "data.producerId": { $ne: authData.organizationId } }, { "data.descriptors": { $size: 1 } }, { - "data.descriptors.state": { $eq: descriptorState.draft }, + "data.descriptors.state": { + $in: [ + descriptorState.draft, + descriptorState.waitingForApproval, + ], + }, }, ], }, @@ -214,7 +246,12 @@ export function readModelServiceBuilder( $and: [ { "data.descriptors": { $size: 1 } }, { - "data.descriptors.state": { $eq: descriptorState.draft }, + "data.descriptors.state": { + $in: [ + descriptorState.draft, + descriptorState.waitingForApproval, + ], + }, }, ], }, @@ -225,18 +262,29 @@ export function readModelServiceBuilder( ? { "data.mode": { $eq: mode } } : {}; + const delegatedFilter: ReadModelFilter = match(delegated) + .with(true, () => ({ + "delegation.data.state": { + $in: [delegationState.active, delegationState.waitingForApproval], + }, + })) + .with(false, () => ({ + "delegation.data.state": { + $nin: [delegationState.active, delegationState.waitingForApproval], + }, + })) + .otherwise(() => ({})); + const aggregationPipeline = [ - { - $match: { - ...nameFilter, - ...idsFilter, - ...producersIdsFilter, - ...descriptorsStateFilter, - ...attributesFilter, - ...visibilityFilter, - ...modeFilter, - } satisfies ReadModelFilter, - }, + ...delegationLookup, + { $match: nameFilter }, + { $match: idsFilter }, + { $match: producersIdsFilter }, + { $match: descriptorsStateFilter }, + { $match: attributesFilter }, + { $match: visibilityFilter }, + { $match: modeFilter }, + { $match: delegatedFilter }, { $project: { data: 1, @@ -498,6 +546,43 @@ export function readModelServiceBuilder( async getTenantById(id: TenantId): Promise { return getTenant(tenants, { "data.id": id }); }, + + async getLatestDelegation({ + eserviceId, + states, + delegateId, + }: { + eserviceId: EServiceId; + states: DelegationState[]; + delegateId?: TenantId; + }): Promise { + const data = await delegations.findOne( + { + "data.eserviceId": eserviceId, + ...(states.length > 0 ? { "data.state": { $in: states } } : {}), + ...(delegateId ? { "data.delegateId": delegateId } : {}), + }, + { + projection: { data: true }, + sort: { "data.createdAt": -1 }, + } + ); + + if (!data) { + return undefined; + } + const result = Delegation.safeParse(data.data); + + if (!result.success) { + throw genericInternalError( + `Unable to parse delegation item: result ${JSON.stringify( + result + )} - data ${JSON.stringify(data)} ` + ); + } + + return result.data; + }, }; } diff --git a/packages/catalog-process/src/services/validators.ts b/packages/catalog-process/src/services/validators.ts index 3f27c0e114..71994b6bbe 100644 --- a/packages/catalog-process/src/services/validators.ts +++ b/packages/catalog-process/src/services/validators.ts @@ -13,6 +13,8 @@ import { Tenant, TenantKind, Descriptor, + EServiceId, + delegationState, } from "pagopa-interop-models"; import { catalogApi } from "pagopa-interop-api-clients"; import { @@ -23,20 +25,66 @@ import { draftDescriptorAlreadyExists, eServiceRiskAnalysisIsRequired, riskAnalysisNotValid, + eserviceWithActiveOrPendingDelegation, } from "../model/domain/errors.js"; +import { ReadModelService } from "./readModelService.js"; -export function assertRequesterAllowed( +export async function assertRequesterIsDelegateOrProducer( + producerId: TenantId, + eserviceId: EServiceId, + authData: AuthData, + readModelService: ReadModelService +): Promise { + if (authData.userRoles.includes("internal")) { + return; + } + + // Search for active delegation + const delegation = await readModelService.getLatestDelegation({ + eserviceId, + states: [delegationState.active], + }); + + // If an active delegation exists, check if the requester is the delegate + if (delegation) { + const isRequesterDelegate = + authData.organizationId === delegation.delegateId; + + if (!isRequesterDelegate) { + throw operationForbidden; + } + } else { + // If no active delegation exists, ensure the requester is the producer + assertRequesterIsProducer(producerId, authData); + } +} + +export function assertRequesterIsProducer( producerId: TenantId, authData: AuthData ): void { - if ( - !authData.userRoles.includes("internal") && - producerId !== authData.organizationId - ) { + if (authData.userRoles.includes("internal")) { + return; + } + if (producerId !== authData.organizationId) { throw operationForbidden; } } +export async function assertNoExistingDelegationInActiveOrPendingState( + eserviceId: EServiceId, + readModelService: ReadModelService +): Promise { + const delegation = await readModelService.getLatestDelegation({ + eserviceId, + states: [delegationState.active, delegationState.waitingForApproval], + }); + + if (delegation) { + throw eserviceWithActiveOrPendingDelegation(eserviceId, delegation.id); + } +} + export function assertIsDraftEservice(eservice: EService): void { if (eservice.descriptors.some((d) => d.state !== descriptorState.draft)) { throw eserviceNotInDraftState(eservice.id); @@ -59,7 +107,9 @@ export function assertTenantKindExists( export function assertHasNoDraftDescriptor(eservice: EService): void { const hasDraftDescriptor = eservice.descriptors.some( - (d: Descriptor) => d.state === descriptorState.draft + (d: Descriptor) => + d.state === descriptorState.draft || + d.state === descriptorState.waitingForApproval ); if (hasDraftDescriptor) { throw draftDescriptorAlreadyExists(eservice.id); diff --git a/packages/catalog-process/src/utilities/errorMappers.ts b/packages/catalog-process/src/utilities/errorMappers.ts index a667f1b13d..718c4eb7e7 100644 --- a/packages/catalog-process/src/utilities/errorMappers.ts +++ b/packages/catalog-process/src/utilities/errorMappers.ts @@ -295,3 +295,21 @@ export const updateEServiceDescriptionErrorMapper = ( .with("operationForbidden", () => HTTP_STATUS_FORBIDDEN) .with("eserviceWithoutValidDescriptors", () => HTTP_STATUS_CONFLICT) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); + +export const approveDelegatedEServiceDescriptorErrorMapper = ( + error: ApiError +): number => + match(error.code) + .with("eServiceNotFound", () => HTTP_STATUS_NOT_FOUND) + .with("eServiceDescriptorNotFound", () => HTTP_STATUS_NOT_FOUND) + .with("operationForbidden", () => HTTP_STATUS_FORBIDDEN) + .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); + +export const rejectDelegatedEServiceDescriptorErrorMapper = ( + error: ApiError +): number => + match(error.code) + .with("eServiceNotFound", () => HTTP_STATUS_NOT_FOUND) + .with("eServiceDescriptorNotFound", () => HTTP_STATUS_NOT_FOUND) + .with("operationForbidden", () => HTTP_STATUS_FORBIDDEN) + .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); diff --git a/packages/catalog-process/test/activateDescriptor.test.ts b/packages/catalog-process/test/activateDescriptor.test.ts index 4bccaad41b..0e93a72309 100644 --- a/packages/catalog-process/test/activateDescriptor.test.ts +++ b/packages/catalog-process/test/activateDescriptor.test.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -8,7 +11,9 @@ import { EServiceDescriptorActivatedV2, toEServiceV2, operationForbidden, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -24,6 +29,7 @@ import { getMockEService, getMockDescriptor, getMockDocument, + addOneDelegation, } from "./utils.js"; describe("activate descriptor", () => { @@ -48,7 +54,7 @@ describe("activate descriptor", () => { logger: genericLogger, }); - const updatedDescriptor = { + const expectedDescriptor = { ...descriptor, state: descriptorState.published, }; @@ -65,7 +71,61 @@ describe("activate descriptor", () => { const expectedEservice = toEServiceV2({ ...eservice, - descriptors: [updatedDescriptor], + descriptors: [expectedDescriptor], + }); + expect(writtenPayload.eservice).toEqual(expectedEservice); + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + }); + + it("should write on event-store for the activation of a descriptor (delegate)", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + interface: mockDocument, + state: descriptorState.suspended, + }; + + const delegate = getMockAuthData(); + + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: delegate.organizationId, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.activateDescriptor(eservice.id, descriptor.id, { + authData: delegate, + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const expectedDescriptor = { + ...descriptor, + state: descriptorState.published, + }; + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceDescriptorActivated"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorActivatedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [expectedDescriptor], }); expect(writtenPayload.eservice).toEqual(expectedEservice); expect(writtenPayload.descriptorId).toEqual(descriptor.id); @@ -122,94 +182,61 @@ describe("activate descriptor", () => { ).rejects.toThrowError(operationForbidden); }); - it("should throw notValidDescriptor if the descriptor is in draft state", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - await addOneEService(eservice); - expect( - catalogService.activateDescriptor(mockEService.id, mockDescriptor.id, { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - }) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.draft) - ); - }); - - it("should throw notValidDescriptor if the descriptor is in published state", async () => { + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { const descriptor: Descriptor = { ...mockDescriptor, interface: mockDocument, - state: descriptorState.published, + state: descriptorState.suspended, }; const eservice: EService = { ...mockEService, descriptors: [descriptor], }; - await addOneEService(eservice); - expect( - catalogService.activateDescriptor(mockEService.id, mockDescriptor.id, { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - }) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.published) - ); - }); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); - it("should throw notValidDescriptor if the descriptor is in deprecated state", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - interface: mockDocument, - state: descriptorState.deprecated, - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; await addOneEService(eservice); - expect( - catalogService.activateDescriptor(mockEService.id, mockDescriptor.id, { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - }) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.deprecated) - ); - }); + await addOneDelegation(delegation); - it("should throw notValidDescriptor if the descriptor is in archived state", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - interface: mockDocument, - state: descriptorState.archived, - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - await addOneEService(eservice); expect( - catalogService.activateDescriptor(mockEService.id, mockDescriptor.id, { + catalogService.activateDescriptor(eservice.id, descriptor.id, { authData: getMockAuthData(eservice.producerId), correlationId: generateId(), serviceName: "", logger: genericLogger, }) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.archived) - ); + ).rejects.toThrowError(operationForbidden); }); + + it.each([ + descriptorState.draft, + descriptorState.published, + descriptorState.deprecated, + descriptorState.archived, + descriptorState.waitingForApproval, + ])( + "should throw notValidDescriptor if the descriptor is in state %s", + async (state) => { + const descriptor: Descriptor = { + ...mockDescriptor, + state, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + expect( + catalogService.activateDescriptor(mockEService.id, mockDescriptor.id, { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError(notValidDescriptor(descriptor.id, state)); + } + ); }); diff --git a/packages/catalog-process/test/approveDelegatedEServiceDescriptor.test.ts b/packages/catalog-process/test/approveDelegatedEServiceDescriptor.test.ts new file mode 100644 index 0000000000..da6a51c32f --- /dev/null +++ b/packages/catalog-process/test/approveDelegatedEServiceDescriptor.test.ts @@ -0,0 +1,362 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { genericLogger } from "pagopa-interop-commons"; +import { + decodeProtobufPayload, + getMockTenant, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; +import { + Descriptor, + descriptorState, + EService, + toEServiceV2, + Tenant, + generateId, + operationForbidden, + delegationState, + EServiceDescriptorDelegatorApprovedV2, + delegationKind, +} from "pagopa-interop-models"; +import { beforeAll, vi, afterAll, expect, describe, it } from "vitest"; +import { + eServiceNotFound, + eServiceDescriptorNotFound, + notValidDescriptor, +} from "../src/model/domain/errors.js"; +import { + addOneEService, + catalogService, + getMockAuthData, + readLastEserviceEvent, + addOneTenant, + addOneAgreement, + getMockEService, + getMockDescriptor, + getMockDocument, + getMockAgreement, + addOneDelegation, +} from "./utils.js"; + +describe("publish descriptor", () => { + const mockEService = getMockEService(); + const mockDescriptor = getMockDescriptor(); + const mockDocument = getMockDocument(); + beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date()); + }); + afterAll(() => { + vi.useRealTimers(); + }); + it("should write on event-store for the publication of a waiting for approval descriptor", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.waitingForApproval, + interface: mockDocument, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + await catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorDelegatorApproved", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDelegatorApprovedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + publishedAt: new Date(), + state: descriptorState.published, + }, + ], + }); + + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + }); + + it("should also archive the previously published descriptor", async () => { + const descriptor1: Descriptor = { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + publishedAt: new Date(), + interface: mockDocument, + }; + const descriptor2: Descriptor = { + ...mockDescriptor, + id: generateId(), + state: descriptorState.waitingForApproval, + interface: mockDocument, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor1, descriptor2], + }; + await addOneEService(eservice); + await catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + descriptor2.id, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorDelegatorApproved", + event_version: 2, + }); + + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDelegatorApprovedV2, + payload: writtenEvent.data, + }); + + const updatedDescriptor1: Descriptor = { + ...descriptor1, + archivedAt: new Date(), + state: descriptorState.archived, + }; + const updatedDescriptor2: Descriptor = { + ...descriptor2, + publishedAt: new Date(), + state: descriptorState.published, + }; + + const expectedEservice: EService = { + ...eservice, + descriptors: [updatedDescriptor1, updatedDescriptor2], + }; + expect(writtenPayload).toEqual({ + eservice: toEServiceV2(expectedEservice), + descriptorId: descriptor2.id, + }); + }); + + it("should also write deprecate the previously published descriptor if there was a valid agreement", async () => { + const descriptor1: Descriptor = { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + publishedAt: new Date(), + interface: mockDocument, + }; + const descriptor2: Descriptor = { + ...mockDescriptor, + id: generateId(), + state: descriptorState.waitingForApproval, + interface: mockDocument, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor1, descriptor2], + }; + await addOneEService(eservice); + const tenant: Tenant = { + ...getMockTenant(), + }; + await addOneTenant(tenant); + const agreement = getMockAgreement({ + eserviceId: eservice.id, + descriptorId: descriptor1.id, + producerId: eservice.producerId, + consumerId: tenant.id, + }); + await addOneAgreement(agreement); + await catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + descriptor2.id, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorDelegatorApproved", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDelegatorApprovedV2, + payload: writtenEvent.data, + }); + + const updatedDescriptor1: Descriptor = { + ...descriptor1, + deprecatedAt: new Date(), + state: descriptorState.deprecated, + }; + const updatedDescriptor2: Descriptor = { + ...descriptor2, + publishedAt: new Date(), + state: descriptorState.published, + }; + + const expectedEservice: EService = { + ...eservice, + descriptors: [updatedDescriptor1, updatedDescriptor2], + }; + expect(writtenPayload).toEqual({ + eservice: toEServiceV2(expectedEservice), + descriptorId: descriptor2.id, + }); + }); + + it("should throw eServiceNotFound if the eService doesn't exist", async () => { + await expect( + catalogService.approveDelegatedEServiceDescriptor( + mockEService.id, + mockDescriptor.id, + { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(eServiceNotFound(mockEService.id)); + }); + + it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { + const eservice: EService = { + ...mockEService, + descriptors: [], + }; + await addOneEService(eservice); + expect( + catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + mockDescriptor.id, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError( + eServiceDescriptorNotFound(eservice.id, mockDescriptor.id) + ); + }); + + it("should throw operationForbidden if the requester is not the producer", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + expect( + catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + { + authData: getMockAuthData(), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + + it("should throw operationForbidden if the requester is the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + + it.each( + Object.values(descriptorState).filter( + (s) => s !== descriptorState.waitingForApproval + ) + )( + "should throw notValidDescriptor if the descriptor is in %s state", + async (state) => { + const descriptor: Descriptor = { + ...mockDescriptor, + interface: mockDocument, + state, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + expect( + catalogService.approveDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(notValidDescriptor(descriptor.id, state)); + } + ); +}); diff --git a/packages/catalog-process/test/archiveDescriptor.test.ts b/packages/catalog-process/test/archiveDescriptor.test.ts index 9b56a416e5..955f9221cd 100644 --- a/packages/catalog-process/test/archiveDescriptor.test.ts +++ b/packages/catalog-process/test/archiveDescriptor.test.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -9,7 +12,9 @@ import { EServiceDescriptorActivatedV2, toEServiceV2, operationForbidden, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -17,6 +22,7 @@ import { eServiceDescriptorNotFound, } from "../src/model/domain/errors.js"; import { + addOneDelegation, addOneEService, catalogService, getMockAuthData, @@ -58,7 +64,62 @@ describe("archive descriptor", () => { payload: writtenEvent.data, }); - const updatedDescriptor = { + const expectedDescriptor = { + ...descriptor, + state: descriptorState.archived, + archivedAt: new Date( + Number(writtenPayload.eservice!.descriptors[0]!.archivedAt) + ), + }; + + const expectedEService = toEServiceV2({ + ...eservice, + descriptors: [expectedDescriptor], + }); + expect(writtenPayload.eservice).toEqual(expectedEService); + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + }); + + it("should write on event-store for the archiving of a descriptor (delegate)", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + interface: mockDocument, + state: descriptorState.suspended, + }; + const delegate = getMockAuthData(); + + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: delegate.organizationId, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.archiveDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(delegate.organizationId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceDescriptorArchived"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorActivatedV2, + payload: writtenEvent.data, + }); + + const expectedDescriptor = { ...descriptor, state: descriptorState.archived, archivedAt: new Date( @@ -68,7 +129,7 @@ describe("archive descriptor", () => { const expectedEService = toEServiceV2({ ...eservice, - descriptors: [updatedDescriptor], + descriptors: [expectedDescriptor], }); expect(writtenPayload.eservice).toEqual(expectedEService); expect(writtenPayload.descriptorId).toEqual(descriptor.id); @@ -123,4 +184,32 @@ describe("archive descriptor", () => { }) ).rejects.toThrowError(operationForbidden); }); + + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.archiveDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError(operationForbidden); + }); }); diff --git a/packages/catalog-process/test/cloneDescriptor.test.ts b/packages/catalog-process/test/cloneDescriptor.test.ts index 5031a026b6..dd754228a5 100644 --- a/packages/catalog-process/test/cloneDescriptor.test.ts +++ b/packages/catalog-process/test/cloneDescriptor.test.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger, FileManagerError } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -12,6 +15,8 @@ import { toEServiceV2, generateId, operationForbidden, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { beforeAll, vi, afterAll, expect, describe, it } from "vitest"; import { formatDateddMMyyyyHHmmss } from "pagopa-interop-commons"; @@ -30,6 +35,7 @@ import { getMockEService, getMockDescriptor, getMockDocument, + addOneDelegation, } from "./utils.js"; describe("clone descriptor", () => { @@ -314,6 +320,33 @@ describe("clone descriptor", () => { }) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester is a producer delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.cloneDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError(operationForbidden); + }); it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { const eservice: EService = { ...mockEService, diff --git a/packages/catalog-process/test/createDescriptor.test.ts b/packages/catalog-process/test/createDescriptor.test.ts index bf74a79e47..410233dabf 100644 --- a/packages/catalog-process/test/createDescriptor.test.ts +++ b/packages/catalog-process/test/createDescriptor.test.ts @@ -3,6 +3,7 @@ import { genericLogger } from "pagopa-interop-commons"; import { decodeProtobufPayload, + getMockDelegation, readEventByStreamIdAndVersion, } from "pagopa-interop-commons-test"; import { @@ -15,6 +16,8 @@ import { descriptorState, operationForbidden, EServiceDescriptorDocumentAddedV2, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { catalogApi } from "pagopa-interop-api-clients"; @@ -35,6 +38,7 @@ import { getMockEService, buildCreateDescriptorSeed, postgresDB, + addOneDelegation, } from "./utils.js"; describe("create descriptor", async () => { @@ -248,31 +252,159 @@ describe("create descriptor", async () => { }); }); - it("should throw draftDescriptorAlreadyExists if a draft descriptor already exists", async () => { - const descriptor: Descriptor = { + it("should write on event-store for the creation of a descriptor (delegate)", async () => { + const mockDocument = getMockDocument(); + const existingDescriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.published, + }; + const mockDescriptor: Descriptor = { ...getMockDescriptor(), - state: descriptorState.draft, + docs: [mockDocument], }; const eservice: EService = { ...getMockEService(), - descriptors: [descriptor], + descriptors: [existingDescriptor], }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); await addOneEService(eservice); - expect( - catalogService.createDescriptor( - eservice.id, - buildCreateDescriptorSeed(descriptor), - { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - } - ) - ).rejects.toThrowError(draftDescriptorAlreadyExists(eservice.id)); + await addOneDelegation(delegation); + + const attribute: Attribute = { + name: "Attribute name", + id: generateId(), + kind: "Declared", + description: "Attribute Description", + creationTime: new Date(), + }; + await addOneAttribute(attribute); + const descriptorSeed: catalogApi.EServiceDescriptorSeed = { + ...buildCreateDescriptorSeed(mockDescriptor), + attributes: { + certified: [], + declared: [ + [{ id: attribute.id, explicitAttributeVerification: false }], + ], + verified: [], + }, + }; + + const returnedDescriptor = await catalogService.createDescriptor( + eservice.id, + descriptorSeed, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const newDescriptorId = returnedDescriptor.id; + const descriptorCreationEvent = await readEventByStreamIdAndVersion( + eservice.id, + 1, + "catalog", + postgresDB + ); + const documentAdditionEvent = await readLastEserviceEvent(eservice.id); + + expect(descriptorCreationEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorAdded", + event_version: 2, + }); + expect(documentAdditionEvent).toMatchObject({ + stream_id: eservice.id, + version: "2", + type: "EServiceDescriptorDocumentAdded", + event_version: 2, + }); + + const descriptorCreationPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorAddedV2, + payload: descriptorCreationEvent.data, + }); + const documentAdditionPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDocumentAddedV2, + payload: documentAdditionEvent.data, + }); + + const newDescriptor: Descriptor = { + ...mockDescriptor, + version: "2", + createdAt: new Date(), + id: newDescriptorId, + serverUrls: [], + attributes: { + certified: [], + declared: [ + [{ id: attribute.id, explicitAttributeVerification: false }], + ], + verified: [], + }, + docs: [], + }; + + const expectedEserviceAfterDescriptorCreation: EService = { + ...eservice, + descriptors: [...eservice.descriptors, newDescriptor], + }; + const expectedEserviceAfterDocumentAddition: EService = { + ...expectedEserviceAfterDescriptorCreation, + descriptors: expectedEserviceAfterDescriptorCreation.descriptors.map( + (d) => + d.id === newDescriptor.id + ? { ...newDescriptor, docs: [mockDocument] } + : d + ), + }; + + expect(descriptorCreationPayload).toEqual({ + descriptorId: newDescriptorId, + eservice: toEServiceV2(expectedEserviceAfterDescriptorCreation), + }); + expect(documentAdditionPayload).toEqual({ + documentId: mockDocument.id, + descriptorId: newDescriptorId, + eservice: toEServiceV2(expectedEserviceAfterDocumentAddition), + }); }); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should throw draftDescriptorAlreadyExists if a descriptor with state %s already exists", + async (state) => { + const descriptor: Descriptor = { + ...getMockDescriptor(), + state, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + }; + + await addOneEService(eservice); + expect( + catalogService.createDescriptor( + eservice.id, + buildCreateDescriptorSeed(descriptor), + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(draftDescriptorAlreadyExists(eservice.id)); + } + ); + it("should throw eServiceNotFound if the eservice doesn't exist", async () => { const mockEService = getMockEService(); expect( @@ -359,6 +491,38 @@ describe("create descriptor", async () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(), + interface: getMockDocument(), + state: descriptorState.published, + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.createDescriptor( + eservice.id, + buildCreateDescriptorSeed(descriptor), + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("should throw inconsistentDailyCalls if dailyCallsPerConsumer is greater than dailyCallsTotal", async () => { const descriptorSeed: catalogApi.EServiceDescriptorSeed = { ...buildCreateDescriptorSeed(getMockDescriptor()), diff --git a/packages/catalog-process/test/createRiskAnalysis.test.ts b/packages/catalog-process/test/createRiskAnalysis.test.ts index 595fde0328..f68c947c36 100644 --- a/packages/catalog-process/test/createRiskAnalysis.test.ts +++ b/packages/catalog-process/test/createRiskAnalysis.test.ts @@ -10,6 +10,7 @@ import { getMockTenant, getMockValidRiskAnalysis, decodeProtobufPayload, + getMockDelegation, } from "pagopa-interop-commons-test/index.js"; import { TenantKind, @@ -22,7 +23,9 @@ import { toEServiceV2, unsafeBrandId, operationForbidden, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { catalogApi } from "pagopa-interop-api-clients"; import { expect, describe, it } from "vitest"; @@ -44,6 +47,7 @@ import { readLastEserviceEvent, getMockDescriptor, getMockEService, + addOneDelegation, } from "./utils.js"; describe("create risk analysis", () => { @@ -141,6 +145,104 @@ describe("create risk analysis", () => { ); expect(writtenPayload.eservice).toEqual(expectedEservice); }); + it("should write on event-store for the creation of a risk analysis (delegate)", async () => { + const producerTenantKind: TenantKind = randomArrayItem( + Object.values(tenantKind) + ); + const producer: Tenant = { + ...getMockTenant(), + kind: producerTenantKind, + }; + + const mockValidRiskAnalysis = getMockValidRiskAnalysis(producerTenantKind); + const riskAnalysisSeed: catalogApi.EServiceRiskAnalysisSeed = + buildRiskAnalysisSeed(mockValidRiskAnalysis); + + const eservice: EService = { + ...mockEService, + producerId: producer.id, + mode: eserviceMode.receive, + descriptors: [ + { + ...mockDescriptor, + state: descriptorState.draft, + }, + ], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneTenant(producer); + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.createRiskAnalysis(eservice.id, riskAnalysisSeed, { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceRiskAnalysisAdded"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceRiskAnalysisAddedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + riskAnalysis: [ + { + ...mockValidRiskAnalysis, + id: unsafeBrandId(writtenPayload.eservice!.riskAnalysis[0]!.id), + createdAt: new Date( + Number(writtenPayload.eservice!.riskAnalysis[0]!.createdAt) + ), + riskAnalysisForm: { + ...mockValidRiskAnalysis.riskAnalysisForm, + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.id + ), + singleAnswers: + mockValidRiskAnalysis.riskAnalysisForm.singleAnswers.map( + (singleAnswer) => ({ + ...singleAnswer, + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.singleAnswers.find( + (sa) => sa.key === singleAnswer.key + )!.id + ), + }) + ), + multiAnswers: + mockValidRiskAnalysis.riskAnalysisForm.multiAnswers.map( + (multiAnswer) => ({ + ...multiAnswer, + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.multiAnswers.find( + (ma) => ma.key === multiAnswer.key + )!.id + ), + }) + ), + }, + }, + ], + }); + + expect(writtenPayload.riskAnalysisId).toEqual( + expectedEservice.riskAnalysis[0].id + ); + expect(writtenPayload.eservice).toEqual(expectedEservice); + }); it("should throw eServiceNotFound if the eservice doesn't exist", async () => { expect( catalogService.createRiskAnalysis( @@ -170,6 +272,28 @@ describe("create risk analysis", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + state: delegationState.active, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + expect( + catalogService.createRiskAnalysis( + mockEService.id, + buildRiskAnalysisSeed(getMockValidRiskAnalysis(tenantKind.PA)), + { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("should throw eserviceNotInDraftState if the eservice is not in draft state", async () => { const eservice: EService = { ...mockEService, diff --git a/packages/catalog-process/test/deleteDocument.test.ts b/packages/catalog-process/test/deleteDocument.test.ts index 971abde23f..30d3be78cf 100644 --- a/packages/catalog-process/test/deleteDocument.test.ts +++ b/packages/catalog-process/test/deleteDocument.test.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger, fileManagerDeleteError } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -9,7 +12,9 @@ import { toEServiceV2, EServiceDescriptorInterfaceDeletedV2, operationForbidden, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { vi, expect, describe, it } from "vitest"; import { @@ -28,6 +33,7 @@ import { getMockDescriptor, getMockEService, getMockDocument, + addOneDelegation, } from "./utils.js"; describe("delete Document", () => { @@ -36,7 +42,9 @@ describe("delete Document", () => { const mockDocument = getMockDocument(); it.each( Object.values(descriptorState).filter( - (state) => state !== descriptorState.archived + (state) => + state !== descriptorState.archived && + state !== descriptorState.waitingForApproval ) )( "should write on event-store for the deletion of a document, and delete the file from the bucket, for %s descriptor", @@ -197,6 +205,92 @@ describe("delete Document", () => { ).not.toContain(interfaceDocument.path); }); + it("should write on event-store for the deletion of a document that is the descriptor interface, and delete the file from the bucket (delegate)", async () => { + vi.spyOn(fileManager, "delete"); + + const interfaceDocument = { + ...mockDocument, + path: `${config.eserviceDocumentsPath}/${mockDocument.id}/${mockDocument.name}`, + }; + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + interface: interfaceDocument, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + await fileManager.storeBytes( + { + bucket: config.s3Bucket, + path: config.eserviceDocumentsPath, + resourceId: interfaceDocument.id, + name: interfaceDocument.name, + content: Buffer.from("testtest"), + }, + genericLogger + ); + expect( + await fileManager.listFiles(config.s3Bucket, genericLogger) + ).toContain(interfaceDocument.path); + + await catalogService.deleteDocument( + eservice.id, + descriptor.id, + interfaceDocument.id, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceDescriptorInterfaceDeleted"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorInterfaceDeletedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + interface: undefined, + serverUrls: [], + }, + ], + }); + + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.documentId).toEqual(interfaceDocument.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + + expect(fileManager.delete).toHaveBeenCalledWith( + config.s3Bucket, + interfaceDocument.path, + genericLogger + ); + expect( + await fileManager.listFiles(config.s3Bucket, genericLogger) + ).not.toContain(interfaceDocument.path); + }); + it("should fail if the file deletion fails", async () => { config.s3Bucket = "invalid-bucket"; // configure an invalid bucket to force a failure @@ -271,6 +365,38 @@ describe("delete Document", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + docs: [mockDocument], + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + expect( + catalogService.deleteDocument( + eservice.id, + descriptor.id, + mockDocument.id, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { const eservice: EService = { ...mockEService, @@ -296,7 +422,9 @@ describe("delete Document", () => { }); it.each( Object.values(descriptorState).filter( - (state) => state === descriptorState.archived + (state) => + state === descriptorState.archived || + state === descriptorState.waitingForApproval ) )( "should throw notValidDescriptor if the descriptor is in s% state", diff --git a/packages/catalog-process/test/deleteDraftDescriptor.test.ts b/packages/catalog-process/test/deleteDraftDescriptor.test.ts index 886c3b146d..3437c6221c 100644 --- a/packages/catalog-process/test/deleteDraftDescriptor.test.ts +++ b/packages/catalog-process/test/deleteDraftDescriptor.test.ts @@ -2,6 +2,7 @@ import { genericLogger, fileManagerDeleteError } from "pagopa-interop-commons"; import { decodeProtobufPayload, + getMockDelegation, readEventByStreamIdAndVersion, } from "pagopa-interop-commons-test/index.js"; import { @@ -14,6 +15,8 @@ import { DescriptorId, generateId, EServiceDeletedV2, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { vi, expect, describe, it } from "vitest"; import { config } from "../src/config/config.js"; @@ -32,6 +35,7 @@ import { getMockDescriptor, getMockDocument, postgresDB, + addOneDelegation, } from "./utils.js"; describe("delete draft descriptor", () => { @@ -293,6 +297,81 @@ describe("delete draft descriptor", () => { }); }); + it("should write on event-store for the deletion of a draft descriptor and the entire eservice (delegate)", async () => { + const draftDescriptor: Descriptor = { + ...getMockDescriptor(descriptorState.draft), + }; + + const eservice: EService = { + ...getMockEService(), + descriptors: [draftDescriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.deleteDraftDescriptor( + eservice.id, + draftDescriptor.id, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const descriptorDeletionEvent = await readEventByStreamIdAndVersion( + eservice.id, + 1, + "catalog", + postgresDB + ); + + const eserviceDeletionEvent = await readLastEserviceEvent(eservice.id); + + expect(descriptorDeletionEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDraftDescriptorDeleted", + event_version: 2, + }); + expect(eserviceDeletionEvent).toMatchObject({ + stream_id: eservice.id, + version: "2", + type: "EServiceDeleted", + event_version: 2, + }); + + const descriptorDeletionPayload = decodeProtobufPayload({ + messageType: EServiceDraftDescriptorDeletedV2, + payload: descriptorDeletionEvent.data, + }); + const eserviceDeletionPayload = decodeProtobufPayload({ + messageType: EServiceDeletedV2, + payload: eserviceDeletionEvent.data, + }); + + const expectedEserviceBeforeDeletion: EService = { + ...eservice, + descriptors: [], + }; + + expect(descriptorDeletionPayload).toEqual({ + eservice: toEServiceV2(expectedEserviceBeforeDeletion), + descriptorId: draftDescriptor.id, + }); + expect(eserviceDeletionPayload).toEqual({ + eserviceId: eservice.id, + eservice: toEServiceV2(expectedEserviceBeforeDeletion), + }); + }); + it("should fail if one of the file deletions fails", async () => { config.s3Bucket = "invalid-bucket"; // configure an invalid bucket to force a failure @@ -383,6 +462,37 @@ describe("delete draft descriptor", () => { ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const publishedDescriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + version: "1", + }; + const descriptorToDelete: Descriptor = { + ...getMockDescriptor(descriptorState.draft), + version: "2", + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [publishedDescriptor, descriptorToDelete], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + expect( + catalogService.deleteDraftDescriptor(eservice.id, descriptorToDelete.id, { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError(operationForbidden); + }); + it.each([descriptorState.published, descriptorState.suspended])( "should throw notValidDescriptor if the eservice is in %s state", async (state) => { diff --git a/packages/catalog-process/test/deleteEservice.test.ts b/packages/catalog-process/test/deleteEservice.test.ts index 48d7925a7d..ea5f24853c 100644 --- a/packages/catalog-process/test/deleteEservice.test.ts +++ b/packages/catalog-process/test/deleteEservice.test.ts @@ -2,6 +2,7 @@ import { genericLogger } from "pagopa-interop-commons"; import { decodeProtobufPayload, + getMockDelegation, readEventByStreamIdAndVersion, } from "pagopa-interop-commons-test/index.js"; import { @@ -12,12 +13,15 @@ import { operationForbidden, EServiceDraftDescriptorDeletedV2, toEServiceV2, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it, vi } from "vitest"; import { eServiceNotFound, eserviceNotInDraftState, + eserviceWithActiveOrPendingDelegation, } from "../src/model/domain/errors.js"; import { config } from "../src/config/config.js"; import { @@ -30,6 +34,7 @@ import { getMockEService, postgresDB, fileManager, + addOneDelegation, } from "./utils.js"; describe("delete eservice", () => { @@ -207,6 +212,54 @@ describe("delete eservice", () => { ).rejects.toThrowError(operationForbidden); }); + it.each([delegationState.active, delegationState.waitingForApproval])( + "should throw eserviceWithActiveOrPendingDelegation if the eservice is associated with a delegation with state %s", + async (delegationState) => { + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + state: delegationState, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + expect( + catalogService.deleteEService(mockEService.id, { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError( + eserviceWithActiveOrPendingDelegation(mockEService.id, delegation.id) + ); + } + ); + + it.each([delegationState.revoked, delegationState.rejected])( + "should not throw eserviceWithActiveOrPendingDelegation if the eservice is associated with a delegation with state %s", + async (delegationState) => { + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + state: delegationState, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + expect( + catalogService.deleteEService(mockEService.id, { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).resolves.not.toThrowError( + eserviceWithActiveOrPendingDelegation(mockEService.id, delegation.id) + ); + } + ); + it("should throw eserviceNotInDraftState if the eservice has both draft and non-draft descriptors", async () => { const descriptor1: Descriptor = { ...mockDescriptor, diff --git a/packages/catalog-process/test/deleteRiskAnalysis.test.ts b/packages/catalog-process/test/deleteRiskAnalysis.test.ts index 5c47a29a81..7df1ab5eb7 100644 --- a/packages/catalog-process/test/deleteRiskAnalysis.test.ts +++ b/packages/catalog-process/test/deleteRiskAnalysis.test.ts @@ -3,6 +3,7 @@ import { genericLogger } from "pagopa-interop-commons"; import { getMockValidRiskAnalysis, decodeProtobufPayload, + getMockDelegation, } from "pagopa-interop-commons-test/index.js"; import { EService, @@ -13,6 +14,8 @@ import { Descriptor, descriptorState, operationForbidden, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -21,6 +24,7 @@ import { eserviceNotInDraftState, } from "../src/model/domain/errors.js"; import { + addOneDelegation, addOneEService, catalogService, getMockAuthData, @@ -74,6 +78,93 @@ describe("delete risk analysis", () => { eservice: expectedEservice, }); }); + it("should write on event-store for the deletion of a risk analysis (delegate)", async () => { + const riskAnalysis = getMockValidRiskAnalysis("PA"); + const eservice: EService = { + ...mockEService, + descriptors: [], + riskAnalysis: [riskAnalysis], + mode: "Receive", + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.deleteRiskAnalysis(eservice.id, riskAnalysis.id, { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + const expectedEservice = toEServiceV2({ + ...eservice, + riskAnalysis: eservice.riskAnalysis.filter( + (r) => r.id !== riskAnalysis.id + ), + }); + + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceRiskAnalysisDeleted", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceRiskAnalysisDeletedV2, + payload: writtenEvent.data, + }); + expect(writtenPayload).toEqual({ + riskAnalysisId: riskAnalysis.id, + eservice: expectedEservice, + }); + }); + it("should write on event-store for the deletion of a risk analysis", async () => { + const riskAnalysis = getMockValidRiskAnalysis("PA"); + const eservice: EService = { + ...mockEService, + descriptors: [], + riskAnalysis: [riskAnalysis], + mode: "Receive", + }; + await addOneEService(eservice); + + await catalogService.deleteRiskAnalysis(eservice.id, riskAnalysis.id, { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + const expectedEservice = toEServiceV2({ + ...eservice, + riskAnalysis: eservice.riskAnalysis.filter( + (r) => r.id !== riskAnalysis.id + ), + }); + + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceRiskAnalysisDeleted", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceRiskAnalysisDeletedV2, + payload: writtenEvent.data, + }); + expect(writtenPayload).toEqual({ + riskAnalysisId: riskAnalysis.id, + eservice: expectedEservice, + }); + }); it("should throw eServiceNotFound if the eservice doesn't exist", () => { expect( catalogService.deleteRiskAnalysis( @@ -166,4 +257,39 @@ describe("delete risk analysis", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.published, + interface: mockDocument, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + riskAnalysis: [getMockValidRiskAnalysis("PA")], + mode: "Receive", + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.deleteRiskAnalysis( + eservice.id, + generateId(), + { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); }); diff --git a/packages/catalog-process/test/getDocumentById.test.ts b/packages/catalog-process/test/getDocumentById.test.ts index a7acd6c4fd..3a33d84f90 100644 --- a/packages/catalog-process/test/getDocumentById.test.ts +++ b/packages/catalog-process/test/getDocumentById.test.ts @@ -5,14 +5,18 @@ import { EService, generateId, descriptorState, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; +import { getMockDelegation } from "pagopa-interop-commons-test/index.js"; import { eServiceNotFound, eServiceDescriptorNotFound, eServiceDocumentNotFound, } from "../src/model/domain/errors.js"; import { + addOneDelegation, addOneEService, catalogService, getMockAuthData, @@ -90,6 +94,49 @@ describe("get document by id", () => { expect(result).toEqual(mockDocument); }); + it("should get the interface if it exists (requester is the delegate, admin)", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + interface: mockDocument, + docs: [], + }; + const eservice: EService = { + ...mockEService, + id: generateId(), + name: "eservice 001", + descriptors: [descriptor], + }; + + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + const authData: AuthData = { + ...getMockAuthData(delegation.delegateId), + userRoles: [userRoles.ADMIN_ROLE], + }; + + await addOneEService(eservice); + await addOneDelegation(delegation); + + const result = await catalogService.getDocumentById( + { + eserviceId: eservice.id, + descriptorId: descriptor.id, + documentId: mockDocument.id, + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "", + } + ); + expect(result).toEqual(mockDocument); + }); + it("should throw eServiceNotFound if the eservice doesn't exist", async () => { const authData: AuthData = { ...getMockAuthData(), diff --git a/packages/catalog-process/test/getEserviceById.test.ts b/packages/catalog-process/test/getEserviceById.test.ts index af88cf1782..bff531bbc6 100644 --- a/packages/catalog-process/test/getEserviceById.test.ts +++ b/packages/catalog-process/test/getEserviceById.test.ts @@ -6,10 +6,14 @@ import { EService, generateId, EServiceId, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; +import { getMockDelegation } from "pagopa-interop-commons-test/index.js"; import { eServiceNotFound } from "../src/model/domain/errors.js"; import { + addOneDelegation, addOneEService, catalogService, getMockAuthData, @@ -88,14 +92,58 @@ describe("get eservice by id", () => { ).rejects.toThrowError(eServiceNotFound(notExistingId)); }); - it("should throw eServiceNotFound if there is only a draft descriptor (requester is not the producer)", async () => { - const descriptor = { - ...mockDescriptor, - state: descriptorState.draft, - }; + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should throw eServiceNotFound if there is only a %s descriptor (requester is not the producer)", + async (state) => { + const descriptor = { + ...mockDescriptor, + state, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(mockEService); + expect( + catalogService.getEServiceById(eservice.id, { + authData: getMockAuthData(), + logger: genericLogger, + correlationId: generateId(), + serviceName: "", + }) + ).rejects.toThrowError(eServiceNotFound(eservice.id)); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should throw eServiceNotFound if there is only a %s descriptor (requester is the producer but not admin nor api, nor support)", + async (state) => { + const descriptor = { + ...mockDescriptor, + state, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const authData: AuthData = { + ...getMockAuthData(), + userRoles: [userRoles.SECURITY_ROLE], + }; + await addOneEService(mockEService); + expect( + catalogService.getEServiceById(eservice.id, { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "", + }) + ).rejects.toThrowError(eServiceNotFound(eservice.id)); + } + ); + it("should throw eServiceNotFound if there are no descriptors (requester is not the producer)", async () => { const eservice: EService = { ...mockEService, - descriptors: [descriptor], + descriptors: [], }; await addOneEService(mockEService); expect( @@ -107,7 +155,7 @@ describe("get eservice by id", () => { }) ).rejects.toThrowError(eServiceNotFound(eservice.id)); }); - it("should throw eServiceNotFound if there is only a draft descriptor (requester is the producer but not admin nor api, nor support)", async () => { + it("should throw eServiceNotFound if there are no descriptors (requester is the producer but not admin, nor api, nor support)", async () => { const descriptor = { ...mockDescriptor, state: descriptorState.draft, @@ -117,7 +165,7 @@ describe("get eservice by id", () => { descriptors: [descriptor], }; const authData: AuthData = { - ...getMockAuthData(), + ...getMockAuthData(eservice.producerId), userRoles: [userRoles.SECURITY_ROLE], }; await addOneEService(mockEService); @@ -130,44 +178,107 @@ describe("get eservice by id", () => { }) ).rejects.toThrowError(eServiceNotFound(eservice.id)); }); - it("should throw eServiceNotFound if there are no descriptors (requester is not the producer)", async () => { - const eservice: EService = { - ...mockEService, - descriptors: [], - }; - await addOneEService(mockEService); - expect( - catalogService.getEServiceById(eservice.id, { - authData: getMockAuthData(), + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should filter out the %s descriptors if the eservice has both of that state and not (requester is not the producer)", + async (state) => { + const descriptorA: Descriptor = { + ...mockDescriptor, + state, + }; + const descriptorB: Descriptor = { + ...mockDescriptor, + state: descriptorState.published, + interface: mockDocument, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptorA, descriptorB], + }; + const authData: AuthData = { + ...getMockAuthData(), + userRoles: [userRoles.ADMIN_ROLE], + }; + await addOneEService(eservice); + const result = await catalogService.getEServiceById(eservice.id, { + authData, logger: genericLogger, correlationId: generateId(), serviceName: "", - }) - ).rejects.toThrowError(eServiceNotFound(eservice.id)); - }); - it("should throw eServiceNotFound if there are no descriptors (requester is the producer but not admin, nor api, nor support)", async () => { - const descriptor = { - ...mockDescriptor, - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - const authData: AuthData = { - ...getMockAuthData(eservice.producerId), - userRoles: [userRoles.SECURITY_ROLE], - }; - await addOneEService(mockEService); - expect( - catalogService.getEServiceById(eservice.id, { + }); + expect(result.descriptors).toEqual([descriptorB]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should filter out the %s descriptors if the eservice has both of that state and not (requester is the producer but not admin nor api, nor support)", + async (state) => { + const descriptorA: Descriptor = { + ...mockDescriptor, + state, + }; + const descriptorB: Descriptor = { + ...mockDescriptor, + state: descriptorState.published, + interface: mockDocument, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptorA, descriptorB], + }; + const authData: AuthData = { + ...getMockAuthData(eservice.producerId), + userRoles: [userRoles.SECURITY_ROLE], + }; + await addOneEService(eservice); + const result = await catalogService.getEServiceById(eservice.id, { authData, logger: genericLogger, correlationId: generateId(), serviceName: "", - }) - ).rejects.toThrowError(eServiceNotFound(eservice.id)); - }); + }); + expect(result.descriptors).toEqual([descriptorB]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should not filter out the %s descriptors if the eservice has both of that state and not (requester is delegate)", + async (state) => { + const descriptorA: Descriptor = { + ...mockDescriptor, + state, + }; + const descriptorB: Descriptor = { + ...mockDescriptor, + state: descriptorState.published, + interface: mockDocument, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptorA, descriptorB], + }; + const authData: AuthData = { + ...getMockAuthData(), + userRoles: [userRoles.ADMIN_ROLE], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: authData.organizationId, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + const result = await catalogService.getEServiceById(eservice.id, { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "", + }); + expect(result.descriptors).toEqual([descriptorA, descriptorB]); + } + ); it("should filter out the draft descriptors if the eservice has both draft and non-draft ones (requester is not the producer)", async () => { const descriptorA: Descriptor = { ...mockDescriptor, @@ -224,4 +335,39 @@ describe("get eservice by id", () => { }); expect(result.descriptors).toEqual([descriptorB]); }); + it("should not filter out the draft descriptors if the eservice has both draft and non-draft ones (requester is delegate)", async () => { + const descriptorA: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const descriptorB: Descriptor = { + ...mockDescriptor, + state: descriptorState.published, + interface: mockDocument, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptorA, descriptorB], + }; + const authData: AuthData = { + ...getMockAuthData(), + userRoles: [userRoles.ADMIN_ROLE], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: authData.organizationId, + eserviceId: eservice.id, + state: delegationState.active, + }); + await addOneEService(eservice); + await addOneDelegation(delegation); + const result = await catalogService.getEServiceById(eservice.id, { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "", + }); + expect(result.descriptors).toEqual([descriptorA, descriptorB]); + }); }); diff --git a/packages/catalog-process/test/getEservices.test.ts b/packages/catalog-process/test/getEservices.test.ts index 49dacf79ac..557be6b713 100644 --- a/packages/catalog-process/test/getEservices.test.ts +++ b/packages/catalog-process/test/getEservices.test.ts @@ -10,8 +10,11 @@ import { eserviceMode, Tenant, agreementState, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { beforeEach, expect, describe, it } from "vitest"; +import { getMockDelegation } from "pagopa-interop-commons-test"; import { addOneEService, addOneTenant, @@ -23,6 +26,7 @@ import { getMockDocument, getMockAgreement, getMockEServiceAttributes, + addOneDelegation, } from "./utils.js"; describe("get eservices", () => { @@ -206,6 +210,62 @@ describe("get eservices", () => { expect(result.totalCount).toBe(3); expect(result.results).toEqual([eservice1, eservice2, eservice3]); }); + it("should get the eServices, including the ones with an active delegation, if they exist (parameters: producersIds)", async () => { + const delegatedOrganization1 = generateId(); + const delegatedOrganization2 = generateId(); + + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: delegatedOrganization1, + eserviceId: eservice4.id, + state: delegationState.active, + }); + await addOneDelegation(delegation1); + + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice5.id, + state: delegationState.active, + delegateId: delegatedOrganization2, + }); + + await addOneDelegation(delegation2); + + const delegation3 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice6.id, + state: delegationState.rejected, + delegateId: delegatedOrganization2, + }); + + await addOneDelegation(delegation3); + + const result = await catalogService.getEServices( + getMockAuthData(), + { + eservicesIds: [], + producersIds: [ + organizationId1, + delegatedOrganization1, + delegatedOrganization2, + ], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(5); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + ]); + }); it("should get the eServices if they exist (parameters: states)", async () => { const result = await catalogService.getEServices( getMockAuthData(), @@ -286,6 +346,105 @@ describe("get eservices", () => { eservice5, ]); }); + it("should get the eServices if they exist (parameters: delegated = true)", async () => { + const delegatedOrganization1 = generateId(); + const delegatedOrganization2 = generateId(); + + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice4.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + await addOneDelegation(delegation1); + + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice5.id, + delegateId: delegatedOrganization2, + state: delegationState.waitingForApproval, + }); + + await addOneDelegation(delegation2); + + const delegation3 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice6.id, + delegateId: delegatedOrganization2, + state: delegationState.rejected, + }); + + await addOneDelegation(delegation3); + + const result = await catalogService.getEServices( + getMockAuthData(), + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + delegated: true, + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(2); + expect(result.results).toEqual([eservice4, eservice5]); + }); + it("should get the eServices if they exist (parameters: delegated = false)", async () => { + const delegatedOrganization1 = generateId(); + const delegatedOrganization2 = generateId(); + + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice4.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + await addOneDelegation(delegation1); + + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice5.id, + delegateId: delegatedOrganization2, + state: delegationState.waitingForApproval, + }); + + await addOneDelegation(delegation2); + + const delegation3 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice6.id, + delegateId: delegatedOrganization2, + state: delegationState.rejected, + }); + + await addOneDelegation(delegation3); + + const result = await catalogService.getEServices( + getMockAuthData(), + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + delegated: false, + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(4); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice6, + ]); + }); it("should get the eServices if they exist (parameters: statestates, name)", async () => { const result = await catalogService.getEServices( getMockAuthData(organizationId3), @@ -340,6 +499,157 @@ describe("get eservices", () => { expect(result.totalCount).toBe(1); expect(result.results).toEqual([eservice5]); }); + it("should get the eServices, including the ones with an active delegation, if they exist (parameters: producersIds, states, name)", async () => { + const delegatedOrganization1: TenantId = generateId(); + const delegatedOrganization2: TenantId = generateId(); + + const delegatedEService1: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 1 test", + producerId: organizationId1, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + interface: mockDocument, + state: descriptorState.published, + }, + ], + }; + + const delegatedEService2: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 2", + producerId: organizationId1, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + interface: mockDocument, + state: descriptorState.published, + }, + ], + }; + + const delegatedEService3: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 3 test", + producerId: organizationId1, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.draft, + }, + ], + }; + + const delegatedEService4: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 4 test", + producerId: organizationId1, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + }, + ], + }; + + const delegatedEService5: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 5 test", + producerId: organizationId1, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + }, + ], + }; + + await addOneEService(delegatedEService1); + await addOneEService(delegatedEService2); + await addOneEService(delegatedEService3); + await addOneEService(delegatedEService4); + await addOneEService(delegatedEService5); + + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService1.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + await addOneDelegation(delegation1); + + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService2.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + + await addOneDelegation(delegation2); + + const delegation3 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService3.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + + await addOneDelegation(delegation3); + + const delegation4 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService4.id, + delegateId: delegatedOrganization2, + state: delegationState.active, + }); + + await addOneDelegation(delegation4); + + const delegation5 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService5.id, + delegateId: delegatedOrganization2, + state: delegationState.waitingForApproval, + }); + + await addOneDelegation(delegation5); + + const result = await catalogService.getEServices( + getMockAuthData(), + { + eservicesIds: [], + producersIds: [ + organizationId2, + delegatedOrganization1, + delegatedOrganization2, + ], + states: ["Published"], + agreementStates: [], + name: "test", + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(3); + expect(result.results).toEqual([ + delegatedEService1, + delegatedEService4, + eservice5, + ]); + }); it("should not get the eServices if they don't exist (parameters: producersIds, states, name)", async () => { const result = await catalogService.getEServices( getMockAuthData(), @@ -440,7 +750,7 @@ describe("get eservices", () => { }); }); - it("should get the eServices if they exist (parameters: producerIds, mode)", async () => { + it("should get the eServices if they exist (parameters: producersIds, mode)", async () => { const result = await catalogService.getEServices( getMockAuthData(), { @@ -461,6 +771,170 @@ describe("get eservices", () => { }); }); + it("should get the eServices if they exist (parameters: producersIds, mode, delegated = true)", async () => { + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice4.id, + delegateId: organizationId3, + state: delegationState.active, + }); + await addOneDelegation(delegation1); + + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice4.id, + delegateId: organizationId3, + state: delegationState.active, + }); + + await addOneDelegation(delegation2); + + const result = await catalogService.getEServices( + getMockAuthData(), + { + eservicesIds: [], + producersIds: [organizationId2], + states: [], + agreementStates: [], + attributesIds: [], + mode: eserviceMode.deliver, + delegated: true, + }, + 0, + 50, + genericLogger + ); + expect(result).toEqual({ + totalCount: 1, + results: [eservice4], + }); + }); + + it("should get the eServices, including the ones with an active delegation, if they exist (parameters: producersIds, mode)", async () => { + const delegatedOrganization1: TenantId = generateId(); + const delegatedOrganization2: TenantId = generateId(); + + const delegatedEService1: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 1", + producerId: organizationId1, + mode: eserviceMode.deliver, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + }, + ], + }; + + const delegatedEService2: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 2", + producerId: organizationId1, + mode: eserviceMode.receive, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + }, + ], + }; + + const delegatedEService3: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 3", + producerId: organizationId1, + mode: eserviceMode.deliver, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + }, + ], + }; + + const delegatedEService4: EService = { + ...mockEService, + id: generateId(), + name: "delegated eservice 4", + producerId: organizationId1, + mode: eserviceMode.deliver, + descriptors: [ + { + ...mockDescriptor, + id: generateId(), + state: descriptorState.published, + }, + ], + }; + + await addOneEService(delegatedEService1); + await addOneEService(delegatedEService2); + await addOneEService(delegatedEService3); + await addOneEService(delegatedEService4); + + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService1.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + await addOneDelegation(delegation1); + + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService2.id, + delegateId: delegatedOrganization1, + state: delegationState.active, + }); + await addOneDelegation(delegation2); + + const delegation3 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService3.id, + delegateId: delegatedOrganization2, + state: delegationState.active, + }); + await addOneDelegation(delegation3); + + const delegation4 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: delegatedEService4.id, + delegateId: delegatedOrganization2, + state: delegationState.rejected, + }); + await addOneDelegation(delegation4); + + const result = await catalogService.getEServices( + getMockAuthData(), + { + eservicesIds: [], + producersIds: [ + organizationId2, + delegatedOrganization1, + delegatedOrganization2, + ], + states: [], + agreementStates: [], + attributesIds: [], + mode: eserviceMode.deliver, + }, + 0, + 50, + genericLogger + ); + expect(result).toEqual({ + totalCount: 4, + results: [delegatedEService1, delegatedEService3, eservice4, eservice5], + }); + }); + it("should not get the eServices if they don't exist (parameters: attributesIds)", async () => { const result = await catalogService.getEServices( getMockAuthData(), @@ -686,278 +1160,356 @@ describe("get eservices", () => { eservice6, ]); }); - it("should include eservices whose only descriptor is draft (requester is the producer, admin)", async () => { - const descriptor8: Descriptor = { - ...mockDescriptor, - id: generateId(), - state: descriptorState.draft, - }; - const eservice8: EService = { - ...mockEService, - id: generateId(), - name: "eservice 008", - producerId: organizationId1, - descriptors: [descriptor8], - }; - const authData: AuthData = { - ...getMockAuthData(organizationId1), - userRoles: [userRoles.ADMIN_ROLE], - }; - await addOneEService(eservice8); - const result = await catalogService.getEServices( - authData, - { - eservicesIds: [], - producersIds: [], - states: [], - agreementStates: [], - attributesIds: [], - }, - 0, - 50, - genericLogger - ); - expect(result.totalCount).toBe(7); - expect(result.results).toEqual([ - eservice1, - eservice2, - eservice3, - eservice4, - eservice5, - eservice6, - eservice8, - ]); - }); - it("should not include eservices whose only descriptor is draft (requester is the producer, not admin nor api, nor support)", async () => { - const descriptor8: Descriptor = { - ...mockDescriptor, - id: generateId(), - state: descriptorState.draft, - }; - const eservice8: EService = { - ...mockEService, - id: generateId(), - name: "eservice 008", - producerId: organizationId1, - descriptors: [descriptor8], - }; - const authData: AuthData = { - ...getMockAuthData(organizationId1), - userRoles: [userRoles.SECURITY_ROLE], - }; - await addOneEService(eservice8); - const result = await catalogService.getEServices( - authData, - { - eservicesIds: [], - producersIds: [], - states: [], - agreementStates: [], - attributesIds: [], - }, - 0, - 50, - genericLogger - ); - expect(result.totalCount).toBe(6); - expect(result.results).toEqual([ - eservice1, - eservice2, - eservice3, - eservice4, - eservice5, - eservice6, - ]); - }); - it("should not include eservices whose only descriptor is draft (requester is not the producer)", async () => { - const descriptor8: Descriptor = { - ...mockDescriptor, - id: generateId(), - state: descriptorState.draft, - }; - const eservice8: EService = { - ...mockEService, - id: generateId(), - name: "eservice 008", - producerId: organizationId1, - descriptors: [descriptor8], - }; - const authData: AuthData = { - ...getMockAuthData(), - userRoles: [userRoles.ADMIN_ROLE], - }; - await addOneEService(eservice8); - const result = await catalogService.getEServices( - authData, - { - eservicesIds: [], - producersIds: [], - states: [], - agreementStates: [], - attributesIds: [], - }, - 0, - 50, - genericLogger - ); - expect(result.totalCount).toBe(6); - expect(result.results).toEqual([ - eservice1, - eservice2, - eservice3, - eservice4, - eservice5, - eservice6, - ]); - }); - it("should not filter out draft descriptors if the eservice has both draft and non-draft ones (requester is the producer, admin)", async () => { - const descriptor9a: Descriptor = { - ...mockDescriptor, - id: generateId(), - interface: mockDocument, - publishedAt: new Date(), - state: descriptorState.published, - }; - const descriptor9b: Descriptor = { - ...mockDescriptor, - id: generateId(), - version: "2", - state: descriptorState.draft, - }; - const eservice9: EService = { - ...mockEService, - id: generateId(), - name: "eservice 008", - producerId: organizationId1, - descriptors: [descriptor9a, descriptor9b], - }; - const authData: AuthData = { - ...getMockAuthData(organizationId1), - userRoles: [userRoles.ADMIN_ROLE], - }; - await addOneEService(eservice9); - const result = await catalogService.getEServices( - authData, - { - eservicesIds: [], - producersIds: [], - states: [], - agreementStates: [], - attributesIds: [], - }, - 0, - 50, - genericLogger - ); - expect(result.totalCount).toBe(7); - expect(result.results).toEqual([ - eservice1, - eservice2, - eservice3, - eservice4, - eservice5, - eservice6, - eservice9, - ]); - }); - it("should filter out draft descriptors if the eservice has both draft and non-draft ones (requester is the producer, but not admin nor api, nor support)", async () => { - const descriptor9a: Descriptor = { - ...mockDescriptor, - id: generateId(), - interface: mockDocument, - publishedAt: new Date(), - state: descriptorState.published, - }; - const descriptor9b: Descriptor = { - ...mockDescriptor, - id: generateId(), - version: "2", - state: descriptorState.draft, - }; - const eservice9: EService = { - ...mockEService, - id: generateId(), - name: "eservice 008", - producerId: organizationId1, - descriptors: [descriptor9a, descriptor9b], - }; - const authData: AuthData = { - ...getMockAuthData(organizationId1), - userRoles: [userRoles.SECURITY_ROLE], - }; - await addOneEService(eservice9); - const result = await catalogService.getEServices( - authData, - { - eservicesIds: [], - producersIds: [], - states: [], - agreementStates: [], - attributesIds: [], - }, - 0, - 50, - genericLogger - ); - expect(result.totalCount).toBe(7); - expect(result.results).toEqual([ - eservice1, - eservice2, - eservice3, - eservice4, - eservice5, - eservice6, - { ...eservice9, descriptors: [descriptor9a] }, - ]); - }); - it("should filter out draft descriptors if the eservice has both draft and non-draft ones (requester is not the producer)", async () => { - const descriptor9a: Descriptor = { - ...mockDescriptor, - id: generateId(), - interface: mockDocument, - publishedAt: new Date(), - state: descriptorState.published, - }; - const descriptor9b: Descriptor = { - ...mockDescriptor, - id: generateId(), - version: "2", - state: descriptorState.draft, - }; - const eservice9: EService = { - ...mockEService, - id: generateId(), - name: "eservice 008", - producerId: organizationId1, - descriptors: [descriptor9a, descriptor9b], - }; - const authData: AuthData = { - ...getMockAuthData(), - userRoles: [userRoles.ADMIN_ROLE], - }; - await addOneEService(eservice9); - const result = await catalogService.getEServices( - authData, - { - eservicesIds: [], - producersIds: [], - states: [], - agreementStates: [], - attributesIds: [], - }, - 0, - 50, - genericLogger - ); - expect(result.totalCount).toBe(7); - expect(result.results).toEqual([ - eservice1, - eservice2, - eservice3, - eservice4, - eservice5, - eservice6, - { ...eservice9, descriptors: [descriptor9a] }, - ]); - }); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should include eservices whose only descriptor is %s (requester is the producer, admin)", + async (state) => { + const descriptor8: Descriptor = { + ...mockDescriptor, + id: generateId(), + state, + }; + const eservice8: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor8], + }; + const authData: AuthData = { + ...getMockAuthData(organizationId1), + userRoles: [userRoles.ADMIN_ROLE], + }; + await addOneEService(eservice8); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(7); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + eservice8, + ]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should not include eservices whose only descriptor is %s (requester is the producer, not admin nor api, nor support)", + async (state) => { + const descriptor8: Descriptor = { + ...mockDescriptor, + id: generateId(), + state, + }; + const eservice8: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor8], + }; + const authData: AuthData = { + ...getMockAuthData(organizationId1), + userRoles: [userRoles.SECURITY_ROLE], + }; + await addOneEService(eservice8); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(6); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + ]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should not include eservices whose only descriptor is %s (requester is not the producer)", + async (state) => { + const descriptor8: Descriptor = { + ...mockDescriptor, + id: generateId(), + state, + }; + const eservice8: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor8], + }; + const authData: AuthData = { + ...getMockAuthData(), + userRoles: [userRoles.ADMIN_ROLE], + }; + await addOneEService(eservice8); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(6); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + ]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should not filter out %s descriptors if the eservice has both of that state and not (requester is the producer, admin)", + async (state) => { + const descriptor9a: Descriptor = { + ...mockDescriptor, + id: generateId(), + interface: mockDocument, + publishedAt: new Date(), + state: descriptorState.published, + }; + const descriptor9b: Descriptor = { + ...mockDescriptor, + id: generateId(), + version: "2", + state, + }; + const eservice9: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor9a, descriptor9b], + }; + const authData: AuthData = { + ...getMockAuthData(organizationId1), + userRoles: [userRoles.ADMIN_ROLE], + }; + await addOneEService(eservice9); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(7); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + eservice9, + ]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should filter out %s descriptors if the eservice has both of that state and not (requester is the producer, but not admin nor api, nor support)", + async (state) => { + const descriptor9a: Descriptor = { + ...mockDescriptor, + id: generateId(), + interface: mockDocument, + publishedAt: new Date(), + state: descriptorState.published, + }; + const descriptor9b: Descriptor = { + ...mockDescriptor, + id: generateId(), + version: "2", + state, + }; + const eservice9: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor9a, descriptor9b], + }; + const authData: AuthData = { + ...getMockAuthData(organizationId1), + userRoles: [userRoles.SECURITY_ROLE], + }; + await addOneEService(eservice9); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(7); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + { ...eservice9, descriptors: [descriptor9a] }, + ]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should filter out %s descriptors if the eservice has both of that state and not (requester is not the producer)", + async (state) => { + const descriptor9a: Descriptor = { + ...mockDescriptor, + id: generateId(), + interface: mockDocument, + publishedAt: new Date(), + state: descriptorState.published, + }; + const descriptor9b: Descriptor = { + ...mockDescriptor, + id: generateId(), + version: "2", + state, + }; + const eservice9: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor9a, descriptor9b], + }; + const authData: AuthData = { + ...getMockAuthData(), + userRoles: [userRoles.ADMIN_ROLE], + }; + await addOneEService(eservice9); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(7); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + { ...eservice9, descriptors: [descriptor9a] }, + ]); + } + ); + it.each([descriptorState.draft, descriptorState.waitingForApproval])( + "should not filter out %s descriptors if the eservice has both of that state and not (requester is delegate, admin)", + async (state) => { + const descriptor9a: Descriptor = { + ...mockDescriptor, + id: generateId(), + interface: mockDocument, + publishedAt: new Date(), + state: descriptorState.published, + }; + const descriptor9b: Descriptor = { + ...mockDescriptor, + id: generateId(), + version: "2", + state, + }; + const eservice9: EService = { + ...mockEService, + id: generateId(), + name: "eservice 008", + producerId: organizationId1, + descriptors: [descriptor9a, descriptor9b], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + delegateId: organizationId2, + eserviceId: eservice9.id, + state: delegationState.active, + }); + const authData: AuthData = { + ...getMockAuthData(organizationId2), + userRoles: [userRoles.ADMIN_ROLE], + }; + await addOneEService(eservice9); + await addOneDelegation(delegation); + const result = await catalogService.getEServices( + authData, + { + eservicesIds: [], + producersIds: [], + states: [], + agreementStates: [], + attributesIds: [], + }, + 0, + 50, + genericLogger + ); + expect(result.totalCount).toBe(7); + expect(result.results).toEqual([ + eservice1, + eservice2, + eservice3, + eservice4, + eservice5, + eservice6, + eservice9, + ]); + } + ); }); diff --git a/packages/catalog-process/test/publishDescriptor.test.ts b/packages/catalog-process/test/publishDescriptor.test.ts index ce807606dc..209a9d4f86 100644 --- a/packages/catalog-process/test/publishDescriptor.test.ts +++ b/packages/catalog-process/test/publishDescriptor.test.ts @@ -5,6 +5,7 @@ import { randomArrayItem, getMockTenant, getMockValidRiskAnalysis, + getMockDelegation, } from "pagopa-interop-commons-test/index.js"; import { Descriptor, @@ -18,6 +19,9 @@ import { Tenant, generateId, operationForbidden, + delegationState, + EServiceDescriptorDelegateSubmittedV2, + delegationKind, } from "pagopa-interop-models"; import { beforeAll, vi, afterAll, expect, describe, it } from "vitest"; import { @@ -42,6 +46,7 @@ import { getMockDescriptor, getMockDocument, getMockAgreement, + addOneDelegation, } from "./utils.js"; describe("publish descriptor", () => { @@ -163,6 +168,80 @@ describe("publish descriptor", () => { expect(writtenPayload.eservice).toEqual(expectedEservice); }); + it("should write on event-store for the submission of the descriptor by the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + interface: mockDocument, + }; + + const producerTenantKind: TenantKind = randomArrayItem( + Object.values(tenantKind) + ); + const producer: Tenant = { + ...getMockTenant(), + kind: producerTenantKind, + }; + + const riskAnalysis = getMockValidRiskAnalysis(producerTenantKind); + + const eservice: EService = { + ...mockEService, + producerId: producer.id, + mode: eserviceMode.receive, + descriptors: [descriptor], + riskAnalysis: [riskAnalysis], + }; + + const delegate = { + ...getMockTenant(), + kind: producerTenantKind, + }; + + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + delegateId: delegate.id, + state: delegationState.active, + }); + + await addOneTenant(producer); + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.publishDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(delegate.id), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorDelegateSubmitted", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDelegateSubmittedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + state: descriptorState.waitingForApproval, + }, + ], + }); + + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + }); + it("should also archive the previously published descriptor", async () => { const descriptor1: Descriptor = { ...mockDescriptor, @@ -202,12 +281,12 @@ describe("publish descriptor", () => { payload: writtenEvent.data, }); - const updatedDescriptor1: Descriptor = { + const expectedDescriptor1: Descriptor = { ...descriptor1, archivedAt: new Date(), state: descriptorState.archived, }; - const updatedDescriptor2: Descriptor = { + const expectedDescriptor2: Descriptor = { ...descriptor2, publishedAt: new Date(), state: descriptorState.published, @@ -215,7 +294,7 @@ describe("publish descriptor", () => { const expectedEservice: EService = { ...eservice, - descriptors: [updatedDescriptor1, updatedDescriptor2], + descriptors: [expectedDescriptor1, expectedDescriptor2], }; expect(writtenPayload).toEqual({ eservice: toEServiceV2(expectedEservice), @@ -273,12 +352,12 @@ describe("publish descriptor", () => { payload: writtenEvent.data, }); - const updatedDescriptor1: Descriptor = { + const expectedDescriptor1: Descriptor = { ...descriptor1, deprecatedAt: new Date(), state: descriptorState.deprecated, }; - const updatedDescriptor2: Descriptor = { + const expectedDescriptor2: Descriptor = { ...descriptor2, publishedAt: new Date(), state: descriptorState.published, @@ -286,7 +365,7 @@ describe("publish descriptor", () => { const expectedEservice: EService = { ...eservice, - descriptors: [updatedDescriptor1, updatedDescriptor2], + descriptors: [expectedDescriptor1, expectedDescriptor2], }; expect(writtenPayload).toEqual({ eservice: toEServiceV2(expectedEservice), @@ -343,6 +422,34 @@ describe("publish descriptor", () => { ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester of the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.publishDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError(operationForbidden); + }); + it("should throw notValidDescriptor if the descriptor is in published state", async () => { const descriptor: Descriptor = { ...mockDescriptor, diff --git a/packages/catalog-process/test/rejectDelegatedEServiceDescriptor.test.ts b/packages/catalog-process/test/rejectDelegatedEServiceDescriptor.test.ts new file mode 100644 index 0000000000..e535e132a7 --- /dev/null +++ b/packages/catalog-process/test/rejectDelegatedEServiceDescriptor.test.ts @@ -0,0 +1,234 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { genericLogger } from "pagopa-interop-commons"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; +import { + Descriptor, + descriptorState, + EService, + toEServiceV2, + operationForbidden, + delegationState, + DescriptorRejectionReason, + EServiceDescriptorDelegatorRejectedV2, + generateId, + delegationKind, +} from "pagopa-interop-models"; +import { beforeAll, vi, afterAll, expect, describe, it } from "vitest"; +import { + eServiceNotFound, + eServiceDescriptorNotFound, + notValidDescriptor, +} from "../src/model/domain/errors.js"; +import { + addOneEService, + catalogService, + getMockAuthData, + readLastEserviceEvent, + getMockEService, + getMockDescriptor, + getMockDocument, + addOneDelegation, +} from "./utils.js"; + +describe("reject descriptor", () => { + const mockEService = getMockEService(); + const mockDescriptor = getMockDescriptor(); + const mockDocument = getMockDocument(); + beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date()); + }); + afterAll(() => { + vi.useRealTimers(); + }); + it("should write on event-store for the rejection of a waiting for approval descriptor", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.waitingForApproval, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + + const newRejectionReason: DescriptorRejectionReason = { + rejectionReason: "testing", + rejectedAt: new Date(), + }; + + await catalogService.rejectDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + { rejectionReason: newRejectionReason.rejectionReason }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorDelegatorRejected", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDelegatorRejectedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + state: descriptorState.draft, + rejectionReasons: [newRejectionReason], + }, + ], + }); + + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + }); + + it("should throw eServiceNotFound if the eService doesn't exist", async () => { + await expect( + catalogService.rejectDelegatedEServiceDescriptor( + mockEService.id, + mockDescriptor.id, + { rejectionReason: "test" }, + { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(eServiceNotFound(mockEService.id)); + }); + + it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { + const eservice: EService = { + ...mockEService, + descriptors: [], + }; + await addOneEService(eservice); + expect( + catalogService.rejectDelegatedEServiceDescriptor( + eservice.id, + mockDescriptor.id, + { rejectionReason: "test" }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError( + eServiceDescriptorNotFound(eservice.id, mockDescriptor.id) + ); + }); + + it("should throw operationForbidden if the requester is not the producer", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + expect( + catalogService.rejectDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + + { rejectionReason: "test" }, + { + authData: getMockAuthData(), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + + it("should throw operationForbidden if the requester is the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.rejectDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + + { rejectionReason: "test" }, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + + it.each( + Object.values(descriptorState).filter( + (s) => s !== descriptorState.waitingForApproval + ) + )( + "should throw notValidDescriptor if the descriptor is in %s state", + async (state) => { + const descriptor: Descriptor = { + ...mockDescriptor, + interface: mockDocument, + state, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + expect( + catalogService.rejectDelegatedEServiceDescriptor( + eservice.id, + descriptor.id, + + { rejectionReason: "test" }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(notValidDescriptor(descriptor.id, state)); + } + ); +}); diff --git a/packages/catalog-process/test/suspendDescriptor.test.ts b/packages/catalog-process/test/suspendDescriptor.test.ts index 321d2d2943..002b212775 100644 --- a/packages/catalog-process/test/suspendDescriptor.test.ts +++ b/packages/catalog-process/test/suspendDescriptor.test.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -9,7 +12,9 @@ import { EServiceDescriptorSuspendedV2, toEServiceV2, operationForbidden, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -25,6 +30,7 @@ import { getMockEService, getMockDescriptor, getMockDocument, + addOneDelegation, } from "./utils.js"; describe("suspend descriptor", () => { @@ -75,62 +81,77 @@ describe("suspend descriptor", () => { expect(writtenPayload.descriptorId).toEqual(descriptor.id); expect(writtenPayload.eservice).toEqual(expectedEservice); }); - - it("should throw eServiceNotFound if the eservice doesn't exist", () => { - expect( - catalogService.suspendDescriptor(mockEService.id, mockDescriptor.id, { - authData: getMockAuthData(mockEService.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - }) - ).rejects.toThrowError(eServiceNotFound(mockEService.id)); - }); - - it("should throw operationForbidden if the requester is not the producer", async () => { + it("should write on event-store for the suspension of a descriptor (delegate)", async () => { const descriptor: Descriptor = { ...mockDescriptor, interface: mockDocument, state: descriptorState.published, }; + const eservice: EService = { ...mockEService, descriptors: [descriptor], }; - await addOneEService(eservice); - expect( - catalogService.suspendDescriptor(eservice.id, descriptor.id, { - authData: getMockAuthData(), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - }) - ).rejects.toThrowError(operationForbidden); - }); - it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { - const eservice: EService = { - ...mockEService, - descriptors: [], - }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + await addOneEService(eservice); + await addOneDelegation(delegation); + + await catalogService.suspendDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceDescriptorSuspended"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorSuspendedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + state: descriptorState.suspended, + suspendedAt: new Date( + Number(writtenPayload.eservice!.descriptors[0]!.suspendedAt) + ), + }, + ], + }); + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + }); + + it("should throw eServiceNotFound if the eservice doesn't exist", () => { expect( - catalogService.suspendDescriptor(eservice.id, mockDescriptor.id, { + catalogService.suspendDescriptor(mockEService.id, mockDescriptor.id, { authData: getMockAuthData(mockEService.producerId), correlationId: generateId(), serviceName: "", logger: genericLogger, }) - ).rejects.toThrowError( - eServiceDescriptorNotFound(eservice.id, mockDescriptor.id) - ); + ).rejects.toThrowError(eServiceNotFound(mockEService.id)); }); - it("should throw notValidDescriptor if the descriptor is in draft state", async () => { + it("should throw operationForbidden if the requester is not the producer", async () => { const descriptor: Descriptor = { ...mockDescriptor, - state: descriptorState.draft, + interface: mockDocument, + state: descriptorState.published, }; const eservice: EService = { ...mockEService, @@ -139,27 +160,32 @@ describe("suspend descriptor", () => { await addOneEService(eservice); expect( catalogService.suspendDescriptor(eservice.id, descriptor.id, { - authData: getMockAuthData(eservice.producerId), + authData: getMockAuthData(), correlationId: generateId(), serviceName: "", logger: genericLogger, }) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.draft) - ); + ).rejects.toThrowError(operationForbidden); }); - it("should throw notValidDescriptor if the descriptor is in suspended state", async () => { + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { const descriptor: Descriptor = { ...mockDescriptor, interface: mockDocument, - state: descriptorState.suspended, + state: descriptorState.published, }; const eservice: EService = { ...mockEService, descriptors: [descriptor], }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + await addOneEService(eservice); + await addOneDelegation(delegation); expect( catalogService.suspendDescriptor(eservice.id, descriptor.id, { authData: getMockAuthData(eservice.producerId), @@ -167,31 +193,53 @@ describe("suspend descriptor", () => { serviceName: "", logger: genericLogger, }) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.suspended) - ); + ).rejects.toThrowError(operationForbidden); }); - it("should throw notValidDescriptor if the descriptor is in archived state", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - interface: mockDocument, - state: descriptorState.archived, - }; + it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { const eservice: EService = { ...mockEService, - descriptors: [descriptor], + descriptors: [], }; await addOneEService(eservice); + expect( - catalogService.suspendDescriptor(eservice.id, descriptor.id, { - authData: getMockAuthData(eservice.producerId), + catalogService.suspendDescriptor(eservice.id, mockDescriptor.id, { + authData: getMockAuthData(mockEService.producerId), correlationId: generateId(), serviceName: "", logger: genericLogger, }) ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.archived) + eServiceDescriptorNotFound(eservice.id, mockDescriptor.id) ); }); + + it.each([ + descriptorState.draft, + descriptorState.waitingForApproval, + descriptorState.suspended, + descriptorState.archived, + ])( + "should throw notValidDescriptor if the descriptor is in %s state", + async (state) => { + const descriptor: Descriptor = { + ...mockDescriptor, + state, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + expect( + catalogService.suspendDescriptor(eservice.id, descriptor.id, { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + }) + ).rejects.toThrowError(notValidDescriptor(descriptor.id, state)); + } + ); }); diff --git a/packages/catalog-process/test/updateDescriptor.test.ts b/packages/catalog-process/test/updateDescriptor.test.ts index d37f3c302c..04c40b9aff 100644 --- a/packages/catalog-process/test/updateDescriptor.test.ts +++ b/packages/catalog-process/test/updateDescriptor.test.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -8,7 +11,9 @@ import { EServiceDescriptorQuotasUpdatedV2, toEServiceV2, operationForbidden, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { catalogApi } from "pagopa-interop-api-clients"; import { expect, describe, it } from "vitest"; @@ -19,6 +24,7 @@ import { inconsistentDailyCalls, } from "../src/model/domain/errors.js"; import { + addOneDelegation, addOneEService, catalogService, getMockAuthData, @@ -32,181 +38,143 @@ describe("update descriptor", () => { const mockEService = getMockEService(); const mockDescriptor = getMockDescriptor(); const mockDocument = getMockDocument(); - it("should write on event-store for the update of a published descriptor", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - state: descriptorState.published, - interface: mockDocument, - publishedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - await addOneEService(eservice); - - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = - { - voucherLifespan: 1000, - dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, - dailyCallsTotal: descriptor.dailyCallsTotal + 10, + it.each([ + descriptorState.published, + descriptorState.suspended, + descriptorState.deprecated, + ])( + "should write on event-store for the update of a descriptor with state %s", + async (descriptorState) => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState, + interface: mockDocument, + publishedAt: new Date(), }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); - const updatedEService: EService = { - ...eservice, - descriptors: [ + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { - ...descriptor, voucherLifespan: 1000, dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, dailyCallsTotal: descriptor.dailyCallsTotal + 10, - }, - ], - }; - const returnedEService = await catalogService.updateDescriptor( - eservice.id, - descriptor.id, - updatedDescriptorQuotasSeed, - { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - } - ); - const writtenEvent = await readLastEserviceEvent(eservice.id); - expect(writtenEvent).toMatchObject({ - stream_id: eservice.id, - version: "1", - type: "EServiceDescriptorQuotasUpdated", - event_version: 2, - }); - const writtenPayload = decodeProtobufPayload({ - messageType: EServiceDescriptorQuotasUpdatedV2, - payload: writtenEvent.data, - }); - expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); - expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); - }); + }; - it("should write on event-store for the update of a suspended descriptor", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - state: descriptorState.suspended, - interface: mockDocument, - publishedAt: new Date(), - suspendedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - await addOneEService(eservice); + const updatedEService: EService = { + ...eservice, + descriptors: [ + { + ...descriptor, + voucherLifespan: 1000, + dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, + dailyCallsTotal: descriptor.dailyCallsTotal + 10, + }, + ], + }; + const returnedEService = await catalogService.updateDescriptor( + eservice.id, + descriptor.id, + expectedDescriptorQuotasSeed, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorQuotasUpdated", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorQuotasUpdatedV2, + payload: writtenEvent.data, + }); + expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); + expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); + } + ); - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = - { - voucherLifespan: 1000, - dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, - dailyCallsTotal: descriptor.dailyCallsTotal + 10, + it.each([ + descriptorState.published, + descriptorState.suspended, + descriptorState.deprecated, + ])( + "should write on event-store for the update of a descriptor with state %s (delegate)", + async (descriptorState) => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState, + interface: mockDocument, + publishedAt: new Date(), + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); - const updatedEService: EService = { - ...eservice, - descriptors: [ + await addOneEService(eservice); + await addOneDelegation(delegation); + + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { - ...descriptor, voucherLifespan: 1000, dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, dailyCallsTotal: descriptor.dailyCallsTotal + 10, - }, - ], - }; - const returnedEService = await catalogService.updateDescriptor( - eservice.id, - descriptor.id, - updatedDescriptorQuotasSeed, - { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - } - ); - const writtenEvent = await readLastEserviceEvent(eservice.id); - expect(writtenEvent).toMatchObject({ - stream_id: eservice.id, - version: "1", - type: "EServiceDescriptorQuotasUpdated", - event_version: 2, - }); - const writtenPayload = decodeProtobufPayload({ - messageType: EServiceDescriptorQuotasUpdatedV2, - payload: writtenEvent.data, - }); - expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); - expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); - }); - - it("should write on event-store for the update of an deprecated descriptor", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - state: descriptorState.deprecated, - interface: mockDocument, - publishedAt: new Date(), - deprecatedAt: new Date(), - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - await addOneEService(eservice); + }; - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = - { - voucherLifespan: 1000, - dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, - dailyCallsTotal: descriptor.dailyCallsTotal + 10, + const updatedEService: EService = { + ...eservice, + descriptors: [ + { + ...descriptor, + voucherLifespan: 1000, + dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, + dailyCallsTotal: descriptor.dailyCallsTotal + 10, + }, + ], }; - - const updatedEService: EService = { - ...eservice, - descriptors: [ + const returnedEService = await catalogService.updateDescriptor( + eservice.id, + descriptor.id, + expectedDescriptorQuotasSeed, { - ...descriptor, - voucherLifespan: 1000, - dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, - dailyCallsTotal: descriptor.dailyCallsTotal + 10, - }, - ], - }; - const returnedEService = await catalogService.updateDescriptor( - eservice.id, - descriptor.id, - updatedDescriptorQuotasSeed, - { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - } - ); - const writtenEvent = await readLastEserviceEvent(eservice.id); - expect(writtenEvent).toMatchObject({ - stream_id: eservice.id, - version: "1", - type: "EServiceDescriptorQuotasUpdated", - event_version: 2, - }); - const writtenPayload = decodeProtobufPayload({ - messageType: EServiceDescriptorQuotasUpdatedV2, - payload: writtenEvent.data, - }); - expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); - expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); - }); + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptorQuotasUpdated", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorQuotasUpdatedV2, + payload: writtenEvent.data, + }); + expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); + expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); + } + ); it("should throw eServiceNotFound if the eservice doesn't exist", () => { - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { voucherLifespan: 1000, dailyCallsPerConsumer: mockDescriptor.dailyCallsPerConsumer + 10, @@ -216,7 +184,7 @@ describe("update descriptor", () => { catalogService.updateDescriptor( mockEService.id, mockDescriptor.id, - updatedDescriptorQuotasSeed, + expectedDescriptorQuotasSeed, { authData: getMockAuthData(mockEService.producerId), correlationId: generateId(), @@ -234,7 +202,7 @@ describe("update descriptor", () => { }; await addOneEService(eservice); - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { voucherLifespan: 1000, dailyCallsPerConsumer: mockDescriptor.dailyCallsPerConsumer + 10, @@ -245,7 +213,7 @@ describe("update descriptor", () => { catalogService.updateDescriptor( mockEService.id, mockDescriptor.id, - updatedDescriptorQuotasSeed, + expectedDescriptorQuotasSeed, { authData: getMockAuthData(mockEService.producerId), correlationId: generateId(), @@ -258,47 +226,50 @@ describe("update descriptor", () => { ); }); - it("should throw notValidDescriptor if the descriptor is in draft state", async () => { - const descriptor: Descriptor = { - ...mockDescriptor, - interface: mockDocument, - state: descriptorState.draft, - }; - const eservice: EService = { - ...mockEService, - descriptors: [descriptor], - }; - await addOneEService(eservice); - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = - { - voucherLifespan: 1000, - dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, - dailyCallsTotal: descriptor.dailyCallsTotal + 10, + it.each([ + descriptorState.draft, + descriptorState.waitingForApproval, + descriptorState.archived, + ])( + "should throw notValidDescriptor if the descriptor is in %s state", + async (state) => { + const descriptor: Descriptor = { + ...mockDescriptor, + interface: mockDocument, + state, }; - - expect( - catalogService.updateDescriptor( - eservice.id, - descriptor.id, - updatedDescriptorQuotasSeed, + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { - authData: getMockAuthData(eservice.producerId), - correlationId: generateId(), - serviceName: "", - logger: genericLogger, - } - ) - ).rejects.toThrowError( - notValidDescriptor(mockDescriptor.id, descriptorState.draft) - ); - }); + voucherLifespan: 1000, + dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, + dailyCallsTotal: descriptor.dailyCallsTotal + 10, + }; - it("should throw notValidDescriptor if the descriptor is in archived state", async () => { + expect( + catalogService.updateDescriptor( + eservice.id, + descriptor.id, + updatedDescriptorQuotasSeed, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(notValidDescriptor(mockDescriptor.id, state)); + } + ); + + it("should throw operationForbidden if the requester is not the producer", async () => { const descriptor: Descriptor = { ...mockDescriptor, - interface: mockDocument, - state: descriptorState.archived, - archivedAt: new Date(), + state: descriptorState.draft, }; const eservice: EService = { ...mockEService, @@ -306,7 +277,7 @@ describe("update descriptor", () => { }; await addOneEService(eservice); - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { voucherLifespan: 1000, dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, @@ -316,20 +287,18 @@ describe("update descriptor", () => { catalogService.updateDescriptor( eservice.id, descriptor.id, - updatedDescriptorQuotasSeed, + expectedDescriptorQuotasSeed, { - authData: getMockAuthData(eservice.producerId), + authData: getMockAuthData(), correlationId: generateId(), serviceName: "", logger: genericLogger, } ) - ).rejects.toThrowError( - notValidDescriptor(mockDescriptor.id, descriptorState.archived) - ); + ).rejects.toThrowError(operationForbidden); }); - it("should throw operationForbidden if the requester is not the producer", async () => { + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { const descriptor: Descriptor = { ...mockDescriptor, state: descriptorState.draft, @@ -338,9 +307,16 @@ describe("update descriptor", () => { ...mockEService, descriptors: [descriptor], }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + await addOneEService(eservice); + await addOneDelegation(delegation); - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { voucherLifespan: 1000, dailyCallsPerConsumer: descriptor.dailyCallsPerConsumer + 10, @@ -350,9 +326,9 @@ describe("update descriptor", () => { catalogService.updateDescriptor( eservice.id, descriptor.id, - updatedDescriptorQuotasSeed, + expectedDescriptorQuotasSeed, { - authData: getMockAuthData(), + authData: getMockAuthData(eservice.producerId), correlationId: generateId(), serviceName: "", logger: genericLogger, @@ -374,7 +350,7 @@ describe("update descriptor", () => { }; await addOneEService(eservice); - const updatedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = + const expectedDescriptorQuotasSeed: catalogApi.UpdateEServiceDescriptorQuotasSeed = { voucherLifespan: 1000, dailyCallsPerConsumer: descriptor.dailyCallsTotal + 11, @@ -384,7 +360,7 @@ describe("update descriptor", () => { catalogService.updateDescriptor( eservice.id, descriptor.id, - updatedDescriptorQuotasSeed, + expectedDescriptorQuotasSeed, { authData: getMockAuthData(eservice.producerId), correlationId: generateId(), diff --git a/packages/catalog-process/test/updateDocument.test.ts b/packages/catalog-process/test/updateDocument.test.ts index 9070d1c5ed..728454dae7 100644 --- a/packages/catalog-process/test/updateDocument.test.ts +++ b/packages/catalog-process/test/updateDocument.test.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -10,6 +13,8 @@ import { operationForbidden, generateId, Document, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -27,6 +32,7 @@ import { getMockDescriptor, getMockDocument, getMockEService, + addOneDelegation, } from "./utils.js"; describe("update Document", () => { @@ -35,7 +41,9 @@ describe("update Document", () => { const mockDocument = getMockDocument(); it.each( Object.values(descriptorState).filter( - (state) => state !== descriptorState.archived + (state) => + state !== descriptorState.archived && + state !== descriptorState.waitingForApproval ) )( "should write on event-store for the update of a document in a descriptor in %s state", @@ -102,6 +110,85 @@ describe("update Document", () => { ); } ); + it.each( + Object.values(descriptorState).filter( + (state) => + state !== descriptorState.archived && + state !== descriptorState.waitingForApproval + ) + )( + "should write on event-store for the update of a document in a descriptor in %s state (delegate)", + async (state) => { + const descriptor: Descriptor = { + ...getMockDescriptor(state), + docs: [mockDocument], + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + const returnedDocument = await catalogService.updateDocument( + eservice.id, + descriptor.id, + mockDocument.id, + { prettyName: "updated prettyName" }, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + docs: [ + { + ...mockDocument, + prettyName: "updated prettyName", + }, + ], + }, + ], + }); + + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceDescriptorDocumentUpdated"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorDocumentUpdatedV2, + payload: writtenEvent.data, + }); + + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.documentId).toEqual(mockDocument.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + expect(writtenPayload.eservice).toEqual( + toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + docs: [returnedDocument], + }, + ], + }) + ); + } + ); it("should throw eServiceNotFound if the eservice doesn't exist", async () => { expect( catalogService.updateDocument( @@ -144,6 +231,39 @@ describe("update Document", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + docs: [mockDocument], + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + expect( + catalogService.updateDocument( + eservice.id, + descriptor.id, + mockDocument.id, + { prettyName: "updated prettyName" }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { const eservice: EService = { ...mockEService, @@ -169,7 +289,9 @@ describe("update Document", () => { }); it.each( Object.values(descriptorState).filter( - (state) => state === descriptorState.archived + (state) => + state === descriptorState.archived || + state === descriptorState.waitingForApproval ) )( "should throw notValidDescriptor if the descriptor is in s% state", @@ -196,9 +318,7 @@ describe("update Document", () => { logger: genericLogger, } ) - ).rejects.toThrowError( - notValidDescriptor(descriptor.id, descriptorState.archived) - ); + ).rejects.toThrowError(notValidDescriptor(descriptor.id, state)); } ); it("should throw eServiceDocumentNotFound if the document doesn't exist", async () => { diff --git a/packages/catalog-process/test/updateDraftDescriptor.test.ts b/packages/catalog-process/test/updateDraftDescriptor.test.ts index 6d631b0be3..383f62e547 100644 --- a/packages/catalog-process/test/updateDraftDescriptor.test.ts +++ b/packages/catalog-process/test/updateDraftDescriptor.test.ts @@ -1,7 +1,10 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; import { catalogApi } from "pagopa-interop-api-clients"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -11,6 +14,8 @@ import { EServiceDraftDescriptorUpdatedV2, toEServiceV2, operationForbidden, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -30,6 +35,7 @@ import { getMockEService, getMockDocument, buildUpdateDescriptorSeed, + addOneDelegation, } from "./utils.js"; describe("update draft descriptor", () => { @@ -55,7 +61,7 @@ describe("update draft descriptor", () => { }; await addOneAttribute(attribute); - const updatedDescriptorSeed: catalogApi.UpdateEServiceDescriptorSeed = { + const expectedDescriptorSeed: catalogApi.UpdateEServiceDescriptorSeed = { ...buildUpdateDescriptorSeed(descriptor), dailyCallsTotal: 200, attributes: { @@ -86,7 +92,7 @@ describe("update draft descriptor", () => { await catalogService.updateDraftDescriptor( eservice.id, descriptor.id, - updatedDescriptorSeed, + expectedDescriptorSeed, { authData: getMockAuthData(eservice.producerId), correlationId: generateId(), @@ -107,6 +113,84 @@ describe("update draft descriptor", () => { }); expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); }); + it("should write on event-store for the update of a draft descriptor (delegate)", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + const attribute: Attribute = { + name: "Attribute name", + id: generateId(), + kind: "Declared", + description: "Attribute Description", + creationTime: new Date(), + }; + await addOneAttribute(attribute); + + const expectedDescriptorSeed: catalogApi.UpdateEServiceDescriptorSeed = { + ...buildUpdateDescriptorSeed(descriptor), + dailyCallsTotal: 200, + attributes: { + certified: [], + declared: [ + [{ id: attribute.id, explicitAttributeVerification: false }], + ], + verified: [], + }, + }; + + const updatedEService: EService = { + ...eservice, + descriptors: [ + { + ...descriptor, + dailyCallsTotal: 200, + attributes: { + certified: [], + declared: [ + [{ id: attribute.id, explicitAttributeVerification: false }], + ], + verified: [], + }, + }, + ], + }; + await catalogService.updateDraftDescriptor( + eservice.id, + descriptor.id, + expectedDescriptorSeed, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDraftDescriptorUpdated", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDraftDescriptorUpdatedV2, + payload: writtenEvent.data, + }); + expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); + }); it("should throw eServiceNotFound if the eservice doesn't exist", () => { const descriptor: Descriptor = { @@ -280,7 +364,7 @@ describe("update draft descriptor", () => { }; await addOneEService(eservice); - const updatedDescriptor = { + const expectedDescriptor = { ...descriptor, dailyCallsTotal: 200, }; @@ -288,7 +372,7 @@ describe("update draft descriptor", () => { catalogService.updateDraftDescriptor( eservice.id, descriptor.id, - buildUpdateDescriptorSeed(updatedDescriptor), + buildUpdateDescriptorSeed(expectedDescriptor), { authData: getMockAuthData(), correlationId: generateId(), @@ -299,6 +383,44 @@ describe("update draft descriptor", () => { ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + const expectedDescriptor = { + ...descriptor, + dailyCallsTotal: 200, + }; + expect( + catalogService.updateDraftDescriptor( + eservice.id, + descriptor.id, + buildUpdateDescriptorSeed(expectedDescriptor), + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + it("should throw inconsistentDailyCalls if dailyCallsPerConsumer is greater than dailyCallsTotal", async () => { const descriptor: Descriptor = { ...mockDescriptor, @@ -310,7 +432,7 @@ describe("update draft descriptor", () => { }; await addOneEService(eservice); - const updatedDescriptor: Descriptor = { + const expectedDescriptor: Descriptor = { ...descriptor, dailyCallsPerConsumer: 100, dailyCallsTotal: 50, @@ -319,7 +441,7 @@ describe("update draft descriptor", () => { catalogService.updateDraftDescriptor( eservice.id, descriptor.id, - buildUpdateDescriptorSeed(updatedDescriptor), + buildUpdateDescriptorSeed(expectedDescriptor), { authData: getMockAuthData(eservice.producerId), correlationId: generateId(), diff --git a/packages/catalog-process/test/updateEservice.test.ts b/packages/catalog-process/test/updateEservice.test.ts index bf454c4542..f54f79df27 100644 --- a/packages/catalog-process/test/updateEservice.test.ts +++ b/packages/catalog-process/test/updateEservice.test.ts @@ -2,6 +2,7 @@ import { genericLogger, fileManagerDeleteError } from "pagopa-interop-commons"; import { decodeProtobufPayload, + getMockDelegation, getMockValidRiskAnalysis, randomArrayItem, } from "pagopa-interop-commons-test/index.js"; @@ -14,6 +15,8 @@ import { eserviceMode, operationForbidden, generateId, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { vi, expect, describe, it } from "vitest"; import { @@ -31,6 +34,7 @@ import { getMockDocument, getMockDescriptor, getMockEService, + addOneDelegation, } from "./utils.js"; describe("update eService", () => { @@ -254,6 +258,52 @@ describe("update eService", () => { expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); }); + it("should write on event-store for the update of an eService (delegate)", async () => { + const updatedDescription = "eservice new description"; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + state: delegationState.active, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + const returnedEService = await catalogService.updateEService( + mockEService.id, + { + name: mockEService.name, + description: updatedDescription, + technology: "REST", + mode: "DELIVER", + }, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const updatedEService: EService = { + ...mockEService, + description: updatedDescription, + }; + + const writtenEvent = await readLastEserviceEvent(mockEService.id); + expect(writtenEvent).toMatchObject({ + stream_id: mockEService.id, + version: "1", + type: "DraftEServiceUpdated", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: DraftEServiceUpdatedV2, + payload: writtenEvent.data, + }); + + expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); + expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); + }); it("should write on event-store for the update of an eService (update mode to DELIVER so risk analysis has to be deleted)", async () => { const riskAnalysis = getMockValidRiskAnalysis("PA"); @@ -345,6 +395,35 @@ describe("update eService", () => { ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + state: delegationState.active, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + + expect( + catalogService.updateEService( + mockEService.id, + { + name: "eservice new name", + description: "eservice description", + technology: "REST", + mode: "DELIVER", + }, + { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + it("should throw eServiceDuplicate if the updated name is already in use, case insensitive", async () => { const eservice1: EService = { ...mockEService, diff --git a/packages/catalog-process/test/updateEserviceDescription.test.ts b/packages/catalog-process/test/updateEserviceDescription.test.ts index da5ddd5c0b..99906570e9 100644 --- a/packages/catalog-process/test/updateEserviceDescription.test.ts +++ b/packages/catalog-process/test/updateEserviceDescription.test.ts @@ -1,6 +1,9 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { genericLogger } from "pagopa-interop-commons"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { Descriptor, descriptorState, @@ -8,7 +11,9 @@ import { toEServiceV2, operationForbidden, EServiceDescriptionUpdatedV2, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; import { @@ -23,6 +28,7 @@ import { getMockDocument, getMockDescriptor, getMockEService, + addOneDelegation, } from "./utils.js"; describe("update eService description", () => { @@ -69,6 +75,56 @@ describe("update eService description", () => { expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); }); + it("should write on event-store for the update of the eService description (delegate)", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + const updatedDescription = "eservice new description"; + const returnedEService = await catalogService.updateEServiceDescription( + eservice.id, + updatedDescription, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const updatedEService: EService = { + ...eservice, + description: updatedDescription, + }; + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceDescriptionUpdated", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptionUpdatedV2, + payload: writtenEvent.data, + }); + + expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEService)); + expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); + }); it("should throw eServiceNotFound if the eservice doesn't exist", async () => { const eservice = getMockEService(); @@ -103,6 +159,30 @@ describe("update eService description", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const eservice = getMockEService(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.updateEServiceDescription( + eservice.id, + "eservice new description", + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("shoudl throw eserviceWithoutValidDescriptors if the eservice doesn't have any descriptors", async () => { const eservice = getMockEService(); await addOneEService(eservice); diff --git a/packages/catalog-process/test/updateRiskAnalysis.test.ts b/packages/catalog-process/test/updateRiskAnalysis.test.ts index fa00e2386b..050a9cff2d 100644 --- a/packages/catalog-process/test/updateRiskAnalysis.test.ts +++ b/packages/catalog-process/test/updateRiskAnalysis.test.ts @@ -10,6 +10,7 @@ import { getMockTenant, getMockValidRiskAnalysis, decodeProtobufPayload, + getMockDelegation, } from "pagopa-interop-commons-test/index.js"; import { TenantKind, @@ -27,6 +28,8 @@ import { generateId, operationForbidden, RiskAnalysisId, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { catalogApi } from "pagopa-interop-api-clients"; import { expect, describe, it } from "vitest"; @@ -49,6 +52,7 @@ import { readLastEserviceEvent, getMockDescriptor, getMockEService, + addOneDelegation, } from "./utils.js"; describe("update risk analysis", () => { @@ -172,6 +176,131 @@ describe("update risk analysis", () => { expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEservice)); }); + it("should write on event-store for the update of a risk analysis (delegate)", async () => { + const producerTenantKind: TenantKind = randomArrayItem( + Object.values(tenantKind) + ); + const producer: Tenant = { + ...getMockTenant(), + kind: producerTenantKind, + }; + + const riskAnalysis = getMockValidRiskAnalysis(producerTenantKind); + + const eservice: EService = { + ...mockEService, + producerId: producer.id, + mode: eserviceMode.receive, + descriptors: [ + { + ...mockDescriptor, + state: descriptorState.draft, + }, + ], + riskAnalysis: [riskAnalysis], + }; + + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneTenant(producer); + await addOneEService(eservice); + await addOneDelegation(delegation); + + const riskAnalysisSeed: catalogApi.EServiceRiskAnalysisSeed = + buildRiskAnalysisSeed(riskAnalysis); + + const riskAnalysisUpdatedSeed: catalogApi.EServiceRiskAnalysisSeed = { + ...riskAnalysisSeed, + riskAnalysisForm: { + ...riskAnalysisSeed.riskAnalysisForm, + answers: { + ...riskAnalysisSeed.riskAnalysisForm.answers, + purpose: ["OTHER"], // we modify the purpose field, present in the mock for all tenant kinds + otherPurpose: ["updated other purpose"], // we add a new field + ruleOfLawText: [], // we remove the ruleOfLawText field, present in the mock for all tenant kinds + }, + }, + }; + + await catalogService.updateRiskAnalysis( + eservice.id, + riskAnalysis.id, + riskAnalysisUpdatedSeed, + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceRiskAnalysisUpdated", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceRiskAnalysisUpdatedV2, + payload: writtenEvent.data, + }); + + const updatedEservice: EService = { + ...eservice, + riskAnalysis: [ + { + ...riskAnalysis, + name: riskAnalysisUpdatedSeed.name, + riskAnalysisForm: { + ...riskAnalysis.riskAnalysisForm, + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.id + ), + multiAnswers: riskAnalysis.riskAnalysisForm.multiAnswers.map( + (multiAnswer) => ({ + ...multiAnswer, + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.multiAnswers.find( + (ma) => ma.key === multiAnswer.key + )!.id + ), + }) + ), + singleAnswers: riskAnalysis.riskAnalysisForm.singleAnswers + .filter((singleAnswer) => singleAnswer.key !== "ruleOfLawText") + .map((singleAnswer) => ({ + ...singleAnswer, + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.singleAnswers.find( + (sa) => sa.key === singleAnswer.key + )!.id + ), + value: + singleAnswer.key === "purpose" ? "OTHER" : singleAnswer.value, + })) + .concat([ + { + key: "otherPurpose", + value: "updated other purpose", + id: unsafeBrandId( + writtenPayload.eservice!.riskAnalysis[0]!.riskAnalysisForm!.singleAnswers.find( + (sa) => sa.key === "otherPurpose" + )!.id + ), + }, + ]), + }, + }, + ], + }; + + expect(writtenPayload.eservice).toEqual(toEServiceV2(updatedEservice)); + }); it("should throw eServiceNotFound if the eservice doesn't exist", async () => { expect( catalogService.updateRiskAnalysis( @@ -203,6 +332,30 @@ describe("update risk analysis", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + state: delegationState.active, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + + expect( + catalogService.updateRiskAnalysis( + mockEService.id, + generateId(), + buildRiskAnalysisSeed(getMockValidRiskAnalysis(tenantKind.PA)), + { + authData: getMockAuthData(mockEService.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("should throw eserviceNotInDraftState if the eservice is not in draft state", async () => { const eservice: EService = { ...mockEService, diff --git a/packages/catalog-process/test/uploadDocument.test.ts b/packages/catalog-process/test/uploadDocument.test.ts index 34c6ae6cd9..a64f91191e 100644 --- a/packages/catalog-process/test/uploadDocument.test.ts +++ b/packages/catalog-process/test/uploadDocument.test.ts @@ -10,10 +10,15 @@ import { unsafeBrandId, operationForbidden, Document, + delegationState, generateId, + delegationKind, } from "pagopa-interop-models"; import { expect, describe, it } from "vitest"; -import { decodeProtobufPayload } from "pagopa-interop-commons-test/index.js"; +import { + decodeProtobufPayload, + getMockDelegation, +} from "pagopa-interop-commons-test/index.js"; import { eServiceNotFound, eServiceDescriptorNotFound, @@ -31,6 +36,7 @@ import { getMockDocument, getMockEService, buildDocumentSeed, + addOneDelegation, } from "./utils.js"; describe("upload Document", () => { @@ -39,7 +45,9 @@ describe("upload Document", () => { const mockDocument = getMockDocument(); it.each( Object.values(descriptorState).filter( - (state) => state !== descriptorState.archived + (state) => + state !== descriptorState.archived && + state !== descriptorState.waitingForApproval ) )( "should write on event-store for the upload of a document when descriptor state is %s", @@ -102,6 +110,80 @@ describe("upload Document", () => { expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); } ); + it.each( + Object.values(descriptorState).filter( + (state) => + state !== descriptorState.archived && + state !== descriptorState.waitingForApproval + ) + )( + "should write on event-store for the upload of a document when descriptor state is %s (delegate)", + async (state) => { + const descriptor: Descriptor = { + ...getMockDescriptor(state), + serverUrls: [], + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + const returnedEService = await catalogService.uploadDocument( + eservice.id, + descriptor.id, + buildInterfaceSeed(), + { + authData: getMockAuthData(delegation.delegateId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent.stream_id).toBe(eservice.id); + expect(writtenEvent.version).toBe("1"); + expect(writtenEvent.type).toBe("EServiceDescriptorInterfaceAdded"); + expect(writtenEvent.event_version).toBe(2); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceDescriptorInterfaceDeletedV2, + payload: writtenEvent.data, + }); + + const expectedEservice = toEServiceV2({ + ...eservice, + descriptors: [ + { + ...descriptor, + interface: { + ...mockDocument, + id: unsafeBrandId( + writtenPayload.eservice!.descriptors[0]!.interface!.id + ), + checksum: + writtenPayload.eservice!.descriptors[0]!.interface!.checksum, + uploadDate: new Date( + writtenPayload.eservice!.descriptors[0]!.interface!.uploadDate + ), + }, + serverUrls: ["pagopa.it"], + }, + ], + }); + + expect(writtenPayload.descriptorId).toEqual(descriptor.id); + expect(writtenPayload.eservice).toEqual(expectedEservice); + expect(writtenPayload.eservice).toEqual(toEServiceV2(returnedEService)); + } + ); it("should throw eServiceNotFound if the eservice doesn't exist", () => { expect( catalogService.uploadDocument( @@ -142,6 +224,63 @@ describe("upload Document", () => { ) ).rejects.toThrowError(operationForbidden); }); + it("should throw operationForbidden if the requester is not the producer", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + await addOneEService(eservice); + + expect( + catalogService.uploadDocument( + eservice.id, + descriptor.id, + buildInterfaceSeed(), + { + authData: getMockAuthData(), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + it("should throw operationForbidden if the requester if the given e-service has been delegated and caller is not the delegate", async () => { + const descriptor: Descriptor = { + ...mockDescriptor, + state: descriptorState.draft, + }; + const eservice: EService = { + ...mockEService, + descriptors: [descriptor], + }; + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(mockEService); + await addOneDelegation(delegation); + + expect( + catalogService.uploadDocument( + eservice.id, + descriptor.id, + buildInterfaceSeed(), + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); it("should throw eServiceDescriptorNotFound if the descriptor doesn't exist", async () => { const eservice: EService = { ...mockEService, @@ -167,7 +306,9 @@ describe("upload Document", () => { it.each( Object.values(descriptorState).filter( - (state) => state === descriptorState.archived + (state) => + state === descriptorState.archived || + state === descriptorState.waitingForApproval ) )( "should throw notValidDescriptor if the descriptor is in %s state", diff --git a/packages/catalog-process/test/utils.ts b/packages/catalog-process/test/utils.ts index f0e220524c..707be43610 100644 --- a/packages/catalog-process/test/utils.ts +++ b/packages/catalog-process/test/utils.ts @@ -27,6 +27,7 @@ import { toReadModelTenant, toReadModelAgreement, DescriptorState, + Delegation, } from "pagopa-interop-models"; import { ReadEvent, @@ -54,6 +55,7 @@ export const agreements = readModelRepository.agreements; export const eservices = readModelRepository.eservices; export const tenants = readModelRepository.tenants; export const attributes = readModelRepository.attributes; +export const delegations = readModelRepository.delegations; export const readModelService = readModelServiceBuilder(readModelRepository); @@ -283,6 +285,12 @@ export const addOneAgreement = async (agreement: Agreement): Promise => { await writeInReadmodel(toReadModelAgreement(agreement), agreements); }; +export const addOneDelegation = async ( + delegation: Delegation +): Promise => { + await writeInReadmodel(delegation, delegations); +}; + export const readLastEserviceEvent = async ( eserviceId: EServiceId ): Promise> => diff --git a/packages/catalog-readmodel-writer/src/consumerServiceV2.ts b/packages/catalog-readmodel-writer/src/consumerServiceV2.ts index 9ac9807ec9..9d6b92b040 100644 --- a/packages/catalog-readmodel-writer/src/consumerServiceV2.ts +++ b/packages/catalog-readmodel-writer/src/consumerServiceV2.ts @@ -41,6 +41,9 @@ export async function handleMessageV2( { type: "EServiceRiskAnalysisUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceDescriptorDelegateSubmitted" }, + { type: "EServiceDescriptorDelegatorApproved" }, + { type: "EServiceDescriptorDelegatorRejected" }, async (message) => await eservices.updateOne( { diff --git a/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts b/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts index 4df63491ba..85d99d9d8c 100644 --- a/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts +++ b/packages/catalog-readmodel-writer/test/catalogReadmodelWriter.integration.test.ts @@ -227,14 +227,14 @@ describe("database test", async () => { descriptors: [descriptor], }; await writeInReadmodel(toReadModelEService(eservice), eservices, 1); - const updatedDescriptor = { + const expectedDescriptor = { ...descriptor, attributes, }; const updatedEService: EService = { ...mockEService, attributes: undefined, - descriptors: [updatedDescriptor], + descriptors: [expectedDescriptor], }; const payload: MovedAttributesFromEserviceToDescriptorsV1 = { eservice: toEServiceV1(updatedEService), diff --git a/packages/commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts b/packages/commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts index 0aa0784697..d6e5df0ef0 100644 --- a/packages/commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts +++ b/packages/commons-test/src/protobufConvertersToV1/catalogProtobufConverterToV1.ts @@ -39,6 +39,7 @@ export const toEServiceDescriptorStateV1 = ( .with("Archived", () => EServiceDescriptorStateV1.ARCHIVED) .with("Published", () => EServiceDescriptorStateV1.PUBLISHED) .with("Deprecated", () => EServiceDescriptorStateV1.DEPRECATED) + .with("WaitingForApproval", () => EServiceDescriptorStateV1.DRAFT) .exhaustive(); export const toEServiceTechnologyV1 = ( diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 6b72aa572a..0a2ffeec75 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -1,3 +1,4 @@ +/* eslint-disable fp/no-delete */ import crypto from "crypto"; import { fail } from "assert"; import { generateMock } from "@anatine/zod-mock"; @@ -67,6 +68,7 @@ import { PlatformStatesClientPK, PlatformStatesClientEntry, makePlatformStatesClientPK, + AgreementStamps, DelegationKind, unsafeBrandId, UserId, @@ -225,6 +227,18 @@ export const getMockTenantMail = ( address: generateMock(z.string().email()), }); +export const getMockAgreementStamps = (): AgreementStamps => { + const stamps = generateMock(AgreementStamps); + delete stamps.submission?.delegateId; + delete stamps.activation?.delegateId; + delete stamps.rejection?.delegateId; + delete stamps.suspensionByConsumer?.delegateId; + delete stamps.suspensionByProducer?.delegateId; + delete stamps.upgrade?.delegateId; + delete stamps.archiving?.delegateId; + return stamps; +}; + export const getMockAgreement = ( eserviceId: EServiceId = generateId(), consumerId: TenantId = generateId(), @@ -234,6 +248,7 @@ export const getMockAgreement = ( eserviceId, consumerId, state, + stamps: getMockAgreementStamps(), }); export const getMockAttribute = ( diff --git a/packages/datalake-data-export/src/services/readModelService.ts b/packages/datalake-data-export/src/services/readModelService.ts index 2dc85fda11..53d1e28708 100644 --- a/packages/datalake-data-export/src/services/readModelService.ts +++ b/packages/datalake-data-export/src/services/readModelService.ts @@ -52,7 +52,9 @@ export function readModelServiceBuilder( ExportedEService.parse({ ...data, descriptors: data.descriptors.filter( - (descriptor) => descriptor.state !== "Draft" + (descriptor) => + descriptor.state !== "Draft" && + descriptor.state !== "WaitingForApproval" ), }) ) diff --git a/packages/datalake-data-export/test/read-model-queries-service.test.ts b/packages/datalake-data-export/test/read-model-queries-service.test.ts index 497bad17da..734ac13c24 100644 --- a/packages/datalake-data-export/test/read-model-queries-service.test.ts +++ b/packages/datalake-data-export/test/read-model-queries-service.test.ts @@ -64,7 +64,9 @@ describe("read-model-queries.service", () => { describe("getEServices", async () => { const validEserviceDescriptorStates = Object.values(descriptorState).filter( - (state) => state !== descriptorState.draft + (state) => + state !== descriptorState.draft && + state !== descriptorState.waitingForApproval ); it("should return all eServices", async () => { @@ -123,6 +125,37 @@ describe("read-model-queries.service", () => { expect(result.at(0)?.descriptors.at(0)?.state).toEqual("Published"); }); + it("should not return waiting for approval descriptors in the e-service", async () => { + const eservicesData = [ + getMockEService(generateId(), generateId(), [ + { + ...getMockDescriptor(), + id: unsafeBrandId("a9c705d9-ecdb-47ff-bcd2-667495b111f2"), + version: "2", + state: descriptorState.published, + }, + { + ...getMockDescriptor(), + id: unsafeBrandId("a9c705d9-ecdb-47ff-bcd2-667495b111f3"), + state: descriptorState.waitingForApproval, + version: "1", + attributes: { + certified: [], + verified: [], + declared: [], + }, + }, + ]), + ].map(toReadModelEService); + + await seedCollection(eservicesData, eservices); + + const result = await readModelService.getEServices(); + expect(result).toHaveLength(eservicesData.length); + expect(result.at(0)?.descriptors).toHaveLength(1); + expect(result.at(0)?.descriptors.at(0)?.state).toEqual("Published"); + }); + it("should return empty array if no eServices are found", async () => { const result = await readModelService.getEServices(); expect(result).toHaveLength(0); diff --git a/packages/datalake-interface-exporter/src/interfaceExporterV2.ts b/packages/datalake-interface-exporter/src/interfaceExporterV2.ts index da5151180c..396755c684 100644 --- a/packages/datalake-interface-exporter/src/interfaceExporterV2.ts +++ b/packages/datalake-interface-exporter/src/interfaceExporterV2.ts @@ -11,25 +11,29 @@ export async function exportInterfaceV2( logger: Logger ): Promise { await match(decodedMsg) - .with({ type: "EServiceDescriptorPublished" }, async ({ data }) => { - if (data.eservice) { - logger.info( - `Processing ${decodedMsg.type} message - Partition number: ${originalPayload.partition} - Offset: ${originalPayload.message.offset}` - ); - const eservice = fromEServiceV2(data.eservice); - const publishedDescriptor = eservice.descriptors.find( - (d) => d.id === data.descriptorId - ); - if (publishedDescriptor) { - await exportInterface( - eservice.id, - publishedDescriptor, - fileManager, - logger + .with( + { type: "EServiceDescriptorPublished" }, + { type: "EServiceDescriptorDelegatorApproved" }, + async ({ data }) => { + if (data.eservice) { + logger.info( + `Processing ${decodedMsg.type} message - Partition number: ${originalPayload.partition} - Offset: ${originalPayload.message.offset}` + ); + const eservice = fromEServiceV2(data.eservice); + const publishedDescriptor = eservice.descriptors.find( + (d) => d.id === data.descriptorId ); + if (publishedDescriptor) { + await exportInterface( + eservice.id, + publishedDescriptor, + fileManager, + logger + ); + } } } - }) + ) .with( { type: "EServiceAdded" }, { type: "DraftEServiceUpdated" }, @@ -52,6 +56,8 @@ export async function exportInterfaceV2( { type: "EServiceRiskAnalysisUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceDescriptorDelegateSubmitted" }, + { type: "EServiceDescriptorDelegatorRejected" }, () => undefined ) .exhaustive(); diff --git a/packages/models/proto/v2/agreement/agreement.proto b/packages/models/proto/v2/agreement/agreement.proto index 0f67571e75..f4d60fb7bc 100644 --- a/packages/models/proto/v2/agreement/agreement.proto +++ b/packages/models/proto/v2/agreement/agreement.proto @@ -61,6 +61,7 @@ enum AgreementStateV2 { message AgreementStampV2 { string who = 1; int64 when = 2; + optional string delegateId = 3; } message AgreementStampsV2 { diff --git a/packages/models/proto/v2/eservice/eservice.proto b/packages/models/proto/v2/eservice/eservice.proto index 4ea2a1c389..3645b9f915 100644 --- a/packages/models/proto/v2/eservice/eservice.proto +++ b/packages/models/proto/v2/eservice/eservice.proto @@ -57,6 +57,11 @@ message EServiceAttributesV2 { repeated EServiceAttributeV2 verified = 3; } +message DescriptorRejectionReasonV2 { + string rejectionReason = 1; + int64 rejectedAt = 2; +} + message EServiceDescriptorV2 { string id = 1; int64 version = 2; @@ -76,6 +81,7 @@ message EServiceDescriptorV2 { optional int64 deprecatedAt = 16; optional int64 archivedAt = 17; EServiceAttributesV2 attributes = 18; + repeated DescriptorRejectionReasonV2 rejectionReasons = 19; } message EServiceDocumentV2 { @@ -94,6 +100,7 @@ enum EServiceDescriptorStateV2 { DEPRECATED = 2; SUSPENDED = 3; ARCHIVED = 4; + WAITING_FOR_APPROVAL = 5; } enum EServiceTechnologyV2 { diff --git a/packages/models/proto/v2/eservice/events.proto b/packages/models/proto/v2/eservice/events.proto index 4506ae3f5e..d014fd4ac7 100644 --- a/packages/models/proto/v2/eservice/events.proto +++ b/packages/models/proto/v2/eservice/events.proto @@ -117,3 +117,18 @@ message EServiceRiskAnalysisDeletedV2 { message EServiceDescriptionUpdatedV2 { EServiceV2 eservice = 1; } + +message EServiceDescriptorDelegateSubmittedV2 { + string descriptorId = 1; + EServiceV2 eservice = 2; +} + +message EServiceDescriptorDelegatorApprovedV2 { + string descriptorId = 1; + EServiceV2 eservice = 2; +} + +message EServiceDescriptorDelegatorRejectedV2 { + string descriptorId = 1; + EServiceV2 eservice = 2; +} diff --git a/packages/models/src/agreement/agreement.ts b/packages/models/src/agreement/agreement.ts index 8aed89d3b3..420a3cb987 100644 --- a/packages/models/src/agreement/agreement.ts +++ b/packages/models/src/agreement/agreement.ts @@ -39,6 +39,7 @@ export type AgreementDocument = z.infer; export const AgreementStamp = z.object({ who: UserId, + delegateId: TenantId.optional(), when: z.coerce.date(), }); export type AgreementStamp = z.infer; diff --git a/packages/models/src/eservice/eservice.ts b/packages/models/src/eservice/eservice.ts index 4d6235171e..85e2fed90d 100644 --- a/packages/models/src/eservice/eservice.ts +++ b/packages/models/src/eservice/eservice.ts @@ -21,6 +21,7 @@ export const descriptorState = { deprecated: "Deprecated", suspended: "Suspended", archived: "Archived", + waitingForApproval: "WaitingForApproval", } as const; export const DescriptorState = z.enum([ Object.values(descriptorState)[0], @@ -62,6 +63,14 @@ export const Document = z.object({ }); export type Document = z.infer; +export const DescriptorRejectionReason = z.object({ + rejectionReason: z.string(), + rejectedAt: z.coerce.date(), +}); +export type DescriptorRejectionReason = z.infer< + typeof DescriptorRejectionReason +>; + export const Descriptor = z.object({ id: DescriptorId, version: z.string(), @@ -81,6 +90,7 @@ export const Descriptor = z.object({ deprecatedAt: z.coerce.date().optional(), archivedAt: z.coerce.date().optional(), attributes: EServiceAttributes, + rejectionReasons: z.array(DescriptorRejectionReason).optional(), }); export type Descriptor = z.infer; diff --git a/packages/models/src/eservice/eserviceEvents.ts b/packages/models/src/eservice/eserviceEvents.ts index 7ce9333ffa..0c9758e99c 100644 --- a/packages/models/src/eservice/eserviceEvents.ts +++ b/packages/models/src/eservice/eserviceEvents.ts @@ -41,6 +41,9 @@ import { EServiceRiskAnalysisUpdatedV2, EServiceRiskAnalysisDeletedV2, EServiceDescriptionUpdatedV2, + EServiceDescriptorDelegateSubmittedV2, + EServiceDescriptorDelegatorApprovedV2, + EServiceDescriptorDelegatorRejectedV2, } from "../gen/v2/eservice/events.js"; export function catalogEventToBinaryData(event: EServiceEvent): Uint8Array { @@ -165,6 +168,15 @@ export function catalogEventToBinaryDataV2(event: EServiceEventV2): Uint8Array { .with({ type: "EServiceDescriptionUpdated" }, ({ data }) => EServiceDescriptionUpdatedV2.toBinary(data) ) + .with({ type: "EServiceDescriptorDelegateSubmitted" }, ({ data }) => + EServiceDescriptorDelegateSubmittedV2.toBinary(data) + ) + .with({ type: "EServiceDescriptorDelegatorApproved" }, ({ data }) => + EServiceDescriptorDelegatorApprovedV2.toBinary(data) + ) + .with({ type: "EServiceDescriptorDelegatorRejected" }, ({ data }) => + EServiceDescriptorDelegatorRejectedV2.toBinary(data) + ) .exhaustive(); } @@ -353,6 +365,21 @@ export const EServiceEventV2 = z.discriminatedUnion("type", [ type: z.literal("EServiceDescriptionUpdated"), data: protobufDecoder(EServiceDescriptionUpdatedV2), }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceDescriptorDelegateSubmitted"), + data: protobufDecoder(EServiceDescriptorDelegateSubmittedV2), + }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceDescriptorDelegatorApproved"), + data: protobufDecoder(EServiceDescriptorDelegatorApprovedV2), + }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceDescriptorDelegatorRejected"), + data: protobufDecoder(EServiceDescriptorDelegatorRejectedV2), + }), ]); export type EServiceEventV2 = z.infer; diff --git a/packages/models/src/eservice/protobufConverterFromV2.ts b/packages/models/src/eservice/protobufConverterFromV2.ts index ef891527d6..c7fc18ac6c 100644 --- a/packages/models/src/eservice/protobufConverterFromV2.ts +++ b/packages/models/src/eservice/protobufConverterFromV2.ts @@ -10,6 +10,7 @@ import { EServiceModeV2, EServiceRiskAnalysisV2, EServiceRiskAnalysisFormV2, + DescriptorRejectionReasonV2, } from "../gen/v2/eservice/eservice.js"; import { RiskAnalysis, @@ -29,6 +30,7 @@ import { Descriptor, EService, Document, + DescriptorRejectionReason, } from "./eservice.js"; export const fromAgreementApprovalPolicyV2 = ( @@ -56,6 +58,8 @@ export const fromEServiceDescriptorStateV2 = ( return descriptorState.published; case EServiceDescriptorStateV2.DEPRECATED: return descriptorState.deprecated; + case EServiceDescriptorStateV2.WAITING_FOR_APPROVAL: + return descriptorState.waitingForApproval; } }; @@ -90,6 +94,13 @@ export const fromDocumentV2 = (input: EServiceDocumentV2): Document => ({ uploadDate: new Date(input.uploadDate), }); +export const fromDescriptorRejectionReasonV2 = ( + input: DescriptorRejectionReasonV2 +): DescriptorRejectionReason => ({ + ...input, + rejectedAt: bigIntToDate(input.rejectedAt), +}); + export const fromDescriptorV2 = (input: EServiceDescriptorV2): Descriptor => ({ ...input, id: unsafeBrandId(input.id), @@ -118,6 +129,10 @@ export const fromDescriptorV2 = (input: EServiceDescriptorV2): Descriptor => ({ suspendedAt: bigIntToDate(input.suspendedAt), deprecatedAt: bigIntToDate(input.deprecatedAt), archivedAt: bigIntToDate(input.archivedAt), + rejectionReasons: + input.rejectionReasons.length > 0 + ? input.rejectionReasons.map(fromDescriptorRejectionReasonV2) + : undefined, }); export const fromRiskAnalysisFormV2 = ( diff --git a/packages/models/src/eservice/protobufConverterToV2.ts b/packages/models/src/eservice/protobufConverterToV2.ts index 893ac2c4e9..8e1bed530b 100644 --- a/packages/models/src/eservice/protobufConverterToV2.ts +++ b/packages/models/src/eservice/protobufConverterToV2.ts @@ -1,6 +1,7 @@ import { P, match } from "ts-pattern"; import { AgreementApprovalPolicyV2, + DescriptorRejectionReasonV2, EServiceAttributeV2, EServiceDescriptorStateV2, EServiceDescriptorV2, @@ -15,6 +16,7 @@ import { dateToBigInt } from "../utils.js"; import { AgreementApprovalPolicy, Descriptor, + DescriptorRejectionReason, DescriptorState, Document, EService, @@ -54,6 +56,10 @@ export const toEServiceDescriptorStateV2 = ( descriptorState.deprecated, () => EServiceDescriptorStateV2.DEPRECATED ) + .with( + descriptorState.waitingForApproval, + () => EServiceDescriptorStateV2.WAITING_FOR_APPROVAL + ) .exhaustive(); export const toEServiceTechnologyV2 = ( @@ -79,6 +85,13 @@ export const toEServiceAttributeV2 = ( })), }); +export const toDescriptorRejectedReasonV2 = ( + input: DescriptorRejectionReason +): DescriptorRejectionReasonV2 => ({ + ...input, + rejectedAt: dateToBigInt(input.rejectedAt), +}); + export const toDocumentV2 = (input: Document): EServiceDocumentV2 => ({ ...input, uploadDate: input.uploadDate.toISOString(), @@ -104,6 +117,8 @@ export const toDescriptorV2 = (input: Descriptor): EServiceDescriptorV2 => ({ suspendedAt: dateToBigInt(input.suspendedAt), deprecatedAt: dateToBigInt(input.deprecatedAt), archivedAt: dateToBigInt(input.archivedAt), + rejectionReasons: + input.rejectionReasons?.map(toDescriptorRejectedReasonV2) ?? [], }); export const toRiskAnalysisV2 = ( diff --git a/packages/models/src/tenant/tenant.ts b/packages/models/src/tenant/tenant.ts index 02f8e93bd7..0566139b3d 100644 --- a/packages/models/src/tenant/tenant.ts +++ b/packages/models/src/tenant/tenant.ts @@ -22,14 +22,26 @@ export const ExternalId = z.object({ export type ExternalId = z.infer; +export const tenantFeatureType = { + persistentCertifier: "PersistentCertifier", + delegatedProducer: "DelegatedProducer", +} as const; + +export const TenantFeatureType = z.enum([ + Object.values(tenantFeatureType)[0], + ...Object.values(tenantFeatureType).slice(1), +]); + +export type TenantFeatureType = z.infer; + export const TenantFeatureCertifier = z.object({ - type: z.literal("PersistentCertifier"), + type: z.literal(tenantFeatureType.persistentCertifier), certifierId: z.string(), }); export type TenantFeatureCertifier = z.infer; export const TenantFeatureDelegatedProducer = z.object({ - type: z.literal("DelegatedProducer"), + type: z.literal(tenantFeatureType.delegatedProducer), availabilityTimestamp: z.coerce.date(), }); export type TenantFeatureDelegatedProducer = z.infer< diff --git a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts index fc0da318ca..fa917cd8e5 100644 --- a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts +++ b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts @@ -108,6 +108,9 @@ export const toCatalogItemEventNotification = ( { type: "EServiceDescriptorArchived" }, // CatalogItemDescriptorUpdatedV1 { type: "EServiceDescriptorPublished" }, // CatalogItemDescriptorUpdatedV1 { type: "EServiceDescriptorSuspended" }, // CatalogItemDescriptorUpdatedV1 + { type: "EServiceDescriptorDelegateSubmitted" }, + { type: "EServiceDescriptorDelegatorApproved" }, + { type: "EServiceDescriptorDelegatorRejected" }, { type: "EServiceDescriptorQuotasUpdated" }, (e): CatalogDescriptorNotification => { const catalogItem = getCatalogItem(e); diff --git a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMappers.ts b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMappers.ts index 0e5ab3a584..894780e623 100644 --- a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMappers.ts +++ b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMappers.ts @@ -42,6 +42,8 @@ export const toCatalogDescriptorStateV1 = (input: DescriptorState): string => { return "Suspended"; case descriptorState.archived: return "Archived"; + case descriptorState.waitingForApproval: + return "WaitingForApproval"; } }; diff --git a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts index cc05355565..4c25629e07 100644 --- a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts +++ b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts @@ -31,6 +31,9 @@ export const eventV2TypeMapper = ( "EServiceDescriptorArchived", "EServiceDescriptorPublished", "EServiceDescriptorSuspended", + "EServiceDescriptorDelegateSubmitted", + "EServiceDescriptorDelegatorApproved", + "EServiceDescriptorDelegatorRejected", () => "catalog_item_descriptor_updated" ) .with( diff --git a/packages/purpose-outbound-writer/package.json b/packages/purpose-outbound-writer/package.json index 88741fed89..4b61fe333d 100644 --- a/packages/purpose-outbound-writer/package.json +++ b/packages/purpose-outbound-writer/package.json @@ -29,7 +29,7 @@ "vitest": "1.6.0" }, "dependencies": { - "@pagopa/interop-outbound-models": "1.0.10", + "@pagopa/interop-outbound-models": "1.0.11a", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", @@ -40,4 +40,4 @@ "ts-pattern": "5.2.0", "zod": "3.23.8" } -} \ No newline at end of file +} diff --git a/packages/purpose-process/src/services/purposeService.ts b/packages/purpose-process/src/services/purposeService.ts index 4c709e7140..6358d76717 100644 --- a/packages/purpose-process/src/services/purposeService.ts +++ b/packages/purpose-process/src/services/purposeService.ts @@ -105,6 +105,8 @@ import { validateRiskAnalysisOrThrow, assertPurposeTitleIsNotDuplicated, isOverQuota, + assertRequesterIsAllowedToRetrieveRiskAnalysisDocument, + assertRequesterIsProducer, } from "./validators.js"; import { riskAnalysisDocumentBuilder } from "./riskAnalysisDocumentBuilder.js"; @@ -267,11 +269,15 @@ export function purposeServiceBuilder( purpose.data.eserviceId, readModelService ); - getOrganizationRole({ + + await assertRequesterIsAllowedToRetrieveRiskAnalysisDocument({ + eserviceId: eservice.id, organizationId, producerId: eservice.producerId, consumerId: purpose.data.consumerId, + readModelService, }); + const version = retrievePurposeVersion(versionId, purpose); return retrievePurposeVersionDocument(purposeId, version, documentId); @@ -341,9 +347,13 @@ export function purposeServiceBuilder( purpose.data.eserviceId, readModelService ); - if (organizationId !== eservice.producerId) { - throw organizationIsNotTheProducer(organizationId); - } + + await assertRequesterIsProducer({ + eserviceId: eservice.id, + organizationId, + producerId: eservice.producerId, + readModelService, + }); const purposeVersion = retrievePurposeVersion(versionId, purpose); @@ -535,10 +545,12 @@ export function purposeServiceBuilder( readModelService ); - const suspender = getOrganizationRole({ + const suspender = await getOrganizationRole({ + eserviceId: eservice.id, organizationId, producerId: eservice.producerId, consumerId: purpose.data.consumerId, + readModelService, }); const suspendedPurposeVersion: PurposeVersion = { @@ -812,10 +824,12 @@ export function purposeServiceBuilder( }); } - const purposeOwnership = getOrganizationRole({ + const purposeOwnership = await getOrganizationRole({ + eserviceId: eservice.id, organizationId, producerId: eservice.producerId, consumerId: purpose.data.consumerId, + readModelService, }); const { event, updatedPurposeVersion } = await match({ @@ -1331,24 +1345,37 @@ const authorizeRiskAnalysisForm = ({ } }; -const getOrganizationRole = ({ +const getOrganizationRole = async ({ + eserviceId, organizationId, producerId, consumerId, + readModelService, }: { + eserviceId: EServiceId; organizationId: TenantId; producerId: TenantId; consumerId: TenantId; -}): Ownership => { + readModelService: ReadModelService; +}): Promise => { if (producerId === consumerId && organizationId === producerId) { return ownership.SELF_CONSUMER; } else if (producerId !== consumerId && organizationId === consumerId) { return ownership.CONSUMER; - } else if (producerId !== consumerId && organizationId === producerId) { + } + + const activeDelegation = await readModelService.getActiveDelegation( + eserviceId + ); + + if ( + (activeDelegation && organizationId === activeDelegation.delegateId) || + (!activeDelegation && organizationId === producerId) + ) { return ownership.PRODUCER; - } else { - throw organizationNotAllowed(organizationId); } + + throw organizationNotAllowed(organizationId); }; const replacePurposeVersion = ( diff --git a/packages/purpose-process/src/services/readModelService.ts b/packages/purpose-process/src/services/readModelService.ts index 1fd4bb75bf..2aad3c8436 100644 --- a/packages/purpose-process/src/services/readModelService.ts +++ b/packages/purpose-process/src/services/readModelService.ts @@ -23,6 +23,8 @@ import { agreementState, PurposeVersionState, TenantReadModel, + delegationState, + Delegation, } from "pagopa-interop-models"; import { Document, Filter, WithId } from "mongodb"; import { z } from "zod"; @@ -233,7 +235,8 @@ async function buildGetPurposesAggregation( export function readModelServiceBuilder( readModelRepository: ReadModelRepository ) { - const { eservices, purposes, tenants, agreements } = readModelRepository; + const { eservices, purposes, tenants, agreements, delegations } = + readModelRepository; return { async getEServiceById(id: EServiceId): Promise { @@ -333,6 +336,23 @@ export function readModelServiceBuilder( return result.data; }, + async getActiveDelegation( + eserviceId: EServiceId + ): Promise { + const data = await delegations.findOne({ + "data.eserviceId": eserviceId, + "data.state": delegationState.active, + }); + if (!data) { + return undefined; + } else { + const result = Delegation.safeParse(data.data); + if (!result.success) { + throw genericError("Unable to parse delegation item"); + } + return result.data; + } + }, }; } diff --git a/packages/purpose-process/src/services/validators.ts b/packages/purpose-process/src/services/validators.ts index bee99fba71..107f18fa73 100644 --- a/packages/purpose-process/src/services/validators.ts +++ b/packages/purpose-process/src/services/validators.ts @@ -23,6 +23,8 @@ import { eServiceModeNotAllowed, missingFreeOfChargeReason, organizationIsNotTheConsumer, + organizationIsNotTheProducer, + organizationNotAllowed, purposeNotInDraftState, riskAnalysisValidationFailed, } from "../model/domain/errors.js"; @@ -248,3 +250,56 @@ export async function isOverQuota( allPurposesRequestsSum + dailyCalls <= maxDailyCallsTotal ); } + +export const assertRequesterIsAllowedToRetrieveRiskAnalysisDocument = async ({ + eserviceId, + organizationId, + producerId, + consumerId, + readModelService, +}: { + eserviceId: EServiceId; + organizationId: TenantId; + producerId: TenantId; + consumerId: TenantId; + readModelService: ReadModelService; +}): Promise => { + if (organizationId === producerId || organizationId === consumerId) { + return; + } + + const activeDelegation = await readModelService.getActiveDelegation( + eserviceId + ); + + if (activeDelegation && organizationId === activeDelegation.delegateId) { + return; + } + + throw organizationNotAllowed(organizationId); +}; + +export const assertRequesterIsProducer = async ({ + eserviceId, + organizationId, + producerId, + readModelService, +}: { + eserviceId: EServiceId; + organizationId: TenantId; + producerId: TenantId; + readModelService: ReadModelService; +}): Promise => { + const activeDelegation = await readModelService.getActiveDelegation( + eserviceId + ); + + if ( + (activeDelegation && organizationId === activeDelegation.delegateId) || + (!activeDelegation && organizationId === producerId) + ) { + return; + } + + throw organizationIsNotTheProducer(organizationId); +}; diff --git a/packages/purpose-process/test/activatePurposeVersion.test.ts b/packages/purpose-process/test/activatePurposeVersion.test.ts index c93c3da085..c0352e1d67 100644 --- a/packages/purpose-process/test/activatePurposeVersion.test.ts +++ b/packages/purpose-process/test/activatePurposeVersion.test.ts @@ -10,9 +10,10 @@ import { getMockEService, getMockAgreement, getMockValidRiskAnalysisForm, - writeInReadmodel, readLastEventByStreamId, decodeProtobufPayload, + getMockDelegation, + getMockAuthData, } from "pagopa-interop-commons-test"; import { PurposeVersion, @@ -24,7 +25,6 @@ import { Agreement, Descriptor, agreementState, - toReadModelEService, TenantKind, PurposeActivatedV2, toPurposeV2, @@ -33,9 +33,9 @@ import { PurposeVersionOverQuotaUnsuspendedV2, PurposeWaitingForApprovalV2, eserviceMode, - toReadModelAgreement, PurposeVersionActivatedV2, - toReadModelTenant, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { @@ -55,11 +55,12 @@ import { agreementNotFound, } from "../src/model/domain/errors.js"; import { - agreements, - eservices, + addOneAgreement, + addOneDelegation, + addOneEService, + addOneTenant, postgresDB, purposeService, - tenants, } from "./utils.js"; import { addOnePurpose } from "./utils.js"; @@ -125,10 +126,10 @@ describe("activatePurposeVersion", () => { it("should write on event-store for the activation of a purpose version in the waiting for approval state", async () => { await addOnePurpose(mockPurpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -179,10 +180,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -235,10 +236,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -292,10 +293,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -349,10 +350,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -404,10 +405,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -455,10 +456,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const purposeVersion = await purposeService.activatePurposeVersion({ purposeId: mockPurpose.id, @@ -503,10 +504,10 @@ describe("activatePurposeVersion", () => { const purpose: Purpose = { ...mockPurpose, versions: [purposeVersion] }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -527,10 +528,10 @@ describe("activatePurposeVersion", () => { const purpose: Purpose = { ...mockPurpose, versions: [purposeVersion] }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -561,10 +562,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(eservice), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(consumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(eservice); + await addOneAgreement(mockAgreement); + await addOneTenant(consumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -589,10 +590,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -607,9 +608,9 @@ describe("activatePurposeVersion", () => { it("should throw eserviceNotFound if the e-service does not exists in the readmodel", async () => { await addOnePurpose(mockPurpose); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -630,9 +631,9 @@ describe("activatePurposeVersion", () => { const purpose: Purpose = { ...mockPurpose, versions: [purposeVersion] }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -663,10 +664,10 @@ describe("activatePurposeVersion", () => { const purpose: Purpose = { ...mockPurpose, versions: [purposeVersion] }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(agreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(agreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -682,15 +683,15 @@ describe("activatePurposeVersion", () => { } ); - it("should throw organizationNotAllowed if the caller is neither the producer or the consumer of the purpose", async () => { + it("should throw organizationNotAllowed if the caller is neither the producer or the consumer of the purpose, nor the delegate", async () => { const anotherTenant: Tenant = { ...getMockTenant(), kind: "PA" }; await addOnePurpose(mockPurpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); - await writeInReadmodel(toReadModelTenant(anotherTenant), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); + await addOneTenant(anotherTenant); expect(async () => { await purposeService.activatePurposeVersion({ @@ -703,6 +704,67 @@ describe("activatePurposeVersion", () => { }).rejects.toThrowError(organizationNotAllowed(anotherTenant.id)); }); + it("should throw organizationNotAllowed if the caller is the producer but the purpose e-service has an active delegation", async () => { + await addOnePurpose(mockPurpose); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await addOneDelegation(delegation); + + expect(async () => { + await purposeService.activatePurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + organizationId: mockProducer.id, + correlationId: generateId(), + logger: genericLogger, + }); + }).rejects.toThrowError(organizationNotAllowed(mockProducer.id)); + }); + + it.each( + Object.values(delegationState).filter((s) => s !== delegationState.active) + )( + "should throw organizationNotAllowed if the caller is the purpose e-service delegate but the delegation is in %s state", + async (delegationState) => { + await addOnePurpose(mockPurpose); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState, + }); + + await addOneDelegation(delegation); + + expect(async () => { + await purposeService.activatePurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + organizationId: delegate.organizationId, + correlationId: generateId(), + logger: genericLogger, + }); + }).rejects.toThrowError(organizationNotAllowed(delegate.organizationId)); + } + ); + it("should throw missingRiskAnalysis if the purpose is in draft and has no risk analysis", async () => { const purposeVersion: PurposeVersion = { ...mockPurposeVersion, @@ -715,10 +777,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -745,10 +807,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); const result = validateRiskAnalysis( riskAnalysisFormToRiskAnalysisFormToValidate(riskAnalysisForm), @@ -782,9 +844,9 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -799,9 +861,9 @@ describe("activatePurposeVersion", () => { it("should throw tenantNotFound if the purpose producer is not found in the readmodel", async () => { await addOnePurpose(mockPurpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -822,10 +884,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(mockPurpose); - await writeInReadmodel(toReadModelEService(eservice), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(consumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(eservice); + await addOneAgreement(mockAgreement); + await addOneTenant(consumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -846,10 +908,10 @@ describe("activatePurposeVersion", () => { }; await addOnePurpose(mockPurpose); - await writeInReadmodel(toReadModelEService(eservice), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(producer), tenants); + await addOneEService(eservice); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(producer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -876,10 +938,10 @@ describe("activatePurposeVersion", () => { const purpose: Purpose = { ...mockPurpose, versions: [purposeVersion] }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ @@ -907,10 +969,10 @@ describe("activatePurposeVersion", () => { const purpose: Purpose = { ...mockPurpose, versions: [purposeVersion] }; await addOnePurpose(purpose); - await writeInReadmodel(toReadModelEService(mockEService), eservices); - await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - await writeInReadmodel(toReadModelTenant(mockProducer), tenants); + await addOneEService(mockEService); + await addOneAgreement(mockAgreement); + await addOneTenant(mockConsumer); + await addOneTenant(mockProducer); expect(async () => { await purposeService.activatePurposeVersion({ diff --git a/packages/purpose-process/test/getRiskAnalysisDocument.test.ts b/packages/purpose-process/test/getRiskAnalysisDocument.test.ts index 4d2ff798a8..49c94cc3ed 100644 --- a/packages/purpose-process/test/getRiskAnalysisDocument.test.ts +++ b/packages/purpose-process/test/getRiskAnalysisDocument.test.ts @@ -4,6 +4,8 @@ import { getMockPurposeVersion, getMockPurpose, writeInReadmodel, + getMockAuthData, + getMockDelegation, } from "pagopa-interop-commons-test"; import { Purpose, @@ -13,6 +15,8 @@ import { PurposeVersionId, PurposeVersionDocumentId, TenantId, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; import { genericLogger } from "pagopa-interop-commons"; @@ -28,28 +32,51 @@ import { addOnePurpose, eservices, purposeService, + delegations, } from "./utils.js"; describe("getRiskAnalysisDocument", () => { - it("should get the purpose version document", async () => { + it("should get the purpose version document (consumer)", async () => { const mockDocument = getMockPurposeVersionDocument(); const mockEService = getMockEService(); const mockPurposeVersion = { ...getMockPurposeVersion(), riskAnalysis: mockDocument, }; - const mockPurpose1: Purpose = { + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const result = await purposeService.getRiskAnalysisDocument({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + documentId: mockDocument.id, + organizationId: mockPurpose.consumerId, + logger: genericLogger, + }); + expect(result).toEqual(mockDocument); + }); + it("should get the purpose version document (producer)", async () => { + const mockDocument = getMockPurposeVersionDocument(); + const mockEService = getMockEService(); + const mockPurposeVersion = { + ...getMockPurposeVersion(), + riskAnalysis: mockDocument, + }; + const mockPurpose: Purpose = { ...getMockPurpose(), eserviceId: mockEService.id, versions: [mockPurposeVersion], }; - const mockPurpose2 = getMockPurpose(); - await addOnePurpose(mockPurpose1); - await addOnePurpose(mockPurpose2); + await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); const result = await purposeService.getRiskAnalysisDocument({ - purposeId: mockPurpose1.id, + purposeId: mockPurpose.id, versionId: mockPurposeVersion.id, documentId: mockDocument.id, organizationId: mockEService.producerId, @@ -57,6 +84,40 @@ describe("getRiskAnalysisDocument", () => { }); expect(result).toEqual(mockDocument); }); + it("should get the purpose version document (delegate)", async () => { + const mockDocument = getMockPurposeVersionDocument(); + const mockEService = getMockEService(); + const mockPurposeVersion = { + ...getMockPurposeVersion(), + riskAnalysis: mockDocument, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + const result = await purposeService.getRiskAnalysisDocument({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + documentId: mockDocument.id, + organizationId: delegate.organizationId, + logger: genericLogger, + }); + expect(result).toEqual(mockDocument); + }); it("should throw purposeNotFound if the purpose doesn't exist", async () => { const notExistingId: PurposeId = generateId(); await addOnePurpose(getMockPurpose()); @@ -134,7 +195,7 @@ describe("getRiskAnalysisDocument", () => { ) ); }); - it("should throw organizationNotAllowed if the requester is not the producer nor the consumer", async () => { + it("should throw organizationNotAllowed if the requester is not the producer nor the consumer nor the delegate", async () => { const randomId: TenantId = generateId(); const mockDocument = getMockPurposeVersionDocument(); const mockEService = getMockEService(); @@ -161,4 +222,45 @@ describe("getRiskAnalysisDocument", () => { }) ).rejects.toThrowError(organizationNotAllowed(randomId)); }); + it.each( + Object.values(delegationState).filter((s) => s !== delegationState.active) + )( + "should throw organizationNotAllowed if the requester is the delegate but the delegation is in %s state", + async (delegationState) => { + const mockDocument = getMockPurposeVersionDocument(); + const mockEService = getMockEService(); + const mockPurposeVersion = { + ...getMockPurposeVersion(), + riskAnalysis: mockDocument, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState, + }); + + await writeInReadmodel(delegation, delegations); + + expect( + purposeService.getRiskAnalysisDocument({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + documentId: mockDocument.id, + organizationId: delegate.organizationId, + logger: genericLogger, + }) + ).rejects.toThrowError(organizationNotAllowed(delegate.organizationId)); + } + ); }); diff --git a/packages/purpose-process/test/rejectPurposeVersion.test.ts b/packages/purpose-process/test/rejectPurposeVersion.test.ts index 3ab8cae276..5312c58641 100644 --- a/packages/purpose-process/test/rejectPurposeVersion.test.ts +++ b/packages/purpose-process/test/rejectPurposeVersion.test.ts @@ -4,6 +4,8 @@ import { getMockPurpose, writeInReadmodel, decodeProtobufPayload, + getMockAuthData, + getMockDelegation, } from "pagopa-interop-commons-test"; import { purposeVersionState, @@ -15,6 +17,8 @@ import { toPurposeV2, PurposeId, PurposeVersionId, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { describe, expect, it, vi } from "vitest"; import { genericLogger } from "pagopa-interop-commons"; @@ -31,10 +35,11 @@ import { readLastPurposeEvent, eservices, purposeService, + delegations, } from "./utils.js"; describe("rejectPurposeVersion", () => { - it("should write on event-store for the rejection of a purpose version", async () => { + it("should write on event-store for the rejection of a purpose version ", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date()); @@ -91,6 +96,73 @@ describe("rejectPurposeVersion", () => { vi.useRealTimers(); }); + it("should write on event-store for the rejection of a purpose version when the requester is delegate", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date()); + + const mockEService = getMockEService(); + const mockPurposeVersion = { + ...getMockPurposeVersion(), + state: purposeVersionState.waitingForApproval, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + await purposeService.rejectPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + organizationId: delegate.organizationId, + correlationId: generateId(), + logger: genericLogger, + }); + + const writtenEvent = await readLastPurposeEvent(mockPurpose.id); + + expect(writtenEvent).toMatchObject({ + stream_id: mockPurpose.id, + version: "1", + type: "PurposeVersionRejected", + event_version: 2, + }); + + const writtenPayload = decodeProtobufPayload({ + messageType: PurposeVersionRejectedV2, + payload: writtenEvent.data, + }); + + const expectedPurposeVersion: PurposeVersion = { + ...mockPurposeVersion, + state: purposeVersionState.rejected, + rejectionReason: "test", + updatedAt: new Date(), + }; + const expectedPurpose: Purpose = { + ...mockPurpose, + versions: [expectedPurposeVersion], + updatedAt: new Date(), + }; + + expect(writtenPayload.purpose).toEqual(toPurposeV2(expectedPurpose)); + + vi.useRealTimers(); + }); it("should throw purposeNotFound if the purpose doesn't exist", async () => { const mockEService = getMockEService(); const mockPurposeVersion = getMockPurposeVersion(); @@ -137,7 +209,7 @@ describe("rejectPurposeVersion", () => { }) ).rejects.toThrowError(eserviceNotFound(mockEService.id)); }); - it("should throw organizationIsNotTheProducer if the requester is not the producer", async () => { + it("should throw organizationIsNotTheProducer if the requester is not the producer nor delegate", async () => { const mockEService = getMockEService(); const mockPurposeVersion = getMockPurposeVersion(); const mockPurpose: Purpose = { @@ -162,6 +234,118 @@ describe("rejectPurposeVersion", () => { organizationIsNotTheProducer(mockPurpose.consumerId) ); }); + it("should throw organizationIsNotTheProducer if the purpose e-service has an active delegation and the requester is the producer", async () => { + const mockEService = getMockEService(); + const mockPurposeVersion = getMockPurposeVersion(); + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + expect( + purposeService.rejectPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + organizationId: mockEService.producerId, + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError( + organizationIsNotTheProducer(mockEService.producerId) + ); + }); + it("should throw organizationIsNotTheProducer if the purpose e-service has an active delegation and the requester is not the producer nor the delegate", async () => { + const mockEService = getMockEService(); + const mockPurposeVersion = getMockPurposeVersion(); + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + const randomCaller = getMockAuthData(); + + expect( + purposeService.rejectPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + organizationId: randomCaller.organizationId, + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError( + organizationIsNotTheProducer(randomCaller.organizationId) + ); + }); + it.each( + Object.values(delegationState).filter((s) => s !== delegationState.active) + )( + "should throw organizationIsNotTheProducer if the requester is the e-service delegate but the delegation is in %s state", + async (delegationState) => { + const mockEService = getMockEService(); + const mockPurposeVersion = getMockPurposeVersion(); + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState, + }); + + await writeInReadmodel(delegation, delegations); + + expect( + purposeService.rejectPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + organizationId: delegate.organizationId, + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError( + organizationIsNotTheProducer(delegate.organizationId) + ); + } + ); it("should throw purposeVersionNotFound if the purpose version doesn't exist", async () => { const mockEService = getMockEService(); const mockPurposeVersion = getMockPurposeVersion(); diff --git a/packages/purpose-process/test/suspendPurposeVersion.test.ts b/packages/purpose-process/test/suspendPurposeVersion.test.ts index 343cfcdf2d..b0f715932a 100644 --- a/packages/purpose-process/test/suspendPurposeVersion.test.ts +++ b/packages/purpose-process/test/suspendPurposeVersion.test.ts @@ -5,6 +5,8 @@ import { getMockPurpose, writeInReadmodel, decodeProtobufPayload, + getMockAuthData, + getMockDelegation, } from "pagopa-interop-commons-test"; import { PurposeVersion, @@ -19,6 +21,8 @@ import { PurposeVersionId, TenantId, toPurposeVersionV2, + delegationState, + delegationKind, } from "pagopa-interop-models"; import { genericLogger } from "pagopa-interop-commons"; import { @@ -29,6 +33,7 @@ import { } from "../src/model/domain/errors.js"; import { addOnePurpose, + delegations, eservices, getMockEService, purposeService, @@ -160,6 +165,78 @@ describe("suspendPurposeVersion", () => { vi.useRealTimers(); }); + it("should write on event-store for the suspension of a purpose version by the delegated producer", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date()); + + const mockEService = getMockEService(); + const mockPurposeVersion1: PurposeVersion = { + ...getMockPurposeVersion(), + state: purposeVersionState.active, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion1], + }; + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + const returnedPurposeVersion = await purposeService.suspendPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + organizationId: delegate.organizationId, + correlationId: generateId(), + logger: genericLogger, + }); + + const writtenEvent = await readLastPurposeEvent(mockPurpose.id); + + expect(writtenEvent).toMatchObject({ + stream_id: mockPurpose.id, + version: "1", + type: "PurposeVersionSuspendedByProducer", + event_version: 2, + }); + + const expectedPurpose: Purpose = { + ...mockPurpose, + versions: [ + { + ...mockPurposeVersion1, + state: purposeVersionState.suspended, + suspendedAt: new Date(), + updatedAt: new Date(), + }, + ], + suspendedByProducer: true, + updatedAt: new Date(), + }; + + const writtenPayload = decodeProtobufPayload({ + messageType: PurposeVersionSuspendedByProducerV2, + payload: writtenEvent.data, + }); + + expect(writtenPayload.purpose).toEqual(toPurposeV2(expectedPurpose)); + expect( + writtenPayload.purpose?.versions.find( + (v) => v.id === returnedPurposeVersion.id + ) + ).toEqual(toPurposeVersionV2(returnedPurposeVersion)); + + vi.useRealTimers(); + }); it("should write on event-store for the suspension of a purpose version by the producer (self consumer)", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date()); @@ -290,6 +367,115 @@ describe("suspendPurposeVersion", () => { }) ).rejects.toThrowError(organizationNotAllowed(randomId)); }); + it("should throw organizationNotAllowed if the requester is not the e-service active delegation delegate", async () => { + const mockEService = getMockEService(); + const mockPurposeVersion: PurposeVersion = { + ...getMockPurposeVersion(), + state: purposeVersionState.active, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + const randomCaller = getMockAuthData(); + + expect( + purposeService.suspendPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + organizationId: randomCaller.organizationId, + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError(organizationNotAllowed(randomCaller.organizationId)); + }); + it.each( + Object.values(delegationState).filter((s) => s !== delegationState.active) + )( + "should throw organizationNotAllowed if the requester is the e-service delegate but the delegation is in %s state", + async (delegationState) => { + const mockEService = getMockEService(); + const mockPurposeVersion: PurposeVersion = { + ...getMockPurposeVersion(), + state: purposeVersionState.active, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState, + }); + + await writeInReadmodel(delegation, delegations); + + expect( + purposeService.suspendPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + organizationId: delegate.organizationId, + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError(organizationNotAllowed(delegate.organizationId)); + } + ); + it("should throw organizationNotAllowed if the requester is the producer but the purpose e-service has an active delegation", async () => { + const mockEService = getMockEService(); + const mockPurposeVersion: PurposeVersion = { + ...getMockPurposeVersion(), + state: purposeVersionState.active, + }; + const mockPurpose: Purpose = { + ...getMockPurpose(), + eserviceId: mockEService.id, + versions: [mockPurposeVersion], + }; + await addOnePurpose(mockPurpose); + await writeInReadmodel(toReadModelEService(mockEService), eservices); + + const delegate = getMockAuthData(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: mockEService.id, + delegateId: delegate.organizationId, + state: delegationState.active, + }); + + await writeInReadmodel(delegation, delegations); + + expect( + purposeService.suspendPurposeVersion({ + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + organizationId: mockEService.producerId, + correlationId: generateId(), + logger: genericLogger, + }) + ).rejects.toThrowError(organizationNotAllowed(mockEService.producerId)); + }); it.each( Object.values(purposeVersionState).filter( (state) => diff --git a/packages/purpose-process/test/utils.ts b/packages/purpose-process/test/utils.ts index fcdd9e28fe..7d67e41b51 100644 --- a/packages/purpose-process/test/utils.ts +++ b/packages/purpose-process/test/utils.ts @@ -24,6 +24,12 @@ import { unsafeBrandId, toReadModelPurpose, PurposeId, + toReadModelEService, + Tenant, + toReadModelTenant, + toReadModelAgreement, + Agreement, + Delegation, } from "pagopa-interop-models"; import { purposeApi } from "pagopa-interop-api-clients"; import { afterAll, afterEach, inject, vi } from "vitest"; @@ -46,6 +52,7 @@ export const eservices = readModelRepository.eservices; export const tenants = readModelRepository.tenants; export const attributes = readModelRepository.attributes; export const purposes = readModelRepository.purposes; +export const delegations = readModelRepository.delegations; export const readModelService = readModelServiceBuilder(readModelRepository); @@ -74,6 +81,24 @@ export const addOnePurpose = async (purpose: Purpose): Promise => { await writeInReadmodel(toReadModelPurpose(purpose), purposes); }; +export const addOneEService = async (eservice: EService): Promise => { + await writeInReadmodel(toReadModelEService(eservice), eservices); +}; + +export const addOneTenant = async (tenant: Tenant): Promise => { + await writeInReadmodel(toReadModelTenant(tenant), tenants); +}; + +export const addOneAgreement = async (agreement: Agreement): Promise => { + await writeInReadmodel(toReadModelAgreement(agreement), agreements); +}; + +export const addOneDelegation = async ( + delegation: Delegation +): Promise => { + await writeInReadmodel(delegation, delegations); +}; + export const writePurposeInEventstore = async ( purpose: Purpose ): Promise => { diff --git a/packages/tenant-outbound-writer/package.json b/packages/tenant-outbound-writer/package.json index e7577c6274..02f287a2c1 100644 --- a/packages/tenant-outbound-writer/package.json +++ b/packages/tenant-outbound-writer/package.json @@ -29,7 +29,7 @@ "vitest": "1.6.0" }, "dependencies": { - "@pagopa/interop-outbound-models": "1.0.10", + "@pagopa/interop-outbound-models": "1.0.11a", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", @@ -40,4 +40,4 @@ "ts-pattern": "5.2.0", "zod": "3.23.8" } -} \ No newline at end of file +} diff --git a/packages/tenant-process/src/model/domain/apiConverter.ts b/packages/tenant-process/src/model/domain/apiConverter.ts index 77db01d68b..0a43fb3f80 100644 --- a/packages/tenant-process/src/model/domain/apiConverter.ts +++ b/packages/tenant-process/src/model/domain/apiConverter.ts @@ -11,6 +11,8 @@ import { tenantMailKind, TenantFeature, tenantAttributeType, + TenantFeatureType, + tenantFeatureType, } from "pagopa-interop-models"; import { tenantApi } from "pagopa-interop-api-clients"; import { match } from "ts-pattern"; @@ -133,3 +135,18 @@ export function toApiTenant(tenant: Tenant): tenantApi.Tenant { subUnitType: tenant.subUnitType, }; } + +export function apiTenantFeatureTypeToTenantFeatureType( + input: tenantApi.TenantFeatureType +): TenantFeatureType { + return match(input) + .with( + tenantApi.TenantFeatureType.Values.DELEGATED_PRODUCER, + () => tenantFeatureType.delegatedProducer + ) + .with( + tenantApi.TenantFeatureType.Values.PERSISTENT_CERTIFIER, + () => tenantFeatureType.persistentCertifier + ) + .exhaustive(); +} diff --git a/packages/tenant-process/src/routers/TenantRouter.ts b/packages/tenant-process/src/routers/TenantRouter.ts index 2416c5457d..3602fe6096 100644 --- a/packages/tenant-process/src/routers/TenantRouter.ts +++ b/packages/tenant-process/src/routers/TenantRouter.ts @@ -12,7 +12,10 @@ import { } from "pagopa-interop-commons"; import { unsafeBrandId } from "pagopa-interop-models"; import { tenantApi } from "pagopa-interop-api-clients"; -import { toApiTenant } from "../model/domain/apiConverter.js"; +import { + apiTenantFeatureTypeToTenantFeatureType, + toApiTenant, +} from "../model/domain/apiConverter.js"; import { makeApiProblem } from "../model/domain/errors.js"; import { getTenantByExternalIdErrorMapper, @@ -168,10 +171,11 @@ const tenantsRouter = ( const ctx = fromAppContext(req.ctx); try { - const { name, offset, limit } = req.query; - const tenants = await tenantService.getTenantsByName( + const { name, features, offset, limit } = req.query; + const tenants = await tenantService.getTenants( { name, + features: features.map(apiTenantFeatureTypeToTenantFeatureType), offset, limit, }, diff --git a/packages/tenant-process/src/services/readModelService.ts b/packages/tenant-process/src/services/readModelService.ts index 27478f3fe1..5e33f483f9 100644 --- a/packages/tenant-process/src/services/readModelService.ts +++ b/packages/tenant-process/src/services/readModelService.ts @@ -20,13 +20,15 @@ import { AgreementState, TenantReadModel, genericInternalError, + TenantFeatureType, } from "pagopa-interop-models"; import { tenantApi } from "pagopa-interop-api-clients"; import { z } from "zod"; import { Document, Filter, WithId } from "mongodb"; function listTenantsFilters( - name: string | undefined + name: string | undefined, + features?: TenantFeatureType[] ): Filter<{ data: TenantReadModel }> { const nameFilter = name ? { @@ -37,6 +39,15 @@ function listTenantsFilters( } : {}; + const featuresFilter = + features && features.length > 0 + ? { + "data.features.type": { + $in: features, + }, + } + : {}; + const withSelfcareIdFilter = { "data.selfcareId": { $exists: true, @@ -45,6 +56,7 @@ function listTenantsFilters( return { ...nameFilter, + ...featuresFilter, ...withSelfcareIdFilter, }; } @@ -147,16 +159,18 @@ export function readModelServiceBuilder( ) { const { attributes, eservices, tenants, agreements } = readModelRepository; return { - async getTenantsByName({ + async getTenants({ name, + features, offset, limit, }: { name: string | undefined; + features: TenantFeatureType[]; offset: number; limit: number; }): Promise> { - const query = listTenantsFilters(name); + const query = listTenantsFilters(name, features); const aggregationPipeline = [ { $match: query }, { $project: { data: 1, lowerName: { $toLower: "$data.name" } } }, diff --git a/packages/tenant-process/src/services/tenantService.ts b/packages/tenant-process/src/services/tenantService.ts index 0b1181838c..bf708a566d 100644 --- a/packages/tenant-process/src/services/tenantService.ts +++ b/packages/tenant-process/src/services/tenantService.ts @@ -32,6 +32,7 @@ import { TenantFeatureCertifier, CorrelationId, tenantKind, + TenantFeatureType, } from "pagopa-interop-models"; import { ExternalId } from "pagopa-interop-models"; import { tenantApi } from "pagopa-interop-api-clients"; @@ -1220,22 +1221,24 @@ export function tenantServiceBuilder( limit, }); }, - async getTenantsByName( + async getTenants( { name, + features, offset, limit, }: { name: string | undefined; + features: TenantFeatureType[]; offset: number; limit: number; }, logger: Logger ): Promise> { logger.info( - `Retrieving Tenants with name = ${name}, limit = ${limit}, offset = ${offset}` + `Retrieving Tenants with name = ${name}, features = ${features}, limit = ${limit}, offset = ${offset}` ); - return readModelService.getTenantsByName({ name, offset, limit }); + return readModelService.getTenants({ name, features, offset, limit }); }, async getTenantById(id: TenantId, logger: Logger): Promise { logger.info(`Retrieving tenant ${id}`); diff --git a/packages/tenant-process/test/getTenants.test.ts b/packages/tenant-process/test/getTenants.test.ts index 0be947c0fe..45c453f391 100644 --- a/packages/tenant-process/test/getTenants.test.ts +++ b/packages/tenant-process/test/getTenants.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { Tenant } from "pagopa-interop-models"; +import { Tenant, tenantFeatureType } from "pagopa-interop-models"; import { getMockTenant } from "pagopa-interop-commons-test"; import { addOneTenant, readModelService } from "./utils.js"; @@ -30,8 +30,9 @@ describe("getTenants", () => { await addOneTenant(tenant2); await addOneTenant(tenant3); - const tenantsByName = await readModelService.getTenantsByName({ + const tenantsByName = await readModelService.getTenants({ name: undefined, + features: [], offset: 0, limit: 50, }); @@ -43,17 +44,115 @@ describe("getTenants", () => { await addOneTenant(tenant2); - const tenantsByName = await readModelService.getTenantsByName({ + const tenantsByName = await readModelService.getTenants({ name: "Tenant 1", + features: [], offset: 0, limit: 50, }); expect(tenantsByName.totalCount).toBe(1); expect(tenantsByName.results).toEqual([tenant1]); }); + it("should get tenants by feature", async () => { + const tenantDelegatedProducer1: Tenant = { + ...tenant1, + features: [ + { + type: tenantFeatureType.delegatedProducer, + availabilityTimestamp: new Date(), + }, + ], + }; + await addOneTenant(tenantDelegatedProducer1); + + const tenantDelegatedProducer2: Tenant = { + ...tenant2, + features: [ + { + type: tenantFeatureType.delegatedProducer, + availabilityTimestamp: new Date(), + }, + ], + }; + await addOneTenant(tenantDelegatedProducer2); + + const tenantCertifier1 = { + ...tenant3, + features: [ + { + type: tenantFeatureType.persistentCertifier, + certifierId: "certifierId", + }, + ], + }; + + await addOneTenant(tenantCertifier1); + await addOneTenant(tenant4); + + const tenantsByName = await readModelService.getTenants({ + name: undefined, + features: [tenantFeatureType.delegatedProducer], + offset: 0, + limit: 50, + }); + expect(tenantsByName.totalCount).toBe(2); + expect(tenantsByName.results).toEqual([ + tenantDelegatedProducer1, + tenantDelegatedProducer2, + ]); + }); + it("should get tenants by feature and name", async () => { + const tenantDelegatedProducer1: Tenant = { + ...tenant1, + features: [ + { + type: tenantFeatureType.delegatedProducer, + availabilityTimestamp: new Date(), + }, + ], + }; + await addOneTenant(tenantDelegatedProducer1); + + const tenantDelegatedProducer2: Tenant = { + ...tenant2, + features: [ + { + type: tenantFeatureType.delegatedProducer, + availabilityTimestamp: new Date(), + }, + ], + }; + await addOneTenant(tenantDelegatedProducer2); + + const tenantCertifier1 = { + ...tenant3, + features: [ + { + type: tenantFeatureType.persistentCertifier, + certifierId: "certifierId", + }, + ], + }; + + await addOneTenant(tenantCertifier1); + await addOneTenant(tenant4); + + const tenantsByName = await readModelService.getTenants({ + name: "Tenant 2", + features: [ + tenantFeatureType.delegatedProducer, + tenantFeatureType.persistentCertifier, + ], + offset: 0, + limit: 50, + }); + expect(tenantsByName.totalCount).toBe(1); + expect(tenantsByName.results).toEqual([tenantDelegatedProducer2]); + }); it("should not get tenants if there are not any tenants", async () => { - const tenantsByName = await readModelService.getTenantsByName({ + const tenantsByName = await readModelService.getTenants({ name: undefined, + features: [], offset: 0, limit: 50, }); @@ -65,8 +164,9 @@ describe("getTenants", () => { await addOneTenant(tenant2); - const tenantsByName = await readModelService.getTenantsByName({ + const tenantsByName = await readModelService.getTenants({ name: "Tenant 6", + features: [], offset: 0, limit: 50, }); @@ -79,8 +179,9 @@ describe("getTenants", () => { await addOneTenant(tenant3); await addOneTenant(tenant4); await addOneTenant(tenant5); - const tenantsByName = await readModelService.getTenantsByName({ + const tenantsByName = await readModelService.getTenants({ name: undefined, + features: [], offset: 0, limit: 4, }); @@ -92,8 +193,9 @@ describe("getTenants", () => { await addOneTenant(tenant3); await addOneTenant(tenant4); await addOneTenant(tenant5); - const tenantsByName = await readModelService.getTenantsByName({ + const tenantsByName = await readModelService.getTenants({ name: undefined, + features: [], offset: 2, limit: 4, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81acb9ffd1..52a4c49eb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,8 +98,8 @@ importers: packages/agreement-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.10 - version: 1.0.10 + specifier: 1.0.11a + version: 1.0.11-a '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -1066,8 +1066,8 @@ importers: packages/catalog-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.10 - version: 1.0.10 + specifier: 1.0.11a + version: 1.0.11-a '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -2543,8 +2543,8 @@ importers: packages/purpose-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.10 - version: 1.0.10 + specifier: 1.0.11a + version: 1.0.11-a '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -2848,8 +2848,8 @@ importers: packages/tenant-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.10 - version: 1.0.10 + specifier: 1.0.11a + version: 1.0.11-a '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -3614,10 +3614,6 @@ packages: resolution: {integrity: sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==} engines: {node: '>=16.0.0'} - '@aws-sdk/types@3.667.0': - resolution: {integrity: sha512-gYq0xCsqFfQaSL/yT1Gl1vIUjtsg7d7RhnUfsXaHt8xTxOKRTdH9GjbesBjXOzgOvB0W0vfssfreSNGFlOOMJg==} - engines: {node: '>=16.0.0'} - '@aws-sdk/util-arn-parser@3.310.0': resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==} engines: {node: '>=14.0.0'} @@ -4209,8 +4205,8 @@ packages: '@pagopa/eslint-config@3.0.0': resolution: {integrity: sha512-eYIPdiuYRbRPR5k0OuteRNqYb0Z2nfJ/lZohejB7ylfBeSDWwkaV8Z19AXP4RymE6oEesyPDZ6i0yNaE9tQrHw==} - '@pagopa/interop-outbound-models@1.0.10': - resolution: {integrity: sha512-/TDVP8j+Q0ErpVs7MzH9LhfiEPcOzqea00um4sjQ9uuA6/Yg0G9A739bDOuaxih2wKpJ3hNPLnm9riM+YAz6Ow==} + '@pagopa/interop-outbound-models@1.0.11-a': + resolution: {integrity: sha512-+hxMAO2ywfH03PZ4iMUn7VHiGEtQUiEa2cEqLp12s6y69b7ClM3dg0ZxTuXnGLUqiiLX4sbSAy26rc8IG0sIdg==} '@pdf-lib/standard-fonts@1.0.0': resolution: {integrity: sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==} @@ -4379,10 +4375,6 @@ packages: resolution: {integrity: sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==} engines: {node: '>=16.0.0'} - '@smithy/abort-controller@3.1.5': - resolution: {integrity: sha512-DhNPnqTqPoG8aZ5dWkFOgsuY+i0GQ3CI6hMmvCoduNsnU9gUZWZBwGfDQsTTB7NvFPkom1df7jMIJWU90kuXXg==} - engines: {node: '>=16.0.0'} - '@smithy/chunked-blob-reader-native@2.2.0': resolution: {integrity: sha512-VNB5+1oCgX3Fzs072yuRsUoC2N4Zg/LJ11DTxX3+Qu+Paa6AmbIF0E9sc2wthz9Psrk/zcOlTCyuposlIhPjZQ==} @@ -4407,10 +4399,6 @@ packages: resolution: {integrity: sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==} engines: {node: '>=16.0.0'} - '@smithy/config-resolver@3.0.9': - resolution: {integrity: sha512-5d9oBf40qC7n2xUoHmntKLdqsyTMMo/r49+eqSIjJ73eDfEtljAxEhzIQ3bkgXJtR3xiv7YzMT/3FF3ORkjWdg==} - engines: {node: '>=16.0.0'} - '@smithy/core@2.2.4': resolution: {integrity: sha512-qdY3LpMOUyLM/gfjjMQZui+UTNS7kBRDWlvyIhVOql5dn2J3isk9qUTBtQ1CbDH8MTugHis1zu3h4rH+Qmmh4g==} engines: {node: '>=16.0.0'} @@ -4419,20 +4407,16 @@ packages: resolution: {integrity: sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==} engines: {node: '>=16.0.0'} - '@smithy/core@2.4.8': - resolution: {integrity: sha512-x4qWk7p/a4dcf7Vxb2MODIf4OIcqNbK182WxRvZ/3oKPrf/6Fdic5sSElhO1UtXpWKBazWfqg0ZEK9xN1DsuHA==} - engines: {node: '>=16.0.0'} - '@smithy/credential-provider-imds@2.3.0': resolution: {integrity: sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==} engines: {node: '>=14.0.0'} - '@smithy/credential-provider-imds@3.2.0': - resolution: {integrity: sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==} + '@smithy/credential-provider-imds@3.1.3': + resolution: {integrity: sha512-U1Yrv6hx/mRK6k8AncuI6jLUx9rn0VVSd9NPEX6pyYFBfkSkChOc/n4zUb8alHUVg83TbI4OdZVo1X0Zfj3ijA==} engines: {node: '>=16.0.0'} - '@smithy/credential-provider-imds@3.2.4': - resolution: {integrity: sha512-S9bb0EIokfYEuar4kEbLta+ivlKCWOCFsLZuilkNy9i0uEUEHSi47IFLPaxqqCl+0ftKmcOTHayY5nQhAuq7+w==} + '@smithy/credential-provider-imds@3.2.0': + resolution: {integrity: sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==} engines: {node: '>=16.0.0'} '@smithy/eventstream-codec@2.2.0': @@ -4482,9 +4466,6 @@ packages: '@smithy/fetch-http-handler@3.2.4': resolution: {integrity: sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==} - '@smithy/fetch-http-handler@3.2.9': - resolution: {integrity: sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A==} - '@smithy/hash-blob-browser@2.2.0': resolution: {integrity: sha512-SGPoVH8mdXBqrkVCJ1Hd1X7vh1zDXojNN1yZyZTZsCno99hVue9+IYzWDjq/EQDDXxmITB0gBmuyPh8oAZSTcg==} @@ -4499,10 +4480,6 @@ packages: resolution: {integrity: sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==} engines: {node: '>=16.0.0'} - '@smithy/hash-node@3.0.7': - resolution: {integrity: sha512-SAGHN+QkrwcHFjfWzs/czX94ZEjPJ0CrWJS3M43WswDXVEuP4AVy9gJ3+AF6JQHZD13bojmuf/Ap/ItDeZ+Qfw==} - engines: {node: '>=16.0.0'} - '@smithy/hash-stream-node@2.2.0': resolution: {integrity: sha512-aT+HCATOSRMGpPI7bi7NSsTNVZE/La9IaxLXWoVAYMxHT5hGO3ZOGEMZQg8A6nNL+pdFGtZQtND1eoY084HgHQ==} engines: {node: '>=14.0.0'} @@ -4517,9 +4494,6 @@ packages: '@smithy/invalid-dependency@3.0.3': resolution: {integrity: sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==} - '@smithy/invalid-dependency@3.0.7': - resolution: {integrity: sha512-Bq00GsAhHeYSuZX8Kpu4sbI9agH2BNYnqUmmbTGWOhki9NVsWn2jFr896vvoTMH8KAjNX/ErC/8t5QHuEXG+IA==} - '@smithy/is-array-buffer@2.2.0': resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} engines: {node: '>=14.0.0'} @@ -4546,10 +4520,6 @@ packages: resolution: {integrity: sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==} engines: {node: '>=16.0.0'} - '@smithy/middleware-content-length@3.0.9': - resolution: {integrity: sha512-t97PidoGElF9hTtLCrof32wfWMqC5g2SEJNxaVH3NjlatuNGsdxXRYO/t+RPnxA15RpYiS0f+zG7FuE2DeGgjA==} - engines: {node: '>=16.0.0'} - '@smithy/middleware-endpoint@2.5.1': resolution: {integrity: sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==} engines: {node: '>=14.0.0'} @@ -4562,10 +4532,6 @@ packages: resolution: {integrity: sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==} engines: {node: '>=16.0.0'} - '@smithy/middleware-endpoint@3.1.4': - resolution: {integrity: sha512-/ChcVHekAyzUbyPRI8CzPPLj6y8QRAfJngWcLMgsWxKVzw/RzBV69mSOzJYDD3pRwushA1+5tHtPF8fjmzBnrQ==} - engines: {node: '>=16.0.0'} - '@smithy/middleware-retry@2.3.1': resolution: {integrity: sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==} engines: {node: '>=14.0.0'} @@ -4574,10 +4540,6 @@ packages: resolution: {integrity: sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==} engines: {node: '>=16.0.0'} - '@smithy/middleware-retry@3.0.23': - resolution: {integrity: sha512-x9PbGXxkcXIpm6L26qRSCC+eaYcHwybRmqU8LO/WM2RRlW0g8lz6FIiKbKgGvHuoK3dLZRiQVSQJveiCzwnA5A==} - engines: {node: '>=16.0.0'} - '@smithy/middleware-retry@3.0.7': resolution: {integrity: sha512-f5q7Y09G+2h5ivkSx5CHvlAT4qRR3jBFEsfXyQ9nFNiWQlr8c48blnu5cmbTQ+p1xmIO14UXzKoF8d7Tm0Gsjw==} engines: {node: '>=16.0.0'} @@ -4590,10 +4552,6 @@ packages: resolution: {integrity: sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==} engines: {node: '>=16.0.0'} - '@smithy/middleware-serde@3.0.7': - resolution: {integrity: sha512-VytaagsQqtH2OugzVTq4qvjkLNbWehHfGcGr0JLJmlDRrNCeZoWkWsSOw1nhS/4hyUUWF/TLGGml4X/OnEep5g==} - engines: {node: '>=16.0.0'} - '@smithy/middleware-stack@2.2.0': resolution: {integrity: sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==} engines: {node: '>=14.0.0'} @@ -4602,10 +4560,6 @@ packages: resolution: {integrity: sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==} engines: {node: '>=16.0.0'} - '@smithy/middleware-stack@3.0.7': - resolution: {integrity: sha512-EyTbMCdqS1DoeQsO4gI7z2Gzq1MoRFAeS8GkFYIwbedB7Lp5zlLHJdg+56tllIIG5Hnf9ZWX48YKSHlsKvugGA==} - engines: {node: '>=16.0.0'} - '@smithy/node-config-provider@2.3.0': resolution: {integrity: sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==} engines: {node: '>=14.0.0'} @@ -4618,10 +4572,6 @@ packages: resolution: {integrity: sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==} engines: {node: '>=16.0.0'} - '@smithy/node-config-provider@3.1.8': - resolution: {integrity: sha512-E0rU0DglpeJn5ge64mk8wTGEXcQwmpUTY5Zr7IzTpDLmHKiIamINERNZYrPQjg58Ck236sEKSwRSHA4CwshU6Q==} - engines: {node: '>=16.0.0'} - '@smithy/node-http-handler@2.5.0': resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==} engines: {node: '>=14.0.0'} @@ -4634,10 +4584,6 @@ packages: resolution: {integrity: sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==} engines: {node: '>=16.0.0'} - '@smithy/node-http-handler@3.2.4': - resolution: {integrity: sha512-49reY3+JgLMFNm7uTAKBWiKCA6XSvkNp9FqhVmusm2jpVnHORYFeFZ704LShtqWfjZW/nhX+7Iexyb6zQfXYIQ==} - engines: {node: '>=16.0.0'} - '@smithy/property-provider@2.2.0': resolution: {integrity: sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==} engines: {node: '>=14.0.0'} @@ -4646,10 +4592,6 @@ packages: resolution: {integrity: sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==} engines: {node: '>=16.0.0'} - '@smithy/property-provider@3.1.7': - resolution: {integrity: sha512-QfzLi1GPMisY7bAM5hOUqBdGYnY5S2JAlr201pghksrQv139f8iiiMalXtjczIP5f6owxFn3MINLNUNvUkgtPw==} - engines: {node: '>=16.0.0'} - '@smithy/protocol-http@2.0.5': resolution: {integrity: sha512-d2hhHj34mA2V86doiDfrsy2fNTnUOowGaf9hKb0hIPHqvcnShU4/OSc4Uf1FwHkAdYF3cFXTrj5VGUYbEuvMdw==} engines: {node: '>=14.0.0'} @@ -4666,10 +4608,6 @@ packages: resolution: {integrity: sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==} engines: {node: '>=16.0.0'} - '@smithy/protocol-http@4.1.4': - resolution: {integrity: sha512-MlWK8eqj0JlpZBnWmjQLqmFp71Ug00P+m72/1xQB3YByXD4zZ+y9N4hYrR0EDmrUCZIkyATWHOXFgtavwGDTzQ==} - engines: {node: '>=16.0.0'} - '@smithy/querystring-builder@2.2.0': resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==} engines: {node: '>=14.0.0'} @@ -4678,10 +4616,6 @@ packages: resolution: {integrity: sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==} engines: {node: '>=16.0.0'} - '@smithy/querystring-builder@3.0.7': - resolution: {integrity: sha512-65RXGZZ20rzqqxTsChdqSpbhA6tdt5IFNgG6o7e1lnPVLCe6TNWQq4rTl4N87hTDD8mV4IxJJnvyE7brbnRkQw==} - engines: {node: '>=16.0.0'} - '@smithy/querystring-parser@2.2.0': resolution: {integrity: sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==} engines: {node: '>=14.0.0'} @@ -4690,10 +4624,6 @@ packages: resolution: {integrity: sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==} engines: {node: '>=16.0.0'} - '@smithy/querystring-parser@3.0.7': - resolution: {integrity: sha512-Fouw4KJVWqqUVIu1gZW8BH2HakwLz6dvdrAhXeXfeymOBrZw+hcqaWs+cS1AZPVp4nlbeIujYrKA921ZW2WMPA==} - engines: {node: '>=16.0.0'} - '@smithy/service-error-classification@2.1.5': resolution: {integrity: sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==} engines: {node: '>=14.0.0'} @@ -4702,20 +4632,16 @@ packages: resolution: {integrity: sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==} engines: {node: '>=16.0.0'} - '@smithy/service-error-classification@3.0.7': - resolution: {integrity: sha512-91PRkTfiBf9hxkIchhRKJfl1rsplRDyBnmyFca3y0Z3x/q0JJN480S83LBd8R6sBCkm2bBbqw2FHp0Mbh+ecSA==} - engines: {node: '>=16.0.0'} - '@smithy/shared-ini-file-loader@2.4.0': resolution: {integrity: sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==} engines: {node: '>=14.0.0'} - '@smithy/shared-ini-file-loader@3.1.4': - resolution: {integrity: sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==} + '@smithy/shared-ini-file-loader@3.1.3': + resolution: {integrity: sha512-Z8Y3+08vgoDgl4HENqNnnzSISAaGrF2RoKupoC47u2wiMp+Z8P/8mDh1CL8+8ujfi2U5naNvopSBmP/BUj8b5w==} engines: {node: '>=16.0.0'} - '@smithy/shared-ini-file-loader@3.1.8': - resolution: {integrity: sha512-0NHdQiSkeGl0ICQKcJQ2lCOKH23Nb0EaAa7RDRId6ZqwXkw4LJyIyZ0t3iusD4bnKYDPLGy2/5e2rfUhrt0Acw==} + '@smithy/shared-ini-file-loader@3.1.4': + resolution: {integrity: sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==} engines: {node: '>=16.0.0'} '@smithy/signature-v4@2.3.0': @@ -4730,10 +4656,6 @@ packages: resolution: {integrity: sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==} engines: {node: '>=16.0.0'} - '@smithy/signature-v4@4.2.0': - resolution: {integrity: sha512-LafbclHNKnsorMgUkKm7Tk7oJ7xizsZ1VwqhGKqoCIrXh4fqDDp73fK99HOEEgcsQbtemmeY/BPv0vTVYYUNEQ==} - engines: {node: '>=16.0.0'} - '@smithy/smithy-client@2.5.1': resolution: {integrity: sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==} engines: {node: '>=14.0.0'} @@ -4746,16 +4668,12 @@ packages: resolution: {integrity: sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==} engines: {node: '>=16.0.0'} - '@smithy/smithy-client@3.4.0': - resolution: {integrity: sha512-nOfJ1nVQsxiP6srKt43r2My0Gp5PLWCW2ASqUioxIiGmu6d32v4Nekidiv5qOmmtzIrmaD+ADX5SKHUuhReeBQ==} - engines: {node: '>=16.0.0'} - '@smithy/types@2.12.0': resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} engines: {node: '>=14.0.0'} - '@smithy/types@3.5.0': - resolution: {integrity: sha512-QN0twHNfe8mNJdH9unwsCK13GURU7oEAZqkBI+rsvpv1jrmserO+WnLE7jidR9W/1dxwZ0u/CB01mV2Gms/K2Q==} + '@smithy/types@3.3.0': + resolution: {integrity: sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==} engines: {node: '>=16.0.0'} '@smithy/url-parser@2.2.0': @@ -4764,9 +4682,6 @@ packages: '@smithy/url-parser@3.0.3': resolution: {integrity: sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==} - '@smithy/url-parser@3.0.7': - resolution: {integrity: sha512-70UbSSR8J97c1rHZOWhl+VKiZDqHWxs/iW8ZHrHp5fCCPLSBE7GcUlUvKSle3Ca+J9LLbYCj/A79BxztBvAfpA==} - '@smithy/util-base64@2.3.0': resolution: {integrity: sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==} engines: {node: '>=14.0.0'} @@ -4813,10 +4728,6 @@ packages: resolution: {integrity: sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==} engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-browser@3.0.23': - resolution: {integrity: sha512-Y07qslyRtXDP/C5aWKqxTPBl4YxplEELG3xRrz2dnAQ6Lq/FgNrcKWmV561nNaZmFH+EzeGOX3ZRMbU8p1T6Nw==} - engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-browser@3.0.7': resolution: {integrity: sha512-Q2txLyvQyGfmjsaDbVV7Sg8psefpFcrnlGapDzXGFRPFKRBeEg6OvFK8FljqjeHSaCZ6/UuzQExUPqBR/2qlDA==} engines: {node: '>= 10.0.0'} @@ -4829,10 +4740,6 @@ packages: resolution: {integrity: sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==} engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-node@3.0.23': - resolution: {integrity: sha512-9Y4WH7f0vnDGuHUa4lGX9e2p+sMwODibsceSV6rfkZOvMC+BY3StB2LdO1NHafpsyHJLpwAgChxQ38tFyd6vkg==} - engines: {node: '>= 10.0.0'} - '@smithy/util-defaults-mode-node@3.0.7': resolution: {integrity: sha512-F4Qcj1fG6MGi2BSWCslfsMSwllws/WzYONBGtLybyY+halAcXdWhcew+mej8M5SKd5hqPYp4f7b+ABQEaeytgg==} engines: {node: '>= 10.0.0'} @@ -4845,10 +4752,6 @@ packages: resolution: {integrity: sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==} engines: {node: '>=16.0.0'} - '@smithy/util-endpoints@2.1.3': - resolution: {integrity: sha512-34eACeKov6jZdHqS5hxBMJ4KyWKztTMulhuQ2UdOoP6vVxMLrOKUqIXAwJe/wiWMhXhydLW664B02CNpQBQ4Aw==} - engines: {node: '>=16.0.0'} - '@smithy/util-hex-encoding@2.2.0': resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} engines: {node: '>=14.0.0'} @@ -4865,10 +4768,6 @@ packages: resolution: {integrity: sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==} engines: {node: '>=16.0.0'} - '@smithy/util-middleware@3.0.7': - resolution: {integrity: sha512-OVA6fv/3o7TMJTpTgOi1H5OTwnuUa8hzRzhSFDtZyNxi6OZ70L/FHattSmhE212I7b6WSOJAAmbYnvcjTHOJCA==} - engines: {node: '>=16.0.0'} - '@smithy/util-retry@2.2.0': resolution: {integrity: sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==} engines: {node: '>= 14.0.0'} @@ -4877,20 +4776,16 @@ packages: resolution: {integrity: sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==} engines: {node: '>=16.0.0'} - '@smithy/util-retry@3.0.7': - resolution: {integrity: sha512-nh1ZO1vTeo2YX1plFPSe/OXaHkLAHza5jpokNiiKX2M5YpNUv6RxGJZhpfmiR4jSvVHCjIDmILjrxKmP+/Ghug==} - engines: {node: '>=16.0.0'} - '@smithy/util-stream@2.2.0': resolution: {integrity: sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==} engines: {node: '>=14.0.0'} - '@smithy/util-stream@3.1.3': - resolution: {integrity: sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==} + '@smithy/util-stream@3.0.5': + resolution: {integrity: sha512-xC3L5PKMAT/Bh8fmHNXP9sdQ4+4aKVUU3EEJ2CF/lLk7R+wtMJM+v/1B4en7jO++Wa5spGzFDBCl0QxgbUc5Ug==} engines: {node: '>=16.0.0'} - '@smithy/util-stream@3.1.9': - resolution: {integrity: sha512-7YAR0Ub3MwTMjDfjnup4qa6W8gygZMxikBhFMPESi6ASsl/rZJhwLpF/0k9TuezScCojsM0FryGdz4LZtjKPPQ==} + '@smithy/util-stream@3.1.3': + resolution: {integrity: sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==} engines: {node: '>=16.0.0'} '@smithy/util-uri-escape@2.2.0': @@ -5964,6 +5859,7 @@ packages: eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@3.0.0-alpha-1: @@ -8035,25 +7931,25 @@ snapshots: '@aws-crypto/crc32@3.0.0': dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 1.14.1 '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 2.6.3 '@aws-crypto/crc32c@3.0.0': dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 1.14.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 2.6.3 '@aws-crypto/ie11-detection@3.0.0': @@ -8065,7 +7961,7 @@ snapshots: '@aws-crypto/ie11-detection': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.568.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -8074,7 +7970,7 @@ snapshots: dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.568.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 @@ -8085,7 +7981,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-crypto/supports-web-crypto': 3.0.0 '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.568.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 @@ -8095,7 +7991,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.568.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 @@ -8103,19 +7999,19 @@ snapshots: '@aws-crypto/sha256-js@3.0.0': dependencies: '@aws-crypto/util': 3.0.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 1.14.1 '@aws-crypto/sha256-js@4.0.0': dependencies: '@aws-crypto/util': 4.0.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 1.14.1 '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 tslib: 2.6.3 '@aws-crypto/supports-web-crypto@3.0.0': @@ -8128,19 +8024,19 @@ snapshots: '@aws-crypto/util@3.0.0': dependencies: - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 '@aws-crypto/util@4.0.0': dependencies: - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.667.0 + '@aws-sdk/types': 3.609.0 '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 @@ -8161,30 +8057,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8222,7 +8118,7 @@ snapshots: '@smithy/node-http-handler': 3.1.4 '@smithy/protocol-http': 4.1.0 '@smithy/smithy-client': 3.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 @@ -8271,7 +8167,7 @@ snapshots: '@smithy/node-http-handler': 3.1.4 '@smithy/protocol-http': 4.1.0 '@smithy/smithy-client': 3.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 @@ -8320,7 +8216,7 @@ snapshots: '@smithy/node-http-handler': 3.1.4 '@smithy/protocol-http': 4.1.0 '@smithy/smithy-client': 3.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 @@ -8354,30 +8250,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.4 + '@smithy/core': 2.2.4 + '@smithy/fetch-http-handler': 3.2.0 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.3 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.1 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.5 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.7 + '@smithy/util-defaults-mode-node': 3.0.7 + '@smithy/util-endpoints': 2.0.4 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8490,7 +8386,7 @@ snapshots: '@smithy/node-http-handler': 3.1.4 '@smithy/protocol-http': 4.1.0 '@smithy/smithy-client': 3.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 @@ -8537,7 +8433,7 @@ snapshots: '@smithy/node-http-handler': 3.1.4 '@smithy/protocol-http': 4.1.0 '@smithy/smithy-client': 3.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 @@ -8585,7 +8481,7 @@ snapshots: '@smithy/node-http-handler': 3.1.1 '@smithy/protocol-http': 4.0.3 '@smithy/smithy-client': 3.1.5 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 @@ -8616,30 +8512,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8661,30 +8557,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8706,30 +8602,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.614.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8751,30 +8647,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.637.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8796,30 +8692,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.645.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8877,30 +8773,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -8920,34 +8816,34 @@ snapshots: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - aws-crt + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - aws-crt '@aws-sdk/client-sso@3.620.1': dependencies: @@ -8963,30 +8859,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.614.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9006,30 +8902,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.637.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9049,30 +8945,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.645.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9136,30 +9032,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.598.0 '@aws-sdk/util-user-agent-browser': 3.598.0 '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9182,30 +9078,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.609.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.609.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9227,30 +9123,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.614.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9272,30 +9168,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.637.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9317,30 +9213,30 @@ snapshots: '@aws-sdk/util-endpoints': 3.645.0 '@aws-sdk/util-user-agent-browser': 3.609.0 '@aws-sdk/util-user-agent-node': 3.614.0 - '@smithy/config-resolver': 3.0.9 - '@smithy/core': 2.4.8 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/hash-node': 3.0.7 - '@smithy/invalid-dependency': 3.0.7 - '@smithy/middleware-content-length': 3.0.9 - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/middleware-stack': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/node-http-handler': 3.2.4 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/config-resolver': 3.0.5 + '@smithy/core': 2.4.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/hash-node': 3.0.3 + '@smithy/invalid-dependency': 3.0.3 + '@smithy/middleware-content-length': 3.0.5 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 '@smithy/util-base64': 3.0.0 '@smithy/util-body-length-browser': 3.0.0 '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.23 - '@smithy/util-defaults-mode-node': 3.0.23 - '@smithy/util-endpoints': 2.1.3 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/util-defaults-mode-browser': 3.0.15 + '@smithy/util-defaults-mode-node': 3.0.15 + '@smithy/util-endpoints': 2.0.5 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: @@ -9348,46 +9244,46 @@ snapshots: '@aws-sdk/core@3.598.0': dependencies: - '@smithy/core': 2.4.8 - '@smithy/protocol-http': 4.1.4 + '@smithy/core': 2.4.0 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 fast-xml-parser: 4.2.5 tslib: 2.6.3 '@aws-sdk/core@3.609.0': dependencies: - '@smithy/core': 2.4.8 - '@smithy/protocol-http': 4.1.4 + '@smithy/core': 2.4.0 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 fast-xml-parser: 4.2.5 tslib: 2.6.3 '@aws-sdk/core@3.620.1': dependencies: - '@smithy/core': 2.4.8 - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/signature-v4': 4.2.0 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/core': 2.4.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/signature-v4': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 fast-xml-parser: 4.2.5 tslib: 2.6.3 '@aws-sdk/core@3.635.0': dependencies: - '@smithy/core': 2.4.8 - '@smithy/node-config-provider': 3.1.8 + '@smithy/core': 2.4.0 + '@smithy/node-config-provider': 3.1.4 '@smithy/property-provider': 3.1.3 - '@smithy/protocol-http': 4.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 4.1.0 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 fast-xml-parser: 4.4.1 tslib: 2.6.3 @@ -9395,8 +9291,8 @@ snapshots: dependencies: '@aws-sdk/client-cognito-identity': 3.609.0 '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - aws-crt @@ -9411,70 +9307,70 @@ snapshots: '@aws-sdk/credential-provider-env@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-env@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-env@3.620.1': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-http@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-stream': 3.1.9 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 '@aws-sdk/credential-provider-http@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-stream': 3.1.9 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 '@aws-sdk/credential-provider-http@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-stream': 3.1.9 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 '@aws-sdk/credential-provider-http@3.635.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-stream': 3.1.9 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.1.3 tslib: 2.6.3 '@aws-sdk/credential-provider-ini@3.387.0': @@ -9501,10 +9397,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0) '@aws-sdk/types': 3.598.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9519,10 +9415,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0(@aws-sdk/client-sts@3.609.0)) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9537,10 +9433,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.620.1(@aws-sdk/client-sso-oidc@3.620.1(@aws-sdk/client-sts@3.620.1)) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.620.1) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9555,10 +9451,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)) '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.637.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9573,10 +9469,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.645.0(@aws-sdk/client-sso-oidc@3.645.0(@aws-sdk/client-sts@3.645.0)) '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.645.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9607,10 +9503,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/credential-provider-web-identity': 3.598.0(@aws-sdk/client-sts@3.600.0) '@aws-sdk/types': 3.598.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9626,10 +9522,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0(@aws-sdk/client-sts@3.609.0)) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9648,7 +9544,7 @@ snapshots: '@smithy/credential-provider-imds': 3.2.0 '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9664,10 +9560,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.637.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)) '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.637.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9683,10 +9579,10 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.645.0(@aws-sdk/client-sso-oidc@3.645.0(@aws-sdk/client-sts@3.645.0)) '@aws-sdk/credential-provider-web-identity': 3.621.0(@aws-sdk/client-sts@3.645.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.2.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9704,25 +9600,25 @@ snapshots: '@aws-sdk/credential-provider-process@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-process@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-process@3.620.1': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-sso@3.387.0': @@ -9742,9 +9638,9 @@ snapshots: '@aws-sdk/client-sso': 3.598.0 '@aws-sdk/token-providers': 3.598.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9755,9 +9651,9 @@ snapshots: '@aws-sdk/client-sso': 3.609.0 '@aws-sdk/token-providers': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0(@aws-sdk/client-sts@3.609.0)) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9768,9 +9664,9 @@ snapshots: '@aws-sdk/client-sso': 3.620.1 '@aws-sdk/token-providers': 3.614.0(@aws-sdk/client-sso-oidc@3.620.1(@aws-sdk/client-sts@3.620.1)) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9781,9 +9677,9 @@ snapshots: '@aws-sdk/client-sso': 3.637.0 '@aws-sdk/token-providers': 3.614.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0)) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9794,9 +9690,9 @@ snapshots: '@aws-sdk/client-sso': 3.645.0 '@aws-sdk/token-providers': 3.614.0(@aws-sdk/client-sso-oidc@3.645.0(@aws-sdk/client-sts@3.645.0)) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9813,40 +9709,40 @@ snapshots: dependencies: '@aws-sdk/client-sts': 3.600.0(@aws-sdk/client-sso-oidc@3.600.0) '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-web-identity@3.609.0(@aws-sdk/client-sts@3.609.0)': dependencies: '@aws-sdk/client-sts': 3.609.0 '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-web-identity@3.609.0(@aws-sdk/client-sts@3.620.1)': dependencies: '@aws-sdk/client-sts': 3.620.1 '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.637.0)': dependencies: '@aws-sdk/client-sts': 3.637.0 '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-provider-web-identity@3.621.0(@aws-sdk/client-sts@3.645.0)': dependencies: '@aws-sdk/client-sts': 3.645.0 '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/credential-providers@3.609.0(@aws-sdk/client-sso-oidc@3.609.0(@aws-sdk/client-sts@3.609.0))': @@ -9863,9 +9759,9 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.609.0(@aws-sdk/client-sso-oidc@3.609.0(@aws-sdk/client-sts@3.609.0)) '@aws-sdk/credential-provider-web-identity': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 + '@smithy/credential-provider-imds': 3.1.3 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' @@ -9889,9 +9785,9 @@ snapshots: dependencies: '@aws-sdk/types': 3.598.0 '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 tslib: 2.6.3 @@ -9899,18 +9795,18 @@ snapshots: dependencies: '@aws-sdk/endpoint-cache': 3.572.0 '@aws-sdk/types': 3.598.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-endpoint-discovery@3.620.0': dependencies: '@aws-sdk/endpoint-cache': 3.572.0 '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-expect-continue@3.387.0': @@ -9923,8 +9819,8 @@ snapshots: '@aws-sdk/middleware-expect-continue@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.0.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-flexible-checksums@3.387.0': @@ -9944,8 +9840,8 @@ snapshots: '@aws-crypto/crc32c': 5.2.0 '@aws-sdk/types': 3.598.0 '@smithy/is-array-buffer': 3.0.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -9959,22 +9855,22 @@ snapshots: '@aws-sdk/middleware-host-header@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-host-header@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-host-header@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-location-constraint@3.387.0': @@ -9986,7 +9882,7 @@ snapshots: '@aws-sdk/middleware-location-constraint@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-logger@3.387.0': @@ -9998,13 +9894,13 @@ snapshots: '@aws-sdk/middleware-logger@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-logger@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-recursion-detection@3.387.0': @@ -10017,22 +9913,22 @@ snapshots: '@aws-sdk/middleware-recursion-detection@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-recursion-detection@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-recursion-detection@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-sdk-s3@3.387.0': @@ -10047,11 +9943,11 @@ snapshots: dependencies: '@aws-sdk/types': 3.598.0 '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 tslib: 2.6.3 @@ -10059,21 +9955,21 @@ snapshots: dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-arn-parser': 3.568.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/signature-v4': 4.2.0 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/signature-v4': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - '@smithy/util-stream': 3.1.9 + '@smithy/util-stream': 3.1.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 '@aws-sdk/middleware-sdk-sqs@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -10099,10 +9995,10 @@ snapshots: dependencies: '@aws-sdk/types': 3.598.0 '@smithy/property-provider': 3.1.3 - '@smithy/protocol-http': 4.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@aws-sdk/middleware-ssec@3.387.0': @@ -10114,7 +10010,7 @@ snapshots: '@aws-sdk/middleware-ssec@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-user-agent@3.387.0': @@ -10129,67 +10025,67 @@ snapshots: dependencies: '@aws-sdk/types': 3.598.0 '@aws-sdk/util-endpoints': 3.598.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-user-agent@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-endpoints': 3.609.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-user-agent@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-endpoints': 3.614.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-user-agent@3.637.0': dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-endpoints': 3.637.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/middleware-user-agent@3.645.0': dependencies: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-endpoints': 3.645.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/region-config-resolver@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@aws-sdk/region-config-resolver@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@aws-sdk/region-config-resolver@3.614.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@aws-sdk/s3-request-presigner@3.623.0': @@ -10200,7 +10096,7 @@ snapshots: '@smithy/middleware-endpoint': 3.1.0 '@smithy/protocol-http': 4.1.0 '@smithy/smithy-client': 3.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/signature-v4-multi-region@3.387.0': @@ -10215,18 +10111,18 @@ snapshots: dependencies: '@aws-sdk/middleware-sdk-s3': 3.598.0 '@aws-sdk/types': 3.598.0 - '@smithy/protocol-http': 4.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 3.1.2 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/signature-v4-multi-region@3.622.0': dependencies: '@aws-sdk/middleware-sdk-s3': 3.622.0 '@aws-sdk/types': 3.609.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/signature-v4': 4.2.0 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/signature-v4': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/token-providers@3.387.0': @@ -10241,45 +10137,45 @@ snapshots: dependencies: '@aws-sdk/client-sso-oidc': 3.600.0 '@aws-sdk/types': 3.598.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/token-providers@3.609.0(@aws-sdk/client-sso-oidc@3.609.0(@aws-sdk/client-sts@3.609.0))': dependencies: '@aws-sdk/client-sso-oidc': 3.609.0(@aws-sdk/client-sts@3.609.0) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.620.1(@aws-sdk/client-sts@3.620.1))': dependencies: '@aws-sdk/client-sso-oidc': 3.620.1(@aws-sdk/client-sts@3.620.1) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.637.0(@aws-sdk/client-sts@3.637.0))': dependencies: '@aws-sdk/client-sso-oidc': 3.637.0(@aws-sdk/client-sts@3.637.0) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.645.0(@aws-sdk/client-sts@3.645.0))': dependencies: '@aws-sdk/client-sso-oidc': 3.645.0(@aws-sdk/client-sts@3.645.0) '@aws-sdk/types': 3.609.0 - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/types@3.387.0': @@ -10289,17 +10185,12 @@ snapshots: '@aws-sdk/types@3.598.0': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/types@3.609.0': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@aws-sdk/types@3.667.0': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/util-arn-parser@3.310.0': @@ -10338,43 +10229,43 @@ snapshots: '@aws-sdk/util-endpoints@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/types': 3.5.0 - '@smithy/util-endpoints': 2.1.3 + '@smithy/types': 3.3.0 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 '@aws-sdk/util-endpoints@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/types': 3.5.0 - '@smithy/util-endpoints': 2.1.3 + '@smithy/types': 3.3.0 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 '@aws-sdk/util-endpoints@3.614.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/types': 3.5.0 - '@smithy/util-endpoints': 2.1.3 + '@smithy/types': 3.3.0 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 '@aws-sdk/util-endpoints@3.637.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/types': 3.5.0 - '@smithy/util-endpoints': 2.1.3 + '@smithy/types': 3.3.0 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 '@aws-sdk/util-endpoints@3.645.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/types': 3.5.0 - '@smithy/util-endpoints': 2.1.3 + '@smithy/types': 3.3.0 + '@smithy/util-endpoints': 2.0.5 tslib: 2.6.3 '@aws-sdk/util-format-url@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/querystring-builder': 3.0.3 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/util-locate-window@3.568.0': @@ -10391,14 +10282,14 @@ snapshots: '@aws-sdk/util-user-agent-browser@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 '@aws-sdk/util-user-agent-browser@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 @@ -10412,22 +10303,22 @@ snapshots: '@aws-sdk/util-user-agent-node@3.598.0': dependencies: '@aws-sdk/types': 3.598.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/util-user-agent-node@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/util-user-agent-node@3.614.0': dependencies: '@aws-sdk/types': 3.609.0 - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@aws-sdk/util-utf8-browser@3.259.0': @@ -10440,7 +10331,7 @@ snapshots: '@aws-sdk/xml-builder@3.598.0': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@babel/code-frame@7.24.7': @@ -10850,7 +10741,7 @@ snapshots: - tsutils - typescript - '@pagopa/interop-outbound-models@1.0.10': + '@pagopa/interop-outbound-models@1.0.11-a': dependencies: '@protobuf-ts/runtime': 2.9.4 ts-pattern: 5.2.0 @@ -11004,12 +10895,7 @@ snapshots: '@smithy/abort-controller@3.1.1': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/abort-controller@3.1.5': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/chunked-blob-reader-native@2.2.0': @@ -11040,62 +10926,41 @@ snapshots: '@smithy/config-resolver@3.0.4': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@smithy/config-resolver@3.0.5': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 - '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.7 - tslib: 2.6.3 - - '@smithy/config-resolver@3.0.9': - dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@smithy/core@2.2.4': dependencies: - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.7 + '@smithy/middleware-serde': 3.0.3 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.5 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@smithy/core@2.4.0': dependencies: - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - - '@smithy/core@2.4.8': - dependencies: - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-retry': 3.0.23 - '@smithy/middleware-serde': 3.0.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-retry': 3.0.15 + '@smithy/middleware-serde': 3.0.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -11107,20 +10972,20 @@ snapshots: '@smithy/url-parser': 2.2.0 tslib: 2.6.3 - '@smithy/credential-provider-imds@3.2.0': + '@smithy/credential-provider-imds@3.1.3': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/node-config-provider': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 tslib: 2.6.3 - '@smithy/credential-provider-imds@3.2.4': + '@smithy/credential-provider-imds@3.2.0': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/property-provider': 3.1.7 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 + '@smithy/node-config-provider': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 tslib: 2.6.3 '@smithy/eventstream-codec@2.2.0': @@ -11133,7 +10998,7 @@ snapshots: '@smithy/eventstream-codec@3.1.2': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-hex-encoding': 3.0.0 tslib: 2.6.3 @@ -11146,7 +11011,7 @@ snapshots: '@smithy/eventstream-serde-browser@3.0.4': dependencies: '@smithy/eventstream-serde-universal': 3.0.4 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/eventstream-serde-config-resolver@2.2.0': @@ -11156,7 +11021,7 @@ snapshots: '@smithy/eventstream-serde-config-resolver@3.0.3': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/eventstream-serde-node@2.2.0': @@ -11168,7 +11033,7 @@ snapshots: '@smithy/eventstream-serde-node@3.0.4': dependencies: '@smithy/eventstream-serde-universal': 3.0.4 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/eventstream-serde-universal@2.2.0': @@ -11180,7 +11045,7 @@ snapshots: '@smithy/eventstream-serde-universal@3.0.4': dependencies: '@smithy/eventstream-codec': 3.1.2 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/fetch-http-handler@2.5.0': @@ -11193,25 +11058,17 @@ snapshots: '@smithy/fetch-http-handler@3.2.0': dependencies: - '@smithy/protocol-http': 4.1.4 - '@smithy/querystring-builder': 3.0.7 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.0.3 + '@smithy/querystring-builder': 3.0.3 + '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 tslib: 2.6.3 '@smithy/fetch-http-handler@3.2.4': dependencies: - '@smithy/protocol-http': 4.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/querystring-builder': 3.0.3 - '@smithy/types': 3.5.0 - '@smithy/util-base64': 3.0.0 - tslib: 2.6.3 - - '@smithy/fetch-http-handler@3.2.9': - dependencies: - '@smithy/protocol-http': 4.1.4 - '@smithy/querystring-builder': 3.0.7 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 tslib: 2.6.3 @@ -11226,7 +11083,7 @@ snapshots: dependencies: '@smithy/chunked-blob-reader': 3.0.0 '@smithy/chunked-blob-reader-native': 3.0.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/hash-node@2.2.0': @@ -11238,14 +11095,7 @@ snapshots: '@smithy/hash-node@3.0.3': dependencies: - '@smithy/types': 3.5.0 - '@smithy/util-buffer-from': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - - '@smithy/hash-node@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -11258,7 +11108,7 @@ snapshots: '@smithy/hash-stream-node@3.1.2': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -11269,12 +11119,7 @@ snapshots: '@smithy/invalid-dependency@3.0.3': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/invalid-dependency@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/is-array-buffer@2.2.0': @@ -11293,7 +11138,7 @@ snapshots: '@smithy/md5-js@3.0.3': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -11305,20 +11150,14 @@ snapshots: '@smithy/middleware-content-length@3.0.3': dependencies: - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.0.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/middleware-content-length@3.0.5': dependencies: - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/middleware-content-length@3.0.9': - dependencies: - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/middleware-endpoint@2.5.1': @@ -11333,32 +11172,22 @@ snapshots: '@smithy/middleware-endpoint@3.0.4': dependencies: - '@smithy/middleware-serde': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 - '@smithy/util-middleware': 3.0.7 + '@smithy/middleware-serde': 3.0.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@smithy/middleware-endpoint@3.1.0': dependencies: - '@smithy/middleware-serde': 3.0.7 - '@smithy/node-config-provider': 3.1.8 + '@smithy/middleware-serde': 3.0.3 + '@smithy/node-config-provider': 3.1.4 '@smithy/shared-ini-file-loader': 3.1.4 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 - '@smithy/util-middleware': 3.0.7 - tslib: 2.6.3 - - '@smithy/middleware-endpoint@3.1.4': - dependencies: - '@smithy/middleware-serde': 3.0.7 - '@smithy/node-config-provider': 3.1.8 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 - '@smithy/url-parser': 3.0.7 - '@smithy/util-middleware': 3.0.7 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-middleware': 3.0.3 tslib: 2.6.3 '@smithy/middleware-retry@2.3.1': @@ -11375,37 +11204,25 @@ snapshots: '@smithy/middleware-retry@3.0.15': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/service-error-classification': 3.0.3 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 - tslib: 2.6.3 - uuid: 9.0.1 - - '@smithy/middleware-retry@3.0.23': - dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/service-error-classification': 3.0.7 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 tslib: 2.6.3 uuid: 9.0.1 '@smithy/middleware-retry@3.0.7': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/protocol-http': 4.1.4 - '@smithy/service-error-classification': 3.0.7 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-retry': 3.0.7 + '@smithy/node-config-provider': 3.1.4 + '@smithy/protocol-http': 4.1.0 + '@smithy/service-error-classification': 3.0.3 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.3 tslib: 2.6.3 uuid: 9.0.1 @@ -11416,12 +11233,7 @@ snapshots: '@smithy/middleware-serde@3.0.3': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/middleware-serde@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/middleware-stack@2.2.0': @@ -11431,12 +11243,7 @@ snapshots: '@smithy/middleware-stack@3.0.3': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/middleware-stack@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/node-config-provider@2.3.0': @@ -11448,23 +11255,16 @@ snapshots: '@smithy/node-config-provider@3.1.3': dependencies: - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/shared-ini-file-loader': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/node-config-provider@3.1.4': dependencies: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/node-config-provider@3.1.8': - dependencies: - '@smithy/property-provider': 3.1.7 - '@smithy/shared-ini-file-loader': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/node-http-handler@2.5.0': @@ -11477,26 +11277,18 @@ snapshots: '@smithy/node-http-handler@3.1.1': dependencies: - '@smithy/abort-controller': 3.1.5 - '@smithy/protocol-http': 4.1.4 - '@smithy/querystring-builder': 3.0.7 - '@smithy/types': 3.5.0 + '@smithy/abort-controller': 3.1.1 + '@smithy/protocol-http': 4.1.0 + '@smithy/querystring-builder': 3.0.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/node-http-handler@3.1.4': dependencies: '@smithy/abort-controller': 3.1.1 - '@smithy/protocol-http': 4.1.4 + '@smithy/protocol-http': 4.1.0 '@smithy/querystring-builder': 3.0.3 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/node-http-handler@3.2.4': - dependencies: - '@smithy/abort-controller': 3.1.5 - '@smithy/protocol-http': 4.1.4 - '@smithy/querystring-builder': 3.0.7 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/property-provider@2.2.0': @@ -11506,12 +11298,7 @@ snapshots: '@smithy/property-provider@3.1.3': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/property-provider@3.1.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/protocol-http@2.0.5': @@ -11526,17 +11313,12 @@ snapshots: '@smithy/protocol-http@4.0.3': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/protocol-http@4.1.0': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/protocol-http@4.1.4': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/querystring-builder@2.2.0': @@ -11547,13 +11329,7 @@ snapshots: '@smithy/querystring-builder@3.0.3': dependencies: - '@smithy/types': 3.5.0 - '@smithy/util-uri-escape': 3.0.0 - tslib: 2.6.3 - - '@smithy/querystring-builder@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-uri-escape': 3.0.0 tslib: 2.6.3 @@ -11564,12 +11340,7 @@ snapshots: '@smithy/querystring-parser@3.0.3': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/querystring-parser@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/service-error-classification@2.1.5': @@ -11578,25 +11349,21 @@ snapshots: '@smithy/service-error-classification@3.0.3': dependencies: - '@smithy/types': 3.5.0 - - '@smithy/service-error-classification@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/shared-ini-file-loader@2.4.0': dependencies: '@smithy/types': 2.12.0 tslib: 2.6.3 - '@smithy/shared-ini-file-loader@3.1.4': + '@smithy/shared-ini-file-loader@3.1.3': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 - '@smithy/shared-ini-file-loader@3.1.8': + '@smithy/shared-ini-file-loader@3.1.4': dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/signature-v4@2.3.0': @@ -11612,9 +11379,9 @@ snapshots: '@smithy/signature-v4@3.1.2': dependencies: '@smithy/is-array-buffer': 3.0.0 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -11622,21 +11389,10 @@ snapshots: '@smithy/signature-v4@4.1.0': dependencies: '@smithy/is-array-buffer': 3.0.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 - '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.7 - '@smithy/util-uri-escape': 3.0.0 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - - '@smithy/signature-v4@4.2.0': - dependencies: - '@smithy/is-array-buffer': 3.0.0 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 '@smithy/util-hex-encoding': 3.0.0 - '@smithy/util-middleware': 3.0.7 + '@smithy/util-middleware': 3.0.3 '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 @@ -11652,36 +11408,27 @@ snapshots: '@smithy/smithy-client@3.1.5': dependencies: - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-stack': 3.0.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 - '@smithy/util-stream': 3.1.9 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-stack': 3.0.3 + '@smithy/protocol-http': 4.0.3 + '@smithy/types': 3.3.0 + '@smithy/util-stream': 3.0.5 tslib: 2.6.3 '@smithy/smithy-client@3.2.0': dependencies: - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-stack': 3.0.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 + '@smithy/middleware-endpoint': 3.1.0 + '@smithy/middleware-stack': 3.0.3 + '@smithy/protocol-http': 4.1.0 + '@smithy/types': 3.3.0 '@smithy/util-stream': 3.1.3 tslib: 2.6.3 - '@smithy/smithy-client@3.4.0': - dependencies: - '@smithy/middleware-endpoint': 3.1.4 - '@smithy/middleware-stack': 3.0.7 - '@smithy/protocol-http': 4.1.4 - '@smithy/types': 3.5.0 - '@smithy/util-stream': 3.1.9 - tslib: 2.6.3 - '@smithy/types@2.12.0': dependencies: tslib: 2.6.3 - '@smithy/types@3.5.0': + '@smithy/types@3.3.0': dependencies: tslib: 2.6.3 @@ -11694,13 +11441,7 @@ snapshots: '@smithy/url-parser@3.0.3': dependencies: '@smithy/querystring-parser': 3.0.3 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/url-parser@3.0.7': - dependencies: - '@smithy/querystring-parser': 3.0.7 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-base64@2.3.0': @@ -11760,24 +11501,16 @@ snapshots: '@smithy/util-defaults-mode-browser@3.0.15': dependencies: '@smithy/property-provider': 3.1.3 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - bowser: 2.11.0 - tslib: 2.6.3 - - '@smithy/util-defaults-mode-browser@3.0.23': - dependencies: - '@smithy/property-provider': 3.1.7 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 '@smithy/util-defaults-mode-browser@3.0.7': dependencies: - '@smithy/property-provider': 3.1.7 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/property-provider': 3.1.3 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 bowser: 2.11.0 tslib: 2.6.3 @@ -11793,50 +11526,34 @@ snapshots: '@smithy/util-defaults-mode-node@3.0.15': dependencies: - '@smithy/config-resolver': 3.0.9 + '@smithy/config-resolver': 3.0.5 '@smithy/credential-provider-imds': 3.2.0 - '@smithy/node-config-provider': 3.1.8 + '@smithy/node-config-provider': 3.1.4 '@smithy/property-provider': 3.1.3 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/util-defaults-mode-node@3.0.23': - dependencies: - '@smithy/config-resolver': 3.0.9 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/node-config-provider': 3.1.8 - '@smithy/property-provider': 3.1.7 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-defaults-mode-node@3.0.7': dependencies: - '@smithy/config-resolver': 3.0.9 - '@smithy/credential-provider-imds': 3.2.4 - '@smithy/node-config-provider': 3.1.8 - '@smithy/property-provider': 3.1.7 - '@smithy/smithy-client': 3.4.0 - '@smithy/types': 3.5.0 + '@smithy/config-resolver': 3.0.5 + '@smithy/credential-provider-imds': 3.1.3 + '@smithy/node-config-provider': 3.1.4 + '@smithy/property-provider': 3.1.3 + '@smithy/smithy-client': 3.2.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-endpoints@2.0.4': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.3 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-endpoints@2.0.5': dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/util-endpoints@2.1.3': - dependencies: - '@smithy/node-config-provider': 3.1.8 - '@smithy/types': 3.5.0 + '@smithy/node-config-provider': 3.1.4 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-hex-encoding@2.2.0': @@ -11854,12 +11571,7 @@ snapshots: '@smithy/util-middleware@3.0.3': dependencies: - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/util-middleware@3.0.7': - dependencies: - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-retry@2.2.0': @@ -11871,13 +11583,7 @@ snapshots: '@smithy/util-retry@3.0.3': dependencies: '@smithy/service-error-classification': 3.0.3 - '@smithy/types': 3.5.0 - tslib: 2.6.3 - - '@smithy/util-retry@3.0.7': - dependencies: - '@smithy/service-error-classification': 3.0.7 - '@smithy/types': 3.5.0 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@smithy/util-stream@2.2.0': @@ -11891,22 +11597,22 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.6.3 - '@smithy/util-stream@3.1.3': + '@smithy/util-stream@3.0.5': dependencies: - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.2.4 - '@smithy/types': 3.5.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 - '@smithy/util-stream@3.1.9': + '@smithy/util-stream@3.1.3': dependencies: - '@smithy/fetch-http-handler': 3.2.9 - '@smithy/node-http-handler': 3.2.4 - '@smithy/types': 3.5.0 + '@smithy/fetch-http-handler': 3.2.4 + '@smithy/node-http-handler': 3.1.4 + '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-hex-encoding': 3.0.0 @@ -11939,8 +11645,8 @@ snapshots: '@smithy/util-waiter@3.1.2': dependencies: - '@smithy/abort-controller': 3.1.5 - '@smithy/types': 3.5.0 + '@smithy/abort-controller': 3.1.1 + '@smithy/types': 3.3.0 tslib: 2.6.3 '@testcontainers/postgresql@10.9.0': @@ -12843,7 +12549,7 @@ snapshots: deep-eql@4.1.4: dependencies: - type-detect: 4.0.8 + type-detect: 4.1.0 deep-is@0.1.4: {}