From d7c0640efc1b9115b273b9d42f498cb5c0bc2bfe Mon Sep 17 00:00:00 2001 From: AsterITA Date: Fri, 10 Jan 2025 16:13:55 +0100 Subject: [PATCH 1/8] PIN-5815: implement getConsumerEservices (#1306) --- .../consumer/getConsumerDelegators.bru | 2 +- .../consumer/getConsumerEservices.bru | 23 ++ .../consumer/getConsumerDelegators.bru | 2 +- .../consumer/getConsumerEservices.bru | 23 ++ packages/api-clients/open-api/bffApi.yml | 69 +++++ .../api-clients/open-api/delegationApi.yml | 78 +++++ .../src/routers/consumerDelegationRouter.ts | 24 ++ .../src/services/delegationService.ts | 38 +++ .../src/model/domain/errors.ts | 12 + .../src/routers/DelegationRouter.ts | 32 +++ .../src/services/delegationService.ts | 99 ++++--- .../src/services/readModelService.ts | 163 +++++++++-- .../src/utilities/errorMappers.ts | 6 + .../test/createConsumerDelegation.test.ts | 2 +- .../test/getConsumerEservices.test.ts | 270 ++++++++++++++++++ 15 files changed, 789 insertions(+), 54 deletions(-) create mode 100644 collections/bff/delegation/consumer/getConsumerEservices.bru create mode 100644 collections/delegation/consumer/getConsumerEservices.bru create mode 100644 packages/delegation-process/test/getConsumerEservices.test.ts diff --git a/collections/bff/delegation/consumer/getConsumerDelegators.bru b/collections/bff/delegation/consumer/getConsumerDelegators.bru index 69758259b1..f306ef6050 100644 --- a/collections/bff/delegation/consumer/getConsumerDelegators.bru +++ b/collections/bff/delegation/consumer/getConsumerDelegators.bru @@ -19,4 +19,4 @@ params:query { headers { Authorization: {{JWT}} x-correlation-id: {{correlation-id}} -} \ No newline at end of file +} diff --git a/collections/bff/delegation/consumer/getConsumerEservices.bru b/collections/bff/delegation/consumer/getConsumerEservices.bru new file mode 100644 index 0000000000..03b2436bd0 --- /dev/null +++ b/collections/bff/delegation/consumer/getConsumerEservices.bru @@ -0,0 +1,23 @@ +meta { + name: getConsumerEservices + type: http + seq: 6 +} + +get { + url: {{host-bff}}/consumer/delegations/eservices + body: none + auth: none +} + +params:query { + offset: 0 + limit: 10 + delegatorId: {{tenantId}} + ~q: +} + +headers { + Authorization: {{JWT}} + x-correlation-id: {{correlation-id}} +} diff --git a/collections/delegation/consumer/getConsumerDelegators.bru b/collections/delegation/consumer/getConsumerDelegators.bru index d7e986956e..1d090bdbfb 100644 --- a/collections/delegation/consumer/getConsumerDelegators.bru +++ b/collections/delegation/consumer/getConsumerDelegators.bru @@ -20,4 +20,4 @@ params:query { headers { Authorization: {{JWT}} x-correlation-id: {{correlation-id}} -} \ No newline at end of file +} diff --git a/collections/delegation/consumer/getConsumerEservices.bru b/collections/delegation/consumer/getConsumerEservices.bru new file mode 100644 index 0000000000..73b3c3ac3a --- /dev/null +++ b/collections/delegation/consumer/getConsumerEservices.bru @@ -0,0 +1,23 @@ +meta { + name: getConsumerEservices + type: http + seq: 6 +} + +get { + url: {{host-delegation}}/consumer/eservices + body: none + auth: none +} + +params:query { + offset: 0 + limit: 10 + delegatorId: {{tenantId2}} + ~eserviceName: +} + +headers { + Authorization: {{JWT}} + x-correlation-id: {{correlation-id}} +} diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index 1e4c522f3c..c7cbbf676e 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -13272,6 +13272,75 @@ paths: 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 + /consumer/delegations/eservices: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - in: query + name: delegatorId + required: true + schema: + type: string + format: uuid + - in: query + name: q + schema: + type: string + - in: query + name: offset + required: true + schema: + type: integer + format: int32 + - in: query + name: limit + required: true + schema: + type: integer + format: int32 + get: + tags: + - consumerDelegations + description: Retrieve requester's delegated eservices + operationId: getConsumerDelegatedEservices + responses: + "200": + description: Delegated eservices + content: + application/json: + schema: + $ref: "#/components/schemas/CompactEServicesLight" + 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 + "429": + description: Too Many Requests + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + 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 /consumer/delegations: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" diff --git a/packages/api-clients/open-api/delegationApi.yml b/packages/api-clients/open-api/delegationApi.yml index 0180fb6996..8dee969df7 100644 --- a/packages/api-clients/open-api/delegationApi.yml +++ b/packages/api-clients/open-api/delegationApi.yml @@ -37,6 +37,58 @@ tags: description: Find out more url: http://swagger.io paths: + /consumer/eservices: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - in: query + name: eserviceName + schema: + type: string + - in: query + name: delegatorId + required: true + schema: + type: string + format: uuid + - in: query + name: offset + required: true + schema: + type: integer + format: int32 + minimum: 0 + - in: query + name: limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + get: + tags: + - consumer + operationId: getConsumerEservices + description: Retrieve eservices of a consumer delegate with active delegation and agreement + responses: + "200": + description: Eservices retrieved + content: + application/json: + schema: + $ref: "#/components/schemas/CompactEservicesLight" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /consumer/delegators: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -683,6 +735,32 @@ components: schema: type: string schemas: + CompactEserviceLight: + type: object + additionalProperties: false + properties: + id: + type: string + format: uuid + name: + type: string + required: + - id + - name + CompactEservicesLight: + type: object + additionalProperties: false + properties: + results: + type: array + items: + $ref: "#/components/schemas/CompactEserviceLight" + totalCount: + type: integer + format: int32 + required: + - results + - totalCount CompactTenant: type: object additionalProperties: false diff --git a/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts b/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts index 39db16f8a9..c0029710c8 100644 --- a/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts +++ b/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts @@ -141,6 +141,30 @@ const consumerDelegationRouter = ( `Error getting delegators` ); + return res.status(errorRes.status).send(errorRes); + } + }) + .get("/consumer/delegations/eservices", async (req, res) => { + const ctx = fromBffAppContext(req.ctx, req.headers); + + try { + const eservices = await delegationService.getConsumerDelegatedEservices( + req.query, + ctx + ); + + return res + .status(200) + .send(bffApi.CompactEServicesLight.parse(eservices)); + } catch (error) { + const errorRes = makeApiProblem( + error, + emptyErrorMapper, + ctx.logger, + ctx.correlationId, + `Error getting consumer delegated eservices` + ); + return res.status(errorRes.status).send(errorRes); } }); diff --git a/packages/backend-for-frontend/src/services/delegationService.ts b/packages/backend-for-frontend/src/services/delegationService.ts index 431ad3c2db..2e249072fa 100644 --- a/packages/backend-for-frontend/src/services/delegationService.ts +++ b/packages/backend-for-frontend/src/services/delegationService.ts @@ -432,5 +432,43 @@ export function delegationServiceBuilder( }, }; }, + async getConsumerDelegatedEservices( + { + delegatorId, + q, + offset, + limit, + }: { + delegatorId: string; + q?: string; + offset: number; + limit: number; + }, + { headers, logger }: WithLogger + ): Promise { + logger.info( + `Retrieving consumer delegated eservices of delegator ${delegatorId} with name ${q}, limit ${limit}, offset ${offset}` + ); + + const eservicesData = + await delegationClients.consumer.getConsumerEservices({ + queries: { + delegatorId, + eserviceName: q, + offset, + limit, + }, + headers, + }); + + return { + results: eservicesData.results, + pagination: { + offset, + limit, + totalCount: eservicesData.totalCount, + }, + }; + }, }; } diff --git a/packages/delegation-process/src/model/domain/errors.ts b/packages/delegation-process/src/model/domain/errors.ts index 02487c89a0..d8dba6fe75 100644 --- a/packages/delegation-process/src/model/domain/errors.ts +++ b/packages/delegation-process/src/model/domain/errors.ts @@ -26,6 +26,7 @@ export const errorCodes = { incorrectState: "0011", differentEserviceProducer: "0012", delegationContractNotFound: "0013", + requesterIsNotConsumerDelegate: "0014", }; export type ErrorCodes = keyof typeof errorCodes; @@ -173,3 +174,14 @@ export function delegationStampNotFound( title: "Stamp not found", }); } + +export function requesterIsNotConsumerDelegate( + requesterId: TenantId, + delegatorId: TenantId +): ApiError { + return new ApiError({ + detail: `Requester ${requesterId} is not a Consumer delegate for delegator ${delegatorId}`, + code: "requesterIsNotConsumerDelegate", + title: "Requester is not a delegate", + }); +} diff --git a/packages/delegation-process/src/routers/DelegationRouter.ts b/packages/delegation-process/src/routers/DelegationRouter.ts index f1474b7182..d908007a94 100644 --- a/packages/delegation-process/src/routers/DelegationRouter.ts +++ b/packages/delegation-process/src/routers/DelegationRouter.ts @@ -32,6 +32,7 @@ import { rejectDelegationErrorMapper, revokeDelegationErrorMapper, getConsumerDelegatorsErrorMapper, + getConsumerEservicesErrorMapper, } from "../utilities/errorMappers.js"; import { delegationServiceBuilder } from "../services/delegationService.js"; @@ -453,6 +454,37 @@ const delegationRouter = ( ctx.correlationId ); + return res.status(errorRes.status).send(errorRes); + } + }) + .get("/consumer/eservices", async (req, res) => { + const ctx = fromAppContext(req.ctx); + + const { delegatorId, eserviceName, limit, offset } = req.query; + + try { + const eservices = await delegationService.getConsumerEservices( + { + delegatorId: unsafeBrandId(delegatorId), + requesterId: ctx.authData.organizationId, + eserviceName, + limit, + offset, + }, + ctx.logger + ); + + return res + .status(200) + .send(delegationApi.CompactEservicesLight.parse(eservices)); + } catch (error) { + const errorRes = makeApiProblem( + error, + getConsumerEservicesErrorMapper, + ctx.logger, + ctx.correlationId + ); + return res.status(errorRes.status).send(errorRes); } }); diff --git a/packages/delegation-process/src/services/delegationService.ts b/packages/delegation-process/src/services/delegationService.ts index 05dde5dbe6..13269dcb39 100644 --- a/packages/delegation-process/src/services/delegationService.ts +++ b/packages/delegation-process/src/services/delegationService.ts @@ -35,6 +35,7 @@ import { eserviceNotFound, tenantNotFound, delegationContractNotFound, + requesterIsNotConsumerDelegate, } from "../model/domain/errors.js"; import { toCreateEventConsumerDelegationApproved, @@ -61,23 +62,20 @@ import { } from "./validators.js"; import { contractBuilder } from "./delegationContractBuilder.js"; -const retrieveDelegationById = async ( - readModelService: ReadModelService, - delegationId: DelegationId -): Promise> => { - const delegation = await readModelService.getDelegation(delegationId); - if (!delegation?.data) { - throw delegationNotFound(delegationId); - } - return delegation; -}; - -export const retrieveDelegation = async ( - readModelService: ReadModelService, - delegationId: DelegationId, - kind: DelegationKind +export const retrieveDelegationById = async ( + { + delegationId, + kind, + }: { + delegationId: DelegationId; + kind: DelegationKind | undefined; + }, + readModelService: ReadModelService ): Promise> => { - const delegation = await readModelService.getDelegation(delegationId, kind); + const delegation = await readModelService.getDelegationById( + delegationId, + kind + ); if (!delegation?.data) { throw delegationNotFound(delegationId, kind); } @@ -200,10 +198,12 @@ export function delegationServiceBuilder( `Approving delegation ${delegationId} by delegate ${delegateId}` ); - const { data: delegation, metadata } = await retrieveDelegation( - readModelService, - delegationId, - kind + const { data: delegation, metadata } = await retrieveDelegationById( + { + delegationId, + kind, + }, + readModelService ); assertIsDelegate(delegation, delegateId); @@ -275,10 +275,12 @@ export function delegationServiceBuilder( `Rejecting delegation ${delegationId} by delegate ${delegateId}` ); - const { data: delegation, metadata } = await retrieveDelegation( - readModelService, - delegationId, - kind + const { data: delegation, metadata } = await retrieveDelegationById( + { + delegationId, + kind, + }, + readModelService ); assertIsDelegate(delegation, delegateId); @@ -330,10 +332,12 @@ export function delegationServiceBuilder( } ${delegatorId}` ); - const { data: delegation, metadata } = await retrieveDelegation( - readModelService, - delegationId, - kind + const { data: delegation, metadata } = await retrieveDelegationById( + { + delegationId, + kind, + }, + readModelService ); assertIsDelegator(delegation, delegatorId); @@ -406,8 +410,8 @@ export function delegationServiceBuilder( logger.info(`Retrieving delegation by id ${delegationId}`); const delegation = await retrieveDelegationById( - readModelService, - delegationId + { delegationId, kind: undefined }, + readModelService ); return delegation.data; }, @@ -454,8 +458,8 @@ export function delegationServiceBuilder( `Retrieving delegation ${delegationId} contract ${contractId}` ); const delegation = await retrieveDelegationById( - readModelService, - delegationId + { delegationId, kind: undefined }, + readModelService ); assertRequesterIsDelegateOrDelegator( @@ -593,5 +597,36 @@ export function delegationServiceBuilder( ); return await readModelService.getConsumerDelegators(filters); }, + async getConsumerEservices( + filters: { + requesterId: TenantId; + delegatorId: TenantId; + limit: number; + offset: number; + eserviceName?: string; + }, + logger: Logger + ): Promise { + logger.info( + `Retrieving delegated consumer eservices with filters: ${JSON.stringify( + filters + )}` + ); + + const delegation = await readModelService.findDelegations({ + delegatorId: filters.delegatorId, + delegateId: filters.requesterId, + delegationKind: delegationKind.delegatedConsumer, + states: [delegationState.active], + }); + if (!delegation || delegation.length === 0) { + throw requesterIsNotConsumerDelegate( + filters.requesterId, + filters.delegatorId + ); + } + + return await readModelService.getConsumerEservices(filters); + }, }; } diff --git a/packages/delegation-process/src/services/readModelService.ts b/packages/delegation-process/src/services/readModelService.ts index 7cdf21f7e9..13d60c34ad 100644 --- a/packages/delegation-process/src/services/readModelService.ts +++ b/packages/delegation-process/src/services/readModelService.ts @@ -10,6 +10,7 @@ import { DelegationId, delegationKind, DelegationKind, + DelegationReadModel, delegationState, DelegationState, EService, @@ -104,28 +105,32 @@ export function readModelServiceBuilder( } }, async getDelegation( - id: DelegationId, - kind: DelegationKind | undefined = undefined + filter: Filter<{ data: DelegationReadModel }> ): Promise | undefined> { - const data = await delegations.findOne( - { "data.id": id, ...(kind ? { "data.kind": kind } : {}) }, - { - projection: { data: true, metadata: true }, + const data = await delegations.findOne(filter, { + projection: { data: true, metadata: true }, + }); + if (data) { + const result = Delegation.safeParse(data.data); + if (!result.success) { + throw genericInternalError( + `Unable to parse delegation item: result ${JSON.stringify( + result + )} - data ${JSON.stringify(data)} ` + ); } - ); - if (!data) { - return undefined; + return data; } - - 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 data; + return undefined; + }, + async getDelegationById( + id: DelegationId, + kind: DelegationKind | undefined = undefined + ): Promise | undefined> { + return this.getDelegation({ + "data.id": id, + ...(kind ? { "data.kind": kind } : {}), + }); }, async findDelegations( filters: GetDelegationsFilters @@ -374,6 +379,126 @@ export function readModelServiceBuilder( ); } + return { + results: result.data, + totalCount: await ReadModelRepository.getTotalCount( + delegations, + aggregationPipeline + ), + }; + }, + async getConsumerEservices(filters: { + requesterId: TenantId; + delegatorId: TenantId; + limit: number; + offset: number; + eserviceName?: string; + }): Promise { + const aggregationPipeline = [ + { + $match: { + "data.kind": delegationKind.delegatedConsumer, + "data.state": delegationState.active, + "data.delegateId": filters.requesterId, + "data.delegatorId": filters.delegatorId, + } satisfies ReadModelFilter, + }, + { + $lookup: { + from: "eservices", + localField: "data.eserviceId", + foreignField: "data.id", + as: "eservice", + }, + }, + { + $unwind: "$eservice", + }, + ...(filters.eserviceName + ? [ + { + $match: { + "eservice.data.name": { + $regex: ReadModelRepository.escapeRegExp( + filters.eserviceName + ), + $options: "i", + }, + }, + }, + ] + : []), + { + $lookup: { + from: "agreements", + let: { + producerId: "$eservice.data.producerId", + consumerId: "$data.delegatorId", + eserviceId: "$eservice.data.id", + }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { $eq: ["$data.producerId", "$$producerId"] }, + { $eq: ["$data.consumerId", "$$consumerId"] }, + { $eq: ["$data.eserviceId", "$$eserviceId"] }, + { $eq: ["$data.state", agreementState.active] }, + ], + }, + }, + }, + ], + as: "activeAgreements", + }, + }, + { + $match: { + activeAgreements: { $ne: [] }, // Keep only delegations with active agreements + }, + }, + { + $group: { + _id: "$eservice.data.id", + name: { $first: "$eservice.data.name" }, + }, + }, + { + $project: { + _id: 0, + id: "$_id", + name: 1, + }, + }, + { + $sort: { name: 1 }, + }, + ]; + + const data = await delegations + .aggregate( + [ + ...aggregationPipeline, + { $skip: filters.offset }, + { $limit: filters.limit }, + ], + { allowDiskUse: true } + ) + .toArray(); + + const result = z + .array(delegationApi.CompactEserviceLight) + .safeParse(data); + + if (!result.success) { + throw genericInternalError( + `Unable to parse compact delegation eservices: result ${JSON.stringify( + result + )} - data ${JSON.stringify(data)}` + ); + } + return { results: result.data, totalCount: await ReadModelRepository.getTotalCount( diff --git a/packages/delegation-process/src/utilities/errorMappers.ts b/packages/delegation-process/src/utilities/errorMappers.ts index 1092720728..880832caaa 100644 --- a/packages/delegation-process/src/utilities/errorMappers.ts +++ b/packages/delegation-process/src/utilities/errorMappers.ts @@ -20,6 +20,12 @@ export const getDelegationsErrorMapper = ( match(error.code).otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); export const getConsumerDelegatorsErrorMapper = getDelegationsErrorMapper; +export const getConsumerEservicesErrorMapper = ( + error: ApiError +): number => + match(error.code) + .with("requesterIsNotConsumerDelegate", () => HTTP_STATUS_FORBIDDEN) + .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); export const getDelegationByIdErrorMapper = ( error: ApiError diff --git a/packages/delegation-process/test/createConsumerDelegation.test.ts b/packages/delegation-process/test/createConsumerDelegation.test.ts index dfcdfebd85..6d8cf90d6a 100644 --- a/packages/delegation-process/test/createConsumerDelegation.test.ts +++ b/packages/delegation-process/test/createConsumerDelegation.test.ts @@ -371,7 +371,7 @@ describe("create consumer delegation", () => { }, ], }; - const eservice = getMockEService(generateId(), delegatorId); + const eservice = getMockEService(); await addOneTenant(delegate); await addOneEservice(eservice); diff --git a/packages/delegation-process/test/getConsumerEservices.test.ts b/packages/delegation-process/test/getConsumerEservices.test.ts new file mode 100644 index 0000000000..3a9abfcf40 --- /dev/null +++ b/packages/delegation-process/test/getConsumerEservices.test.ts @@ -0,0 +1,270 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { + getMockAgreement, + getMockDelegation, + getMockEService, + randomArrayItem, +} from "pagopa-interop-commons-test"; +import { + generateId, + delegationKind, + delegationState, + agreementState, + TenantId, +} from "pagopa-interop-models"; +import { describe, beforeEach, it, expect } from "vitest"; +import { genericLogger } from "pagopa-interop-commons"; +import { requesterIsNotConsumerDelegate } from "../src/model/domain/errors.js"; +import { + addOneAgreement, + addOneDelegation, + addOneEservice, + delegationService, +} from "./utils.js"; + +describe("getConsumerEservices", () => { + const delegatorId1 = generateId(); + const delegatorId2 = generateId(); + + const requesterId = generateId(); + + const eservice1 = { ...getMockEService(), name: "Servizio 1" }; + const eservice2 = { ...getMockEService(), name: "Servizio 2" }; + const eservice3 = { ...getMockEService(), name: "PagoPaService" }; + const eservice4 = { ...getMockEService(), name: "Pippo" }; + const eservice5 = { ...getMockEService(), name: "Paperino" }; + const eservice6 = getMockEService(); + + const agreementInvalidStates = Object.values(agreementState).filter( + (state) => state !== agreementState.active + ); + + const delegationInvalidStates = Object.values(delegationState).filter( + (state) => state !== delegationState.active + ); + + const delegationDelegator1Eservice1 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegatorId1, + eserviceId: eservice1.id, + }); + + const agreementDelegator1Eservice1 = { + ...getMockAgreement(eservice1.id, delegatorId1, agreementState.active), + producerId: eservice1.producerId, + }; + + const delegationDelegator1Eservice2 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegatorId1, + eserviceId: eservice2.id, + }); + + const agreementDelegator1Eservice2 = { + ...getMockAgreement(eservice2.id, delegatorId1, agreementState.active), + producerId: eservice2.producerId, + }; + + const delegationDelegator1Eservice3 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegatorId1, + eserviceId: eservice3.id, + }); + + const agreementDelegator1Eservice3 = { + ...getMockAgreement(eservice3.id, delegatorId1, agreementState.active), + producerId: eservice3.producerId, + }; + + const delegationDelegator1Eservice4 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegatorId1, + eserviceId: eservice4.id, + }); + + const agreementDelegator1Eservice4 = { + ...getMockAgreement(eservice4.id, delegatorId1, agreementState.active), + producerId: eservice4.producerId, + }; + + const invalidDelegationDelegator1Eservice5 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: randomArrayItem(delegationInvalidStates), + delegateId: requesterId, + delegatorId: delegatorId1, + eserviceId: eservice5.id, + }); + + const agreementDelegator1Eservice5 = { + ...getMockAgreement(eservice5.id, delegatorId1, agreementState.active), + producerId: eservice5.producerId, + }; + + const delegationDelegator1Eservice6 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegatorId1, + eserviceId: eservice6.id, + }); + + const invalidAgreementDelegator1Eservice6 = { + ...getMockAgreement( + eservice6.id, + delegatorId1, + randomArrayItem(agreementInvalidStates) + ), + producerId: eservice6.producerId, + }; + + // Delegator1 and Requester have 4 active delegations with 4 active agreements, 1 invalid delegation and 1 invalid agreement + + const delegationDelegator2Eservice1 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegatorId2, + eserviceId: eservice1.id, + }); + + const agreementDelegator2Eservice1 = { + ...getMockAgreement(eservice1.id, delegatorId2, agreementState.active), + producerId: eservice1.producerId, + }; + + // Delegator2 and delegate1 have 1 active delegation and 1 active agreement + + const delegationDelegator1Delegate2Eservice1 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: generateId(), + delegatorId: delegatorId1, + eserviceId: eservice1.id, + }); + + const agreementDelegator1Delegate2Eservice1 = { + ...getMockAgreement(eservice1.id, delegatorId1, agreementState.active), + producerId: eservice1.producerId, + }; + + // Delegator1 and another tenant have 1 active delegation and 1 active agreement + + beforeEach(async () => { + await addOneEservice(eservice1); + await addOneEservice(eservice2); + await addOneEservice(eservice3); + await addOneEservice(eservice4); + await addOneEservice(eservice5); + await addOneEservice(eservice6); + await addOneDelegation(delegationDelegator1Eservice1); + await addOneDelegation(delegationDelegator1Eservice2); + await addOneDelegation(delegationDelegator1Eservice3); + await addOneDelegation(delegationDelegator1Eservice4); + await addOneDelegation(invalidDelegationDelegator1Eservice5); + await addOneDelegation(delegationDelegator1Eservice6); + await addOneDelegation(delegationDelegator1Delegate2Eservice1); + await addOneDelegation(delegationDelegator2Eservice1); + await addOneAgreement(agreementDelegator1Eservice1); + await addOneAgreement(agreementDelegator1Eservice2); + await addOneAgreement(agreementDelegator1Eservice3); + await addOneAgreement(agreementDelegator1Eservice4); + await addOneAgreement(agreementDelegator1Eservice5); + await addOneAgreement(invalidAgreementDelegator1Eservice6); + await addOneAgreement(agreementDelegator2Eservice1); + await addOneAgreement(agreementDelegator1Delegate2Eservice1); + }); + + it("should apply offset and limit", async () => { + expect( + await delegationService.getConsumerEservices( + { + delegatorId: delegatorId1, + requesterId, + offset: 1, + limit: 1, + }, + genericLogger + ) + ).toEqual({ + results: [ + { + name: eservice4.name, + id: eservice4.id, + }, + ], + totalCount: 4, + }); + }); + it("should filter eservices by the 'eserviceName' parameter", async () => { + expect( + await delegationService.getConsumerEservices( + { + delegatorId: delegatorId1, + requesterId, + eserviceName: "servizio", + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [ + { + name: eservice1.name, + id: eservice1.id, + }, + { + name: eservice2.name, + id: eservice2.id, + }, + ], + totalCount: 2, + }); + + expect( + await delegationService.getConsumerEservices( + { + delegatorId: delegatorId1, + requesterId, + eserviceName: "pippo", + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [ + { + name: eservice4.name, + id: eservice4.id, + }, + ], + totalCount: 1, + }); + }); + it("should throw requesterIsNotConsumerDelegate if the requester is not a consumer delegate of the delegator", async () => { + const invalidRequesterId = generateId(); + + expect( + delegationService.getConsumerEservices( + { + delegatorId: delegatorId1, + requesterId: invalidRequesterId, + offset: 0, + limit: 50, + }, + genericLogger + ) + ).rejects.toThrowError( + requesterIsNotConsumerDelegate(invalidRequesterId, delegatorId1) + ); + }); +}); From 5d2515bd91cd210b52e1c87a040dd35f0d6980e8 Mon Sep 17 00:00:00 2001 From: AsterITA Date: Fri, 10 Jan 2025 16:41:48 +0100 Subject: [PATCH 2/8] Pin 5902: getConsumerDelegators minor fixes (#1336) --- .../consumer/getConsumerDelegators.bru | 1 - .../api-clients/open-api/delegationApi.yml | 6 -- .../src/services/delegationService.ts | 1 - .../src/model/domain/errors.ts | 6 +- .../src/routers/DelegationRouter.ts | 4 +- .../src/services/delegationService.ts | 12 ++- .../src/services/readModelService.ts | 4 +- .../test/getConsumerDelegators.test.ts | 89 ++++--------------- 8 files changed, 36 insertions(+), 87 deletions(-) diff --git a/collections/delegation/consumer/getConsumerDelegators.bru b/collections/delegation/consumer/getConsumerDelegators.bru index 1d090bdbfb..fac425b64b 100644 --- a/collections/delegation/consumer/getConsumerDelegators.bru +++ b/collections/delegation/consumer/getConsumerDelegators.bru @@ -13,7 +13,6 @@ get { params:query { offset: 0 limit: 10 - delegateId: {{tenantId}} ~delegatorName: } diff --git a/packages/api-clients/open-api/delegationApi.yml b/packages/api-clients/open-api/delegationApi.yml index 8dee969df7..980abe44a4 100644 --- a/packages/api-clients/open-api/delegationApi.yml +++ b/packages/api-clients/open-api/delegationApi.yml @@ -96,12 +96,6 @@ paths: name: delegatorName schema: type: string - - in: query - name: delegateId - required: true - schema: - type: string - format: uuid - in: query name: offset required: true diff --git a/packages/backend-for-frontend/src/services/delegationService.ts b/packages/backend-for-frontend/src/services/delegationService.ts index 2e249072fa..bc2de5f949 100644 --- a/packages/backend-for-frontend/src/services/delegationService.ts +++ b/packages/backend-for-frontend/src/services/delegationService.ts @@ -415,7 +415,6 @@ export function delegationServiceBuilder( const delegatorsData = await delegationClients.consumer.getConsumerDelegators({ queries: { - delegateId: authData.organizationId, delegatorName: q, offset, limit, diff --git a/packages/delegation-process/src/model/domain/errors.ts b/packages/delegation-process/src/model/domain/errors.ts index d8dba6fe75..becaeefcbc 100644 --- a/packages/delegation-process/src/model/domain/errors.ts +++ b/packages/delegation-process/src/model/domain/errors.ts @@ -177,10 +177,12 @@ export function delegationStampNotFound( export function requesterIsNotConsumerDelegate( requesterId: TenantId, - delegatorId: TenantId + delegatorId?: TenantId ): ApiError { return new ApiError({ - detail: `Requester ${requesterId} is not a Consumer delegate for delegator ${delegatorId}`, + detail: `Requester ${requesterId} is not a Consumer delegate${ + delegatorId ? ` for delegator ${delegatorId}` : "" + }`, code: "requesterIsNotConsumerDelegate", title: "Requester is not a delegate", }); diff --git a/packages/delegation-process/src/routers/DelegationRouter.ts b/packages/delegation-process/src/routers/DelegationRouter.ts index d908007a94..66a4b9dd9f 100644 --- a/packages/delegation-process/src/routers/DelegationRouter.ts +++ b/packages/delegation-process/src/routers/DelegationRouter.ts @@ -430,12 +430,12 @@ const delegationRouter = ( .get("/consumer/delegators", async (req, res) => { const ctx = fromAppContext(req.ctx); - const { delegateId, delegatorName, limit, offset } = req.query; + const { delegatorName, limit, offset } = req.query; try { const delegators = await delegationService.getConsumerDelegators( { - delegateId: unsafeBrandId(delegateId), + requesterId: ctx.authData.organizationId, delegatorName, limit, offset, diff --git a/packages/delegation-process/src/services/delegationService.ts b/packages/delegation-process/src/services/delegationService.ts index 13269dcb39..34dc75dd05 100644 --- a/packages/delegation-process/src/services/delegationService.ts +++ b/packages/delegation-process/src/services/delegationService.ts @@ -583,7 +583,7 @@ export function delegationServiceBuilder( }, async getConsumerDelegators( filters: { - delegateId: TenantId; + requesterId: TenantId; limit: number; offset: number; delegatorName?: string; @@ -595,6 +595,16 @@ export function delegationServiceBuilder( filters )}` ); + + const delegation = await readModelService.findDelegations({ + delegateId: filters.requesterId, + delegationKind: delegationKind.delegatedConsumer, + states: [delegationState.active], + }); + if (!delegation || delegation.length === 0) { + throw requesterIsNotConsumerDelegate(filters.requesterId); + } + return await readModelService.getConsumerDelegators(filters); }, async getConsumerEservices( diff --git a/packages/delegation-process/src/services/readModelService.ts b/packages/delegation-process/src/services/readModelService.ts index 13d60c34ad..c4978282c2 100644 --- a/packages/delegation-process/src/services/readModelService.ts +++ b/packages/delegation-process/src/services/readModelService.ts @@ -261,7 +261,7 @@ export function readModelServiceBuilder( }; }, async getConsumerDelegators(filters: { - delegateId: TenantId; + requesterId: TenantId; limit: number; offset: number; delegatorName?: string; @@ -271,7 +271,7 @@ export function readModelServiceBuilder( $match: { "data.kind": delegationKind.delegatedConsumer, "data.state": delegationState.active, - "data.delegateId": filters.delegateId, + "data.delegateId": filters.requesterId, } satisfies ReadModelFilter, }, { diff --git a/packages/delegation-process/test/getConsumerDelegators.test.ts b/packages/delegation-process/test/getConsumerDelegators.test.ts index 499a8d8c96..747559b8cf 100644 --- a/packages/delegation-process/test/getConsumerDelegators.test.ts +++ b/packages/delegation-process/test/getConsumerDelegators.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ import { getMockAgreement, getMockDelegation, @@ -13,6 +14,7 @@ import { } from "pagopa-interop-models"; import { describe, beforeEach, it, expect } from "vitest"; import { genericLogger } from "pagopa-interop-commons"; +import { requesterIsNotConsumerDelegate } from "../src/model/domain/errors.js"; import { addOneAgreement, addOneDelegation, @@ -28,7 +30,7 @@ describe("getConsumerDelegators", () => { const delegator4 = { ...getMockTenant(), name: "DeleganteQuattro" }; const delegator5 = { ...getMockTenant(), name: "PagoPA" }; const delegator6 = getMockTenant(); - const delegateId = generateId(); + const requesterId = generateId(); const eservice1 = getMockEService(); const eservice2 = getMockEService(); const eservice3 = getMockEService(); @@ -36,7 +38,7 @@ describe("getConsumerDelegators", () => { const mockDelegation1 = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, - delegateId, + delegateId: requesterId, delegatorId: delegator1.id, eserviceId: eservice1.id, }); @@ -49,7 +51,7 @@ describe("getConsumerDelegators", () => { const mockDelegation1Bis = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, - delegateId, + delegateId: requesterId, delegatorId: delegator1.id, eserviceId: eservice2.id, }); @@ -64,7 +66,7 @@ describe("getConsumerDelegators", () => { const mockDelegation2 = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, - delegateId, + delegateId: requesterId, delegatorId: delegator2.id, eserviceId: eservice1.id, }); @@ -79,7 +81,7 @@ describe("getConsumerDelegators", () => { const mockDelegation3 = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.rejected, - delegateId, + delegateId: requesterId, delegatorId: delegator3.id, eserviceId: eservice1.id, }); @@ -89,7 +91,7 @@ describe("getConsumerDelegators", () => { const mockDelegation4 = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, - delegateId, + delegateId: requesterId, delegatorId: delegator4.id, eserviceId: eservice1.id, }); @@ -104,7 +106,7 @@ describe("getConsumerDelegators", () => { const mockDelegation5 = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, - delegateId, + delegateId: requesterId, delegatorId: delegator5.id, eserviceId: eservice3.id, }); @@ -156,39 +158,11 @@ describe("getConsumerDelegators", () => { await addOneAgreement(mockAgreement6); }); - it("should get delegators filtered by delegateId", async () => { - expect( - await delegationService.getConsumerDelegators( - { - delegateId, - offset: 0, - limit: 50, - }, - genericLogger - ) - ).toEqual({ - results: [ - { - id: delegator1.id, - name: delegator1.name, - }, - { - id: delegator2.id, - name: delegator2.name, - }, - { - id: delegator5.id, - name: delegator5.name, - }, - ], - totalCount: 3, - }); - }); it("should apply offset and limit", async () => { expect( await delegationService.getConsumerDelegators( { - delegateId, + requesterId, offset: 1, limit: 1, }, @@ -208,7 +182,7 @@ describe("getConsumerDelegators", () => { expect( await delegationService.getConsumerDelegators( { - delegateId, + requesterId, offset: 0, limit: 50, delegatorName: "Comune", @@ -232,7 +206,7 @@ describe("getConsumerDelegators", () => { expect( await delegationService.getConsumerDelegators( { - delegateId, + requesterId, offset: 0, limit: 50, delegatorName: "PagoPA", @@ -249,47 +223,18 @@ describe("getConsumerDelegators", () => { totalCount: 1, }); }); - it("should return no results if no delegations match the criteria", async () => { - expect( - await delegationService.getConsumerDelegators( - { - delegateId: generateId(), - offset: 0, - limit: 50, - }, - genericLogger - ) - ).toEqual({ - results: [], - totalCount: 0, - }); + it("should throw requesterIsNotConsumerDelegate if the requester is not a consumer delegate", async () => { + const invalidRequesterId = generateId(); expect( - await delegationService.getConsumerDelegators( + delegationService.getConsumerDelegators( { - delegateId: delegator3.id, // No active delegation + requesterId: invalidRequesterId, offset: 0, limit: 50, }, genericLogger ) - ).toEqual({ - results: [], - totalCount: 0, - }); - - expect( - await delegationService.getConsumerDelegators( - { - delegateId: delegator4.id, // No active agreements - offset: 0, - limit: 50, - }, - genericLogger - ) - ).toEqual({ - results: [], - totalCount: 0, - }); + ).rejects.toThrowError(requesterIsNotConsumerDelegate(invalidRequesterId)); }); }); From 66228e86c19e5e4bfe85fb56d900625830dcdf19 Mon Sep 17 00:00:00 2001 From: AsterITA Date: Mon, 13 Jan 2025 13:56:06 +0100 Subject: [PATCH 3/8] Pin 5917: add delegate contactMail to agreement (#1352) --- packages/api-clients/open-api/bffApi.yml | 10 +++------- .../src/services/agreementService.ts | 8 ++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index c7cbbf676e..e63ae03e6c 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -14799,15 +14799,11 @@ components: id: type: string format: uuid - delegateId: - type: string - format: uuid - delegateName: - type: string + delegate: + $ref: "#/components/schemas/CompactOrganization" required: - id - - delegateId - - delegateName + - delegate producer: $ref: "#/components/schemas/CompactOrganization" consumer: diff --git a/packages/backend-for-frontend/src/services/agreementService.ts b/packages/backend-for-frontend/src/services/agreementService.ts index 10cd750855..0cc8e11647 100644 --- a/packages/backend-for-frontend/src/services/agreementService.ts +++ b/packages/backend-for-frontend/src/services/agreementService.ts @@ -693,8 +693,12 @@ export async function enrichAgreement( return { id: delegation.id, - delegateId: delegation.delegateId, - delegateName: tenant.name, + delegate: { + id: tenant.id, + name: tenant.name, + kind: tenant.kind, + contactMail: getLatestTenantContactEmail(tenant), + }, }; }); From cbb48640accac9c36975a637995650f215b4d814 Mon Sep 17 00:00:00 2001 From: AsterITA Date: Mon, 13 Jan 2025 15:22:01 +0100 Subject: [PATCH 4/8] Pin 5918 get consumer with agreements (#1351) --- packages/api-clients/open-api/bffApi.yml | 72 +++++ .../api-clients/open-api/delegationApi.yml | 51 ++- .../src/routers/consumerDelegationRouter.ts | 23 ++ .../src/services/delegationService.ts | 40 ++- .../src/model/domain/models.ts | 19 +- .../src/routers/DelegationRouter.ts | 35 ++- .../src/services/delegationService.ts | 23 +- .../src/services/readModelService.ts | 170 +++++++--- .../src/utilities/errorMappers.ts | 2 + .../test/getConsumerDelegators.test.ts | 101 +++--- ...etConsumerDelegatorsWithAgreements.test.ts | 295 ++++++++++++++++++ 11 files changed, 697 insertions(+), 134 deletions(-) create mode 100644 packages/delegation-process/test/getConsumerDelegatorsWithAgreements.test.ts diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index e63ae03e6c..ff7b9d62ae 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -2166,6 +2166,15 @@ paths: name: q schema: type: string + - in: query + name: eserviceIds + schema: + type: array + items: + type: string + format: uuid + default: [] + explode: false - in: query name: offset required: true @@ -2222,6 +2231,69 @@ paths: 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 + "/consumer/delegations/delegatorsWithAgreements": + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - in: query + name: q + schema: + type: string + - in: query + name: offset + required: true + schema: + type: integer + format: int32 + - in: query + name: limit + required: true + schema: + type: integer + format: int32 + get: + tags: + - consumerDelegations + operationId: getConsumerDelegatorsWithAgreements + description: Retrieve requester's delegators with active agreements + responses: + "200": + description: Delegators retrieved + 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/DelegationTenants" + "429": + description: Too Many Requests + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + 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 "/consumers": parameters: - $ref: "#/components/parameters/CorrelationIdHeader" diff --git a/packages/api-clients/open-api/delegationApi.yml b/packages/api-clients/open-api/delegationApi.yml index 980abe44a4..ce53005e52 100644 --- a/packages/api-clients/open-api/delegationApi.yml +++ b/packages/api-clients/open-api/delegationApi.yml @@ -96,6 +96,15 @@ paths: name: delegatorName schema: type: string + - in: query + name: eserviceIds + schema: + type: array + items: + type: string + format: uuid + default: [] + explode: false - in: query name: offset required: true @@ -115,7 +124,47 @@ paths: tags: - consumer operationId: getConsumerDelegators - description: Retrieve delegators of a delegate with active delegation and agreement + description: Retrieve requester's delegators + responses: + "200": + description: Tenants retrieved + content: + application/json: + schema: + $ref: "#/components/schemas/CompactTenants" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + /consumer/delegatorsWithAgreements: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - in: query + name: delegatorName + schema: + type: string + - in: query + name: offset + required: true + schema: + type: integer + format: int32 + minimum: 0 + - in: query + name: limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + maximum: 50 + get: + tags: + - consumer + operationId: getConsumerDelegatorsWithAgreements + description: Retrieve requester's delegators with active agreements responses: "200": description: Tenants retrieved diff --git a/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts b/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts index c0029710c8..b97fb64aad 100644 --- a/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts +++ b/packages/backend-for-frontend/src/routers/consumerDelegationRouter.ts @@ -144,6 +144,29 @@ const consumerDelegationRouter = ( return res.status(errorRes.status).send(errorRes); } }) + .get("/consumer/delegations/delegatorsWithAgreements", async (req, res) => { + const ctx = fromBffAppContext(req.ctx, req.headers); + + try { + const delegators = + await delegationService.getConsumerDelegatorsWithAgreements( + req.query, + ctx + ); + + return res.status(200).send(bffApi.DelegationTenants.parse(delegators)); + } catch (error) { + const errorRes = makeApiProblem( + error, + emptyErrorMapper, + ctx.logger, + ctx.correlationId, + `Error getting delegators with active agreements` + ); + + return res.status(errorRes.status).send(errorRes); + } + }) .get("/consumer/delegations/eservices", async (req, res) => { const ctx = fromBffAppContext(req.ctx, req.headers); diff --git a/packages/backend-for-frontend/src/services/delegationService.ts b/packages/backend-for-frontend/src/services/delegationService.ts index bc2de5f949..0bb350eeaf 100644 --- a/packages/backend-for-frontend/src/services/delegationService.ts +++ b/packages/backend-for-frontend/src/services/delegationService.ts @@ -399,21 +399,59 @@ export function delegationServiceBuilder( async getConsumerDelegators( { q, + eserviceIds, offset, limit, }: { q?: string; + eserviceIds: string[]; offset: number; limit: number; }, { headers, authData, logger }: WithLogger ): Promise { logger.info( - `Retrieving consumer delegators of delegate ${authData.organizationId} with name ${q}, limit ${limit}, offset ${offset}` + `Retrieving consumer delegators of requester ${authData.organizationId} with name ${q}, eserviceIds ${eserviceIds}, limit ${limit}, offset ${offset}` ); const delegatorsData = await delegationClients.consumer.getConsumerDelegators({ + queries: { + delegatorName: q, + eserviceIds, + offset, + limit, + }, + headers, + }); + + return { + results: delegatorsData.results, + pagination: { + offset, + limit, + totalCount: delegatorsData.totalCount, + }, + }; + }, + async getConsumerDelegatorsWithAgreements( + { + q, + offset, + limit, + }: { + q?: string; + offset: number; + limit: number; + }, + { headers, authData, logger }: WithLogger + ): Promise { + logger.info( + `Retrieving consumer delegators with active agreements of requester ${authData.organizationId} with name ${q}, limit ${limit}, offset ${offset}` + ); + + const delegatorsData = + await delegationClients.consumer.getConsumerDelegatorsWithAgreements({ queries: { delegatorName: q, offset, diff --git a/packages/delegation-process/src/model/domain/models.ts b/packages/delegation-process/src/model/domain/models.ts index 788a5df4ba..be7188f1c7 100644 --- a/packages/delegation-process/src/model/domain/models.ts +++ b/packages/delegation-process/src/model/domain/models.ts @@ -1,21 +1,4 @@ -import { - DelegationId, - DelegationKind, - DelegationState, - EService, - EServiceId, - Tenant, - TenantId, - UserId, -} from "pagopa-interop-models"; - -export type GetDelegationsFilters = { - eserviceId?: EServiceId; - delegatorId?: TenantId; - delegateId?: TenantId; - delegationKind?: DelegationKind; - states?: DelegationState[]; -}; +import { DelegationId, EService, Tenant, UserId } from "pagopa-interop-models"; export type DelegationActivationPDFPayload = { delegationKindText: string; diff --git a/packages/delegation-process/src/routers/DelegationRouter.ts b/packages/delegation-process/src/routers/DelegationRouter.ts index 66a4b9dd9f..bb1ad80e2b 100644 --- a/packages/delegation-process/src/routers/DelegationRouter.ts +++ b/packages/delegation-process/src/routers/DelegationRouter.ts @@ -33,6 +33,7 @@ import { revokeDelegationErrorMapper, getConsumerDelegatorsErrorMapper, getConsumerEservicesErrorMapper, + getConsumerDelegatorsWithAgreementsErrorMapper, } from "../utilities/errorMappers.js"; import { delegationServiceBuilder } from "../services/delegationService.js"; @@ -430,13 +431,14 @@ const delegationRouter = ( .get("/consumer/delegators", async (req, res) => { const ctx = fromAppContext(req.ctx); - const { delegatorName, limit, offset } = req.query; + const { delegatorName, eserviceIds, limit, offset } = req.query; try { const delegators = await delegationService.getConsumerDelegators( { requesterId: ctx.authData.organizationId, delegatorName, + eserviceIds: eserviceIds.map(unsafeBrandId), limit, offset, }, @@ -457,6 +459,37 @@ const delegationRouter = ( return res.status(errorRes.status).send(errorRes); } }) + .get("/consumer/delegatorsWithAgreements", async (req, res) => { + const ctx = fromAppContext(req.ctx); + + const { delegatorName, limit, offset } = req.query; + + try { + const delegators = + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId: ctx.authData.organizationId, + delegatorName, + limit, + offset, + }, + ctx.logger + ); + + return res + .status(200) + .send(delegationApi.CompactTenants.parse(delegators)); + } catch (error) { + const errorRes = makeApiProblem( + error, + getConsumerDelegatorsWithAgreementsErrorMapper, + ctx.logger, + ctx.correlationId + ); + + return res.status(errorRes.status).send(errorRes); + } + }) .get("/consumer/eservices", async (req, res) => { const ctx = fromAppContext(req.ctx); diff --git a/packages/delegation-process/src/services/delegationService.ts b/packages/delegation-process/src/services/delegationService.ts index 34dc75dd05..59aa181a58 100644 --- a/packages/delegation-process/src/services/delegationService.ts +++ b/packages/delegation-process/src/services/delegationService.ts @@ -584,14 +584,15 @@ export function delegationServiceBuilder( async getConsumerDelegators( filters: { requesterId: TenantId; + delegatorName?: string; + eserviceIds: EServiceId[]; limit: number; offset: number; - delegatorName?: string; }, logger: Logger ): Promise { logger.info( - `Retrieving delegations tenants with filters: ${JSON.stringify( + `Retrieving consumer delegators with filters: ${JSON.stringify( filters )}` ); @@ -607,6 +608,24 @@ export function delegationServiceBuilder( return await readModelService.getConsumerDelegators(filters); }, + async getConsumerDelegatorsWithAgreements( + filters: { + requesterId: TenantId; + delegatorName?: string; + limit: number; + offset: number; + }, + logger: Logger + ): Promise { + logger.info( + `Retrieving consumer delegators with active agreements and filters: ${JSON.stringify( + filters + )}` + ); + return await readModelService.getConsumerDelegatorsWithAgreements( + filters + ); + }, async getConsumerEservices( filters: { requesterId: TenantId; diff --git a/packages/delegation-process/src/services/readModelService.ts b/packages/delegation-process/src/services/readModelService.ts index c4978282c2..37eb01183a 100644 --- a/packages/delegation-process/src/services/readModelService.ts +++ b/packages/delegation-process/src/services/readModelService.ts @@ -24,49 +24,6 @@ import { } from "pagopa-interop-models"; import { z } from "zod"; import { delegationApi } from "pagopa-interop-api-clients"; -import { GetDelegationsFilters } from "../model/domain/models.js"; - -const toReadModelFilter = ( - filters: GetDelegationsFilters -): ReadModelFilter => { - const { delegateId, delegatorId, eserviceId, delegationKind, states } = - filters; - - const delegatorIdFilter = delegatorId - ? { - "data.delegatorId": { $eq: delegatorId }, - } - : {}; - const delegateIdFilter = delegateId - ? { - "data.delegateId": { $eq: delegateId }, - } - : {}; - const eserviceIdFilter = eserviceId - ? { - "data.eserviceId": { $eq: eserviceId }, - } - : {}; - const delegationKindFilter = delegationKind - ? { - "data.kind": { $eq: delegationKind }, - } - : {}; - const stateFilter = - states && states.length > 0 - ? { - "data.state": { $in: states }, - } - : {}; - - return { - ...delegatorIdFilter, - ...delegateIdFilter, - ...eserviceIdFilter, - ...delegationKindFilter, - ...stateFilter, - }; -}; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function readModelServiceBuilder( @@ -132,13 +89,38 @@ export function readModelServiceBuilder( ...(kind ? { "data.kind": kind } : {}), }); }, - async findDelegations( - filters: GetDelegationsFilters - ): Promise { + async findDelegations(filters: { + eserviceId?: EServiceId; + delegatorId?: TenantId; + delegateId?: TenantId; + delegationKind: DelegationKind; + states: DelegationState[]; + }): Promise { const results = await delegations - .aggregate([{ $match: toReadModelFilter(filters) }], { - allowDiskUse: true, - }) + .aggregate( + [ + { + $match: { + ...(filters.delegatorId + ? { "data.delegatorId": filters.delegatorId } + : {}), + ...(filters.eserviceId + ? { "data.eserviceId": filters.eserviceId } + : {}), + ...(filters.delegateId + ? { "data.delegateId": filters.delegateId } + : {}), + "data.kind": filters.delegationKind, + ...ReadModelRepository.arrayToFilter(filters.states, { + "data.state": { $in: filters.states }, + }), + } satisfies ReadModelFilter, + }, + ], + { + allowDiskUse: true, + } + ) .toArray(); if (!results) { @@ -262,9 +244,99 @@ export function readModelServiceBuilder( }, async getConsumerDelegators(filters: { requesterId: TenantId; + delegatorName?: string; + eserviceIds: EServiceId[]; limit: number; offset: number; + }): Promise { + const aggregationPipeline = [ + { + $match: { + "data.kind": delegationKind.delegatedConsumer, + "data.state": delegationState.active, + "data.delegateId": filters.requesterId, + ...ReadModelRepository.arrayToFilter(filters.eserviceIds, { + "data.eserviceId": { $in: filters.eserviceIds }, + }), + } satisfies ReadModelFilter, + }, + { + $lookup: { + from: "tenants", + localField: "data.delegatorId", + foreignField: "data.id", + as: "delegator", + }, + }, + { + $unwind: "$delegator", + }, + ...(filters.delegatorName + ? [ + { + $match: { + "delegator.data.name": { + $regex: ReadModelRepository.escapeRegExp( + filters.delegatorName + ), + $options: "i", + }, + }, + }, + ] + : []), + { + $group: { + _id: "$delegator.data.id", + name: { $first: "$delegator.data.name" }, + }, + }, + { + $project: { + _id: 0, + id: "$_id", + name: 1, + }, + }, + { + $sort: { name: 1 }, + }, + ]; + + const data = await delegations + .aggregate( + [ + ...aggregationPipeline, + { $skip: filters.offset }, + { $limit: filters.limit }, + ], + { allowDiskUse: true } + ) + .toArray(); + + const result = z.array(delegationApi.CompactTenant).safeParse(data); + + if (!result.success) { + throw genericInternalError( + `Unable to parse compact delegation tenants: result ${JSON.stringify( + result + )} - data ${JSON.stringify(data)}` + ); + } + + return { + results: result.data, + totalCount: await ReadModelRepository.getTotalCount( + delegations, + aggregationPipeline + ), + }; + }, + async getConsumerDelegatorsWithAgreements(filters: { + requesterId: TenantId; delegatorName?: string; + limit: number; + offset: number; }): Promise { const aggregationPipeline = [ { diff --git a/packages/delegation-process/src/utilities/errorMappers.ts b/packages/delegation-process/src/utilities/errorMappers.ts index 880832caaa..c63d347961 100644 --- a/packages/delegation-process/src/utilities/errorMappers.ts +++ b/packages/delegation-process/src/utilities/errorMappers.ts @@ -20,6 +20,8 @@ export const getDelegationsErrorMapper = ( match(error.code).otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); export const getConsumerDelegatorsErrorMapper = getDelegationsErrorMapper; +export const getConsumerDelegatorsWithAgreementsErrorMapper = + getConsumerDelegatorsErrorMapper; export const getConsumerEservicesErrorMapper = ( error: ApiError ): number => diff --git a/packages/delegation-process/test/getConsumerDelegators.test.ts b/packages/delegation-process/test/getConsumerDelegators.test.ts index 747559b8cf..34c5b423f7 100644 --- a/packages/delegation-process/test/getConsumerDelegators.test.ts +++ b/packages/delegation-process/test/getConsumerDelegators.test.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-floating-promises */ import { - getMockAgreement, getMockDelegation, getMockEService, getMockTenant, @@ -9,14 +8,12 @@ import { generateId, delegationKind, delegationState, - agreementState, TenantId, } from "pagopa-interop-models"; import { describe, beforeEach, it, expect } from "vitest"; import { genericLogger } from "pagopa-interop-commons"; import { requesterIsNotConsumerDelegate } from "../src/model/domain/errors.js"; import { - addOneAgreement, addOneDelegation, addOneEservice, addOneTenant, @@ -27,9 +24,8 @@ describe("getConsumerDelegators", () => { const delegator1 = { ...getMockTenant(), name: "Comune di Burione" }; const delegator2 = { ...getMockTenant(), name: "Comune di Milano" }; const delegator3 = { ...getMockTenant(), name: "DeleganteTre" }; - const delegator4 = { ...getMockTenant(), name: "DeleganteQuattro" }; - const delegator5 = { ...getMockTenant(), name: "PagoPA" }; - const delegator6 = getMockTenant(); + const delegator4 = { ...getMockTenant(), name: "PagoPA" }; + const delegator5 = { ...getMockTenant(), name: "DeleganteCinque" }; const requesterId = generateId(); const eservice1 = getMockEService(); const eservice2 = getMockEService(); @@ -43,11 +39,6 @@ describe("getConsumerDelegators", () => { eserviceId: eservice1.id, }); - const mockAgreement1 = { - ...getMockAgreement(eservice1.id, delegator1.id, agreementState.active), - producerId: eservice1.producerId, - }; - const mockDelegation1Bis = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, @@ -56,12 +47,7 @@ describe("getConsumerDelegators", () => { eserviceId: eservice2.id, }); - const mockAgreement1bis = { - ...getMockAgreement(eservice2.id, delegator1.id, agreementState.active), - producerId: eservice2.producerId, - }; - - // Delegator1 has 2 active delegations and 2 active agreements + // Delegator1 has 2 active delegations with 2 published eservices const mockDelegation2 = getMockDelegation({ kind: delegationKind.delegatedConsumer, @@ -71,12 +57,7 @@ describe("getConsumerDelegators", () => { eserviceId: eservice1.id, }); - const mockAgreement2 = { - ...getMockAgreement(eservice1.id, delegator2.id, agreementState.active), - producerId: eservice1.producerId, - }; - - // Delegator2 has 1 active delegation and 1 active agreement + // Delegator2 has 1 active delegation with 1 published agreement const mockDelegation3 = getMockDelegation({ kind: delegationKind.delegatedConsumer, @@ -93,45 +74,20 @@ describe("getConsumerDelegators", () => { state: delegationState.active, delegateId: requesterId, delegatorId: delegator4.id, - eserviceId: eservice1.id, - }); - - const mockAgreement4 = { - ...getMockAgreement(eservice1.id, delegator4.id, agreementState.rejected), - producerId: eservice1.producerId, - }; - - // Delegator4 has 1 active delegation and 1 rejected agreement - - const mockDelegation5 = getMockDelegation({ - kind: delegationKind.delegatedConsumer, - state: delegationState.active, - delegateId: requesterId, - delegatorId: delegator5.id, eserviceId: eservice3.id, }); - const mockAgreement5 = { - ...getMockAgreement(eservice3.id, delegator5.id, agreementState.active), - producerId: eservice3.producerId, - }; + // Delegator4 has 1 active delegation with 1 published eservice - // Delegator5 has 1 active delegation and 1 active agreement - - const mockDelegation6 = getMockDelegation({ + const mockDelegation5 = getMockDelegation({ kind: delegationKind.delegatedConsumer, state: delegationState.active, delegateId: generateId(), - delegatorId: delegator6.id, + delegatorId: delegator5.id, eserviceId: eservice3.id, }); - const mockAgreement6 = { - ...getMockAgreement(eservice3.id, delegator6.id, agreementState.active), - producerId: eservice3.producerId, - }; - - // Delegator6 has 1 active delegation and 1 active agreement but a different delegateId + // Delegator5 has 1 active delegation with 1 published eservice but a different delegateId beforeEach(async () => { await addOneEservice(eservice1); @@ -143,19 +99,11 @@ describe("getConsumerDelegators", () => { await addOneDelegation(mockDelegation3); await addOneDelegation(mockDelegation4); await addOneDelegation(mockDelegation5); - await addOneDelegation(mockDelegation6); await addOneTenant(delegator1); await addOneTenant(delegator2); await addOneTenant(delegator3); await addOneTenant(delegator4); await addOneTenant(delegator5); - await addOneTenant(delegator6); - await addOneAgreement(mockAgreement1); - await addOneAgreement(mockAgreement1bis); - await addOneAgreement(mockAgreement2); - await addOneAgreement(mockAgreement4); - await addOneAgreement(mockAgreement5); - await addOneAgreement(mockAgreement6); }); it("should apply offset and limit", async () => { @@ -163,6 +111,7 @@ describe("getConsumerDelegators", () => { await delegationService.getConsumerDelegators( { requesterId, + eserviceIds: [], offset: 1, limit: 1, }, @@ -183,6 +132,7 @@ describe("getConsumerDelegators", () => { await delegationService.getConsumerDelegators( { requesterId, + eserviceIds: [], offset: 0, limit: 50, delegatorName: "Comune", @@ -207,6 +157,7 @@ describe("getConsumerDelegators", () => { await delegationService.getConsumerDelegators( { requesterId, + eserviceIds: [], offset: 0, limit: 50, delegatorName: "PagoPA", @@ -216,13 +167,38 @@ describe("getConsumerDelegators", () => { ).toEqual({ results: [ { - id: delegator5.id, - name: delegator5.name, + id: delegator4.id, + name: delegator4.name, }, ], totalCount: 1, }); }); + it("should filter delegators by the 'eserviceIds' parameter", async () => { + expect( + await delegationService.getConsumerDelegators( + { + requesterId, + eserviceIds: [eservice1.id, eservice2.id], + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [ + { + id: delegator1.id, + name: delegator1.name, + }, + { + id: delegator2.id, + name: delegator2.name, + }, + ], + totalCount: 2, + }); + }); it("should throw requesterIsNotConsumerDelegate if the requester is not a consumer delegate", async () => { const invalidRequesterId = generateId(); @@ -230,6 +206,7 @@ describe("getConsumerDelegators", () => { delegationService.getConsumerDelegators( { requesterId: invalidRequesterId, + eserviceIds: [], offset: 0, limit: 50, }, diff --git a/packages/delegation-process/test/getConsumerDelegatorsWithAgreements.test.ts b/packages/delegation-process/test/getConsumerDelegatorsWithAgreements.test.ts new file mode 100644 index 0000000000..cb0e3733ed --- /dev/null +++ b/packages/delegation-process/test/getConsumerDelegatorsWithAgreements.test.ts @@ -0,0 +1,295 @@ +import { + getMockAgreement, + getMockDelegation, + getMockEService, + getMockTenant, +} from "pagopa-interop-commons-test"; +import { + generateId, + delegationKind, + delegationState, + agreementState, + TenantId, +} from "pagopa-interop-models"; +import { describe, beforeEach, it, expect } from "vitest"; +import { genericLogger } from "pagopa-interop-commons"; +import { + addOneAgreement, + addOneDelegation, + addOneEservice, + addOneTenant, + delegationService, +} from "./utils.js"; + +describe("getConsumerDelegatorsWithAgreements", () => { + const delegator1 = { ...getMockTenant(), name: "Comune di Burione" }; + const delegator2 = { ...getMockTenant(), name: "Comune di Milano" }; + const delegator3 = { ...getMockTenant(), name: "DeleganteTre" }; + const delegator4 = { ...getMockTenant(), name: "DeleganteQuattro" }; + const delegator5 = { ...getMockTenant(), name: "PagoPA" }; + const delegator6 = getMockTenant(); + const requesterId = generateId(); + const eservice1 = getMockEService(); + const eservice2 = getMockEService(); + const eservice3 = getMockEService(); + + const mockDelegation1 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegator1.id, + eserviceId: eservice1.id, + }); + + const mockAgreement1 = { + ...getMockAgreement(eservice1.id, delegator1.id, agreementState.active), + producerId: eservice1.producerId, + }; + + const mockDelegation1Bis = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegator1.id, + eserviceId: eservice2.id, + }); + + const mockAgreement1bis = { + ...getMockAgreement(eservice2.id, delegator1.id, agreementState.active), + producerId: eservice2.producerId, + }; + + // Delegator1 has 2 active delegations and 2 active agreements + + const mockDelegation2 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegator2.id, + eserviceId: eservice1.id, + }); + + const mockAgreement2 = { + ...getMockAgreement(eservice1.id, delegator2.id, agreementState.active), + producerId: eservice1.producerId, + }; + + // Delegator2 has 1 active delegation and 1 active agreement + + const mockDelegation3 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.rejected, + delegateId: requesterId, + delegatorId: delegator3.id, + eserviceId: eservice1.id, + }); + + // Delegator3 has 1 rejected delegation + + const mockDelegation4 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegator4.id, + eserviceId: eservice1.id, + }); + + const mockAgreement4 = { + ...getMockAgreement(eservice1.id, delegator4.id, agreementState.rejected), + producerId: eservice1.producerId, + }; + + // Delegator4 has 1 active delegation and 1 rejected agreement + + const mockDelegation5 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: requesterId, + delegatorId: delegator5.id, + eserviceId: eservice3.id, + }); + + const mockAgreement5 = { + ...getMockAgreement(eservice3.id, delegator5.id, agreementState.active), + producerId: eservice3.producerId, + }; + + // Delegator5 has 1 active delegation and 1 active agreement + + const mockDelegation6 = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + state: delegationState.active, + delegateId: generateId(), + delegatorId: delegator6.id, + eserviceId: eservice3.id, + }); + + const mockAgreement6 = { + ...getMockAgreement(eservice3.id, delegator6.id, agreementState.active), + producerId: eservice3.producerId, + }; + + // Delegator6 has 1 active delegation and 1 active agreement but a different delegateId + + beforeEach(async () => { + await addOneEservice(eservice1); + await addOneEservice(eservice2); + await addOneEservice(eservice3); + await addOneDelegation(mockDelegation1); + await addOneDelegation(mockDelegation1Bis); + await addOneDelegation(mockDelegation2); + await addOneDelegation(mockDelegation3); + await addOneDelegation(mockDelegation4); + await addOneDelegation(mockDelegation5); + await addOneDelegation(mockDelegation6); + await addOneTenant(delegator1); + await addOneTenant(delegator2); + await addOneTenant(delegator3); + await addOneTenant(delegator4); + await addOneTenant(delegator5); + await addOneTenant(delegator6); + await addOneAgreement(mockAgreement1); + await addOneAgreement(mockAgreement1bis); + await addOneAgreement(mockAgreement2); + await addOneAgreement(mockAgreement4); + await addOneAgreement(mockAgreement5); + await addOneAgreement(mockAgreement6); + }); + + it("should get delegators filtered by delegateId", async () => { + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId, + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [ + { + id: delegator1.id, + name: delegator1.name, + }, + { + id: delegator2.id, + name: delegator2.name, + }, + { + id: delegator5.id, + name: delegator5.name, + }, + ], + totalCount: 3, + }); + }); + it("should apply offset and limit", async () => { + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId, + offset: 1, + limit: 1, + }, + genericLogger + ) + ).toEqual({ + results: [ + { + id: delegator2.id, + name: delegator2.name, + }, + ], + totalCount: 3, + }); + }); + it("should filter delegators by the 'delegatorName' parameter", async () => { + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId, + offset: 0, + limit: 50, + delegatorName: "Comune", + }, + genericLogger + ) + ).toEqual({ + results: [ + { + id: delegator1.id, + name: delegator1.name, + }, + { + id: delegator2.id, + name: delegator2.name, + }, + ], + totalCount: 2, + }); + + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId, + offset: 0, + limit: 50, + delegatorName: "PagoPA", + }, + genericLogger + ) + ).toEqual({ + results: [ + { + id: delegator5.id, + name: delegator5.name, + }, + ], + totalCount: 1, + }); + }); + it("should return no results if no delegations match the criteria", async () => { + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId: generateId(), + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [], + totalCount: 0, + }); + + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId: delegator3.id, // No active delegation + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [], + totalCount: 0, + }); + + expect( + await delegationService.getConsumerDelegatorsWithAgreements( + { + requesterId: delegator4.id, // No active agreements + offset: 0, + limit: 50, + }, + genericLogger + ) + ).toEqual({ + results: [], + totalCount: 0, + }); + }); +}); From 70e4644aff9ce1533391c1e9c5ce383d3d8466be Mon Sep 17 00:00:00 2001 From: AsterITA Date: Tue, 14 Jan 2025 09:42:43 +0100 Subject: [PATCH 5/8] Pin 5816: purposes with consumer delegation (#1326) --- .../agreement-outbound-writer/package.json | 2 +- packages/api-clients/open-api/bffApi.yml | 3 + packages/api-clients/open-api/purposeApi.yml | 3 + .../src/services/purposeService.ts | 1 + packages/catalog-outbound-writer/package.json | 2 +- .../src/config/models/models.ts | 1 + .../delegation-outbound-writer/package.json | 2 +- .../models/proto/v2/purpose/purpose.proto | 2 + .../src/purpose/protobufConverterFromV2.ts | 5 +- packages/models/src/purpose/purpose.ts | 2 + packages/purpose-outbound-writer/package.json | 2 +- .../src/model/domain/apiConverter.ts | 1 + .../src/model/domain/errors.ts | 35 +- .../src/routers/PurposeRouter.ts | 129 ++--- .../src/services/purposeService.ts | 503 +++++++++--------- .../src/services/readModelService.ts | 52 +- .../src/services/validators.ts | 171 ++++-- .../src/utilities/errorMappers.ts | 40 +- .../test/activatePurposeVersion.test.ts | 485 ++++++++++------- .../test/archivePurposeVersion.test.ts | 115 ++-- .../test/createPurpose.test.ts | 107 ++-- .../test/createPurposeVersion.test.ts | 250 +++++---- .../test/createReversePurpose.test.ts | 109 ++-- .../test/deletePurpose.test.ts | 37 +- .../test/deletePurposeVersion.test.ts | 115 ++-- .../purpose-process/test/getPurposes.test.ts | 1 + .../test/getRiskAnalysisDocument.test.ts | 1 + .../test/rejectPurposeVersion.test.ts | 227 +++++--- .../test/suspendPurposeVersion.test.ts | 241 +++++---- .../test/updatePurpose.test.ts | 175 +++--- packages/tenant-outbound-writer/package.json | 2 +- pnpm-lock.yaml | 35 +- 32 files changed, 1674 insertions(+), 1182 deletions(-) diff --git a/packages/agreement-outbound-writer/package.json b/packages/agreement-outbound-writer/package.json index e2301b26bc..9f2abc9464 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.12-b", + "@pagopa/interop-outbound-models": "1.0.12-l", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index ff7b9d62ae..3f41635e02 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -15498,6 +15498,9 @@ components: description: "total daily calls available for this e-service." type: integer format: int32 + delegationId: + type: string + format: uuid required: - id - title diff --git a/packages/api-clients/open-api/purposeApi.yml b/packages/api-clients/open-api/purposeApi.yml index eef5a3b4e8..832b89e19b 100644 --- a/packages/api-clients/open-api/purposeApi.yml +++ b/packages/api-clients/open-api/purposeApi.yml @@ -911,6 +911,9 @@ components: type: boolean freeOfChargeReason: type: string + delegationId: + type: string + format: uuid required: - id - eserviceId diff --git a/packages/backend-for-frontend/src/services/purposeService.ts b/packages/backend-for-frontend/src/services/purposeService.ts index 9bc499771e..0f52fd28d1 100644 --- a/packages/backend-for-frontend/src/services/purposeService.ts +++ b/packages/backend-for-frontend/src/services/purposeService.ts @@ -175,6 +175,7 @@ export function purposeServiceBuilder( dailyCallsTotal: currentDescriptor.dailyCallsTotal, rejectedVersion: rejectedVersion && toBffApiPurposeVersion(rejectedVersion), + delegationId: purpose.delegationId, }; }; diff --git a/packages/catalog-outbound-writer/package.json b/packages/catalog-outbound-writer/package.json index 3c9d5e26e5..ed1f2e9196 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.12-h", + "@pagopa/interop-outbound-models": "1.0.12-l", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", diff --git a/packages/datalake-data-export/src/config/models/models.ts b/packages/datalake-data-export/src/config/models/models.ts index 081661b129..688423e4a2 100644 --- a/packages/datalake-data-export/src/config/models/models.ts +++ b/packages/datalake-data-export/src/config/models/models.ts @@ -98,6 +98,7 @@ export const ExportedPurpose = Purpose.pick({ id: true, eserviceId: true, consumerId: true, + delegationId: true, suspendedByConsumer: true, suspendedByProducer: true, title: true, diff --git a/packages/delegation-outbound-writer/package.json b/packages/delegation-outbound-writer/package.json index 0dba703a55..b8379ece99 100644 --- a/packages/delegation-outbound-writer/package.json +++ b/packages/delegation-outbound-writer/package.json @@ -27,7 +27,7 @@ "vitest": "1.6.0" }, "dependencies": { - "@pagopa/interop-outbound-models": "1.0.12-h", + "@pagopa/interop-outbound-models": "1.0.12-l", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", diff --git a/packages/models/proto/v2/purpose/purpose.proto b/packages/models/proto/v2/purpose/purpose.proto index eb8dc99afc..6933ee3210 100644 --- a/packages/models/proto/v2/purpose/purpose.proto +++ b/packages/models/proto/v2/purpose/purpose.proto @@ -5,6 +5,7 @@ package purpose.v2; import "v2/purpose/riskAnalysis.proto"; message PurposeV2 { + reserved 4; string id = 1; string eserviceId = 2; string consumerId = 3; @@ -18,6 +19,7 @@ message PurposeV2 { optional int64 updatedAt = 12; bool isFreeOfCharge = 13; optional string freeOfChargeReason = 14; + optional string delegationId = 15; } message PurposeVersionV2 { diff --git a/packages/models/src/purpose/protobufConverterFromV2.ts b/packages/models/src/purpose/protobufConverterFromV2.ts index 64ec302423..471875a254 100644 --- a/packages/models/src/purpose/protobufConverterFromV2.ts +++ b/packages/models/src/purpose/protobufConverterFromV2.ts @@ -1,4 +1,4 @@ -import { RiskAnalysisId, unsafeBrandId } from "../brandedIds.js"; +import { DelegationId, RiskAnalysisId, unsafeBrandId } from "../brandedIds.js"; import { PurposeStateV2, PurposeVersionDocumentV2, @@ -87,4 +87,7 @@ export const fromPurposeV2 = (input: PurposeV2): Purpose => ({ riskAnalysisForm: input.riskAnalysisForm ? fromPurposeRiskAnalysisFormV2(input.riskAnalysisForm) : undefined, + delegationId: input.delegationId + ? unsafeBrandId(input.delegationId) + : undefined, }); diff --git a/packages/models/src/purpose/purpose.ts b/packages/models/src/purpose/purpose.ts index 1a36620c92..952e6088e1 100644 --- a/packages/models/src/purpose/purpose.ts +++ b/packages/models/src/purpose/purpose.ts @@ -1,5 +1,6 @@ import { z } from "zod"; import { + DelegationId, EServiceId, PurposeId, PurposeVersionDocumentId, @@ -48,6 +49,7 @@ export const Purpose = z.object({ id: PurposeId, eserviceId: EServiceId, consumerId: TenantId, + delegationId: DelegationId.optional(), versions: z.array(PurposeVersion), suspendedByConsumer: z.boolean().optional(), suspendedByProducer: z.boolean().optional(), diff --git a/packages/purpose-outbound-writer/package.json b/packages/purpose-outbound-writer/package.json index 5f7f62b872..9406a34c35 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.12-b", + "@pagopa/interop-outbound-models": "1.0.12-l", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", diff --git a/packages/purpose-process/src/model/domain/apiConverter.ts b/packages/purpose-process/src/model/domain/apiConverter.ts index 5b58558474..f435a97bfa 100644 --- a/packages/purpose-process/src/model/domain/apiConverter.ts +++ b/packages/purpose-process/src/model/domain/apiConverter.ts @@ -117,6 +117,7 @@ export const purposeToApiPurpose = ( id: purpose.id, eserviceId: purpose.eserviceId, consumerId: purpose.consumerId, + delegationId: purpose.delegationId, versions: purpose.versions.map(purposeVersionToApiPurposeVersion), suspendedByConsumer: purpose.suspendedByConsumer, suspendedByProducer: purpose.suspendedByProducer, diff --git a/packages/purpose-process/src/model/domain/errors.ts b/packages/purpose-process/src/model/domain/errors.ts index fe5f43c493..84d1ac3da8 100644 --- a/packages/purpose-process/src/model/domain/errors.ts +++ b/packages/purpose-process/src/model/domain/errors.ts @@ -1,5 +1,6 @@ import { ApiError, + DelegationId, DescriptorId, EServiceId, EServiceMode, @@ -41,6 +42,8 @@ export const errorCodes = { missingRiskAnalysis: "0024", purposeVersionStateConflict: "0025", riskAnalysisConfigLatestVersionNotFound: "0026", + organizationIsNotTheDelegatedConsumer: "0027", + organizationIsNotTheDelegatedProducer: "0028", }; export type ErrorCodes = keyof typeof errorCodes; @@ -106,7 +109,7 @@ export function organizationNotAllowed( organizationId: TenantId ): ApiError { return new ApiError({ - detail: `Organization ${organizationId} is not allowed to perform the operation`, + detail: `Organization ${organizationId} is not allowed to perform the operation because is neither producer/consumer nor delegate`, code: "organizationNotAllowed", title: "Organization not allowed", }); @@ -116,12 +119,25 @@ export function organizationIsNotTheConsumer( organizationId: TenantId ): ApiError { return new ApiError({ - detail: `Organization ${organizationId} is not allowed to perform the operation`, + detail: `Organization ${organizationId} is not allowed to perform the operation because is not the consumer`, code: "organizationIsNotTheConsumer", title: "Organization not allowed", }); } +export function organizationIsNotTheDelegatedConsumer( + organizationId: TenantId, + delegationId: DelegationId | undefined +): ApiError { + return new ApiError({ + detail: `Organization ${organizationId} is not allowed to perform the operation because is not the delegated consumer${ + delegationId ? ` of delegation ${delegationId}` : "" + }`, + code: "organizationIsNotTheDelegatedConsumer", + title: "Organization not allowed", + }); +} + export function purposeVersionCannotBeDeleted( purposeId: PurposeId, versionId: PurposeVersionId @@ -137,12 +153,25 @@ export function organizationIsNotTheProducer( organizationId: TenantId ): ApiError { return new ApiError({ - detail: `Organization ${organizationId} is not allowed to perform the operation`, + detail: `Organization ${organizationId} is not allowed to perform the operation because is not the producer`, code: "organizationIsNotTheProducer", title: "Organization not allowed", }); } +export function organizationIsNotTheDelegatedProducer( + organizationId: TenantId, + delegationId: DelegationId | undefined +): ApiError { + return new ApiError({ + detail: `Organization ${organizationId} is not allowed to perform the operation because is not the delegate producer${ + delegationId ? ` of delegation ${delegationId}` : "" + }`, + code: "organizationIsNotTheDelegatedProducer", + title: "Organization not allowed", + }); +} + export function eServiceModeNotAllowed( eserviceId: EServiceId, mode: EServiceMode diff --git a/packages/purpose-process/src/routers/PurposeRouter.ts b/packages/purpose-process/src/routers/PurposeRouter.ts index 9caf3ab83d..236a4c3ae8 100644 --- a/packages/purpose-process/src/routers/PurposeRouter.ts +++ b/packages/purpose-process/src/routers/PurposeRouter.ts @@ -141,12 +141,7 @@ const purposeRouter = ( const ctx = fromAppContext(req.ctx); try { const { purpose, isRiskAnalysisValid } = - await purposeService.createPurpose( - req.body, - req.ctx.authData.organizationId, - req.ctx.correlationId, - ctx.logger - ); + await purposeService.createPurpose(req.body, ctx); return res .status(200) .send( @@ -172,12 +167,7 @@ const purposeRouter = ( const ctx = fromAppContext(req.ctx); try { const { purpose, isRiskAnalysisValid } = - await purposeService.createReversePurpose( - req.ctx.authData.organizationId, - req.body, - ctx.correlationId, - ctx.logger - ); + await purposeService.createReversePurpose(req.body, ctx); return res .status(200) .send( @@ -203,13 +193,11 @@ const purposeRouter = ( const ctx = fromAppContext(req.ctx); try { const { purpose, isRiskAnalysisValid } = - await purposeService.updateReversePurpose({ - purposeId: unsafeBrandId(req.params.id), - reversePurposeUpdateContent: req.body, - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + await purposeService.updateReversePurpose( + unsafeBrandId(req.params.id), + req.body, + ctx + ); return res .status(200) .send( @@ -271,13 +259,11 @@ const purposeRouter = ( const ctx = fromAppContext(req.ctx); try { const { purpose, isRiskAnalysisValid } = - await purposeService.updatePurpose({ - purposeId: unsafeBrandId(req.params.id), - purposeUpdateContent: req.body, - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + await purposeService.updatePurpose( + unsafeBrandId(req.params.id), + req.body, + ctx + ); return res .status(200) .send( @@ -302,12 +288,7 @@ const purposeRouter = ( async (req, res) => { const ctx = fromAppContext(req.ctx); try { - await purposeService.deletePurpose({ - purposeId: unsafeBrandId(req.params.id), - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + await purposeService.deletePurpose(unsafeBrandId(req.params.id), ctx); return res.status(204).send(); } catch (error) { const errorRes = makeApiProblem( @@ -326,13 +307,11 @@ const purposeRouter = ( async (req, res) => { const ctx = fromAppContext(req.ctx); try { - const purposeVersion = await purposeService.createPurposeVersion({ - purposeId: unsafeBrandId(req.params.purposeId), - seed: req.body, - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + const purposeVersion = await purposeService.createPurposeVersion( + unsafeBrandId(req.params.purposeId), + req.body, + ctx + ); return res .status(200) .send( @@ -357,13 +336,13 @@ const purposeRouter = ( async (req, res) => { const ctx = fromAppContext(req.ctx); try { - await purposeService.deletePurposeVersion({ - purposeId: unsafeBrandId(req.params.purposeId), - versionId: unsafeBrandId(req.params.versionId), - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + await purposeService.deletePurposeVersion( + { + purposeId: unsafeBrandId(req.params.purposeId), + versionId: unsafeBrandId(req.params.versionId), + }, + ctx + ); return res.status(204).send(); } catch (error) { const errorRes = makeApiProblem( @@ -413,14 +392,14 @@ const purposeRouter = ( async (req, res) => { const ctx = fromAppContext(req.ctx); try { - await purposeService.rejectPurposeVersion({ - purposeId: unsafeBrandId(req.params.purposeId), - versionId: unsafeBrandId(req.params.versionId), - organizationId: req.ctx.authData.organizationId, - rejectionReason: req.body.rejectionReason, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + await purposeService.rejectPurposeVersion( + { + purposeId: unsafeBrandId(req.params.purposeId), + versionId: unsafeBrandId(req.params.versionId), + rejectionReason: req.body.rejectionReason, + }, + ctx + ); return res.status(204).send(); } catch (error) { const errorRes = makeApiProblem( @@ -440,13 +419,13 @@ const purposeRouter = ( const ctx = fromAppContext(req.ctx); try { const { purposeId, versionId } = req.params; - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: unsafeBrandId(purposeId), - versionId: unsafeBrandId(versionId), - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: unsafeBrandId(purposeId), + versionId: unsafeBrandId(versionId), + }, + ctx + ); return res .status(200) .send( @@ -503,13 +482,13 @@ const purposeRouter = ( async (req, res) => { const ctx = fromAppContext(req.ctx); try { - const suspendedVersion = await purposeService.suspendPurposeVersion({ - purposeId: unsafeBrandId(req.params.purposeId), - versionId: unsafeBrandId(req.params.versionId), - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + const suspendedVersion = await purposeService.suspendPurposeVersion( + { + purposeId: unsafeBrandId(req.params.purposeId), + versionId: unsafeBrandId(req.params.versionId), + }, + ctx + ); return res .status(200) .send( @@ -534,13 +513,13 @@ const purposeRouter = ( async (req, res) => { const ctx = fromAppContext(req.ctx); try { - const archivedVersion = await purposeService.archivePurposeVersion({ - purposeId: unsafeBrandId(req.params.purposeId), - versionId: unsafeBrandId(req.params.versionId), - organizationId: req.ctx.authData.organizationId, - correlationId: req.ctx.correlationId, - logger: ctx.logger, - }); + const archivedVersion = await purposeService.archivePurposeVersion( + { + purposeId: unsafeBrandId(req.params.purposeId), + versionId: unsafeBrandId(req.params.versionId), + }, + ctx + ); return res .status(200) .send( diff --git a/packages/purpose-process/src/services/purposeService.ts b/packages/purpose-process/src/services/purposeService.ts index a1f65b3e81..80d153b6d2 100644 --- a/packages/purpose-process/src/services/purposeService.ts +++ b/packages/purpose-process/src/services/purposeService.ts @@ -1,11 +1,14 @@ /* eslint-disable sonarjs/no-identical-functions */ import { + AppContext, + AuthData, CreateEvent, DB, FileManager, Logger, PDFGenerator, RiskAnalysisFormRules, + WithLogger, eventRepository, formatDateddMMyyyyHHmmss, getFormRulesByVersion, @@ -41,9 +44,6 @@ import { RiskAnalysisId, RiskAnalysis, CorrelationId, - delegationKind, - Delegation, - DelegationKind, } from "pagopa-interop-models"; import { purposeApi } from "pagopa-interop-api-clients"; import { P, match } from "ts-pattern"; @@ -55,7 +55,6 @@ import { notValidVersionState, organizationIsNotTheConsumer, organizationIsNotTheProducer, - organizationNotAllowed, purposeCannotBeDeleted, purposeCannotBeCloned, purposeNotFound, @@ -68,6 +67,7 @@ import { riskAnalysisConfigLatestVersionNotFound, tenantKindNotFound, unchangedDailyCalls, + organizationNotAllowed, } from "../model/domain/errors.js"; import { toCreateEventDraftPurposeDeleted, @@ -92,7 +92,6 @@ import { import { config } from "../config/config.js"; import { GetPurposesFilters, ReadModelService } from "./readModelService.js"; import { - assertOrganizationIsAConsumer, assertEserviceMode, assertConsistentFreeOfCharge, isRiskAnalysisFormValid, @@ -108,8 +107,9 @@ import { validateRiskAnalysisOrThrow, assertPurposeTitleIsNotDuplicated, isOverQuota, + assertRequesterCanActAsConsumer, + assertRequesterCanActAsProducer, assertRequesterIsAllowedToRetrieveRiskAnalysisDocument, - assertRequesterIsProducer, } from "./validators.js"; import { riskAnalysisDocumentBuilder } from "./riskAnalysisDocumentBuilder.js"; @@ -220,14 +220,6 @@ async function retrieveTenantKind( return tenant.kind; } -async function retrieveActiveDelegation( - eserviceId: EServiceId, - delegationKind: DelegationKind, - readModelService: ReadModelService -): Promise { - return readModelService.getActiveDelegation(eserviceId, delegationKind); -} - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function purposeServiceBuilder( dbInstance: DB, @@ -251,19 +243,38 @@ export function purposeServiceBuilder( readModelService ); - const activeProducerDelegation = - await readModelService.getActiveDelegation( - purpose.data.eserviceId, - delegationKind.delegatedProducer + const tenantKind = await retrieveTenantKind( + organizationId, + readModelService + ); + + const isAllowedToRetrieveRiskAnalysis = + await assertRequesterIsAllowedToRetrieveRiskAnalysisDocument( + purpose.data, + eservice, + { organizationId }, + readModelService + ).then( + () => true, + () => false ); - return authorizeRiskAnalysisForm({ - purpose: purpose.data, - producerId: eservice.producerId, - organizationId, - tenantKind: await retrieveTenantKind(organizationId, readModelService), - activeProducerDelegation, - }); + if (!isAllowedToRetrieveRiskAnalysis) { + return { + purpose: { ...purpose.data, riskAnalysisForm: undefined }, + isRiskAnalysisValid: false, + }; + } + + const isRiskAnalysisValid = purposeIsDraft(purpose.data) + ? isRiskAnalysisFormValid( + purpose.data.riskAnalysisForm, + false, + tenantKind + ) + : true; + + return { purpose: purpose.data, isRiskAnalysisValid }; }, async getRiskAnalysisDocument({ purposeId, @@ -288,38 +299,38 @@ export function purposeServiceBuilder( readModelService ); - await assertRequesterIsAllowedToRetrieveRiskAnalysisDocument({ - eserviceId: eservice.id, - organizationId, - producerId: eservice.producerId, - consumerId: purpose.data.consumerId, - readModelService, - }); + await assertRequesterIsAllowedToRetrieveRiskAnalysisDocument( + purpose.data, + eservice, + { organizationId }, + readModelService + ); const version = retrievePurposeVersion(versionId, purpose); return retrievePurposeVersionDocument(purposeId, version, documentId); }, - async deletePurposeVersion({ - purposeId, - versionId, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - versionId: PurposeVersionId; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async deletePurposeVersion( + { + purposeId, + versionId, + }: { + purposeId: PurposeId; + versionId: PurposeVersionId; + }, + { correlationId, authData, logger }: WithLogger + ): Promise { logger.info(`Deleting Version ${versionId} in Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); - if (organizationId !== purpose.data.consumerId) { - throw organizationIsNotTheConsumer(organizationId); - } + assertRequesterCanActAsConsumer( + purpose.data, + authData, + await readModelService.getActiveConsumerDelegationByPurpose( + purpose.data + ) + ); const purposeVersion = retrievePurposeVersion(versionId, purpose); @@ -343,21 +354,18 @@ export function purposeServiceBuilder( }); await repository.createEvent(event); }, - async rejectPurposeVersion({ - purposeId, - versionId, - rejectionReason, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - versionId: PurposeVersionId; - rejectionReason: string; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async rejectPurposeVersion( + { + purposeId, + versionId, + rejectionReason, + }: { + purposeId: PurposeId; + versionId: PurposeVersionId; + rejectionReason: string; + }, + { correlationId, authData, logger }: WithLogger + ): Promise { logger.info(`Rejecting Version ${versionId} in Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); @@ -366,12 +374,13 @@ export function purposeServiceBuilder( readModelService ); - await assertRequesterIsProducer({ - eserviceId: eservice.id, - organizationId, - producerId: eservice.producerId, - readModelService, - }); + assertRequesterCanActAsProducer( + eservice, + authData, + await readModelService.getActiveProducerDelegationByEserviceId( + purpose.data.eserviceId + ) + ); const purposeVersion = retrievePurposeVersion(versionId, purpose); @@ -399,19 +408,11 @@ export function purposeServiceBuilder( }); await repository.createEvent(event); }, - async updatePurpose({ - purposeId, - purposeUpdateContent, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - purposeUpdateContent: purposeApi.PurposeUpdateContent; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> { + async updatePurpose( + purposeId: PurposeId, + purposeUpdateContent: purposeApi.PurposeUpdateContent, + { authData, correlationId, logger }: WithLogger + ): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> { logger.info(`Updating Purpose ${purposeId}`); return await performUpdatePurpose( purposeId, @@ -419,25 +420,17 @@ export function purposeServiceBuilder( updateContent: purposeUpdateContent, mode: eserviceMode.deliver, }, - organizationId, + authData, readModelService, correlationId, repository ); }, - async updateReversePurpose({ - purposeId, - reversePurposeUpdateContent, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - reversePurposeUpdateContent: purposeApi.ReversePurposeUpdateContent; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> { + async updateReversePurpose( + purposeId: PurposeId, + reversePurposeUpdateContent: purposeApi.ReversePurposeUpdateContent, + { authData, correlationId, logger }: WithLogger + ): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> { logger.info(`Updating Reverse Purpose ${purposeId}`); return await performUpdatePurpose( purposeId, @@ -445,33 +438,32 @@ export function purposeServiceBuilder( updateContent: reversePurposeUpdateContent, mode: eserviceMode.receive, }, - organizationId, + authData, readModelService, correlationId, repository ); }, - async deletePurpose({ - purposeId, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async deletePurpose( + purposeId: PurposeId, + { authData, correlationId, logger }: WithLogger + ): Promise { logger.info(`Deleting Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); - assertOrganizationIsAConsumer(organizationId, purpose.data.consumerId); - if (!isDeletable(purpose.data)) { throw purposeCannotBeDeleted(purpose.data.id); } + assertRequesterCanActAsConsumer( + purpose.data, + authData, + await readModelService.getActiveConsumerDelegationByPurpose( + purpose.data + ) + ); + const event = purposeIsDraft(purpose.data) ? toCreateEventDraftPurposeDeleted({ purpose: purpose.data, @@ -486,24 +478,28 @@ export function purposeServiceBuilder( await repository.createEvent(event); }, - async archivePurposeVersion({ - purposeId, - versionId, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - versionId: PurposeVersionId; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async archivePurposeVersion( + { + purposeId, + versionId, + }: { + purposeId: PurposeId; + versionId: PurposeVersionId; + }, + { authData, correlationId, logger }: WithLogger + ): Promise { logger.info(`Archiving Version ${versionId} in Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); - assertOrganizationIsAConsumer(organizationId, purpose.data.consumerId); + assertRequesterCanActAsConsumer( + purpose.data, + authData, + await readModelService.getActiveConsumerDelegationByPurpose( + purpose.data + ) + ); + const purposeVersion = retrievePurposeVersion(versionId, purpose); if (!isArchivable(purposeVersion)) { @@ -536,19 +532,16 @@ export function purposeServiceBuilder( await repository.createEvent(event); return archivedVersion; }, - async suspendPurposeVersion({ - purposeId, - versionId, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - versionId: PurposeVersionId; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async suspendPurposeVersion( + { + purposeId, + versionId, + }: { + purposeId: PurposeId; + versionId: PurposeVersionId; + }, + { authData, correlationId, logger }: WithLogger + ): Promise { logger.info(`Suspending Version ${versionId} in Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); @@ -564,10 +557,10 @@ export function purposeServiceBuilder( ); const suspender = await getOrganizationRole({ - eserviceId: eservice.id, - organizationId, + eservice, producerId: eservice.producerId, consumerId: purpose.data.consumerId, + authData, readModelService, }); @@ -629,38 +622,29 @@ export function purposeServiceBuilder( purpose.eserviceId, readModelService ); - if (eservice === undefined) { - throw eserviceNotFound(purpose.eserviceId); - } - const activeProducerDelegation = await retrieveActiveDelegation( - eservice.id, - delegationKind.delegatedProducer, - readModelService - ); + const isAllowedToRetrieveRiskAnalysis = + await assertRequesterIsAllowedToRetrieveRiskAnalysisDocument( + purpose, + eservice, + { organizationId }, + readModelService + ).then( + () => true, + () => false + ); - return { - purpose, - eservice, - activeProducerDelegation, - }; + return { purpose, isAllowedToRetrieveRiskAnalysis }; }) ); const purposesToReturn = mappingPurposeEservice.map( - ({ purpose, eservice, activeProducerDelegation }) => { - const isProducerOrConsumer = - organizationId === purpose.consumerId || - organizationId === eservice.producerId || - organizationId === activeProducerDelegation?.delegateId; - - return { - ...purpose, - riskAnalysisForm: isProducerOrConsumer - ? purpose.riskAnalysisForm - : undefined, - }; - } + ({ purpose, isAllowedToRetrieveRiskAnalysis }) => ({ + ...purpose, + riskAnalysisForm: isAllowedToRetrieveRiskAnalysis + ? purpose.riskAnalysisForm + : undefined, + }) ); return { @@ -668,24 +652,22 @@ export function purposeServiceBuilder( totalCount: purposesList.totalCount, }; }, - async createPurposeVersion({ - purposeId, - seed, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - seed: purposeApi.PurposeVersionSeed; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async createPurposeVersion( + purposeId: PurposeId, + seed: purposeApi.PurposeVersionSeed, + { authData, correlationId, logger }: WithLogger + ): Promise { logger.info(`Creating Version for Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); - assertOrganizationIsAConsumer(organizationId, purpose.data.consumerId); + assertRequesterCanActAsConsumer( + purpose.data, + authData, + await readModelService.getActiveConsumerDelegationByPurpose( + purpose.data + ) + ); const previousVersion = [ ...purpose.data.versions.filter( @@ -806,19 +788,16 @@ export function purposeServiceBuilder( return newPurposeVersion; }, - async activatePurposeVersion({ - purposeId, - versionId, - organizationId, - correlationId, - logger, - }: { - purposeId: PurposeId; - versionId: PurposeVersionId; - organizationId: TenantId; - correlationId: CorrelationId; - logger: Logger; - }): Promise { + async activatePurposeVersion( + { + purposeId, + versionId, + }: { + purposeId: PurposeId; + versionId: PurposeVersionId; + }, + { authData, correlationId, logger }: WithLogger + ): Promise { logger.info(`Activating Version ${versionId} in Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); @@ -851,10 +830,10 @@ export function purposeServiceBuilder( } const purposeOwnership = await getOrganizationRole({ - eserviceId: eservice.id, - organizationId, + eservice, producerId: eservice.producerId, consumerId: purpose.data.consumerId, + authData, readModelService, }); @@ -904,7 +883,7 @@ export function purposeServiceBuilder( purposeOwnership: ownership.PRODUCER, }, () => { - throw organizationIsNotTheConsumer(organizationId); + throw organizationIsNotTheConsumer(authData.organizationId); } ) .with( @@ -913,7 +892,7 @@ export function purposeServiceBuilder( purposeOwnership: ownership.CONSUMER, }, () => { - throw organizationIsNotTheProducer(organizationId); + throw organizationIsNotTheProducer(authData.organizationId); } ) .with( @@ -1024,7 +1003,7 @@ export function purposeServiceBuilder( ) ) .otherwise(() => { - throw organizationNotAllowed(organizationId); + throw organizationNotAllowed(authData.organizationId); }); await repository.createEvent(event); @@ -1032,26 +1011,35 @@ export function purposeServiceBuilder( }, async createPurpose( purposeSeed: purposeApi.PurposeSeed, - organizationId: TenantId, - correlationId: CorrelationId, - logger: Logger + { authData, correlationId, logger }: WithLogger ): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> { logger.info( `Creating Purpose for EService ${purposeSeed.eserviceId} and Consumer ${purposeSeed.consumerId}` ); const eserviceId = unsafeBrandId(purposeSeed.eserviceId); const consumerId = unsafeBrandId(purposeSeed.consumerId); - assertOrganizationIsAConsumer(organizationId, consumerId); assertConsistentFreeOfCharge( purposeSeed.isFreeOfCharge, purposeSeed.freeOfChargeReason ); + const consumerDelegation = + await readModelService.getActiveConsumerDelegationByPurpose({ + eserviceId, + consumerId, + }); + + assertRequesterCanActAsConsumer( + { eserviceId, consumerId }, + authData, + consumerDelegation + ); + const validatedFormSeed = validateAndTransformRiskAnalysis( purposeSeed.riskAnalysisForm, false, - await retrieveTenantKind(organizationId, readModelService) + await retrieveTenantKind(authData.organizationId, readModelService) ); await retrieveActiveAgreement(eserviceId, consumerId, readModelService); @@ -1070,6 +1058,7 @@ export function purposeServiceBuilder( createdAt: new Date(), eserviceId, consumerId, + delegationId: consumerDelegation?.id, versions: [ { id: generateId(), @@ -1089,10 +1078,8 @@ export function purposeServiceBuilder( return { purpose, isRiskAnalysisValid: validatedFormSeed !== undefined }; }, async createReversePurpose( - organizationId: TenantId, seed: purposeApi.EServicePurposeSeed, - correlationId: CorrelationId, - logger: Logger + { authData, correlationId, logger }: WithLogger ): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> { logger.info( `Creating Purpose for EService ${seed.eServiceId}, Consumer ${seed.consumerId}` @@ -1101,7 +1088,18 @@ export function purposeServiceBuilder( const eserviceId: EServiceId = unsafeBrandId(seed.eServiceId); const consumerId: TenantId = unsafeBrandId(seed.consumerId); - assertOrganizationIsAConsumer(organizationId, consumerId); + const consumerDelegation = + await readModelService.getActiveConsumerDelegationByPurpose({ + eserviceId, + consumerId, + }); + + assertRequesterCanActAsConsumer( + { eserviceId, consumerId }, + authData, + consumerDelegation + ); + const eservice = await retrieveEService(eserviceId, readModelService); assertEserviceMode(eservice, eserviceMode.receive); @@ -1147,6 +1145,7 @@ export function purposeServiceBuilder( createdAt: new Date(), eserviceId, consumerId, + delegationId: consumerDelegation?.id, description: seed.description, versions: [newVersion], isFreeOfCharge: seed.isFreeOfCharge, @@ -1340,75 +1339,47 @@ export function purposeServiceBuilder( export type PurposeService = ReturnType; -const authorizeRiskAnalysisForm = ({ - purpose, - producerId, - organizationId, - tenantKind, - activeProducerDelegation, -}: { - purpose: Purpose; - producerId: TenantId; - organizationId: TenantId; - tenantKind: TenantKind; - activeProducerDelegation: Delegation | undefined; -}): { purpose: Purpose; isRiskAnalysisValid: boolean } => { - if ( - organizationId === purpose.consumerId || - organizationId === producerId || - organizationId === activeProducerDelegation?.delegateId - ) { - if (purposeIsDraft(purpose)) { - const isRiskAnalysisValid = isRiskAnalysisFormValid( - purpose.riskAnalysisForm, - false, - tenantKind - ); - return { purpose, isRiskAnalysisValid }; - } else { - return { purpose, isRiskAnalysisValid: true }; - } - } else { - return { - purpose: { ...purpose, riskAnalysisForm: undefined }, - isRiskAnalysisValid: false, - }; - } -}; - const getOrganizationRole = async ({ - eserviceId, - organizationId, + eservice, producerId, consumerId, + authData, readModelService, }: { - eserviceId: EServiceId; - organizationId: TenantId; + eservice: EService; producerId: TenantId; consumerId: TenantId; + authData: AuthData; readModelService: ReadModelService; }): Promise => { - if (producerId === consumerId && organizationId === producerId) { + if (producerId === consumerId && authData.organizationId === producerId) { return ownership.SELF_CONSUMER; - } else if (producerId !== consumerId && organizationId === consumerId) { - return ownership.CONSUMER; } - const activeProducerDelegation = await readModelService.getActiveDelegation( - eserviceId, - delegationKind.delegatedProducer - ); - - if ( - (activeProducerDelegation && - organizationId === activeProducerDelegation.delegateId) || - (!activeProducerDelegation && organizationId === producerId) - ) { + try { + assertRequesterCanActAsProducer( + eservice, + authData, + await readModelService.getActiveProducerDelegationByEserviceId( + eservice.id + ) + ); return ownership.PRODUCER; + } catch { + try { + assertRequesterCanActAsConsumer( + { eserviceId: eservice.id, consumerId }, + authData, + await readModelService.getActiveConsumerDelegationByPurpose({ + eserviceId: eservice.id, + consumerId, + }) + ); + return ownership.CONSUMER; + } catch { + throw organizationNotAllowed(authData.organizationId); + } } - - throw organizationNotAllowed(organizationId); }; const replacePurposeVersion = ( @@ -1462,7 +1433,7 @@ const performUpdatePurpose = async ( mode: "Receive"; updateContent: purposeApi.ReversePurposeUpdateContent; }, - organizationId: TenantId, + authData: AuthData, readModelService: ReadModelService, correlationId: CorrelationId, repository: { @@ -1471,7 +1442,6 @@ const performUpdatePurpose = async ( // eslint-disable-next-line max-params ): Promise<{ purpose: Purpose; isRiskAnalysisValid: boolean }> => { const purpose = await retrievePurpose(purposeId, readModelService); - assertOrganizationIsAConsumer(organizationId, purpose.data.consumerId); assertPurposeIsDraft(purpose.data); if (updateContent.title !== purpose.data.title) { @@ -1482,6 +1452,13 @@ const performUpdatePurpose = async ( title: updateContent.title, }); } + + assertRequesterCanActAsConsumer( + purpose.data, + authData, + await readModelService.getActiveConsumerDelegationByPurpose(purpose.data) + ); + const eservice = await retrieveEService( purpose.data.eserviceId, readModelService diff --git a/packages/purpose-process/src/services/readModelService.ts b/packages/purpose-process/src/services/readModelService.ts index 3651e52a83..c41e99672b 100644 --- a/packages/purpose-process/src/services/readModelService.ts +++ b/packages/purpose-process/src/services/readModelService.ts @@ -4,6 +4,7 @@ import { TenantCollection, PurposeCollection, ReadModelFilter, + DelegationCollection, } from "pagopa-interop-commons"; import { EService, @@ -25,8 +26,8 @@ import { TenantReadModel, delegationState, Delegation, - DelegationKind, delegationKind, + DelegationReadModel, } from "pagopa-interop-models"; import { Document, Filter, WithId } from "mongodb"; import { z } from "zod"; @@ -112,6 +113,27 @@ async function getTenant( } } +async function getDelegation( + delegations: DelegationCollection, + filter: Filter<{ data: DelegationReadModel }> +): Promise { + const data = await delegations.findOne(filter, { + projection: { data: true }, + }); + if (data) { + 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; + } + return undefined; +} + async function buildGetPurposesAggregation( filters: GetPurposesFilters, eservices: EServiceCollection @@ -375,24 +397,24 @@ export function readModelServiceBuilder( return result.data; }, - async getActiveDelegation( - eserviceId: EServiceId, - kind: DelegationKind + async getActiveProducerDelegationByEserviceId( + eserviceId: EServiceId ): Promise { - const data = await delegations.findOne({ + return getDelegation(delegations, { "data.eserviceId": eserviceId, - "data.kind": kind, "data.state": delegationState.active, + "data.kind": delegationKind.delegatedProducer, + }); + }, + async getActiveConsumerDelegationByPurpose( + purpose: Pick + ): Promise { + return getDelegation(delegations, { + "data.eserviceId": purpose.eserviceId, + "data.delegatorId": purpose.consumerId, + "data.state": delegationState.active, + "data.kind": delegationKind.delegatedConsumer, }); - 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 22a682c858..50e3b5a3d0 100644 --- a/packages/purpose-process/src/services/validators.ts +++ b/packages/purpose-process/src/services/validators.ts @@ -10,12 +10,15 @@ import { purposeVersionState, EServiceId, delegationKind, + Delegation, + delegationState, } from "pagopa-interop-models"; import { validateRiskAnalysis, riskAnalysisFormToRiskAnalysisFormToValidate, RiskAnalysisValidatedForm, riskAnalysisValidatedFormToNewRiskAnalysisForm, + AuthData, } from "pagopa-interop-commons"; import { purposeApi } from "pagopa-interop-api-clients"; import { @@ -24,6 +27,8 @@ import { eServiceModeNotAllowed, missingFreeOfChargeReason, organizationIsNotTheConsumer, + organizationIsNotTheDelegatedConsumer, + organizationIsNotTheDelegatedProducer, organizationIsNotTheProducer, organizationNotAllowed, purposeNotInDraftState, @@ -81,12 +86,12 @@ export const assertConsistentFreeOfCharge = ( } }; -export const assertOrganizationIsAConsumer = ( - organizationId: TenantId, - consumerId: TenantId +const assertRequesterIsConsumer = ( + purpose: Pick, + authData: Pick ): void => { - if (organizationId !== consumerId) { - throw organizationIsNotTheConsumer(organizationId); + if (authData.organizationId !== purpose.consumerId) { + throw organizationIsNotTheConsumer(authData.organizationId); } }; @@ -252,61 +257,129 @@ export async function isOverQuota( ); } -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; +export const assertRequesterIsAllowedToRetrieveRiskAnalysisDocument = async ( + purpose: Purpose, + eservice: EService, + authData: Pick, + readModelService: ReadModelService +): Promise => { + // This operation has a dedicated assertion because it's the only operation that + // can be performed also by the producer/consumer even when active producer/consumer delegations exist + try { + assertRequesterIsConsumer(purpose, authData); + } catch (error) { + try { + assertRequesterIsProducer(eservice, authData); + } catch (error) { + try { + const activeProducerDelegation = + await readModelService.getActiveProducerDelegationByEserviceId( + purpose.eserviceId + ); + assertRequesterIsDelegateProducer( + eservice, + authData, + activeProducerDelegation + ); + } catch (error) { + try { + const activeConsumerDelegation = + await readModelService.getActiveConsumerDelegationByPurpose( + purpose + ); + + assertRequesterIsDelegateConsumer( + purpose, + authData, + activeConsumerDelegation + ); + } catch { + throw organizationNotAllowed(authData.organizationId); + } + } + } } +}; - const activeProducerDelegation = await readModelService.getActiveDelegation( - eserviceId, - delegationKind.delegatedProducer - ); +const assertRequesterIsProducer = ( + eservice: Pick, + authData: Pick +): void => { + if (authData.organizationId !== eservice.producerId) { + throw organizationIsNotTheProducer(authData.organizationId); + } +}; +const assertRequesterIsDelegateProducer = ( + eservice: EService, + authData: Pick, + activeProducerDelegation: Delegation | undefined +): void => { if ( - activeProducerDelegation && - organizationId === activeProducerDelegation.delegateId + activeProducerDelegation?.delegateId !== authData.organizationId || + activeProducerDelegation?.delegatorId !== eservice.producerId || + activeProducerDelegation?.kind !== delegationKind.delegatedProducer || + activeProducerDelegation?.state !== delegationState.active || + activeProducerDelegation?.eserviceId !== eservice.id ) { - return; + throw organizationIsNotTheDelegatedProducer( + authData.organizationId, + activeProducerDelegation?.id + ); } +}; - throw organizationNotAllowed(organizationId); +export const assertRequesterCanActAsProducer = ( + eservice: EService, + authData: AuthData, + activeProducerDelegation: Delegation | undefined +): void => { + if (!activeProducerDelegation) { + // No active producer delegation, the requester is authorized only if they are the producer + assertRequesterIsProducer(eservice, authData); + } else { + // Active producer delegation, the requester is authorized only if they are the delegate + assertRequesterIsDelegateProducer( + eservice, + authData, + activeProducerDelegation + ); + } }; -export const assertRequesterIsProducer = async ({ - eserviceId, - organizationId, - producerId, - readModelService, -}: { - eserviceId: EServiceId; - organizationId: TenantId; - producerId: TenantId; - readModelService: ReadModelService; -}): Promise => { - const activeProducerDelegation = await readModelService.getActiveDelegation( - eserviceId, - delegationKind.delegatedProducer - ); +export const assertRequesterCanActAsConsumer = ( + purpose: Pick, + authData: AuthData, + activeConsumerDelegation: Delegation | undefined +): void => { + if (!activeConsumerDelegation) { + // No active consumer delegation, the requester is authorized only if they are the consumer + assertRequesterIsConsumer(purpose, authData); + } else { + // Active consumer delegation, the requester is authorized only if they are the delegate + assertRequesterIsDelegateConsumer( + purpose, + authData, + activeConsumerDelegation + ); + } +}; +const assertRequesterIsDelegateConsumer = ( + purpose: Pick, + authData: Pick, + activeConsumerDelegation: Delegation | undefined +): void => { if ( - (activeProducerDelegation && - organizationId === activeProducerDelegation.delegateId) || - (!activeProducerDelegation && organizationId === producerId) + activeConsumerDelegation?.delegateId !== authData.organizationId || + activeConsumerDelegation?.delegatorId !== purpose.consumerId || + activeConsumerDelegation?.eserviceId !== purpose.eserviceId || + activeConsumerDelegation?.kind !== delegationKind.delegatedConsumer || + activeConsumerDelegation?.state !== delegationState.active ) { - return; + throw organizationIsNotTheDelegatedConsumer( + authData.organizationId, + activeConsumerDelegation?.id + ); } - - throw organizationIsNotTheProducer(organizationId); }; diff --git a/packages/purpose-process/src/utilities/errorMappers.ts b/packages/purpose-process/src/utilities/errorMappers.ts index fb3dafb012..a3c1b6a831 100644 --- a/packages/purpose-process/src/utilities/errorMappers.ts +++ b/packages/purpose-process/src/utilities/errorMappers.ts @@ -40,7 +40,11 @@ export const deletePurposeVersionErrorMapper = ( "purposeVersionNotFound", () => HTTP_STATUS_NOT_FOUND ) - .with("organizationIsNotTheConsumer", () => HTTP_STATUS_FORBIDDEN) + .with( + "organizationIsNotTheConsumer", + "organizationNotAllowed", + () => HTTP_STATUS_FORBIDDEN + ) .with("purposeVersionCannotBeDeleted", () => HTTP_STATUS_CONFLICT) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); @@ -68,6 +72,7 @@ export const updatePurposeErrorMapper = (error: ApiError): number => .with( "organizationIsNotTheConsumer", "purposeNotInDraftState", + "organizationNotAllowed", () => HTTP_STATUS_FORBIDDEN ) .with("purposeNotFound", () => HTTP_STATUS_NOT_FOUND) @@ -79,7 +84,11 @@ export const updateReversePurposeErrorMapper = updatePurposeErrorMapper; export const deletePurposeErrorMapper = (error: ApiError): number => match(error.code) .with("purposeNotFound", () => HTTP_STATUS_NOT_FOUND) - .with("organizationIsNotTheConsumer", () => HTTP_STATUS_FORBIDDEN) + .with( + "organizationIsNotTheConsumer", + "organizationNotAllowed", + () => HTTP_STATUS_FORBIDDEN + ) .with("purposeCannotBeDeleted", () => HTTP_STATUS_CONFLICT) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); @@ -92,7 +101,11 @@ export const archivePurposeVersionErrorMapper = ( "purposeVersionNotFound", () => HTTP_STATUS_NOT_FOUND ) - .with("organizationIsNotTheConsumer", () => HTTP_STATUS_FORBIDDEN) + .with( + "organizationIsNotTheConsumer", + "organizationNotAllowed", + () => HTTP_STATUS_FORBIDDEN + ) .with("notValidVersionState", () => HTTP_STATUS_BAD_REQUEST) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); @@ -125,10 +138,17 @@ export const createPurposeVersionErrorMapper = ( export const createPurposeErrorMapper = (error: ApiError): number => match(error.code) - .with("organizationIsNotTheConsumer", () => HTTP_STATUS_FORBIDDEN) - .with("missingFreeOfChargeReason", () => HTTP_STATUS_BAD_REQUEST) - .with("agreementNotFound", () => HTTP_STATUS_BAD_REQUEST) - .with("riskAnalysisValidationFailed", () => HTTP_STATUS_BAD_REQUEST) + .with( + "organizationIsNotTheConsumer", + "organizationNotAllowed", + () => HTTP_STATUS_FORBIDDEN + ) + .with( + "missingFreeOfChargeReason", + "agreementNotFound", + "riskAnalysisValidationFailed", + () => HTTP_STATUS_BAD_REQUEST + ) .with("duplicatedPurposeTitle", () => HTTP_STATUS_CONFLICT) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); @@ -136,7 +156,11 @@ export const createReversePurposeErrorMapper = ( error: ApiError ): number => match(error.code) - .with("organizationIsNotTheConsumer", () => HTTP_STATUS_FORBIDDEN) + .with( + "organizationIsNotTheConsumer", + "organizationNotAllowed", + () => HTTP_STATUS_FORBIDDEN + ) .with( "eserviceNotFound", "eServiceModeNotAllowed", diff --git a/packages/purpose-process/test/activatePurposeVersion.test.ts b/packages/purpose-process/test/activatePurposeVersion.test.ts index c24c7cd809..3cfae4c8b4 100644 --- a/packages/purpose-process/test/activatePurposeVersion.test.ts +++ b/packages/purpose-process/test/activatePurposeVersion.test.ts @@ -13,7 +13,7 @@ import { readLastEventByStreamId, decodeProtobufPayload, getMockDelegation, - getMockAuthData, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { PurposeVersion, @@ -131,13 +131,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -187,13 +192,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -243,13 +253,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -300,13 +315,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -366,13 +386,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -421,13 +446,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -472,13 +502,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const purposeVersion = await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + const purposeVersion = await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -521,13 +556,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: purpose.id, - versionId: purposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: purpose.id, + versionId: purposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(organizationIsNotTheProducer(mockConsumer.id)); }); @@ -545,13 +585,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: purpose.id, - versionId: purposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: purpose.id, + versionId: purposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(organizationIsNotTheConsumer(mockProducer.id)); }); @@ -579,13 +624,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: consumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantKindNotFound(consumer.id)); }); @@ -607,13 +657,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(missingRiskAnalysis(mockPurpose.id)); }); @@ -624,13 +679,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(eserviceNotFound(mockEService.id)); }); @@ -647,13 +707,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError( agreementNotFound(mockEService.id, mockConsumer.id) ); @@ -681,13 +746,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError( agreementNotFound(mockEService.id, mockConsumer.id) ); @@ -705,13 +775,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(anotherTenant); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: anotherTenant.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(anotherTenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(organizationNotAllowed(anotherTenant.id)); }); @@ -722,24 +797,28 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const delegate = getMockAuthData(); const delegation = getMockDelegation({ + delegatorId: mockProducer.id, 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, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(organizationNotAllowed(mockProducer.id)); }); @@ -754,25 +833,29 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); await addOneTenant(mockProducer); - const delegate = getMockAuthData(); const delegation = getMockDelegation({ + delegatorId: mockEService.producerId, 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)); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(delegation.delegateId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); + }).rejects.toThrowError(organizationNotAllowed(delegation.delegateId)); } ); @@ -794,13 +877,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: purpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: purpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(missingRiskAnalysis(purpose.id)); }); @@ -830,13 +918,18 @@ describe("activatePurposeVersion", () => { ); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: purpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: purpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError( riskAnalysisValidationFailed( result.type === "invalid" ? result.issues : [] @@ -860,13 +953,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantNotFound(mockConsumer.id)); }); @@ -877,13 +975,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockConsumer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantNotFound(mockProducer.id)); }); @@ -901,13 +1004,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantKindNotFound(consumer.id)); }); @@ -925,13 +1033,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(producer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantKindNotFound(producer.id)); }); @@ -955,13 +1068,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: purpose.id, - versionId: purposeVersion.id, - organizationId: mockProducer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: purpose.id, + versionId: purposeVersion.id, + }, + { + authData: getRandomAuthData(mockProducer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(organizationNotAllowed(mockProducer.id)); } ); @@ -986,13 +1104,18 @@ describe("activatePurposeVersion", () => { await addOneTenant(mockProducer); expect(async () => { - await purposeService.activatePurposeVersion({ - purposeId: purpose.id, - versionId: purposeVersion.id, - organizationId: mockConsumer.id, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.activatePurposeVersion( + { + purposeId: purpose.id, + versionId: purposeVersion.id, + }, + { + authData: getRandomAuthData(mockConsumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(organizationNotAllowed(mockConsumer.id)); } ); diff --git a/packages/purpose-process/test/archivePurposeVersion.test.ts b/packages/purpose-process/test/archivePurposeVersion.test.ts index c34386fb83..61aa92644e 100644 --- a/packages/purpose-process/test/archivePurposeVersion.test.ts +++ b/packages/purpose-process/test/archivePurposeVersion.test.ts @@ -3,6 +3,7 @@ import { getMockPurposeVersion, getMockPurpose, decodeProtobufPayload, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { PurposeVersion, @@ -45,13 +46,18 @@ describe("archivePurposeVersion", () => { }; await addOnePurpose(mockPurpose); - const returnedPurposeVersion = await purposeService.archivePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + const returnedPurposeVersion = await purposeService.archivePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -106,13 +112,18 @@ describe("archivePurposeVersion", () => { }; await addOnePurpose(mockPurpose); - const returnedPurposeVersion = await purposeService.archivePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion1.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + const returnedPurposeVersion = await purposeService.archivePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -156,13 +167,18 @@ describe("archivePurposeVersion", () => { await addOnePurpose(mockPurpose); expect( - purposeService.archivePurposeVersion({ - purposeId: randomPurposeId, - versionId: randomVersionId, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.archivePurposeVersion( + { + purposeId: randomPurposeId, + versionId: randomVersionId, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(purposeNotFound(randomPurposeId)); }); it("should throw organizationIsNotTheConsumer if the requester is not the consumer", async () => { @@ -180,13 +196,18 @@ describe("archivePurposeVersion", () => { await addOnePurpose(mockPurpose); expect( - purposeService.archivePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: randomOrganizationId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.archivePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(randomOrganizationId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(organizationIsNotTheConsumer(randomOrganizationId)); }); it("should throw purposeVersionNotFound if the purpose version doesn't exist", async () => { @@ -199,13 +220,18 @@ describe("archivePurposeVersion", () => { await addOnePurpose(mockPurpose); expect( - purposeService.archivePurposeVersion({ - purposeId: mockPurpose.id, - versionId: randomVersionId, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.archivePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: randomVersionId, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( purposeVersionNotFound(mockPurpose.id, randomVersionId) ); @@ -229,13 +255,18 @@ describe("archivePurposeVersion", () => { await addOnePurpose(mockPurpose); expect( - purposeService.archivePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.archivePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( notValidVersionState(mockPurposeVersion.id, mockPurposeVersion.state) ); diff --git a/packages/purpose-process/test/createPurpose.test.ts b/packages/purpose-process/test/createPurpose.test.ts index a367beae58..99783089be 100644 --- a/packages/purpose-process/test/createPurpose.test.ts +++ b/packages/purpose-process/test/createPurpose.test.ts @@ -19,6 +19,7 @@ import { toReadModelAgreement, unsafeBrandId, toReadModelTenant, + TenantId, } from "pagopa-interop-models"; import { purposeApi } from "pagopa-interop-api-clients"; import { describe, expect, it, vi } from "vitest"; @@ -30,6 +31,7 @@ import { getMockTenant, getMockPurpose, getMockDescriptor, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { genericLogger, @@ -103,9 +105,14 @@ describe("createPurpose", () => { const { purpose, isRiskAnalysisValid } = await purposeService.createPurpose( purposeSeed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger + { + authData: getRandomAuthData( + unsafeBrandId(purposeSeed.consumerId) + ), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } ); const writtenEvent = await readLastPurposeEvent(purpose.id); @@ -174,12 +181,14 @@ describe("createPurpose", () => { }; expect( - purposeService.createPurpose( - seed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(seed, { + authData: getRandomAuthData( + unsafeBrandId(purposeSeed.consumerId) + ), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(missingFreeOfChargeReason()); }); it("should throw tenantKindNotFound if the kind doesn't exists", async () => { @@ -209,22 +218,26 @@ describe("createPurpose", () => { await writeInReadmodel(toReadModelEService(eService), eservices); expect( - purposeService.createPurpose( - seed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(seed, { + authData: getRandomAuthData( + unsafeBrandId(purposeSeed.consumerId) + ), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(tenantKindNotFound(tenantWithoutKind.id)); }); it("should throw tenantNotFound if the tenant doesn't exists", async () => { expect( - purposeService.createPurpose( - purposeSeed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(purposeSeed, { + authData: getRandomAuthData( + unsafeBrandId(purposeSeed.consumerId) + ), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(tenantNotFound(tenant.id)); }); it("should throw agreementNotFound if the agreement doesn't exists ", async () => { @@ -261,12 +274,12 @@ describe("createPurpose", () => { await writeInReadmodel(toReadModelEService(eService), eservices); expect( - purposeService.createPurpose( - seed, - unsafeBrandId(seed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(seed, { + authData: getRandomAuthData(unsafeBrandId(seed.consumerId)), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(agreementNotFound(eService.id, tenant.id)); }); it("should throw organizationIsNotTheConsumer if the requester is not the consumer", async () => { @@ -283,12 +296,14 @@ describe("createPurpose", () => { }; expect( - purposeService.createPurpose( - seed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(seed, { + authData: getRandomAuthData( + unsafeBrandId(purposeSeed.consumerId) + ), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError( organizationIsNotTheConsumer(unsafeBrandId(purposeSeed.consumerId)) ); @@ -312,12 +327,12 @@ describe("createPurpose", () => { }; expect( - purposeService.createPurpose( - seed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(seed, { + authData: getRandomAuthData(unsafeBrandId(seed.consumerId)), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError( riskAnalysisValidationFailed([ unexpectedRulesVersionError(mockInvalidRiskAnalysisForm.version), @@ -341,12 +356,14 @@ describe("createPurpose", () => { await writeInReadmodel(toReadModelEService(eService1), eservices); expect( - purposeService.createPurpose( - purposeSeed, - unsafeBrandId(purposeSeed.consumerId), - generateId(), - genericLogger - ) + purposeService.createPurpose(purposeSeed, { + authData: getRandomAuthData( + unsafeBrandId(purposeSeed.consumerId) + ), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(duplicatedPurposeTitle(purposeSeed.title)); }); }); diff --git a/packages/purpose-process/test/createPurposeVersion.test.ts b/packages/purpose-process/test/createPurposeVersion.test.ts index 4594aabc46..5e017b4377 100644 --- a/packages/purpose-process/test/createPurposeVersion.test.ts +++ b/packages/purpose-process/test/createPurposeVersion.test.ts @@ -13,6 +13,7 @@ import { getMockPurpose, getMockValidRiskAnalysisForm, writeInReadmodel, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { purposeVersionState, @@ -38,7 +39,6 @@ import { eserviceNotFound, missingRiskAnalysis, organizationIsNotTheConsumer, - organizationNotAllowed, tenantKindNotFound, tenantNotFound, unchangedDailyCalls, @@ -121,15 +121,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); await writeInReadmodel(toReadModelTenant(mockProducer), tenants); - const returnedPurposeVersion = await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + const returnedPurposeVersion = await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 24, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -182,15 +185,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); await writeInReadmodel(toReadModelTenant(mockProducer), tenants); - const returnedPurposeVersion = await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + const returnedPurposeVersion = await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 4, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -249,15 +255,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); await writeInReadmodel(toReadModelTenant(mockProducer), tenants); - const returnedPurposeVersion = await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + const returnedPurposeVersion = await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 30, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastEventByStreamId( mockPurpose.id, @@ -306,19 +315,22 @@ describe("createPurposeVersion", () => { expect( async () => - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: mockPurposeVersion.dailyCalls, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(unchangedDailyCalls(mockPurpose.id)); }); - it("should throw organizationIsNotTheConsumer if the caller is not the consumer", async () => { + it("should throw organizationIsNotTheConsumer if the caller is the producer", async () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); @@ -326,15 +338,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 1000, }, - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockEService.producerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError( organizationIsNotTheConsumer(mockEService.producerId) ); @@ -342,25 +357,27 @@ describe("createPurposeVersion", () => { it("should throw eserviceNotFound if the e-service does not exists in the readmodel", async () => { await addOnePurpose(mockPurpose); - // await writeInReadmodel(toReadModelEService(mockEService), eservices); await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(eserviceNotFound(mockEService.id)); }); - it("should throw organizationNotAllowed if the caller is neither the producer or the consumer of the purpose", async () => { + it("should throw organizationIsNotTheConsumer if the caller is not the consumer", async () => { const anotherTenant: Tenant = { ...getMockTenant(), kind: "PA" }; await addOnePurpose(mockPurpose); @@ -371,35 +388,40 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(anotherTenant), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: anotherTenant.id, - correlationId: generateId(), - logger: genericLogger, - }); - }).rejects.toThrowError(organizationNotAllowed(anotherTenant.id)); + { + authData: getRandomAuthData(anotherTenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); + }).rejects.toThrowError(organizationIsNotTheConsumer(anotherTenant.id)); }); it("should throw agreementNotFound if the caller has no agreement associated with the purpose in the read model", async () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - // await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError( agreementNotFound(mockEService.id, mockConsumer.id) ); @@ -424,15 +446,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError( agreementNotFound(mockEService.id, mockConsumer.id) ); @@ -443,19 +468,21 @@ describe("createPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); - // await writeInReadmodel(mockConsumer, tenants); await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantNotFound(mockConsumer.id)); }); @@ -464,18 +491,20 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); await writeInReadmodel(toReadModelTenant(mockConsumer), tenants); - // await writeInReadmodel(mockProducer, tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantNotFound(mockProducer.id)); }); @@ -493,15 +522,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantKindNotFound(consumer.id)); }); @@ -519,15 +551,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(producer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(tenantKindNotFound(producer.id)); }); @@ -544,15 +579,18 @@ describe("createPurposeVersion", () => { await writeInReadmodel(toReadModelTenant(mockProducer), tenants); expect(async () => { - await purposeService.createPurposeVersion({ - purposeId: mockPurpose.id, - seed: { + await purposeService.createPurposeVersion( + mockPurpose.id, + { dailyCalls: 20, }, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); }).rejects.toThrowError(missingRiskAnalysis(purpose.id)); }); }); diff --git a/packages/purpose-process/test/createReversePurpose.test.ts b/packages/purpose-process/test/createReversePurpose.test.ts index c017399dcb..028fef9340 100644 --- a/packages/purpose-process/test/createReversePurpose.test.ts +++ b/packages/purpose-process/test/createReversePurpose.test.ts @@ -8,6 +8,7 @@ import { getMockPurpose, getMockTenant, getMockValidRiskAnalysis, + getRandomAuthData, writeInReadmodel, } from "pagopa-interop-commons-test"; import { @@ -105,12 +106,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); const { purpose, isRiskAnalysisValid } = - await purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ); + await purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }); const writtenEvent = await readLastPurposeEvent(purpose.id); @@ -200,12 +201,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - producer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(producer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(organizationIsNotTheConsumer(producer.id)); }); it("should throw eserviceModeNotAllowed if the eservice is in deliver mode", async () => { @@ -252,12 +253,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError( eServiceModeNotAllowed(mockEService.id, eserviceMode.receive) ); @@ -306,12 +307,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError( eserviceRiskAnalysisNotFound(mockEService.id, randomRiskAnalysisId) ); @@ -360,12 +361,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(missingFreeOfChargeReason()); }); it("should throw tenantKindNotFound if the tenant kind doesn't exist", async () => { @@ -412,12 +413,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(tenantKindNotFound(producer.id)); }); it("should throw agreementNotFound if the requester doesn't have an agreement for the selected eservice", async () => { @@ -456,12 +457,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelTenant(consumer), tenants); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(agreementNotFound(mockEService.id, consumer.id)); }); it("should throw duplicatedPurposeTitle if a purpose with the same name already exists", async () => { @@ -517,12 +518,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError(duplicatedPurposeTitle(purposeTitle)); }); it("should throw riskAnalysisValidationFailed if the risk analysis is not valid", async () => { @@ -577,12 +578,12 @@ describe("createReversePurpose", () => { await writeInReadmodel(toReadModelAgreement(mockAgreement), agreements); expect( - purposeService.createReversePurpose( - consumer.id, - reversePurposeSeed, - generateId(), - genericLogger - ) + purposeService.createReversePurpose(reversePurposeSeed, { + authData: getRandomAuthData(consumer.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + }) ).rejects.toThrowError( riskAnalysisValidationFailed([ unexpectedRulesVersionError(mockRiskAnalysis.riskAnalysisForm.version), diff --git a/packages/purpose-process/test/deletePurpose.test.ts b/packages/purpose-process/test/deletePurpose.test.ts index a0dc5e2dea..09af22f879 100644 --- a/packages/purpose-process/test/deletePurpose.test.ts +++ b/packages/purpose-process/test/deletePurpose.test.ts @@ -16,6 +16,7 @@ import { writeInReadmodel, decodeProtobufPayload, getMockPurposeVersion, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { genericLogger } from "pagopa-interop-commons"; import { @@ -43,11 +44,11 @@ describe("deletePurpose", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - await purposeService.deletePurpose({ - purposeId: mockPurpose.id, - organizationId: mockPurpose.consumerId, + await purposeService.deletePurpose(mockPurpose.id, { + authData: getRandomAuthData(mockPurpose.consumerId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -78,11 +79,11 @@ describe("deletePurpose", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - await purposeService.deletePurpose({ - purposeId: mockPurpose.id, - organizationId: mockPurpose.consumerId, + await purposeService.deletePurpose(mockPurpose.id, { + authData: getRandomAuthData(mockPurpose.consumerId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -115,11 +116,11 @@ describe("deletePurpose", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - await purposeService.deletePurpose({ - purposeId: mockPurpose.id, - organizationId: mockPurpose.consumerId, + await purposeService.deletePurpose(mockPurpose.id, { + authData: getRandomAuthData(mockPurpose.consumerId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -144,11 +145,11 @@ describe("deletePurpose", () => { await addOnePurpose(mockPurpose); expect( - purposeService.deletePurpose({ - purposeId: randomId, - organizationId: mockPurpose.consumerId, + purposeService.deletePurpose(randomId, { + authData: getRandomAuthData(mockPurpose.consumerId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(purposeNotFound(randomId)); }); @@ -167,11 +168,11 @@ describe("deletePurpose", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurpose({ - purposeId: mockPurpose.id, - organizationId: mockEService.producerId, + purposeService.deletePurpose(mockPurpose.id, { + authData: getRandomAuthData(mockEService.producerId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError( organizationIsNotTheConsumer(mockEService.producerId) @@ -199,11 +200,11 @@ describe("deletePurpose", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurpose({ - purposeId: mockPurpose.id, - organizationId: mockPurpose.consumerId, + purposeService.deletePurpose(mockPurpose.id, { + authData: getRandomAuthData(mockPurpose.consumerId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(purposeCannotBeDeleted(mockPurpose.id)); } diff --git a/packages/purpose-process/test/deletePurposeVersion.test.ts b/packages/purpose-process/test/deletePurposeVersion.test.ts index bd17341ee0..ebb0f42cb9 100644 --- a/packages/purpose-process/test/deletePurposeVersion.test.ts +++ b/packages/purpose-process/test/deletePurposeVersion.test.ts @@ -5,6 +5,7 @@ import { getMockPurpose, writeInReadmodel, decodeProtobufPayload, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { purposeVersionState, @@ -52,13 +53,18 @@ describe("deletePurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - await purposeService.deletePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion1.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.deletePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -98,13 +104,18 @@ describe("deletePurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurposeVersion({ - purposeId: randomId, - versionId: mockPurposeVersion.id, - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.deletePurposeVersion( + { + purposeId: randomId, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockEService.producerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(purposeNotFound(randomId)); }); it("should throw purposeVersionNotFound if the purpose version doesn't exist", async () => { @@ -120,13 +131,18 @@ describe("deletePurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurposeVersion({ - purposeId: mockPurpose.id, - versionId: randomVersionId, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.deletePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: randomVersionId, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( purposeVersionNotFound(mockPurpose.id, randomVersionId) ); @@ -147,13 +163,18 @@ describe("deletePurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.deletePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockEService.producerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( organizationIsNotTheConsumer(mockEService.producerId) ); @@ -180,13 +201,18 @@ describe("deletePurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.deletePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( purposeVersionCannotBeDeleted(mockPurpose.id, mockPurposeVersion.id) ); @@ -207,13 +233,18 @@ describe("deletePurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.deletePurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.deletePurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( purposeVersionCannotBeDeleted(mockPurpose.id, mockPurposeVersion.id) ); diff --git a/packages/purpose-process/test/getPurposes.test.ts b/packages/purpose-process/test/getPurposes.test.ts index a2ee002bde..da22824a20 100644 --- a/packages/purpose-process/test/getPurposes.test.ts +++ b/packages/purpose-process/test/getPurposes.test.ts @@ -642,6 +642,7 @@ describe("getPurposes", async () => { await addOneTenant(delegate); const delegation = getMockDelegation({ + delegatorId: producerId, delegateId, state: delegationState.active, eserviceId: eservice.id, diff --git a/packages/purpose-process/test/getRiskAnalysisDocument.test.ts b/packages/purpose-process/test/getRiskAnalysisDocument.test.ts index 49c94cc3ed..b28ca6cca2 100644 --- a/packages/purpose-process/test/getRiskAnalysisDocument.test.ts +++ b/packages/purpose-process/test/getRiskAnalysisDocument.test.ts @@ -105,6 +105,7 @@ describe("getRiskAnalysisDocument", () => { eserviceId: mockEService.id, delegateId: delegate.organizationId, state: delegationState.active, + delegatorId: mockEService.producerId, }); await writeInReadmodel(delegation, delegations); diff --git a/packages/purpose-process/test/rejectPurposeVersion.test.ts b/packages/purpose-process/test/rejectPurposeVersion.test.ts index 5312c58641..024305afa7 100644 --- a/packages/purpose-process/test/rejectPurposeVersion.test.ts +++ b/packages/purpose-process/test/rejectPurposeVersion.test.ts @@ -6,6 +6,7 @@ import { decodeProtobufPayload, getMockAuthData, getMockDelegation, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { purposeVersionState, @@ -28,6 +29,7 @@ import { organizationIsNotTheProducer, purposeVersionNotFound, notValidVersionState, + organizationIsNotTheDelegatedProducer, } from "../src/model/domain/errors.js"; import { getMockEService, @@ -57,14 +59,19 @@ describe("rejectPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - await purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockEService.producerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -120,18 +127,24 @@ describe("rejectPurposeVersion", () => { eserviceId: mockEService.id, delegateId: delegate.organizationId, state: delegationState.active, + delegatorId: mockEService.producerId, }); await writeInReadmodel(delegation, delegations); - await purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: delegate.organizationId, - correlationId: generateId(), - logger: genericLogger, - }); + await purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(delegate.organizationId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -177,14 +190,19 @@ describe("rejectPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.rejectPurposeVersion({ - purposeId: randomId, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: randomId, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockEService.producerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError(purposeNotFound(randomId)); }); it("Should throw eserviceNotFound if the eservice doesn't exist", async () => { @@ -199,14 +217,19 @@ describe("rejectPurposeVersion", () => { await addOnePurpose(mockPurpose); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError(eserviceNotFound(mockEService.id)); }); it("should throw organizationIsNotTheProducer if the requester is not the producer nor delegate", async () => { @@ -222,19 +245,24 @@ describe("rejectPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError( organizationIsNotTheProducer(mockPurpose.consumerId) ); }); - it("should throw organizationIsNotTheProducer if the purpose e-service has an active delegation and the requester is the producer", async () => { + it("should throw organizationIsNotTheDelegatedProducer 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 = { @@ -257,19 +285,27 @@ describe("rejectPurposeVersion", () => { await writeInReadmodel(delegation, delegations); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockEService.producerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError( - organizationIsNotTheProducer(mockEService.producerId) + organizationIsNotTheDelegatedProducer( + mockEService.producerId, + delegation.id + ) ); }); - it("should throw organizationIsNotTheProducer if the purpose e-service has an active delegation and the requester is not the producer nor the delegate", async () => { + it("should throw organizationIsNotTheDelegatedProducer 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 = { @@ -294,16 +330,24 @@ describe("rejectPurposeVersion", () => { const randomCaller = getMockAuthData(); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: randomCaller.organizationId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(randomCaller.organizationId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError( - organizationIsNotTheProducer(randomCaller.organizationId) + organizationIsNotTheDelegatedProducer( + randomCaller.organizationId, + delegation.id + ) ); }); it.each( @@ -333,14 +377,19 @@ describe("rejectPurposeVersion", () => { await writeInReadmodel(delegation, delegations); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: delegate.organizationId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(delegate.organizationId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError( organizationIsNotTheProducer(delegate.organizationId) ); @@ -360,14 +409,19 @@ describe("rejectPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: randomVersionId, - rejectionReason: "test", - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: randomVersionId, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockEService.producerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError( purposeVersionNotFound(mockPurpose.id, randomVersionId) ); @@ -392,14 +446,19 @@ describe("rejectPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.rejectPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - rejectionReason: "test", - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.rejectPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + rejectionReason: "test", + }, + { + authData: getRandomAuthData(mockEService.producerId), + serviceName: "", + correlationId: generateId(), + logger: genericLogger, + } + ) ).rejects.toThrowError( notValidVersionState(mockPurposeVersion.id, mockPurposeVersion.state) ); diff --git a/packages/purpose-process/test/suspendPurposeVersion.test.ts b/packages/purpose-process/test/suspendPurposeVersion.test.ts index b0f715932a..90b2ad80b9 100644 --- a/packages/purpose-process/test/suspendPurposeVersion.test.ts +++ b/packages/purpose-process/test/suspendPurposeVersion.test.ts @@ -5,8 +5,8 @@ import { getMockPurpose, writeInReadmodel, decodeProtobufPayload, - getMockAuthData, getMockDelegation, + getRandomAuthData, } from "pagopa-interop-commons-test"; import { PurposeVersion, @@ -58,13 +58,18 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const returnedPurposeVersion = await purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion1.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }); + const returnedPurposeVersion = await purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -120,13 +125,18 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const returnedPurposeVersion = await purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion1.id, - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }); + const returnedPurposeVersion = await purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + }, + { + authData: getRandomAuthData(mockEService.producerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -182,23 +192,27 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const delegate = getMockAuthData(); const delegation = getMockDelegation({ + delegatorId: mockEService.producerId, 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 returnedPurposeVersion = await purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + }, + { + authData: getRandomAuthData(delegation.delegateId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -255,13 +269,18 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const returnedPurposeVersion = await purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion1.id, - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }); + const returnedPurposeVersion = await purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion1.id, + }, + { + authData: getRandomAuthData(mockEService.producerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(mockPurpose.id); @@ -307,13 +326,18 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); expect( - purposeService.suspendPurposeVersion({ - purposeId: randomPurposeId, - versionId: randomVersionId, - organizationId: generateId(), - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.suspendPurposeVersion( + { + purposeId: randomPurposeId, + versionId: randomVersionId, + }, + { + authData: getRandomAuthData(), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(purposeNotFound(randomPurposeId)); }); it("should throw purposeVersionNotFound if the purpose version doesn't exist", async () => { @@ -330,20 +354,25 @@ describe("suspendPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: randomVersionId, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: randomVersionId, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( purposeVersionNotFound(mockPurpose.id, randomVersionId) ); }); it("should throw organizationNotAllowed if the requester is not the producer nor the consumer", async () => { const mockEService = getMockEService(); - const randomId: TenantId = generateId(); + const randomAuthData = getRandomAuthData(); const mockPurposeVersion: PurposeVersion = { ...getMockPurposeVersion(), state: purposeVersionState.active, @@ -358,14 +387,21 @@ describe("suspendPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: randomId, - correlationId: generateId(), - logger: genericLogger, - }) - ).rejects.toThrowError(organizationNotAllowed(randomId)); + purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: randomAuthData, + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) + ).rejects.toThrowError( + organizationNotAllowed(randomAuthData.organizationId) + ); }); it("should throw organizationNotAllowed if the requester is not the e-service active delegation delegate", async () => { const mockEService = getMockEService(); @@ -381,26 +417,32 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const delegate = getMockAuthData(); + const delegateId = generateId(); const delegation = getMockDelegation({ + delegatorId: mockEService.producerId, kind: delegationKind.delegatedProducer, eserviceId: mockEService.id, - delegateId: delegate.organizationId, + delegateId, state: delegationState.active, }); await writeInReadmodel(delegation, delegations); - const randomCaller = getMockAuthData(); + const randomCaller = getRandomAuthData(); expect( - purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: randomCaller.organizationId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: randomCaller, + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(organizationNotAllowed(randomCaller.organizationId)); }); it.each( @@ -421,25 +463,33 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const delegate = getMockAuthData(); + const delegateAuthData = getRandomAuthData(); const delegation = getMockDelegation({ + delegatorId: mockEService.producerId, kind: delegationKind.delegatedProducer, eserviceId: mockEService.id, - delegateId: delegate.organizationId, + delegateId: delegateAuthData.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)); + purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: delegateAuthData, + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) + ).rejects.toThrowError( + organizationNotAllowed(delegateAuthData.organizationId) + ); } ); it("should throw organizationNotAllowed if the requester is the producer but the purpose e-service has an active delegation", async () => { @@ -456,24 +506,30 @@ describe("suspendPurposeVersion", () => { await addOnePurpose(mockPurpose); await writeInReadmodel(toReadModelEService(mockEService), eservices); - const delegate = getMockAuthData(); + const delegateId = generateId(); const delegation = getMockDelegation({ + delegatorId: mockEService.producerId, kind: delegationKind.delegatedProducer, eserviceId: mockEService.id, - delegateId: delegate.organizationId, + delegateId, state: delegationState.active, }); await writeInReadmodel(delegation, delegations); expect( - purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockEService.producerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockEService.producerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(organizationNotAllowed(mockEService.producerId)); }); it.each( @@ -498,13 +554,18 @@ describe("suspendPurposeVersion", () => { await writeInReadmodel(toReadModelEService(mockEService), eservices); expect( - purposeService.suspendPurposeVersion({ - purposeId: mockPurpose.id, - versionId: mockPurposeVersion.id, - organizationId: mockPurpose.consumerId, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.suspendPurposeVersion( + { + purposeId: mockPurpose.id, + versionId: mockPurposeVersion.id, + }, + { + authData: getRandomAuthData(mockPurpose.consumerId), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( notValidVersionState(mockPurposeVersion.id, mockPurposeVersion.state) ); diff --git a/packages/purpose-process/test/updatePurpose.test.ts b/packages/purpose-process/test/updatePurpose.test.ts index 8de30ed8db..9c0df40116 100644 --- a/packages/purpose-process/test/updatePurpose.test.ts +++ b/packages/purpose-process/test/updatePurpose.test.ts @@ -14,6 +14,7 @@ import { getMockValidRiskAnalysis, writeInReadmodel, decodeProtobufPayload, + getRandomAuthData, } from "pagopa-interop-commons-test/index.js"; import { tenantKind, @@ -136,12 +137,13 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); const { purpose, isRiskAnalysisValid } = await purposeService.updatePurpose( + purposeForDeliver.id, + purposeUpdateContent, { - purposeId: purposeForDeliver.id, - purposeUpdateContent, - organizationId: tenant.id, + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", } ); @@ -182,12 +184,13 @@ describe("updatePurpose and updateReversePurpose", () => { }; const { purpose, isRiskAnalysisValid } = await purposeService.updatePurpose( + purposeForDeliver.id, + updateContentWithoutTitle, { - purposeId: purposeForDeliver.id, - purposeUpdateContent: updateContentWithoutTitle, - organizationId: tenant.id, + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", } ); @@ -222,13 +225,16 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); const { purpose, isRiskAnalysisValid } = - await purposeService.updateReversePurpose({ - purposeId: purposeForReceive.id, + await purposeService.updateReversePurpose( + purposeForReceive.id, reversePurposeUpdateContent, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }); + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ); const writtenEvent = await readLastPurposeEvent(purposeForReceive.id); expect(writtenEvent).toMatchObject({ @@ -261,12 +267,11 @@ describe("updatePurpose and updateReversePurpose", () => { const purposeId: PurposeId = unsafeBrandId(generateId()); expect( - purposeService.updatePurpose({ - purposeId, - purposeUpdateContent, - organizationId: tenant.id, + purposeService.updatePurpose(purposeId, purposeUpdateContent, { + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(purposeNotFound(purposeId)); }); @@ -283,12 +288,11 @@ describe("updatePurpose and updateReversePurpose", () => { const organizationId: TenantId = unsafeBrandId(generateId()); expect( - purposeService.updatePurpose({ - purposeId: mockPurpose.id, - purposeUpdateContent, - organizationId, + purposeService.updatePurpose(mockPurpose.id, purposeUpdateContent, { + authData: getRandomAuthData(organizationId), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(organizationIsNotTheConsumer(organizationId)); }); @@ -309,12 +313,11 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); expect( - purposeService.updatePurpose({ - purposeId: mockPurpose.id, - purposeUpdateContent, - organizationId: tenant.id, + purposeService.updatePurpose(mockPurpose.id, purposeUpdateContent, { + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(purposeNotInDraftState(mockPurpose.id)); } @@ -329,16 +332,19 @@ describe("updatePurpose and updateReversePurpose", () => { await addOnePurpose(purposeWithDuplicatedTitle); expect( - purposeService.updatePurpose({ - purposeId: purposeForDeliver.id, - purposeUpdateContent: { + purposeService.updatePurpose( + purposeForDeliver.id, + { ...purposeUpdateContent, title: purposeWithDuplicatedTitle.title, }, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }) + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( duplicatedPurposeTitle(purposeWithDuplicatedTitle.title) ); @@ -354,12 +360,11 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); expect( - purposeService.updatePurpose({ - purposeId: mockPurpose.id, - purposeUpdateContent, - organizationId: tenant.id, + purposeService.updatePurpose(mockPurpose.id, purposeUpdateContent, { + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(eserviceNotFound(eserviceId)); }); @@ -369,12 +374,11 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); expect( - purposeService.updatePurpose({ - purposeId: purposeForReceive.id, - purposeUpdateContent, - organizationId: tenant.id, + purposeService.updatePurpose(purposeForReceive.id, purposeUpdateContent, { + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError( eServiceModeNotAllowed(eServiceReceive.id, "Deliver") @@ -386,13 +390,16 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); expect( - purposeService.updateReversePurpose({ - purposeId: purposeForDeliver.id, + purposeService.updateReversePurpose( + purposeForDeliver.id, reversePurposeUpdateContent, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }) + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( eServiceModeNotAllowed(eServiceDeliver.id, "Receive") ); @@ -403,16 +410,19 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); expect( - purposeService.updatePurpose({ - purposeId: purposeForDeliver.id, - purposeUpdateContent: { + purposeService.updatePurpose( + purposeForDeliver.id, + { ...purposeUpdateContent, isFreeOfCharge: true, }, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }) + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(missingFreeOfChargeReason()); }); it("Should throw tenantNotFound if the tenant does not exist", async () => { @@ -420,12 +430,11 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelEService(eServiceDeliver), eservices); expect( - purposeService.updatePurpose({ - purposeId: purposeForDeliver.id, - purposeUpdateContent, - organizationId: tenant.id, + purposeService.updatePurpose(purposeForDeliver.id, purposeUpdateContent, { + authData: getRandomAuthData(tenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(tenantNotFound(tenant.id)); @@ -433,13 +442,16 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelEService(eServiceReceive), eservices); expect( - purposeService.updateReversePurpose({ - purposeId: purposeForReceive.id, + purposeService.updateReversePurpose( + purposeForReceive.id, reversePurposeUpdateContent, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }) + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError(tenantNotFound(tenant.id)); }); it("Should throw tenantKindNotFound if the tenant kind does not exist", async () => { @@ -453,12 +465,11 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(mockTenant), tenants); expect( - purposeService.updatePurpose({ - purposeId: purposeForDeliver.id, - purposeUpdateContent, - organizationId: mockTenant.id, + purposeService.updatePurpose(purposeForDeliver.id, purposeUpdateContent, { + authData: getRandomAuthData(mockTenant.id), correlationId: generateId(), logger: genericLogger, + serviceName: "", }) ).rejects.toThrowError(tenantKindNotFound(mockTenant.id)); }); @@ -481,13 +492,16 @@ describe("updatePurpose and updateReversePurpose", () => { }; expect( - purposeService.updatePurpose({ - purposeId: purposeForDeliver.id, - purposeUpdateContent: mockPurposeUpdateContent, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }) + purposeService.updatePurpose( + purposeForDeliver.id, + mockPurposeUpdateContent, + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( riskAnalysisValidationFailed([unexpectedRulesVersionError("0")]) ); @@ -507,13 +521,16 @@ describe("updatePurpose and updateReversePurpose", () => { await writeInReadmodel(toReadModelTenant(tenant), tenants); expect( - purposeService.updateReversePurpose({ - purposeId: purposeWithInvalidRiskAnalysis.id, + purposeService.updateReversePurpose( + purposeWithInvalidRiskAnalysis.id, reversePurposeUpdateContent, - organizationId: tenant.id, - correlationId: generateId(), - logger: genericLogger, - }) + { + authData: getRandomAuthData(tenant.id), + correlationId: generateId(), + logger: genericLogger, + serviceName: "", + } + ) ).rejects.toThrowError( riskAnalysisValidationFailed([unexpectedRulesVersionError("0")]) ); diff --git a/packages/tenant-outbound-writer/package.json b/packages/tenant-outbound-writer/package.json index 6d8a08b450..6535889bc3 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.12-b", + "@pagopa/interop-outbound-models": "1.0.12-l", "@protobuf-ts/runtime": "2.9.4", "connection-string": "4.4.0", "dotenv-flow": "4.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 93f831f5cf..3002551d3e 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.12-b - version: 1.0.12-b + specifier: 1.0.12-l + version: 1.0.12-l '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -1063,8 +1063,8 @@ importers: packages/catalog-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.12-h - version: 1.0.12-h + specifier: 1.0.12-l + version: 1.0.12-l '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -1794,8 +1794,8 @@ importers: packages/delegation-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.12-h - version: 1.0.12-h + specifier: 1.0.12-l + version: 1.0.12-l '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -2653,8 +2653,8 @@ importers: packages/purpose-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.12-b - version: 1.0.12-b + specifier: 1.0.12-l + version: 1.0.12-l '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -2958,8 +2958,8 @@ importers: packages/tenant-outbound-writer: dependencies: '@pagopa/interop-outbound-models': - specifier: 1.0.12-b - version: 1.0.12-b + specifier: 1.0.12-l + version: 1.0.12-l '@protobuf-ts/runtime': specifier: 2.9.4 version: 2.9.4 @@ -4021,11 +4021,8 @@ packages: '@pagopa/eslint-config@3.0.0': resolution: {integrity: sha512-eYIPdiuYRbRPR5k0OuteRNqYb0Z2nfJ/lZohejB7ylfBeSDWwkaV8Z19AXP4RymE6oEesyPDZ6i0yNaE9tQrHw==} - '@pagopa/interop-outbound-models@1.0.12-b': - resolution: {integrity: sha512-9ae07TWBNxAm5SgvjCHNekcUTidI2Jf0LZeofqQHhPPk10/WmkX1ZufvrKbyKYwSCw0BjpwhQwJLKVh+xRgJ8A==} - - '@pagopa/interop-outbound-models@1.0.12-h': - resolution: {integrity: sha512-9HCSpSBbj7Id2kHSozP5inaepVK8ktlzQHfy6epablGyMhH0ZAzZoEqakad7UPVPeT/qBx5wrydBm/Y5c2iQFg==} + '@pagopa/interop-outbound-models@1.0.12-l': + resolution: {integrity: sha512-c1ZT5a7q7Mv/xgTqTT+5UVkdFGHY07YRnyFKA5yju1vI3bBmHFpEAsAIDoMcP13ue/cVxI1/mVtsX893StZO5A==} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -9330,13 +9327,7 @@ snapshots: - tsutils - typescript - '@pagopa/interop-outbound-models@1.0.12-b': - dependencies: - '@protobuf-ts/runtime': 2.9.4 - ts-pattern: 5.2.0 - zod: 3.23.8 - - '@pagopa/interop-outbound-models@1.0.12-h': + '@pagopa/interop-outbound-models@1.0.12-l': dependencies: '@protobuf-ts/runtime': 2.9.4 ts-pattern: 5.2.0 From c5d4b4c65b06b9b2e810669645ed326ce906a7cb Mon Sep 17 00:00:00 2001 From: Andrea Zerbini Date: Tue, 14 Jan 2025 10:43:50 +0100 Subject: [PATCH 6/8] PIN-5617 Add delegation to GET /purpose/:purposeId (#1335) --- packages/api-clients/open-api/bffApi.yml | 20 ++++++- .../src/routers/purposeRouter.ts | 1 + .../src/services/purposeService.ts | 60 ++++++++++++++++++- .../src/services/purposeService.ts | 19 +++--- 4 files changed, 84 insertions(+), 16 deletions(-) diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index 3f41635e02..46e2d69e8b 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -15498,9 +15498,8 @@ components: description: "total daily calls available for this e-service." type: integer format: int32 - delegationId: - type: string - format: uuid + delegation: + $ref: "#/components/schemas/DelegationWithCompactTenants" required: - id - title @@ -15753,6 +15752,21 @@ components: required: - results - pagination + DelegationWithCompactTenants: + type: object + additionalProperties: false + required: + - id + - delegate + - delegator + properties: + id: + type: string + format: uuid + delegate: + $ref: "#/components/schemas/CompactOrganization" + delegator: + $ref: "#/components/schemas/CompactOrganization" PurposeVersion: type: object additionalProperties: false diff --git a/packages/backend-for-frontend/src/routers/purposeRouter.ts b/packages/backend-for-frontend/src/routers/purposeRouter.ts index fde2a18b80..92451acf7c 100644 --- a/packages/backend-for-frontend/src/routers/purposeRouter.ts +++ b/packages/backend-for-frontend/src/routers/purposeRouter.ts @@ -34,6 +34,7 @@ const purposeRouter = ( clients.catalogProcessClient, clients.tenantProcessClient, clients.agreementProcessClient, + clients.delegationProcessClient, clients.authorizationClient, initFileManager(config) ); diff --git a/packages/backend-for-frontend/src/services/purposeService.ts b/packages/backend-for-frontend/src/services/purposeService.ts index 0f52fd28d1..3512f55758 100644 --- a/packages/backend-for-frontend/src/services/purposeService.ts +++ b/packages/backend-for-frontend/src/services/purposeService.ts @@ -21,6 +21,7 @@ import { AgreementProcessClient, AuthorizationProcessClient, CatalogProcessClient, + DelegationProcessClient, PurposeProcessClient, TenantProcessClient, } from "../clients/clientsProvider.js"; @@ -36,10 +37,57 @@ import { BffAppContext, Headers } from "../utilities/context.js"; import { config } from "../config/config.js"; import { toBffApiCompactClient } from "../api/authorizationApiConverter.js"; import { toBffApiPurposeVersion } from "../api/purposeApiConverter.js"; +import { getLatestTenantContactEmail } from "../model/modelMappingUtils.js"; import { getLatestAgreement } from "./agreementService.js"; import { getAllClients } from "./clientService.js"; import { isAgreementUpgradable } from "./validators.js"; +const enrichPurposeDelegation = async ( + delegationId: string, + delegationProcessClient: DelegationProcessClient, + tenantProcessClient: TenantProcessClient, + headers: Headers +): Promise => { + const delegation = await delegationProcessClient.delegation.getDelegation({ + headers, + params: { + delegationId, + }, + }); + + const [delegate, delegator] = await Promise.all( + [delegation.delegateId, delegation.delegatorId].map((id) => + tenantProcessClient.tenant.getTenant({ + headers, + params: { id }, + }) + ) + ); + + if (!delegate) { + throw tenantNotFound(delegation.delegateId); + } + if (!delegator) { + throw tenantNotFound(delegation.delegatorId); + } + + return { + id: delegationId, + delegate: { + id: delegate.id, + name: delegate.name, + contactMail: getLatestTenantContactEmail(delegate), + kind: delegate.kind, + }, + delegator: { + id: delegator.id, + name: delegator.name, + contactMail: getLatestTenantContactEmail(delegator), + kind: delegator.kind, + }, + }; +}; + export const getCurrentVersion = ( purposeVersions: purposeApi.PurposeVersion[] ): purposeApi.PurposeVersion | undefined => { @@ -62,6 +110,7 @@ export function purposeServiceBuilder( catalogProcessClient: CatalogProcessClient, tenantProcessClient: TenantProcessClient, agreementProcessClient: AgreementProcessClient, + delegationProcessClient: DelegationProcessClient, authorizationClient: AuthorizationProcessClient, fileManager: FileManager ) { @@ -138,6 +187,15 @@ export function purposeServiceBuilder( ? latestVersion : undefined; + const delegation = purpose.delegationId + ? await enrichPurposeDelegation( + purpose.delegationId, + delegationProcessClient, + tenantProcessClient, + headers + ) + : undefined; + return { id: purpose.id, title: purpose.title, @@ -175,7 +233,7 @@ export function purposeServiceBuilder( dailyCallsTotal: currentDescriptor.dailyCallsTotal, rejectedVersion: rejectedVersion && toBffApiPurposeVersion(rejectedVersion), - delegationId: purpose.delegationId, + delegation, }; }; diff --git a/packages/purpose-process/src/services/purposeService.ts b/packages/purpose-process/src/services/purposeService.ts index 80d153b6d2..acb277585e 100644 --- a/packages/purpose-process/src/services/purposeService.ts +++ b/packages/purpose-process/src/services/purposeService.ts @@ -238,15 +238,11 @@ export function purposeServiceBuilder( logger.info(`Retrieving Purpose ${purposeId}`); const purpose = await retrievePurpose(purposeId, readModelService); - const eservice = await retrieveEService( - purpose.data.eserviceId, - readModelService - ); - const tenantKind = await retrieveTenantKind( - organizationId, - readModelService - ); + const [eservice, tenantKind] = await Promise.all([ + retrieveEService(purpose.data.eserviceId, readModelService), + retrieveTenantKind(organizationId, readModelService), + ]); const isAllowedToRetrieveRiskAnalysis = await assertRequesterIsAllowedToRetrieveRiskAnalysisDocument( @@ -254,10 +250,9 @@ export function purposeServiceBuilder( eservice, { organizationId }, readModelService - ).then( - () => true, - () => false - ); + ) + .then(() => true) + .catch(() => false); if (!isAllowedToRetrieveRiskAnalysis) { return { From 2dbc16afae11188a8168380249df539411327a76 Mon Sep 17 00:00:00 2001 From: Simone Camito <32327779+MalpenZibo@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:50:24 +0100 Subject: [PATCH 7/8] PIN 5700 - Published EService delegation flags update (#1273) --- .../Updates EService delegation flags.bru | 27 ++ .../catalog/Updates EService description.bru | 2 +- .../Update Eservice delegation flags.bru | 27 ++ .../catalog/Update Eservice description.bru | 2 +- packages/api-clients/open-api/bffApi.yml | 135 ++++++- packages/api-clients/open-api/catalogApi.yml | 69 +++- packages/authorization-updater/src/index.ts | 4 + .../src/routers/catalogRouter.ts | 22 +- .../src/services/catalogService.ts | 18 + .../src/converters/toOutboundEventV2.ts | 4 + .../src/consumerServiceV2.ts | 4 + .../src/model/domain/errors.ts | 19 +- .../src/model/domain/toEvent.ts | 68 ++++ .../src/routers/EServiceRouter.ts | 32 +- .../src/services/catalogService.ts | 161 ++++++++ .../src/utilities/errorMappers.ts | 10 + .../updateEserviceDelegationFlags.test.ts | 373 ++++++++++++++++++ .../src/consumerServiceV2.ts | 4 + .../src/interfaceExporterV2.ts | 4 + .../models/proto/v2/eservice/events.proto | 16 + .../models/src/eservice/eserviceEvents.ts | 36 ++ .../catalogItemEventNotificationConverter.ts | 4 + .../catalogItemEventNotificationMessage.ts | 4 + 23 files changed, 1034 insertions(+), 11 deletions(-) create mode 100644 collections/bff/catalog/Updates EService delegation flags.bru create mode 100644 collections/catalog/Update Eservice delegation flags.bru create mode 100644 packages/catalog-process/test/updateEserviceDelegationFlags.test.ts diff --git a/collections/bff/catalog/Updates EService delegation flags.bru b/collections/bff/catalog/Updates EService delegation flags.bru new file mode 100644 index 0000000000..34a41319cd --- /dev/null +++ b/collections/bff/catalog/Updates EService delegation flags.bru @@ -0,0 +1,27 @@ +meta { + name: Updates EService delegation flags + type: http + seq: 3 +} + +post { + url: {{host-bff}}/eservices/:eServiceId/delegationFlags + body: json + auth: none +} + +params:path { + eserviceId: {{eserviceId}} +} + +headers { + Authorization: {{JWT}} + X-Correlation-Id: {{correlation-id}} +} + +body:json { + { + "isDelegable": true, + "isClientAccessDelegable": false + } +} diff --git a/collections/bff/catalog/Updates EService description.bru b/collections/bff/catalog/Updates EService description.bru index 39be0cd78c..62eb34cc68 100644 --- a/collections/bff/catalog/Updates EService description.bru +++ b/collections/bff/catalog/Updates EService description.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{host-bff}}/eservices/:eServiceId/update + url: {{host-bff}}/eservices/:eServiceId/description body: json auth: none } diff --git a/collections/catalog/Update Eservice delegation flags.bru b/collections/catalog/Update Eservice delegation flags.bru new file mode 100644 index 0000000000..91a64e44fe --- /dev/null +++ b/collections/catalog/Update Eservice delegation flags.bru @@ -0,0 +1,27 @@ +meta { + name: Update Eservice delegation flags + type: http + seq: 15 +} + +post { + url: {{host-catalog}}/eservices/:eserviceId/delegationFlags + body: json + auth: none +} + +params:path { + eserviceId: {{eserviceId}} +} + +headers { + Authorization: {{JWT}} + X-Correlation-Id: {{correlation-id}} +} + +body:json { + { + "isDelegable": true, + "isClientAccessDelegable": false + } +} diff --git a/collections/catalog/Update Eservice description.bru b/collections/catalog/Update Eservice description.bru index 7d22000d8c..f79744b97c 100644 --- a/collections/catalog/Update Eservice description.bru +++ b/collections/catalog/Update Eservice description.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{host-catalog}}/eservices/:eserviceId/update + url: {{host-catalog}}/eservices/:eserviceId/description body: json auth: none } diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index 46e2d69e8b..be0e9f3e94 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -4071,7 +4071,7 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" - /eservices/{eServiceId}/update: + /eservices/{eServiceId}/description: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" post: @@ -4173,6 +4173,127 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + /eservices/{eServiceId}/delegationFlags: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + security: + - bearerAuth: [] + tags: + - eservices + summary: Update an e-service delegation flags + operationId: updateEServiceDelegationFlags + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + requestBody: + description: A payload containing the new flags + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EServiceFlagsSeed" + responses: + "200": + description: EService flags updated + 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 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" + "409": + description: Conflict + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + 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 /eservices/{eServiceId}/descriptors/{descriptorId}/attributes/update: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -4245,7 +4366,6 @@ paths: schema: $ref: "#/components/schemas/Problem" "404": - description: EService or Descriptor not found headers: "X-Rate-Limit-Limit": schema: @@ -14436,6 +14556,17 @@ components: type: string required: - description + EServiceFlagsSeed: + type: object + additionalProperties: false + properties: + isDelegable: + type: boolean + isClientAccessDelegable: + type: boolean + required: + - isDelegable + - isClientAccessDelegable RejectDelegatedEServiceDescriptorSeed: type: object additionalProperties: false diff --git a/packages/api-clients/open-api/catalogApi.yml b/packages/api-clients/open-api/catalogApi.yml index a1038d4e3f..80423afcfe 100644 --- a/packages/api-clients/open-api/catalogApi.yml +++ b/packages/api-clients/open-api/catalogApi.yml @@ -1024,7 +1024,7 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" - /eservices/{eServiceId}/update: + /eservices/{eServiceId}/description: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" post: @@ -1074,6 +1074,62 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + /eservices/{eServiceId}/delegationFlags: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + security: + - bearerAuth: [] + tags: + - process + summary: Update an e-service isDelegable and isClientAccessDelegable flags + operationId: updateEServiceDelegationFlags + parameters: + - name: eServiceId + in: path + description: the eservice id + required: true + schema: + type: string + format: uuid + requestBody: + description: A payload containing the new flags + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EServiceFlagsSeed" + responses: + "200": + description: EService flags updated + content: + application/json: + schema: + $ref: "#/components/schemas/EService" + "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" + "409": + description: Conflict + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /eservices/:eServiceId/descriptors/:descriptorId/approve: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -1778,6 +1834,17 @@ components: maxLength: 250 required: - description + EServiceFlagsSeed: + type: object + additionalProperties: false + properties: + isDelegable: + type: boolean + isClientAccessDelegable: + type: boolean + required: + - isDelegable + - isClientAccessDelegable EServiceDescriptor: type: object additionalProperties: false diff --git a/packages/authorization-updater/src/index.ts b/packages/authorization-updater/src/index.ts index fccf0ed1dc..58ee24f15d 100644 --- a/packages/authorization-updater/src/index.ts +++ b/packages/authorization-updater/src/index.ts @@ -137,6 +137,10 @@ export async function sendCatalogAuthUpdate( "EServiceRiskAnalysisDeleted", "EServiceDescriptorAttributesUpdated", "EServiceDescriptionUpdated", + "EServiceIsDelegableEnabled", + "EServiceIsDelegableDisabled", + "EServiceIsClientAccessDelegableEnabled", + "EServiceIsClientAccessDelegableDisabled", "EServiceDescriptorSubmittedByDelegate", "EServiceDescriptorRejectedByDelegator" ), diff --git a/packages/backend-for-frontend/src/routers/catalogRouter.ts b/packages/backend-for-frontend/src/routers/catalogRouter.ts index ffa7818c2c..8d333fb8d9 100644 --- a/packages/backend-for-frontend/src/routers/catalogRouter.ts +++ b/packages/backend-for-frontend/src/routers/catalogRouter.ts @@ -587,7 +587,7 @@ const catalogRouter = ( return res.status(errorRes.status).send(errorRes); } }) - .post("/eservices/:eServiceId/update", async (req, res) => { + .post("/eservices/:eServiceId/description", async (req, res) => { const ctx = fromBffAppContext(req.ctx, req.headers); try { const id = await catalogService.updateEServiceDescription( @@ -607,6 +607,26 @@ const catalogRouter = ( return res.status(errorRes.status).send(errorRes); } }) + .post("/eservices/:eServiceId/delegationFlags", async (req, res) => { + const ctx = fromBffAppContext(req.ctx, req.headers); + try { + const id = await catalogService.updateEServiceFlags( + ctx, + unsafeBrandId(req.params.eServiceId), + req.body + ); + return res.status(200).send(bffApi.CreatedResource.parse(id)); + } catch (error) { + const errorRes = makeApiProblem( + error, + emptyErrorMapper, + ctx.logger, + ctx.correlationId, + `Error updating delegation flags of eservice with Id: ${req.params.eServiceId}` + ); + return res.status(errorRes.status).send(errorRes); + } + }) .get( "/eservices/:eServiceId/riskAnalysis/:riskAnalysisId", async (req, res) => { diff --git a/packages/backend-for-frontend/src/services/catalogService.ts b/packages/backend-for-frontend/src/services/catalogService.ts index b46f83fe90..077eafe0b6 100644 --- a/packages/backend-for-frontend/src/services/catalogService.ts +++ b/packages/backend-for-frontend/src/services/catalogService.ts @@ -383,6 +383,24 @@ export function catalogServiceBuilder( id: updatedEservice.id, }; }, + updateEServiceFlags: async ( + { headers, logger }: WithLogger, + eServiceId: EServiceId, + updateSeed: bffApi.EServiceFlagsSeed + ): Promise => { + logger.info(`Updating EService Flags for eserviceId = ${eServiceId}`); + const updatedEservice = + await catalogProcessClient.updateEServiceDelegationFlags(updateSeed, { + headers, + params: { + eServiceId, + }, + }); + + return { + id: updatedEservice.id, + }; + }, createEService: async ( eServiceSeed: bffApi.EServiceSeed, { headers, logger }: WithLogger diff --git a/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts b/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts index 9d99682c56..78fd8f447b 100644 --- a/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts +++ b/packages/catalog-outbound-writer/src/converters/toOutboundEventV2.ts @@ -52,6 +52,10 @@ export function toOutboundEventV2( { type: "EServiceAdded" }, { type: "DraftEServiceUpdated" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceIsDelegableEnabled" }, + { type: "EServiceIsDelegableDisabled" }, + { type: "EServiceIsClientAccessDelegableEnabled" }, + { type: "EServiceIsClientAccessDelegableDisabled" }, (msg) => ({ event_version: msg.event_version, type: msg.type, diff --git a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts index cf3a5b569d..c21714cf41 100644 --- a/packages/catalog-platformstate-writer/src/consumerServiceV2.ts +++ b/packages/catalog-platformstate-writer/src/consumerServiceV2.ts @@ -243,6 +243,10 @@ export async function handleMessageV2( { type: "EServiceRiskAnalysisUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceIsDelegableEnabled" }, + { type: "EServiceIsDelegableDisabled" }, + { type: "EServiceIsClientAccessDelegableEnabled" }, + { type: "EServiceIsClientAccessDelegableDisabled" }, { type: "EServiceDescriptorRejectedByDelegator" }, { type: "EServiceDescriptorSubmittedByDelegate" }, { type: "EServiceDescriptorAttributesUpdated" }, diff --git a/packages/catalog-process/src/model/domain/errors.ts b/packages/catalog-process/src/model/domain/errors.ts index 5c8299ca8c..3ed5f9a03e 100644 --- a/packages/catalog-process/src/model/domain/errors.ts +++ b/packages/catalog-process/src/model/domain/errors.ts @@ -34,10 +34,11 @@ export const errorCodes = { riskAnalysisDuplicated: "0021", eserviceWithoutValidDescriptors: "0022", audienceCannotBeEmpty: "0023", - inconsistentAttributesSeedGroupsCount: "0024", - descriptorAttributeGroupSupersetMissingInAttributesSeed: "0025", - unchangedAttributes: "0026", - eserviceWithActiveOrPendingDelegation: "0027", + eserviceWithActiveOrPendingDelegation: "0024", + invalidEServiceFlags: "0025", + inconsistentAttributesSeedGroupsCount: "0026", + descriptorAttributeGroupSupersetMissingInAttributesSeed: "0027", + unchangedAttributes: "0028", }; export type ErrorCodes = keyof typeof errorCodes; @@ -320,3 +321,13 @@ export function eserviceWithActiveOrPendingDelegation( title: "E-service with active or pending delegation", }); } + +export function invalidEServiceFlags( + eserviceId: EServiceId +): ApiError { + return new ApiError({ + detail: `EService ${eserviceId} flags are not valid`, + code: "invalidEServiceFlags", + title: "Invalid EService flags", + }); +} diff --git a/packages/catalog-process/src/model/domain/toEvent.ts b/packages/catalog-process/src/model/domain/toEvent.ts index eeeaff4ce0..040b80d6e8 100644 --- a/packages/catalog-process/src/model/domain/toEvent.ts +++ b/packages/catalog-process/src/model/domain/toEvent.ts @@ -558,3 +558,71 @@ export const toCreateEventEServiceDescriptorAttributesUpdated = ( }, correlationId, }); + +export const toCreateEventEServiceIsDelegableEnabled = ( + version: number, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceIsDelegableEnabled", + event_version: 2, + data: { + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); + +export const toCreateEventEServiceIsDelegableDisabled = ( + version: number, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceIsDelegableDisabled", + event_version: 2, + data: { + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); + +export const toCreateEventEServiceIsClientAccessDelegableEnabled = ( + version: number, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceIsClientAccessDelegableEnabled", + event_version: 2, + data: { + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); + +export const toCreateEventEServiceIsClientAccessDelegableDisabled = ( + version: number, + eservice: EService, + correlationId: CorrelationId +): CreateEvent => ({ + streamId: eservice.id, + version, + event: { + type: "EServiceIsClientAccessDelegableDisabled", + event_version: 2, + data: { + eservice: toEServiceV2(eservice), + }, + }, + correlationId, +}); diff --git a/packages/catalog-process/src/routers/EServiceRouter.ts b/packages/catalog-process/src/routers/EServiceRouter.ts index 2fdf7b0eb7..6f79cb66f1 100644 --- a/packages/catalog-process/src/routers/EServiceRouter.ts +++ b/packages/catalog-process/src/routers/EServiceRouter.ts @@ -57,6 +57,7 @@ import { updateDescriptorAttributesErrorMapper, approveDelegatedEServiceDescriptorErrorMapper, rejectDelegatedEServiceDescriptorErrorMapper, + updateEServiceFlagsErrorMapper, } from "../utilities/errorMappers.js"; const readModelService = readModelServiceBuilder( @@ -715,7 +716,7 @@ const eservicesRouter = ( } ) .post( - "/eservices/:eServiceId/update", + "/eservices/:eServiceId/description", authorizationMiddleware([ADMIN_ROLE, API_ROLE]), async (req, res) => { const ctx = fromAppContext(req.ctx); @@ -743,6 +744,35 @@ const eservicesRouter = ( } } ) + .post( + "/eservices/:eServiceId/delegationFlags", + authorizationMiddleware([ADMIN_ROLE, API_ROLE]), + async (req, res) => { + const ctx = fromAppContext(req.ctx); + + try { + const updatedEService = + await catalogService.updateEServiceDelegationFlags( + unsafeBrandId(req.params.eServiceId), + req.body, + ctx + ); + return res + .status(200) + .send( + catalogApi.EService.parse(eServiceToApiEService(updatedEService)) + ); + } catch (error) { + const errorRes = makeApiProblem( + error, + updateEServiceFlagsErrorMapper, + ctx.logger, + ctx.correlationId + ); + return res.status(errorRes.status).send(errorRes); + } + } + ) .delete( "/eservices/:eServiceId/riskAnalysis/:riskAnalysisId", authorizationMiddleware([ADMIN_ROLE, API_ROLE]), diff --git a/packages/catalog-process/src/services/catalogService.ts b/packages/catalog-process/src/services/catalogService.ts index d00c555e1d..c21949c1bd 100644 --- a/packages/catalog-process/src/services/catalogService.ts +++ b/packages/catalog-process/src/services/catalogService.ts @@ -63,6 +63,7 @@ import { eserviceWithoutValidDescriptors, inconsistentDailyCalls, interfaceAlreadyExists, + invalidEServiceFlags, notValidDescriptorState, originNotCompliant, prettyNameDuplicate, @@ -96,6 +97,10 @@ import { toCreateEventEServiceInterfaceAdded, toCreateEventEServiceInterfaceDeleted, toCreateEventEServiceInterfaceUpdated, + toCreateEventEServiceIsClientAccessDelegableDisabled, + toCreateEventEServiceIsClientAccessDelegableEnabled, + toCreateEventEServiceIsDelegableDisabled, + toCreateEventEServiceIsDelegableEnabled, toCreateEventEServiceRiskAnalysisAdded, toCreateEventEServiceRiskAnalysisDeleted, toCreateEventEServiceRiskAnalysisUpdated, @@ -1818,6 +1823,162 @@ export function catalogServiceBuilder( ); return updatedEservice; }, + async updateEServiceDelegationFlags( + eserviceId: EServiceId, + { + isDelegable, + isClientAccessDelegable, + }: { + isDelegable: boolean; + isClientAccessDelegable: boolean; + }, + { authData, correlationId, logger }: WithLogger + ): Promise { + logger.info(`Updating EService ${eserviceId} delegation flags`); + const eservice = await retrieveEService(eserviceId, readModelService); + + await assertRequesterIsDelegateProducerOrProducer( + eservice.data.producerId, + eservice.data.id, + authData, + readModelService + ); + + const hasValidDescriptor = eservice.data.descriptors.some( + // eslint-disable-next-line sonarjs/no-identical-functions + (descriptor) => + descriptor.state !== descriptorState.draft && + descriptor.state !== descriptorState.waitingForApproval && + descriptor.state !== descriptorState.archived + ); + if (!hasValidDescriptor) { + throw eserviceWithoutValidDescriptors(eserviceId); + } + + if (!isDelegable && isClientAccessDelegable) { + throw invalidEServiceFlags(eserviceId); + } + + const updatedEservice: EService = { + ...eservice.data, + isDelegable, + isClientAccessDelegable, + }; + + const events = match({ + isDelegable, + oldIsDelegable: eservice.data.isDelegable || false, + isClientAccessDelegable, + oldIsClientAccessDelegable: + eservice.data.isClientAccessDelegable || false, + }) + .with( + { + isDelegable: true, + oldIsDelegable: false, + isClientAccessDelegable: false, + oldIsClientAccessDelegable: false, + }, + { + isDelegable: true, + oldIsDelegable: false, + isClientAccessDelegable: false, + oldIsClientAccessDelegable: true, // should never happen + }, + () => [ + toCreateEventEServiceIsDelegableEnabled( + eservice.metadata.version, + updatedEservice, + correlationId + ), + ] + ) + .with( + { + isDelegable: true, + oldIsDelegable: false, + isClientAccessDelegable: true, + oldIsClientAccessDelegable: false, + }, + () => [ + toCreateEventEServiceIsDelegableEnabled( + eservice.metadata.version, + updatedEservice, + correlationId + ), + toCreateEventEServiceIsClientAccessDelegableEnabled( + eservice.metadata.version + 1, + updatedEservice, + correlationId + ), + ] + ) + .with( + { + isDelegable: false, + oldIsDelegable: true, + }, + () => [ + toCreateEventEServiceIsDelegableDisabled( + eservice.metadata.version, + updatedEservice, + correlationId + ), + ] + ) + .with( + { + isDelegable: true, + oldIsDelegable: true, + isClientAccessDelegable: true, + oldIsClientAccessDelegable: false, + }, + () => [ + toCreateEventEServiceIsClientAccessDelegableEnabled( + eservice.metadata.version, + updatedEservice, + correlationId + ), + ] + ) + .with( + { + isDelegable: true, + oldIsDelegable: true, + isClientAccessDelegable: false, + oldIsClientAccessDelegable: true, + }, + () => [ + toCreateEventEServiceIsClientAccessDelegableDisabled( + eservice.metadata.version, + updatedEservice, + correlationId + ), + ] + ) + .with( + { + isDelegable: false, + oldIsDelegable: false, + }, + { + isClientAccessDelegable: true, + oldIsClientAccessDelegable: true, + }, + { + isClientAccessDelegable: false, + oldIsClientAccessDelegable: false, + }, + () => undefined + ) + .exhaustive(); + + if (events) { + await repository.createEvents(events); + } + + return updatedEservice; + }, async approveDelegatedEServiceDescriptor( eserviceId: EServiceId, descriptorId: DescriptorId, diff --git a/packages/catalog-process/src/utilities/errorMappers.ts b/packages/catalog-process/src/utilities/errorMappers.ts index c9ffe4efd1..e804006932 100644 --- a/packages/catalog-process/src/utilities/errorMappers.ts +++ b/packages/catalog-process/src/utilities/errorMappers.ts @@ -296,6 +296,16 @@ export const updateEServiceDescriptionErrorMapper = ( .with("eserviceWithoutValidDescriptors", () => HTTP_STATUS_CONFLICT) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); +export const updateEServiceFlagsErrorMapper = ( + error: ApiError +): number => + match(error.code) + .with("eServiceNotFound", () => HTTP_STATUS_NOT_FOUND) + .with("operationForbidden", () => HTTP_STATUS_FORBIDDEN) + .with("eserviceWithoutValidDescriptors", () => HTTP_STATUS_CONFLICT) + .with("invalidEServiceFlags", () => HTTP_STATUS_BAD_REQUEST) + .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); + export const updateDescriptorAttributesErrorMapper = ( error: ApiError ): number => diff --git a/packages/catalog-process/test/updateEserviceDelegationFlags.test.ts b/packages/catalog-process/test/updateEserviceDelegationFlags.test.ts new file mode 100644 index 0000000000..d5ce2667cf --- /dev/null +++ b/packages/catalog-process/test/updateEserviceDelegationFlags.test.ts @@ -0,0 +1,373 @@ +/* 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, + generateId, + delegationKind, + EServiceIsDelegableEnabledV2, + EServiceIsClientAccessDelegableEnabledV2, + EServiceIsDelegableDisabledV2, + EServiceIsClientAccessDelegableDisabledV2, +} from "pagopa-interop-models"; +import { expect, describe, it } from "vitest"; +import { + eserviceWithoutValidDescriptors, + eServiceNotFound, + invalidEServiceFlags, +} from "../src/model/domain/errors.js"; +import { + addOneEService, + catalogService, + getMockAuthData, + readLastEserviceEvent, + getMockDocument, + getMockDescriptor, + getMockEService, + addOneDelegation, +} from "./utils.js"; + +describe("update eService flags", () => { + it("should write on event-store for the update of the eService isDelegable flag (false -> true)", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + isDelegable: false, + }; + await addOneEService(eservice); + + const returnedEService = await catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const updatedEService: EService = { + ...eservice, + isDelegable: true, + isClientAccessDelegable: false, + }; + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceIsDelegableEnabled", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceIsDelegableEnabledV2, + 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 the eService isDelegable flag (true -> false)", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + isDelegable: true, + }; + await addOneEService(eservice); + + const returnedEService = await catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: false, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const updatedEService: EService = { + ...eservice, + isDelegable: false, + isClientAccessDelegable: false, + }; + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceIsDelegableDisabled", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceIsDelegableDisabledV2, + 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 the eService isClientAccessDelegable flag (false -> true)", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + isDelegable: true, + isClientAccessDelegable: false, + }; + await addOneEService(eservice); + + const returnedEService = await catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: true, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const updatedEService: EService = { + ...eservice, + isDelegable: true, + isClientAccessDelegable: true, + }; + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceIsClientAccessDelegableEnabled", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceIsClientAccessDelegableEnabledV2, + 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 the eService isClientAccessDelegable flag (true -> false)", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + isDelegable: true, + isClientAccessDelegable: true, + }; + await addOneEService(eservice); + + const returnedEService = await catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ); + + const updatedEService: EService = { + ...eservice, + isDelegable: true, + isClientAccessDelegable: false, + }; + + const writtenEvent = await readLastEserviceEvent(eservice.id); + expect(writtenEvent).toMatchObject({ + stream_id: eservice.id, + version: "1", + type: "EServiceIsClientAccessDelegableDisabled", + event_version: 2, + }); + const writtenPayload = decodeProtobufPayload({ + messageType: EServiceIsClientAccessDelegableDisabledV2, + 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(); + + expect( + catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(eServiceNotFound(eservice.id)); + }); + it("should throw operationForbidden if the requester is not the producer", async () => { + const eservice = getMockEService(); + await addOneEService(eservice); + + expect( + catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + 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 eservice = getMockEService(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, + eserviceId: eservice.id, + state: delegationState.active, + }); + + await addOneEService(eservice); + await addOneDelegation(delegation); + + expect( + catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(operationForbidden); + }); + it("should throw eserviceWithoutValidDescriptors if the eservice doesn't have any descriptors", async () => { + const eservice = getMockEService(); + await addOneEService(eservice); + + expect( + catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(eserviceWithoutValidDescriptors(eservice.id)); + }); + it.each([descriptorState.draft, descriptorState.archived])( + "should throw eserviceWithoutValidDescriptors if the eservice doesn't have valid descriptors (Descriptor with state %s)", + async (state) => { + const descriptor: Descriptor = { + ...getMockDescriptor(state), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + }; + await addOneEService(eservice); + + expect( + catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: true, + isClientAccessDelegable: false, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(eserviceWithoutValidDescriptors(eservice.id)); + } + ); + it("should throw invalidEServiceFlags if the isDelegable is false and isClientAccessDelegable is true", async () => { + const descriptor: Descriptor = { + ...getMockDescriptor(descriptorState.published), + interface: getMockDocument(), + }; + const eservice: EService = { + ...getMockEService(), + descriptors: [descriptor], + isDelegable: false, + }; + await addOneEService(eservice); + + expect( + catalogService.updateEServiceDelegationFlags( + eservice.id, + { + isDelegable: false, + isClientAccessDelegable: true, + }, + { + authData: getMockAuthData(eservice.producerId), + correlationId: generateId(), + serviceName: "", + logger: genericLogger, + } + ) + ).rejects.toThrowError(invalidEServiceFlags(eservice.id)); + }); +}); diff --git a/packages/catalog-readmodel-writer/src/consumerServiceV2.ts b/packages/catalog-readmodel-writer/src/consumerServiceV2.ts index 62591ebcae..38f0d590df 100644 --- a/packages/catalog-readmodel-writer/src/consumerServiceV2.ts +++ b/packages/catalog-readmodel-writer/src/consumerServiceV2.ts @@ -41,6 +41,10 @@ export async function handleMessageV2( { type: "EServiceRiskAnalysisUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceIsDelegableEnabled" }, + { type: "EServiceIsDelegableDisabled" }, + { type: "EServiceIsClientAccessDelegableEnabled" }, + { type: "EServiceIsClientAccessDelegableDisabled" }, { type: "EServiceDescriptorSubmittedByDelegate" }, { type: "EServiceDescriptorApprovedByDelegator" }, { type: "EServiceDescriptorRejectedByDelegator" }, diff --git a/packages/datalake-interface-exporter/src/interfaceExporterV2.ts b/packages/datalake-interface-exporter/src/interfaceExporterV2.ts index 33a45bb8e6..af63fb6e28 100644 --- a/packages/datalake-interface-exporter/src/interfaceExporterV2.ts +++ b/packages/datalake-interface-exporter/src/interfaceExporterV2.ts @@ -56,6 +56,10 @@ export async function exportInterfaceV2( { type: "EServiceRiskAnalysisUpdated" }, { type: "EServiceRiskAnalysisDeleted" }, { type: "EServiceDescriptionUpdated" }, + { type: "EServiceIsDelegableEnabled" }, + { type: "EServiceIsDelegableDisabled" }, + { type: "EServiceIsClientAccessDelegableEnabled" }, + { type: "EServiceIsClientAccessDelegableDisabled" }, { type: "EServiceDescriptorSubmittedByDelegate" }, { type: "EServiceDescriptorRejectedByDelegator" }, { type: "EServiceDescriptorAttributesUpdated" }, diff --git a/packages/models/proto/v2/eservice/events.proto b/packages/models/proto/v2/eservice/events.proto index 9fe2a962da..b222cdf045 100644 --- a/packages/models/proto/v2/eservice/events.proto +++ b/packages/models/proto/v2/eservice/events.proto @@ -133,6 +133,22 @@ message EServiceDescriptorRejectedByDelegatorV2 { EServiceV2 eservice = 2; } +message EServiceIsDelegableEnabledV2 { + EServiceV2 eservice = 1; +} + +message EServiceIsDelegableDisabledV2 { + EServiceV2 eservice = 1; +} + +message EServiceIsClientAccessDelegableEnabledV2 { + EServiceV2 eservice = 1; +} + +message EServiceIsClientAccessDelegableDisabledV2 { + EServiceV2 eservice = 1; +} + message EServiceDescriptorAttributesUpdatedV2 { string descriptorId = 1; repeated string attributeIds = 2; diff --git a/packages/models/src/eservice/eserviceEvents.ts b/packages/models/src/eservice/eserviceEvents.ts index 3c60456e52..144a061328 100644 --- a/packages/models/src/eservice/eserviceEvents.ts +++ b/packages/models/src/eservice/eserviceEvents.ts @@ -41,6 +41,10 @@ import { EServiceRiskAnalysisUpdatedV2, EServiceRiskAnalysisDeletedV2, EServiceDescriptionUpdatedV2, + EServiceIsDelegableEnabledV2, + EServiceIsDelegableDisabledV2, + EServiceIsClientAccessDelegableEnabledV2, + EServiceIsClientAccessDelegableDisabledV2, EServiceDescriptorSubmittedByDelegateV2, EServiceDescriptorApprovedByDelegatorV2, EServiceDescriptorRejectedByDelegatorV2, @@ -181,6 +185,18 @@ export function catalogEventToBinaryDataV2(event: EServiceEventV2): Uint8Array { .with({ type: "EServiceDescriptorAttributesUpdated" }, ({ data }) => EServiceDescriptorAttributesUpdatedV2.toBinary(data) ) + .with({ type: "EServiceIsDelegableEnabled" }, ({ data }) => + EServiceIsDelegableEnabledV2.toBinary(data) + ) + .with({ type: "EServiceIsDelegableDisabled" }, ({ data }) => + EServiceIsDelegableDisabledV2.toBinary(data) + ) + .with({ type: "EServiceIsClientAccessDelegableEnabled" }, ({ data }) => + EServiceIsClientAccessDelegableEnabledV2.toBinary(data) + ) + .with({ type: "EServiceIsClientAccessDelegableDisabled" }, ({ data }) => + EServiceIsClientAccessDelegableDisabledV2.toBinary(data) + ) .exhaustive(); } @@ -389,6 +405,26 @@ export const EServiceEventV2 = z.discriminatedUnion("type", [ type: z.literal("EServiceDescriptorAttributesUpdated"), data: protobufDecoder(EServiceDescriptorAttributesUpdatedV2), }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceIsDelegableEnabled"), + data: protobufDecoder(EServiceIsDelegableEnabledV2), + }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceIsDelegableDisabled"), + data: protobufDecoder(EServiceIsDelegableDisabledV2), + }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceIsClientAccessDelegableEnabled"), + data: protobufDecoder(EServiceIsClientAccessDelegableEnabledV2), + }), + z.object({ + event_version: z.literal(2), + type: z.literal("EServiceIsClientAccessDelegableDisabled"), + data: protobufDecoder(EServiceIsClientAccessDelegableDisabledV2), + }), ]); export type EServiceEventV2 = z.infer; diff --git a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts index 09d9fa2424..00b13722fb 100644 --- a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts +++ b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationConverter.ts @@ -91,6 +91,10 @@ export const toCatalogItemEventNotification = ( { type: "EServiceCloned" }, // ClonedCatalogItemV1AddedV1 { type: "DraftEServiceUpdated" }, // CatalogItemV1UpdatedV1 { type: "EServiceDescriptionUpdated" }, // CatalogItemV1UpdatedV1 + { type: "EServiceIsDelegableEnabled" }, + { type: "EServiceIsDelegableDisabled" }, + { type: "EServiceIsClientAccessDelegableEnabled" }, + { type: "EServiceIsClientAccessDelegableDisabled" }, (e): CatalogItemNotification => ({ catalogItem: getCatalogItem(e), }) diff --git a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts index 3d6f0354ee..3e396bdd42 100644 --- a/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts +++ b/packages/notifier-seeder/src/models/catalog/catalogItemEventNotificationMessage.ts @@ -12,6 +12,10 @@ export const eventV2TypeMapper = ( .with( "DraftEServiceUpdated", "EServiceDescriptionUpdated", + "EServiceIsDelegableEnabled", + "EServiceIsDelegableDisabled", + "EServiceIsClientAccessDelegableEnabled", + "EServiceIsClientAccessDelegableDisabled", () => "catalog_item_updated" ) .with( From 29c166378dd8f9ceb78e1e0f61aa1ac92acebc20 Mon Sep 17 00:00:00 2001 From: AsterITA Date: Wed, 15 Jan 2025 14:31:00 +0100 Subject: [PATCH 8/8] Improve purpose delegation handling (#1368) --- packages/api-clients/open-api/bffApi.yml | 214 +++++++++++++----- packages/api-clients/open-api/purposeApi.yml | 108 +++++++-- .../src/routers/PurposeRouter.ts | 3 +- .../src/services/purposeService.ts | 91 ++++---- .../src/services/readModelService.ts | 22 +- .../src/services/validators.ts | 20 +- .../src/utilities/errorMappers.ts | 34 ++- 7 files changed, 349 insertions(+), 143 deletions(-) diff --git a/packages/api-clients/open-api/bffApi.yml b/packages/api-clients/open-api/bffApi.yml index be0e9f3e94..ff718c0f02 100644 --- a/packages/api-clients/open-api/bffApi.yml +++ b/packages/api-clients/open-api/bffApi.yml @@ -4366,6 +4366,7 @@ paths: schema: $ref: "#/components/schemas/Problem" "404": + description: Not found headers: "X-Rate-Limit-Limit": schema: @@ -5572,6 +5573,63 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "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: Purpose 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" + "409": + description: Conflict + 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" "/catalog/eservices/{eserviceId}/descriptor/{descriptorId}": parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -6754,8 +6812,8 @@ paths: 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 - "400": - description: Bad request + "403": + description: Forbidden content: application/json: schema: @@ -7106,6 +7164,25 @@ paths: 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 + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + 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 "404": description: Purpose Not Found content: @@ -7183,6 +7260,25 @@ paths: 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 + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + 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 "404": description: Purpose Not Found content: @@ -7395,25 +7491,6 @@ paths: application/json: schema: $ref: "#/components/schemas/Purpose" - "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" "404": description: Purpose Not Found headers: @@ -7456,25 +7533,6 @@ paths: 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 - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" - 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 "403": description: Forbidden content: @@ -7584,6 +7642,63 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "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: Purpose 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" + "409": + description: Conflict + 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" /purposes/{purposeId}/versions/{versionId}: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -7621,25 +7736,6 @@ paths: 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 - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" - 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 "403": description: Forbidden content: diff --git a/packages/api-clients/open-api/purposeApi.yml b/packages/api-clients/open-api/purposeApi.yml index 832b89e19b..5d2e995b62 100644 --- a/packages/api-clients/open-api/purposeApi.yml +++ b/packages/api-clients/open-api/purposeApi.yml @@ -225,6 +225,24 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "403": + description: Organization not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: Purpose Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "409": + description: Duplicated purpose title + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /purposes/{id}: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -247,12 +265,6 @@ paths: application/json: schema: $ref: "#/components/schemas/Purpose" - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" "404": description: Purpose Not Found content: @@ -285,6 +297,24 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "403": + description: Organization not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: Purpose Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "409": + description: Duplicated purpose title + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" delete: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -295,12 +325,6 @@ paths: responses: "204": description: Purpose deleted - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/components/schemas/Problem" "403": description: Purpose has at least one version that is not in draft state or more than one version content: @@ -313,6 +337,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "409": + description: Conflict + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /purposes/{purposeId}/versions: parameters: - $ref: "#/components/parameters/CorrelationIdHeader" @@ -341,6 +371,24 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "403": + description: Organization not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "404": + description: Purpose Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "409": + description: Conflict + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" requestBody: content: application/json: @@ -368,8 +416,8 @@ paths: responses: "204": description: Purpose Version Deleted - "400": - description: Bad Request + "403": + description: Organization not allowed content: application/json: schema: @@ -380,6 +428,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "409": + description: Version cannot be deleted + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" tags: - purpose description: deletes the purpose version by id @@ -419,14 +473,14 @@ paths: application/json: schema: $ref: "#/components/schemas/PurposeVersionDocument" - "404": - description: Resource not found + "403": + description: Organization not allowed content: application/json: schema: $ref: "#/components/schemas/Problem" - "400": - description: Bad request + "404": + description: Document Not Found content: application/json: schema: @@ -525,6 +579,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "403": + description: Organization not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" "404": description: Purpose Not Found content: @@ -611,6 +671,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "403": + description: Organization not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" "404": description: Purpose Not Found content: @@ -651,6 +717,12 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + "403": + description: Organization not allowed + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" "404": description: Purpose Not Found content: diff --git a/packages/purpose-process/src/routers/PurposeRouter.ts b/packages/purpose-process/src/routers/PurposeRouter.ts index 236a4c3ae8..3ff24bd771 100644 --- a/packages/purpose-process/src/routers/PurposeRouter.ts +++ b/packages/purpose-process/src/routers/PurposeRouter.ts @@ -40,6 +40,7 @@ import { suspendPurposeVersionErrorMapper, updatePurposeErrorMapper, updateReversePurposeErrorMapper, + getPurposesErrorMapper, } from "../utilities/errorMappers.js"; import { purposeServiceBuilder } from "../services/purposeService.js"; @@ -126,7 +127,7 @@ const purposeRouter = ( } catch (error) { const errorRes = makeApiProblem( error, - () => 500, + getPurposesErrorMapper, ctx.logger, ctx.correlationId ); diff --git a/packages/purpose-process/src/services/purposeService.ts b/packages/purpose-process/src/services/purposeService.ts index acb277585e..501a102c24 100644 --- a/packages/purpose-process/src/services/purposeService.ts +++ b/packages/purpose-process/src/services/purposeService.ts @@ -322,9 +322,10 @@ export function purposeServiceBuilder( assertRequesterCanActAsConsumer( purpose.data, authData, - await readModelService.getActiveConsumerDelegationByPurpose( - purpose.data - ) + purpose.data.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.data.delegationId + )) ); const purposeVersion = retrievePurposeVersion(versionId, purpose); @@ -454,9 +455,10 @@ export function purposeServiceBuilder( assertRequesterCanActAsConsumer( purpose.data, authData, - await readModelService.getActiveConsumerDelegationByPurpose( - purpose.data - ) + purpose.data.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.data.delegationId + )) ); const event = purposeIsDraft(purpose.data) @@ -490,9 +492,10 @@ export function purposeServiceBuilder( assertRequesterCanActAsConsumer( purpose.data, authData, - await readModelService.getActiveConsumerDelegationByPurpose( - purpose.data - ) + purpose.data.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.data.delegationId + )) ); const purposeVersion = retrievePurposeVersion(versionId, purpose); @@ -552,9 +555,8 @@ export function purposeServiceBuilder( ); const suspender = await getOrganizationRole({ - eservice, + purpose: purpose.data, producerId: eservice.producerId, - consumerId: purpose.data.consumerId, authData, readModelService, }); @@ -624,10 +626,9 @@ export function purposeServiceBuilder( eservice, { organizationId }, readModelService - ).then( - () => true, - () => false - ); + ) + .then(() => true) + .catch(() => false); return { purpose, isAllowedToRetrieveRiskAnalysis }; }) @@ -659,9 +660,10 @@ export function purposeServiceBuilder( assertRequesterCanActAsConsumer( purpose.data, authData, - await readModelService.getActiveConsumerDelegationByPurpose( - purpose.data - ) + purpose.data.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.data.delegationId + )) ); const previousVersion = [ @@ -825,9 +827,8 @@ export function purposeServiceBuilder( } const purposeOwnership = await getOrganizationRole({ - eservice, + purpose: purpose.data, producerId: eservice.producerId, - consumerId: purpose.data.consumerId, authData, readModelService, }); @@ -1020,10 +1021,12 @@ export function purposeServiceBuilder( ); const consumerDelegation = - await readModelService.getActiveConsumerDelegationByPurpose({ - eserviceId, - consumerId, - }); + await readModelService.getActiveConsumerDelegationByEserviceAndConsumerIds( + { + eserviceId, + consumerId, + } + ); assertRequesterCanActAsConsumer( { eserviceId, consumerId }, @@ -1084,10 +1087,12 @@ export function purposeServiceBuilder( const consumerId: TenantId = unsafeBrandId(seed.consumerId); const consumerDelegation = - await readModelService.getActiveConsumerDelegationByPurpose({ - eserviceId, - consumerId, - }); + await readModelService.getActiveConsumerDelegationByEserviceAndConsumerIds( + { + eserviceId, + consumerId, + } + ); assertRequesterCanActAsConsumer( { eserviceId, consumerId }, @@ -1335,40 +1340,41 @@ export function purposeServiceBuilder( export type PurposeService = ReturnType; const getOrganizationRole = async ({ - eservice, + purpose, producerId, - consumerId, authData, readModelService, }: { - eservice: EService; + purpose: Purpose; producerId: TenantId; - consumerId: TenantId; authData: AuthData; readModelService: ReadModelService; }): Promise => { - if (producerId === consumerId && authData.organizationId === producerId) { + if ( + producerId === purpose.consumerId && + authData.organizationId === producerId + ) { return ownership.SELF_CONSUMER; } try { assertRequesterCanActAsProducer( - eservice, + { id: purpose.eserviceId, producerId }, authData, await readModelService.getActiveProducerDelegationByEserviceId( - eservice.id + purpose.eserviceId ) ); return ownership.PRODUCER; } catch { try { assertRequesterCanActAsConsumer( - { eserviceId: eservice.id, consumerId }, + purpose, authData, - await readModelService.getActiveConsumerDelegationByPurpose({ - eserviceId: eservice.id, - consumerId, - }) + purpose.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.delegationId + )) ); return ownership.CONSUMER; } catch { @@ -1451,7 +1457,10 @@ const performUpdatePurpose = async ( assertRequesterCanActAsConsumer( purpose.data, authData, - await readModelService.getActiveConsumerDelegationByPurpose(purpose.data) + purpose.data.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.data.delegationId + )) ); const eservice = await retrieveEService( diff --git a/packages/purpose-process/src/services/readModelService.ts b/packages/purpose-process/src/services/readModelService.ts index c41e99672b..26854e5e10 100644 --- a/packages/purpose-process/src/services/readModelService.ts +++ b/packages/purpose-process/src/services/readModelService.ts @@ -28,6 +28,7 @@ import { Delegation, delegationKind, DelegationReadModel, + DelegationId, } from "pagopa-interop-models"; import { Document, Filter, WithId } from "mongodb"; import { z } from "zod"; @@ -406,12 +407,25 @@ export function readModelServiceBuilder( "data.kind": delegationKind.delegatedProducer, }); }, - async getActiveConsumerDelegationByPurpose( - purpose: Pick + async getActiveConsumerDelegationByEserviceAndConsumerIds({ + eserviceId, + consumerId, + }: { + eserviceId: EServiceId; + consumerId: TenantId; + }): Promise { + return getDelegation(delegations, { + "data.eserviceId": eserviceId, + "data.delegatorId": consumerId, + "data.state": delegationState.active, + "data.kind": delegationKind.delegatedConsumer, + }); + }, + async getActiveConsumerDelegationByDelegationId( + delegationId: DelegationId ): Promise { return getDelegation(delegations, { - "data.eserviceId": purpose.eserviceId, - "data.delegatorId": purpose.consumerId, + "data.delegationId": delegationId, "data.state": delegationState.active, "data.kind": delegationKind.delegatedConsumer, }); diff --git a/packages/purpose-process/src/services/validators.ts b/packages/purpose-process/src/services/validators.ts index 50e3b5a3d0..45b5b8ac31 100644 --- a/packages/purpose-process/src/services/validators.ts +++ b/packages/purpose-process/src/services/validators.ts @@ -267,10 +267,10 @@ export const assertRequesterIsAllowedToRetrieveRiskAnalysisDocument = async ( // can be performed also by the producer/consumer even when active producer/consumer delegations exist try { assertRequesterIsConsumer(purpose, authData); - } catch (error) { + } catch { try { assertRequesterIsProducer(eservice, authData); - } catch (error) { + } catch { try { const activeProducerDelegation = await readModelService.getActiveProducerDelegationByEserviceId( @@ -281,17 +281,15 @@ export const assertRequesterIsAllowedToRetrieveRiskAnalysisDocument = async ( authData, activeProducerDelegation ); - } catch (error) { + } catch { try { - const activeConsumerDelegation = - await readModelService.getActiveConsumerDelegationByPurpose( - purpose - ); - assertRequesterIsDelegateConsumer( purpose, authData, - activeConsumerDelegation + purpose.delegationId && + (await readModelService.getActiveConsumerDelegationByDelegationId( + purpose.delegationId + )) ); } catch { throw organizationNotAllowed(authData.organizationId); @@ -311,7 +309,7 @@ const assertRequesterIsProducer = ( }; const assertRequesterIsDelegateProducer = ( - eservice: EService, + eservice: Pick, authData: Pick, activeProducerDelegation: Delegation | undefined ): void => { @@ -330,7 +328,7 @@ const assertRequesterIsDelegateProducer = ( }; export const assertRequesterCanActAsProducer = ( - eservice: EService, + eservice: Pick, authData: AuthData, activeProducerDelegation: Delegation | undefined ): void => { diff --git a/packages/purpose-process/src/utilities/errorMappers.ts b/packages/purpose-process/src/utilities/errorMappers.ts index a3c1b6a831..6980be1f60 100644 --- a/packages/purpose-process/src/utilities/errorMappers.ts +++ b/packages/purpose-process/src/utilities/errorMappers.ts @@ -18,6 +18,11 @@ export const getPurposeErrorMapper = (error: ApiError): number => .with("purposeNotFound", () => HTTP_STATUS_NOT_FOUND) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); +export const getPurposesErrorMapper = (error: ApiError): number => + match(error.code) + .with("eserviceNotFound", () => HTTP_STATUS_BAD_REQUEST) + .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); + export const getRiskAnalysisDocumentErrorMapper = ( error: ApiError ): number => @@ -28,7 +33,14 @@ export const getRiskAnalysisDocumentErrorMapper = ( "purposeVersionDocumentNotFound", () => HTTP_STATUS_NOT_FOUND ) - .with("organizationNotAllowed", () => HTTP_STATUS_FORBIDDEN) + .with( + "organizationNotAllowed", + "organizationIsNotTheConsumer", + "organizationIsNotTheProducer", + "organizationIsNotTheDelegatedProducer", + "organizationIsNotTheDelegatedConsumer", + () => HTTP_STATUS_FORBIDDEN + ) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); export const deletePurposeVersionErrorMapper = ( @@ -42,7 +54,7 @@ export const deletePurposeVersionErrorMapper = ( ) .with( "organizationIsNotTheConsumer", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with("purposeVersionCannotBeDeleted", () => HTTP_STATUS_CONFLICT) @@ -57,7 +69,11 @@ export const rejectPurposeVersionErrorMapper = ( "purposeVersionNotFound", () => HTTP_STATUS_NOT_FOUND ) - .with("organizationIsNotTheProducer", () => HTTP_STATUS_FORBIDDEN) + .with( + "organizationIsNotTheProducer", + "organizationIsNotTheDelegatedProducer", + () => HTTP_STATUS_FORBIDDEN + ) .with("notValidVersionState", () => HTTP_STATUS_BAD_REQUEST) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); @@ -72,7 +88,7 @@ export const updatePurposeErrorMapper = (error: ApiError): number => .with( "organizationIsNotTheConsumer", "purposeNotInDraftState", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with("purposeNotFound", () => HTTP_STATUS_NOT_FOUND) @@ -86,7 +102,7 @@ export const deletePurposeErrorMapper = (error: ApiError): number => .with("purposeNotFound", () => HTTP_STATUS_NOT_FOUND) .with( "organizationIsNotTheConsumer", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with("purposeCannotBeDeleted", () => HTTP_STATUS_CONFLICT) @@ -103,7 +119,7 @@ export const archivePurposeVersionErrorMapper = ( ) .with( "organizationIsNotTheConsumer", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with("notValidVersionState", () => HTTP_STATUS_BAD_REQUEST) @@ -129,7 +145,7 @@ export const createPurposeVersionErrorMapper = ( .with("unchangedDailyCalls", () => HTTP_STATUS_BAD_REQUEST) .with( "organizationIsNotTheConsumer", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with("purposeVersionStateConflict", () => HTTP_STATUS_CONFLICT) @@ -140,7 +156,7 @@ export const createPurposeErrorMapper = (error: ApiError): number => match(error.code) .with( "organizationIsNotTheConsumer", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with( @@ -158,7 +174,7 @@ export const createReversePurposeErrorMapper = ( match(error.code) .with( "organizationIsNotTheConsumer", - "organizationNotAllowed", + "organizationIsNotTheDelegatedConsumer", () => HTTP_STATUS_FORBIDDEN ) .with(