diff --git a/collections/collection.bru b/collections/collection.bru index 2597da5bd6..5c993b3ebd 100644 --- a/collections/collection.bru +++ b/collections/collection.bru @@ -1,5 +1,6 @@ vars:pre-request { tenantId: 69e2865e-65ab-4e48-a638-2037a9ee2ee7 + tenantId2: 0cf1db41-3085-43a6-9e4c-57e0fb81a916 userId1: f07ddb8f-17f9-47d4-b31e-35d1ac10e521 userId2: 2a1614d7-c1aa-4148-895f-dcadb75b6660 keyEncodedPem: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FETUVrdEZVVyszY2VWN01FT2RBVG5ZYWc3eQpJc3JtRDZ6eVBTZHhJTDlJczNmdnlGREMvbnVCWVFpa3Izampjc21aREdTN0RGKzNWRUJ3UXFYUldGM3NObElRCnFIc21Td2x2NjZ2ZDQ0OHEzSXpSb1JBWktGMGc3c3BGcUJ5bi9DTXZaM0RET2xVK2V0c2xDYWRNa084UktyM1YKd2xqQjFJdk90TWtCd2lLTU53SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo= diff --git a/collections/delegation/Delegation Process Micro Service/producer/List producer delegations.bru b/collections/delegation/List delegations.bru similarity index 79% rename from collections/delegation/Delegation Process Micro Service/producer/List producer delegations.bru rename to collections/delegation/List delegations.bru index 733ec7c274..a7503108d1 100644 --- a/collections/delegation/Delegation Process Micro Service/producer/List producer delegations.bru +++ b/collections/delegation/List delegations.bru @@ -1,5 +1,5 @@ meta { - name: List producer delegations + name: List delegations type: http seq: 1 } @@ -24,3 +24,7 @@ headers { Authorization: {{JWT}} X-Correlation-Id: {{correlation-id}} } + +vars:post-response { + delegationId: res.body.results.at(-1).id +} diff --git a/collections/delegation/Delegation Process Micro Service/producer/Retrieves a delegation.bru b/collections/delegation/Retrieves a delegation.bru similarity index 82% rename from collections/delegation/Delegation Process Micro Service/producer/Retrieves a delegation.bru rename to collections/delegation/Retrieves a delegation.bru index 4e9425bd6d..398d20875c 100644 --- a/collections/delegation/Delegation Process Micro Service/producer/Retrieves a delegation.bru +++ b/collections/delegation/Retrieves a delegation.bru @@ -11,7 +11,7 @@ get { } params:path { - delegationId: 123e4567-e89b-12d3-a456-426614174000 + delegationId: {{delegationId}} } headers { diff --git a/collections/delegation/consumer/Delegation Creation.bru b/collections/delegation/consumer/Delegation Creation.bru new file mode 100644 index 0000000000..a927ca6197 --- /dev/null +++ b/collections/delegation/consumer/Delegation Creation.bru @@ -0,0 +1,27 @@ +meta { + name: Delegation Creation + type: http + seq: 1 +} + +post { + url: {{host-delegation}}/consumer/delegations + body: json + auth: none +} + +headers { + Authorization: {{JWT}} + X-Correlation-Id: {{correlation-id}} +} + +body:json { + { + "eserviceId": "{{eserviceId}}", + "delegateId": "{{tenantId2}}" + } +} + +vars:post-response { + delegationId: res.body.id +} diff --git a/collections/delegation/Delegation Process Micro Service/health/Health status endpoint.bru b/collections/delegation/health/Health status endpoint.bru similarity index 100% rename from collections/delegation/Delegation Process Micro Service/health/Health status endpoint.bru rename to collections/delegation/health/Health status endpoint.bru diff --git a/collections/delegation/Delegation Process Micro Service/producer/Approves a delegation.bru b/collections/delegation/producer/Approves a delegation.bru similarity index 76% rename from collections/delegation/Delegation Process Micro Service/producer/Approves a delegation.bru rename to collections/delegation/producer/Approves a delegation.bru index 23bd4e2e56..3f94f781fd 100644 --- a/collections/delegation/Delegation Process Micro Service/producer/Approves a delegation.bru +++ b/collections/delegation/producer/Approves a delegation.bru @@ -1,7 +1,7 @@ meta { name: Approves a delegation type: http - seq: 4 + seq: 2 } post { @@ -11,10 +11,10 @@ post { } params:path { - delegationId: + delegationId: {{delegationId}} } headers { - Authorization: {{JWT}} + Authorization: {{JWT2}} X-Correlation-Id: {{correlation-id}} } diff --git a/collections/delegation/Delegation Process Micro Service/producer/Delegation Creation.bru b/collections/delegation/producer/Delegation Creation.bru similarity index 64% rename from collections/delegation/Delegation Process Micro Service/producer/Delegation Creation.bru rename to collections/delegation/producer/Delegation Creation.bru index fa36a2e7e5..4e1656fadf 100644 --- a/collections/delegation/Delegation Process Micro Service/producer/Delegation Creation.bru +++ b/collections/delegation/producer/Delegation Creation.bru @@ -1,7 +1,7 @@ meta { name: Delegation Creation type: http - seq: 3 + seq: 1 } post { @@ -17,7 +17,11 @@ headers { body:json { { - "eserviceId": "", - "delegateId": "" + "eserviceId": "{{eserviceId}}", + "delegateId": "{{tenantId2}}" } } + +vars:post-response { + delegationId: res.body.id +} diff --git a/collections/delegation/Delegation Process Micro Service/producer/Rejects a delegation.bru b/collections/delegation/producer/Rejects a delegation.bru similarity index 68% rename from collections/delegation/Delegation Process Micro Service/producer/Rejects a delegation.bru rename to collections/delegation/producer/Rejects a delegation.bru index b2b1477d62..57f0709361 100644 --- a/collections/delegation/Delegation Process Micro Service/producer/Rejects a delegation.bru +++ b/collections/delegation/producer/Rejects a delegation.bru @@ -1,7 +1,7 @@ meta { name: Rejects a delegation type: http - seq: 5 + seq: 3 } post { @@ -11,16 +11,16 @@ post { } params:path { - delegationId: + delegationId: {{delegationId}} } headers { - Authorization: {{JWT}} + Authorization: {{JWT2}} X-Correlation-Id: {{correlation-id}} } body:json { { - "rejectionReason": "" + "rejectionReason": "test rejection reason" } } diff --git a/collections/delegation/Delegation Process Micro Service/producer/Revoke a delegation.bru b/collections/delegation/producer/Revoke a delegation.bru similarity index 79% rename from collections/delegation/Delegation Process Micro Service/producer/Revoke a delegation.bru rename to collections/delegation/producer/Revoke a delegation.bru index 69741abb1d..392a524b0d 100644 --- a/collections/delegation/Delegation Process Micro Service/producer/Revoke a delegation.bru +++ b/collections/delegation/producer/Revoke a delegation.bru @@ -1,7 +1,7 @@ meta { name: Revoke a delegation type: http - seq: 6 + seq: 4 } delete { @@ -10,6 +10,10 @@ delete { auth: none } +params:path { + delegationId: {{delegationId}} +} + headers { Authorization: {{JWT}} X-Correlation-Id: {{correlation-id}} diff --git a/collections/environments/PagoPA local.bru b/collections/environments/PagoPA local.bru index f0051f88bb..30c756afeb 100644 --- a/collections/environments/PagoPA local.bru +++ b/collections/environments/PagoPA local.bru @@ -12,4 +12,5 @@ vars { host-delegation: http://localhost:3800 host-api-gw: http://localhost:3700/api-gateway/0.0 JWT-M2M: Bearer {{process.env.JWT-M2M}} + JWT2: Bearer {{process.env.JWT2}} } diff --git a/packages/api-clients/open-api/delegationApi.yml b/packages/api-clients/open-api/delegationApi.yml index c87eeff3b2..9b3243c0fb 100644 --- a/packages/api-clients/open-api/delegationApi.yml +++ b/packages/api-clients/open-api/delegationApi.yml @@ -33,6 +33,8 @@ tags: url: http://swagger.io paths: /delegations: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" get: description: List delegations summary: List delegations @@ -118,19 +120,21 @@ paths: schema: $ref: "#/components/schemas/Problem" /delegations/{delegationId}: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - name: delegationId + in: path + description: The delegation id + required: true + schema: + type: string + format: uuid get: description: Retrieves a delegation summary: Retrieves a delegation tags: - delegation operationId: getDelegation - parameters: - - name: delegationId - in: path - description: The delegation id - required: true - schema: - type: string responses: "200": description: Delegation retrieved @@ -156,10 +160,53 @@ paths: application/json: schema: $ref: "#/components/schemas/Problem" + /consumer/delegations: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + post: + description: Creates a consumer delegation + summary: Consumer Delegation Creation + tags: + - consumer + operationId: createConsumerDelegation + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/DelegationSeed" + description: Payload for delegation creation + required: true + responses: + "200": + description: Delegation created. + content: + application/json: + schema: + $ref: "#/components/schemas/Delegation" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Problem" /producer/delegations: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" post: - description: creates the delegation - summary: Delegation Creation + description: Creates a producer delegation + summary: Producer delegation creation tags: - producer operationId: createProducerDelegation @@ -168,7 +215,7 @@ paths: application/json: schema: $ref: "#/components/schemas/DelegationSeed" - description: payload for delegation creation + description: Payload for delegation creation required: true responses: "200": @@ -196,20 +243,21 @@ paths: schema: $ref: "#/components/schemas/Problem" /producer/delegations/{delegationId}/approve: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - name: delegationId + in: path + description: The delegation id + required: true + schema: + type: string + format: uuid post: - description: Approves a delegation - summary: Approves a delegation + description: Approves a producer delegation + summary: Producer delegation approval tags: - producer operationId: approveProducerDelegation - parameters: - - name: delegationId - in: path - description: The delegation id - required: true - schema: - type: string - format: uuid responses: "204": description: Delegation approved @@ -238,9 +286,18 @@ paths: schema: $ref: "#/components/schemas/Problem" /producer/delegations/{delegationId}/reject: + parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" + - name: delegationId + in: path + description: The delegation id + required: true + schema: + type: string + format: uuid post: - description: Rejects a delegation - summary: Rejects a delegation + description: Rejects a producer delegation + summary: Producer delegation rejection tags: - producer operationId: rejectProducerDelegation @@ -251,14 +308,6 @@ paths: $ref: "#/components/schemas/RejectDelegationPayload" required: true description: payload for delegation rejection - parameters: - - name: delegationId - in: path - description: The delegation id - required: true - schema: - type: string - format: uuid responses: "204": description: Delegation rejected @@ -288,15 +337,17 @@ paths: $ref: "#/components/schemas/Problem" /producer/delegations/{delegationId}: parameters: + - $ref: "#/components/parameters/CorrelationIdHeader" - name: delegationId in: path description: The delegation id required: true schema: type: string + format: uuid delete: - description: Revokes a delegation - summary: Revokes a delegation + description: Revokes a producer delegation + summary: Producer delegation revocation tags: - producer operationId: revokeProducerDelegation @@ -337,6 +388,13 @@ paths: schema: $ref: "#/components/schemas/Problem" components: + parameters: + CorrelationIdHeader: + in: header + name: X-Correlation-Id + required: true + schema: + type: string schemas: Delegations: type: object diff --git a/packages/commons-test/src/testUtils.ts b/packages/commons-test/src/testUtils.ts index 1b1a0051e9..7fa8394f79 100644 --- a/packages/commons-test/src/testUtils.ts +++ b/packages/commons-test/src/testUtils.ts @@ -49,7 +49,6 @@ import { PurposeVersionId, ProducerKeychain, Delegation, - delegationKind, DelegationId, DelegationContractDocument, DelegationContractId, @@ -67,6 +66,7 @@ import { PlatformStatesClientPK, PlatformStatesClientEntry, makePlatformStatesClientPK, + DelegationKind, } from "pagopa-interop-models"; import { AuthData } from "pagopa-interop-commons"; import { z } from "zod"; @@ -367,7 +367,8 @@ export const getMockAuthData = (organizationId?: TenantId): AuthData => ({ selfcareId: generateId(), }); -export const getMockDelegationProducer = ({ +export const getMockDelegation = ({ + kind, id = generateId(), delegatorId = generateId(), delegateId = generateId(), @@ -376,6 +377,7 @@ export const getMockDelegationProducer = ({ activationContract = undefined, revocationContract = undefined, }: { + kind: DelegationKind; id?: DelegationId; delegatorId?: TenantId; delegateId?: TenantId; @@ -383,7 +385,7 @@ export const getMockDelegationProducer = ({ state?: DelegationState; activationContract?: DelegationContractDocument; revocationContract?: DelegationContractDocument; -} = {}): Delegation => { +}): Delegation => { const creationTime = new Date(); return { @@ -396,7 +398,7 @@ export const getMockDelegationProducer = ({ state, activationContract, revocationContract, - kind: delegationKind.delegatedProducer, + kind, stamps: { submission: { who: delegatorId, diff --git a/packages/delegation-process/src/app.ts b/packages/delegation-process/src/app.ts index f111eb52fa..f742f9109c 100644 --- a/packages/delegation-process/src/app.ts +++ b/packages/delegation-process/src/app.ts @@ -4,12 +4,35 @@ import { loggerMiddleware, zodiosCtx, buildJwksClients, + initDB, + initFileManager, + initPDFGenerator, + ReadModelRepository, } from "pagopa-interop-commons"; import healthRouter from "./routers/HealthRouter.js"; import delegationProducerRouter from "./routers/DelegationProducerRouter.js"; import delegationRouter from "./routers/DelegationRouter.js"; import { config } from "./config/config.js"; +import { readModelServiceBuilder } from "./services/readModelService.js"; +import delegationConsumerRouter from "./routers/DelegationConsumerRouter.js"; + +const readModelService = readModelServiceBuilder( + ReadModelRepository.init(config) +); + +const pdfGenerator = await initPDFGenerator(); +const fileManager = initFileManager(config); + +const eventStore = initDB({ + username: config.eventStoreDbUsername, + password: config.eventStoreDbPassword, + host: config.eventStoreDbHost, + port: config.eventStoreDbPort, + database: config.eventStoreDbName, + schema: config.eventStoreDbSchema, + useSSL: config.eventStoreDbUseSSL, +}); const serviceName = "delegation-process"; @@ -25,7 +48,16 @@ app.use(healthRouter); app.use(contextMiddleware(serviceName)); app.use(authenticationMiddleware(config, jwksClients)); app.use(loggerMiddleware(serviceName)); -app.use(delegationRouter(zodiosCtx)); -app.use(delegationProducerRouter(zodiosCtx)); +app.use(delegationRouter(zodiosCtx, readModelService)); +app.use( + delegationProducerRouter( + zodiosCtx, + eventStore, + readModelService, + pdfGenerator, + fileManager + ) +); +app.use(delegationConsumerRouter(zodiosCtx, eventStore, readModelService)); export default app; diff --git a/packages/delegation-process/src/model/domain/errors.ts b/packages/delegation-process/src/model/domain/errors.ts index b5b0064bb6..f0d36a02a6 100644 --- a/packages/delegation-process/src/model/domain/errors.ts +++ b/packages/delegation-process/src/model/domain/errors.ts @@ -5,15 +5,18 @@ import { makeApiProblemBuilder, TenantId, DelegationState, + DelegationKind, + Tenant, } from "pagopa-interop-models"; +import { match } from "ts-pattern"; export const errorCodes = { delegationNotFound: "0001", eserviceNotFound: "0002", delegationAlreadyExists: "0003", tenantNotFound: "0004", - invalidDelegatorAndDelegateIds: "0005", - invalidExternalOriginId: "0006", + delegatorAndDelegateSameId: "0005", + tenantIsNotIPAError: "0006", tenantNotAllowedToDelegation: "0007", delegationNotRevokable: "0008", operationNotAllowOnDelegation: "0009", @@ -65,26 +68,32 @@ export function tenantNotFound(tenantId: TenantId): ApiError { export function delegatorAndDelegateSameIdError(): ApiError { return new ApiError({ detail: `Error occurs because Delegator and Delegate have the same Id`, - code: "invalidDelegatorAndDelegateIds", - title: "Invalid Delegator and Delegate", + code: "delegatorAndDelegateSameId", + title: "Delegator and Delegate have the same Id", }); } -export function invalidExternalOriginError( - externalOrigin?: string +export function tenantIsNotIPAError( + tenant: Tenant, + delegatorOrDelegate: "Delegator" | "Delegate" ): ApiError { + const delegatorOrDelegateString = match(delegatorOrDelegate) + .with("Delegator", () => "Delegator") + .with("Delegate", () => "Delegate") + .exhaustive(); return new ApiError({ - detail: `Delegator is not an IPA`, - code: "invalidExternalOriginId", - title: `Invalid External origin ${externalOrigin}`, + detail: `${delegatorOrDelegateString} ${tenant.id} with external origin ${tenant.externalId.origin} is not an IPA`, + code: "tenantIsNotIPAError", + title: `Invalid external origin`, }); } export function tenantNotAllowedToDelegation( - tenantId: string + tenantId: string, + kind: DelegationKind ): ApiError { return new ApiError({ - detail: `Tenant ${tenantId} not allowed to delegation`, + detail: `Tenant ${tenantId} not allowed to receive delegations of kind: ${kind}`, code: "tenantNotAllowedToDelegation", title: "Tenant not allowed to delegation", }); diff --git a/packages/delegation-process/src/model/domain/toEvent.ts b/packages/delegation-process/src/model/domain/toEvent.ts index 8241c4daf9..ebe0574764 100644 --- a/packages/delegation-process/src/model/domain/toEvent.ts +++ b/packages/delegation-process/src/model/domain/toEvent.ts @@ -79,3 +79,21 @@ export function toCreateEventProducerDelegationRejected( correlationId, }; } + +export function toCreateEventConsumerDelegationSubmitted( + delegation: Delegation, + correlationId: CorrelationId +): CreateEvent { + return { + streamId: delegation.id, + version: 0, + event: { + type: "ConsumerDelegationSubmitted", + event_version: 2, + data: { + delegation: toDelegationV2(delegation), + }, + }, + correlationId, + }; +} diff --git a/packages/delegation-process/src/routers/DelegationConsumerRouter.ts b/packages/delegation-process/src/routers/DelegationConsumerRouter.ts new file mode 100644 index 0000000000..28b90c5689 --- /dev/null +++ b/packages/delegation-process/src/routers/DelegationConsumerRouter.ts @@ -0,0 +1,69 @@ +import { ZodiosRouter } from "@zodios/express"; +import { delegationApi } from "pagopa-interop-api-clients"; +import { + ZodiosContext, + ExpressContext, + zodiosValidationErrorToApiProblem, + authorizationMiddleware, + userRoles, + fromAppContext, + DB, +} from "pagopa-interop-commons"; +import { makeApiProblem } from "../model/domain/errors.js"; +import { delegationToApiDelegation } from "../model/domain/apiConverter.js"; +import { delegationConsumerServiceBuilder } from "../services/delegationConsumerService.js"; +import { ReadModelService } from "../services/readModelService.js"; +import { createConsumerDelegationErrorMapper } from "../utilities/errorMappers.js"; + +const { ADMIN_ROLE } = userRoles; + +const delegationConsumerRouter = ( + ctx: ZodiosContext, + eventStore: DB, + readModelService: ReadModelService +): ZodiosRouter => { + const delegationConsumerRouter = ctx.router(delegationApi.consumerApi.api, { + validationErrorHandler: zodiosValidationErrorToApiProblem, + }); + + const delegationConsumerService = delegationConsumerServiceBuilder( + eventStore, + readModelService + ); + + delegationConsumerRouter.post( + "/consumer/delegations", + authorizationMiddleware([ADMIN_ROLE]), + async (req, res) => { + const ctx = fromAppContext(req.ctx); + + try { + const delegation = + await delegationConsumerService.createConsumerDelegation( + req.body, + ctx + ); + return res + .status(200) + .json( + delegationApi.Delegation.parse( + delegationToApiDelegation(delegation) + ) + ); + } catch (error) { + const errorRes = makeApiProblem( + error, + createConsumerDelegationErrorMapper, + ctx.logger, + ctx.correlationId + ); + + return res.status(errorRes.status).send(errorRes); + } + } + ); + + return delegationConsumerRouter; +}; + +export default delegationConsumerRouter; diff --git a/packages/delegation-process/src/routers/DelegationProducerRouter.ts b/packages/delegation-process/src/routers/DelegationProducerRouter.ts index f9fbe342b3..dd2f883153 100644 --- a/packages/delegation-process/src/routers/DelegationProducerRouter.ts +++ b/packages/delegation-process/src/routers/DelegationProducerRouter.ts @@ -3,18 +3,16 @@ import { delegationApi } from "pagopa-interop-api-clients"; import { ExpressContext, fromAppContext, - ReadModelRepository, userRoles, ZodiosContext, zodiosValidationErrorToApiProblem, authorizationMiddleware, - initDB, - initPDFGenerator, - initFileManager, + PDFGenerator, + FileManager, + DB, } from "pagopa-interop-commons"; import { unsafeBrandId } from "pagopa-interop-models"; -import { readModelServiceBuilder } from "../services/readModelService.js"; -import { config } from "../config/config.js"; +import { ReadModelService } from "../services/readModelService.js"; import { delegationToApiDelegation } from "../model/domain/apiConverter.js"; import { makeApiProblem } from "../model/domain/errors.js"; import { delegationProducerServiceBuilder } from "../services/delegationProducerService.js"; @@ -25,37 +23,26 @@ import { rejectDelegationErrorMapper, } from "../utilities/errorMappers.js"; -const readModelService = readModelServiceBuilder( - ReadModelRepository.init(config) -); - -const pdfGenerator = await initPDFGenerator(); -const fileManager = initFileManager(config); - -const delegationProducerService = delegationProducerServiceBuilder( - initDB({ - username: config.eventStoreDbUsername, - password: config.eventStoreDbPassword, - host: config.eventStoreDbHost, - port: config.eventStoreDbPort, - database: config.eventStoreDbName, - schema: config.eventStoreDbSchema, - useSSL: config.eventStoreDbUseSSL, - }), - readModelService, - pdfGenerator, - fileManager -); - const { ADMIN_ROLE } = userRoles; const delegationProducerRouter = ( - ctx: ZodiosContext + ctx: ZodiosContext, + eventStore: DB, + readModelService: ReadModelService, + pdfGenerator: PDFGenerator, + fileManager: FileManager ): ZodiosRouter => { const delegationProducerRouter = ctx.router(delegationApi.producerApi.api, { validationErrorHandler: zodiosValidationErrorToApiProblem, }); + const delegationProducerService = delegationProducerServiceBuilder( + eventStore, + readModelService, + pdfGenerator, + fileManager + ); + delegationProducerRouter .post( "/producer/delegations", diff --git a/packages/delegation-process/src/routers/DelegationRouter.ts b/packages/delegation-process/src/routers/DelegationRouter.ts index 82aa33063b..6f0c1c1f60 100644 --- a/packages/delegation-process/src/routers/DelegationRouter.ts +++ b/packages/delegation-process/src/routers/DelegationRouter.ts @@ -2,7 +2,6 @@ import { ZodiosRouter } from "@zodios/express"; import { delegationApi } from "pagopa-interop-api-clients"; import { ExpressContext, - ReadModelRepository, ZodiosContext, authorizationMiddleware, fromAppContext, @@ -10,8 +9,7 @@ import { zodiosValidationErrorToApiProblem, } from "pagopa-interop-commons"; import { EServiceId, TenantId, unsafeBrandId } from "pagopa-interop-models"; -import { readModelServiceBuilder } from "../services/readModelService.js"; -import { config } from "../config/config.js"; +import { ReadModelService } from "../services/readModelService.js"; import { apiDelegationKindToDelegationKind, apiDelegationStateToDelegationState, @@ -24,22 +22,19 @@ import { } from "../utilities/errorMappers.js"; import { delegationServiceBuilder } from "../services/delegationService.js"; -const readModelService = readModelServiceBuilder( - ReadModelRepository.init(config) -); - -const delegationService = delegationServiceBuilder(readModelService); - const { ADMIN_ROLE, API_ROLE, SECURITY_ROLE, M2M_ROLE, SUPPORT_ROLE } = userRoles; const delegationRouter = ( - ctx: ZodiosContext + ctx: ZodiosContext, + readModelService: ReadModelService ): ZodiosRouter => { const delegationRouter = ctx.router(delegationApi.delegationApi.api, { validationErrorHandler: zodiosValidationErrorToApiProblem, }); + const delegationService = delegationServiceBuilder(readModelService); + delegationRouter .get( "/delegations", diff --git a/packages/delegation-process/src/services/delegationConsumerService.ts b/packages/delegation-process/src/services/delegationConsumerService.ts new file mode 100644 index 0000000000..3affa63e30 --- /dev/null +++ b/packages/delegation-process/src/services/delegationConsumerService.ts @@ -0,0 +1,99 @@ +import { delegationApi } from "pagopa-interop-api-clients"; +import { + AppContext, + DB, + eventRepository, + WithLogger, +} from "pagopa-interop-commons"; +import { + Delegation, + delegationEventToBinaryDataV2, + DelegationId, + delegationKind, + delegationState, + EServiceId, + generateId, + TenantId, + unsafeBrandId, +} from "pagopa-interop-models"; +import { toCreateEventConsumerDelegationSubmitted } from "../model/domain/toEvent.js"; +import { + retrieveTenantById, + retrieveEserviceById, +} from "./delegationService.js"; +import { + assertDelegatorIsNotDelegate, + assertDelegationNotExists, + assertTenantAllowedToReceiveDelegation, + assertDelegatorAndDelegateIPA, +} from "./validators.js"; +import { ReadModelService } from "./readModelService.js"; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function delegationConsumerServiceBuilder( + dbInstance: DB, + readModelService: ReadModelService +) { + const repository = eventRepository(dbInstance, delegationEventToBinaryDataV2); + return { + async createConsumerDelegation( + delegationSeed: delegationApi.DelegationSeed, + { authData, logger, correlationId }: WithLogger + ): Promise { + const delegatorId = unsafeBrandId(authData.organizationId); + const delegateId = unsafeBrandId(delegationSeed.delegateId); + const eserviceId = unsafeBrandId(delegationSeed.eserviceId); + + logger.info( + `Creating a delegation for tenant ${delegationSeed.delegateId} by consumer ${delegatorId}` + ); + + assertDelegatorIsNotDelegate(delegatorId, delegateId); + + const delegator = await retrieveTenantById(readModelService, delegatorId); + const delegate = await retrieveTenantById(readModelService, delegateId); + + assertTenantAllowedToReceiveDelegation( + delegate, + delegationKind.delegatedConsumer + ); + await assertDelegatorAndDelegateIPA(delegator, delegate); + + await retrieveEserviceById(readModelService, eserviceId); + await assertDelegationNotExists( + delegator, + eserviceId, + delegationKind.delegatedConsumer, + readModelService + ); + + const creationDate = new Date(); + const delegation = { + id: generateId(), + delegatorId, + delegateId, + eserviceId, + createdAt: creationDate, + submittedAt: creationDate, + state: delegationState.waitingForApproval, + kind: delegationKind.delegatedConsumer, + stamps: { + submission: { + who: delegatorId, + when: creationDate, + }, + }, + }; + + await repository.createEvent( + toCreateEventConsumerDelegationSubmitted(delegation, correlationId) + ); + + return delegation; + }, + }; +} + +export type DelegationConsumerService = ReturnType< + typeof delegationConsumerServiceBuilder +>; diff --git a/packages/delegation-process/src/services/delegationProducerService.ts b/packages/delegation-process/src/services/delegationProducerService.ts index f323a8de45..5eba83ddf3 100644 --- a/packages/delegation-process/src/services/delegationProducerService.ts +++ b/packages/delegation-process/src/services/delegationProducerService.ts @@ -12,15 +12,12 @@ import { DelegationId, delegationEventToBinaryDataV2, delegationKind, - EService, delegationState, EServiceId, generateId, - Tenant, TenantId, unsafeBrandId, } from "pagopa-interop-models"; -import { eserviceNotFound, tenantNotFound } from "../model/domain/errors.js"; import { toCreateEventProducerDelegationSubmitted, toCreateEventProducerDelegationRevoked, @@ -32,15 +29,19 @@ import { ReadModelService } from "./readModelService.js"; import { assertDelegationIsRevokable, assertDelegationNotExists, - assertDelegatorIsIPA, assertDelegatorIsNotDelegate, - assertEserviceExists, - assertTenantAllowedToReceiveProducerDelegation, assertIsDelegate, assertIsState, + assertDelegatorIsProducer, + assertTenantAllowedToReceiveDelegation, + assertDelegatorAndDelegateIPA, } from "./validators.js"; import { contractBuilder } from "./delegationContractBuilder.js"; -import { retrieveDelegationById } from "./delegationService.js"; +import { + retrieveDelegationById, + retrieveEserviceById, + retrieveTenantById, +} from "./delegationService.js"; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function delegationProducerServiceBuilder( @@ -49,22 +50,6 @@ export function delegationProducerServiceBuilder( pdfGenerator: PDFGenerator, fileManager: FileManager ) { - const retrieveTenantById = async (tenantId: TenantId): Promise => { - const tenant = await readModelService.getTenantById(tenantId); - if (!tenant) { - throw tenantNotFound(tenantId); - } - return tenant; - }; - - const retrieveEserviceById = async (id: EServiceId): Promise => { - const eservice = await readModelService.getEServiceById(id); - if (!eservice) { - throw eserviceNotFound(id); - } - return eservice.data; - }; - const repository = eventRepository(dbInstance, delegationEventToBinaryDataV2); return { async createProducerDelegation( @@ -81,12 +66,17 @@ export function delegationProducerServiceBuilder( assertDelegatorIsNotDelegate(delegatorId, delegateId); - const delegator = await retrieveTenantById(delegatorId); - const delegate = await retrieveTenantById(delegateId); + const delegator = await retrieveTenantById(readModelService, delegatorId); + const delegate = await retrieveTenantById(readModelService, delegateId); + + assertTenantAllowedToReceiveDelegation( + delegate, + delegationKind.delegatedProducer + ); + await assertDelegatorAndDelegateIPA(delegator, delegate); - assertTenantAllowedToReceiveProducerDelegation(delegate); - await assertDelegatorIsIPA(delegator); - await assertEserviceExists(delegatorId, eserviceId, readModelService); + const eservice = await retrieveEserviceById(readModelService, eserviceId); + assertDelegatorIsProducer(delegatorId, eservice); await assertDelegationNotExists( delegator, eserviceId, @@ -134,9 +124,15 @@ export function delegationProducerServiceBuilder( assertDelegationIsRevokable(currentDelegation.data, delegatorId); const [delegator, delegate, eservice] = await Promise.all([ - retrieveTenantById(currentDelegation.data.delegatorId), - retrieveTenantById(currentDelegation.data.delegateId), - retrieveEserviceById(currentDelegation.data.eserviceId), + retrieveTenantById( + readModelService, + currentDelegation.data.delegatorId + ), + retrieveTenantById(readModelService, currentDelegation.data.delegateId), + retrieveEserviceById( + readModelService, + currentDelegation.data.eserviceId + ), ]); const revocationContract = await contractBuilder.createRevocationContract( @@ -196,9 +192,9 @@ export function delegationProducerServiceBuilder( assertIsState(delegationState.waitingForApproval, delegation); const [delegator, delegate, eservice] = await Promise.all([ - retrieveTenantById(delegation.delegatorId), - retrieveTenantById(delegation.delegateId), - retrieveEserviceById(delegation.eserviceId), + retrieveTenantById(readModelService, delegation.delegatorId), + retrieveTenantById(readModelService, delegation.delegateId), + retrieveEserviceById(readModelService, delegation.eserviceId), ]); const activationContract = await contractBuilder.createActivationContract( diff --git a/packages/delegation-process/src/services/delegationService.ts b/packages/delegation-process/src/services/delegationService.ts index 0b55683fb4..8aaf172fb6 100644 --- a/packages/delegation-process/src/services/delegationService.ts +++ b/packages/delegation-process/src/services/delegationService.ts @@ -3,12 +3,18 @@ import { DelegationId, DelegationKind, DelegationState, + EService, EServiceId, + Tenant, TenantId, WithMetadata, } from "pagopa-interop-models"; import { Logger } from "pagopa-interop-commons"; -import { delegationNotFound } from "../model/domain/errors.js"; +import { + delegationNotFound, + eserviceNotFound, + tenantNotFound, +} from "../model/domain/errors.js"; import { ReadModelService } from "./readModelService.js"; export const retrieveDelegationById = async ( @@ -22,6 +28,28 @@ export const retrieveDelegationById = async ( return delegation; }; +export const retrieveTenantById = async ( + readModelService: ReadModelService, + tenantId: TenantId +): Promise => { + const tenant = await readModelService.getTenantById(tenantId); + if (!tenant) { + throw tenantNotFound(tenantId); + } + return tenant; +}; + +export const retrieveEserviceById = async ( + readModelService: ReadModelService, + id: EServiceId +): Promise => { + const eservice = await readModelService.getEServiceById(id); + if (!eservice) { + throw eserviceNotFound(id); + } + return eservice.data; +}; + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export function delegationServiceBuilder(readModelService: ReadModelService) { return { diff --git a/packages/delegation-process/src/services/validators.ts b/packages/delegation-process/src/services/validators.ts index c5a6437d4a..2ea9964921 100644 --- a/packages/delegation-process/src/services/validators.ts +++ b/packages/delegation-process/src/services/validators.ts @@ -1,30 +1,31 @@ import { Delegation, + delegationKind, DelegationKind, DelegationState, delegationState, + EService, EServiceId, PUBLIC_ADMINISTRATIONS_IDENTIFIER, Tenant, TenantId, } from "pagopa-interop-models"; +import { match } from "ts-pattern"; import { delegationAlreadyExists, delegationNotRevokable, delegatorAndDelegateSameIdError, delegatorNotAllowToRevoke, differentEServiceProducer, - eserviceNotFound, incorrectState, - invalidExternalOriginError, + tenantIsNotIPAError, operationRestrictedToDelegate, tenantNotAllowedToDelegation, - tenantNotFound, } from "../model/domain/errors.js"; import { ReadModelService } from "./readModelService.js"; /* ========= STATES ========= */ -export const delegationNotActivableStates: DelegationState[] = [ +export const inactiveDelegationStates: DelegationState[] = [ delegationState.rejected, delegationState.revoked, ]; @@ -34,17 +35,11 @@ export const activeDelegationStates: DelegationState[] = [ delegationState.active, ]; -export const assertEserviceExists = async ( +export const assertDelegatorIsProducer = ( delegatorId: TenantId, - eserviceId: EServiceId, - readModelService: ReadModelService -): Promise => { - const eservice = await readModelService.getEServiceById(eserviceId); - if (!eservice) { - throw eserviceNotFound(eserviceId); - } - - if (eservice.data.producerId !== delegatorId) { + eservice: EService +): void => { + if (eservice.producerId !== delegatorId) { throw differentEServiceProducer(delegatorId); } }; @@ -58,33 +53,34 @@ export const assertDelegatorIsNotDelegate = ( } }; -export const assertDelegatorIsIPA = async ( - delegator?: Tenant +export const assertDelegatorAndDelegateIPA = async ( + delegator: Tenant, + delegate: Tenant ): Promise => { if (delegator?.externalId?.origin !== PUBLIC_ADMINISTRATIONS_IDENTIFIER) { - throw invalidExternalOriginError(delegator?.externalId?.origin); + throw tenantIsNotIPAError(delegator, "Delegator"); + } + + if (delegate?.externalId?.origin !== PUBLIC_ADMINISTRATIONS_IDENTIFIER) { + throw tenantIsNotIPAError(delegate, "Delegate"); } }; -export const assertTenantAllowedToReceiveProducerDelegation = ( - tenant: Tenant +export const assertTenantAllowedToReceiveDelegation = ( + tenant: Tenant, + kind: DelegationKind ): void => { const delegationFeature = tenant.features.find( - (f) => f.type === "DelegatedProducer" + (f) => + f.type === + match(kind) + .with(delegationKind.delegatedProducer, () => "DelegatedProducer") + .with(delegationKind.delegatedConsumer, () => "DelegatedConsumer") + .exhaustive() ); if (!delegationFeature) { - throw tenantNotAllowedToDelegation(tenant.id); - } -}; - -export const assertTenantExists = async ( - tenantId: TenantId, - readModelService: ReadModelService -): Promise => { - const tenant = await readModelService.getTenantById(tenantId); - if (!tenant) { - throw tenantNotFound(tenantId); + throw tenantNotAllowedToDelegation(tenant.id, kind); } }; diff --git a/packages/delegation-process/src/utilities/errorMappers.ts b/packages/delegation-process/src/utilities/errorMappers.ts index 98e2a40d3a..bf61b1817c 100644 --- a/packages/delegation-process/src/utilities/errorMappers.ts +++ b/packages/delegation-process/src/utilities/errorMappers.ts @@ -12,6 +12,7 @@ const { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_FORBIDDEN, HTTP_STATUS_UNAUTHORIZED, + HTTP_STATUS_CONFLICT, } = constants; export const getDelegationsErrorMapper = ( @@ -32,13 +33,30 @@ export const createProducerDelegationErrorMapper = ( match(error.code) .with( "eserviceNotFound", - "delegationAlreadyExists", "tenantNotFound", - "invalidDelegatorAndDelegateIds", - "invalidExternalOriginId", + "delegatorAndDelegateSameId", + () => HTTP_STATUS_BAD_REQUEST + ) + .with( + "tenantIsNotIPAError", "tenantNotAllowedToDelegation", + () => HTTP_STATUS_FORBIDDEN + ) + .with("delegationAlreadyExists", () => HTTP_STATUS_CONFLICT) + .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); + +export const createConsumerDelegationErrorMapper = ( + error: ApiError +): number => + match(error.code) + .with( + "eserviceNotFound", + "tenantNotFound", + "delegatorAndDelegateSameId", () => HTTP_STATUS_BAD_REQUEST ) + .with("tenantNotAllowedToDelegation", () => HTTP_STATUS_FORBIDDEN) + .with("delegationAlreadyExists", () => HTTP_STATUS_CONFLICT) .otherwise(() => HTTP_STATUS_INTERNAL_SERVER_ERROR); export const revokeDelegationErrorMapper = ( diff --git a/packages/delegation-process/test/approveProducerDelegation.test.ts b/packages/delegation-process/test/approveProducerDelegation.test.ts index dc1841a5ca..69199af69a 100644 --- a/packages/delegation-process/test/approveProducerDelegation.test.ts +++ b/packages/delegation-process/test/approveProducerDelegation.test.ts @@ -1,7 +1,7 @@ /* eslint-disable functional/no-let */ import { decodeProtobufPayload, - getMockDelegationProducer, + getMockDelegation, getMockTenant, getMockEService, getMockAuthData, @@ -16,6 +16,7 @@ import { Tenant, toDelegationV2, unsafeBrandId, + delegationKind, } from "pagopa-interop-models"; import { delegationState } from "pagopa-interop-models"; import { @@ -40,7 +41,7 @@ import { flushPDFMetadata, } from "./utils.js"; -describe("approve delegation", () => { +describe("approve producer delegation", () => { const currentExecutionTime = new Date(); beforeAll(async () => { vi.useFakeTimers(); @@ -63,7 +64,8 @@ describe("approve delegation", () => { it("should approve delegation if validations succeed", async () => { const delegationId = generateId(); - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, id: delegationId, state: "WaitingForApproval", delegateId: delegate.id, @@ -173,7 +175,8 @@ describe("approve delegation", () => { it("should throw operationRestrictedToDelegate when approver is not the delegate", async () => { const wrongDelegate = getMockTenant(); await addOneTenant(wrongDelegate); - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, state: "WaitingForApproval", delegateId: delegate.id, delegatorId: delegator.id, @@ -194,7 +197,8 @@ describe("approve delegation", () => { }); it("should throw incorrectState when delegation is not in WaitingForApproval state", async () => { - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, state: "Active", delegateId: delegate.id, delegatorId: delegator.id, @@ -219,7 +223,8 @@ describe("approve delegation", () => { }); it("should generete a pdf document for a delegation", async () => { - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, state: "WaitingForApproval", delegateId: delegate.id, delegatorId: delegator.id, diff --git a/packages/delegation-process/test/createConsumerDelegation.test.ts b/packages/delegation-process/test/createConsumerDelegation.test.ts new file mode 100644 index 0000000000..b415342d77 --- /dev/null +++ b/packages/delegation-process/test/createConsumerDelegation.test.ts @@ -0,0 +1,575 @@ +import { fail } from "assert"; +import { genericLogger } from "pagopa-interop-commons"; +import { + decodeProtobufPayload, + getMockDelegation, + getMockEService, + getMockTenant, + getRandomAuthData, + randomArrayItem, +} from "pagopa-interop-commons-test"; +import { + Delegation, + DelegationId, + delegationKind, + delegationState, + ConsumerDelegationSubmittedV2, + EServiceId, + generateId, + TenantId, + toDelegationV2, +} from "pagopa-interop-models"; +import { describe, expect, it, vi } from "vitest"; +import { + delegationAlreadyExists, + delegatorAndDelegateSameIdError, + eserviceNotFound, + tenantIsNotIPAError, + tenantNotAllowedToDelegation, + tenantNotFound, +} from "../src/model/domain/errors.js"; + +import { + activeDelegationStates, + inactiveDelegationStates, +} from "../src/services/validators.js"; +import { + addOneDelegation, + addOneEservice, + addOneTenant, + delegationConsumerService, + readLastDelegationEvent, +} from "./utils.js"; + +/** + * Validates the creation of a delegation by comparing the actual delegation + * with the expected delegation. It ensures that the delegation IDs are defined + * and equal, and verifies that the last delegation event matches the expected + * delegation data. + * + * @param actualDelegation - The actual delegation object to be validated, + * typically a response from an API. + * @param expectedDelegation - The expected delegation object to compare against. + * @returns A promise that resolves to void. + * @throws Will fail if the delegation is not found in the event store. + */ +const expectedDelegationCreation = async ( + actualDelegation: Delegation, + expectedDelegation: Delegation +): Promise => { + expect(actualDelegation.id).toBeDefined(); + expect(expectedDelegation.id).toBeDefined(); + expect(actualDelegation.id).toEqual(expectedDelegation.id); + + const lastDelegationEvent = await readLastDelegationEvent( + actualDelegation.id + ); + + if (!lastDelegationEvent) { + fail("Creation fails: delegation not found in event-store"); + } + + const actualDelegationData = decodeProtobufPayload({ + messageType: ConsumerDelegationSubmittedV2, + payload: lastDelegationEvent.data, + }); + + expect(actualDelegation).toMatchObject(expectedDelegation); + expect(actualDelegationData.delegation).toEqual( + toDelegationV2(expectedDelegation) + ); +}; + +describe("create consumer delegation", () => { + it("should create a delegation if it does not exist", async () => { + const currentExecutionTime = new Date(); + vi.useFakeTimers(); + vi.setSystemTime(currentExecutionTime); + + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: currentExecutionTime, + }, + ], + }; + const eservice = getMockEService(generateId(), delegatorId); + + await addOneTenant(delegator); + await addOneTenant(delegate); + await addOneEservice(eservice); + + const actualDelegation = + await delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId: eservice.id, + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ); + + const expectedDelegation: Delegation = { + id: actualDelegation.id, + delegatorId, + delegateId: delegate.id, + eserviceId: eservice.id, + kind: delegationKind.delegatedConsumer, + state: delegationState.waitingForApproval, + createdAt: currentExecutionTime, + submittedAt: currentExecutionTime, + stamps: { + submission: { + who: delegatorId, + when: currentExecutionTime, + }, + }, + }; + + await expectedDelegationCreation(actualDelegation, expectedDelegation); + vi.useRealTimers(); + }); + + it.each(inactiveDelegationStates)( + "should create a delegation the same delegation exists and is in state %s", + async (inactiveDelegationState) => { + const currentExecutionTime = new Date(); + vi.useFakeTimers(); + vi.setSystemTime(currentExecutionTime); + + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: currentExecutionTime, + }, + ], + }; + const eservice = getMockEService(generateId(), delegatorId); + + const existentDelegation = { + ...getMockDelegation({ + kind: delegationKind.delegatedConsumer, + id: generateId(), + delegatorId, + delegateId: delegate.id, + eserviceId: eservice.id, + }), + state: inactiveDelegationState, + }; + + await addOneTenant(delegator); + await addOneTenant(delegate); + await addOneEservice(eservice); + await addOneDelegation(existentDelegation); + + const actualDelegation = + await delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId: eservice.id, + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ); + + const expectedDelegation: Delegation = { + id: actualDelegation.id, + delegatorId, + delegateId: delegate.id, + eserviceId: eservice.id, + kind: delegationKind.delegatedConsumer, + state: delegationState.waitingForApproval, + createdAt: currentExecutionTime, + submittedAt: currentExecutionTime, + stamps: { + submission: { + who: delegatorId, + when: currentExecutionTime, + }, + }, + }; + + await expectedDelegationCreation(actualDelegation, expectedDelegation); + vi.useRealTimers(); + } + ); + + it.each(activeDelegationStates)( + "should throw a delegationAlreadyExists error when a consumer Delegation in state %s already exists with for same delegator, delegate and eservice", + async (activeDelegationState) => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: new Date(), + }, + ], + }; + const eservice = getMockEService(generateId(), delegatorId); + const existientActiveDelegation = { + ...getMockDelegation({ + kind: delegationKind.delegatedConsumer, + id: generateId(), + delegatorId, + delegateId: delegate.id, + eserviceId: eservice.id, + }), + state: activeDelegationState, + }; + + await addOneTenant(delegate); + await addOneTenant(delegator); + await addOneEservice(eservice); + // Add existent active delegation for the same delegator, delegate and eservice + await addOneDelegation(existientActiveDelegation); + // Add existent inactive delegation for the same delegator, delegate and eservice + await addOneDelegation({ + ...existientActiveDelegation, + id: generateId(), + state: randomArrayItem(inactiveDelegationStates), + }); + + // Add another generic delegation + await addOneDelegation( + getMockDelegation({ + kind: delegationKind.delegatedConsumer, + }) + ); + + // Add another delegation with same delegator + await addOneDelegation( + getMockDelegation({ + kind: delegationKind.delegatedConsumer, + delegatorId, + }) + ); + + // Add another delegation with same delegate + await addOneDelegation( + getMockDelegation({ + kind: delegationKind.delegatedConsumer, + delegateId: delegate.id, + }) + ); + + // Add another delegation for the same eservice + await addOneDelegation( + getMockDelegation({ + kind: delegationKind.delegatedConsumer, + eserviceId: eservice.id, + }) + ); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId: eservice.id, + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError( + delegationAlreadyExists( + delegatorId, + existientActiveDelegation.eserviceId, + delegationKind.delegatedConsumer + ) + ); + } + ); + + it("should throw an tenantNotFound error if delegated tenant does not exist", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = getMockTenant(delegatorId); + + const delegateId = generateId(); + + await addOneTenant(delegator); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(tenantNotFound(delegateId)); + }); + + it("should throw a tenantNotFound error if delegator tenant does not exist", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + + const delegateId = generateId(); + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: new Date(), + }, + ], + }; + + await addOneTenant(delegate); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(tenantNotFound(delegatorId)); + }); + + it("should throw an invalidDelegatorAndDelegateAreSame error if delegatorId and delegateId is the same", async () => { + const sameTenantId = generateId(); + const authData = getRandomAuthData(sameTenantId); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId: sameTenantId, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(delegatorAndDelegateSameIdError()); + }); + + it("should throw an tenantIsNotIPAError error if delegator has externalId origin different from IPA", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "NOT_IPA", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: new Date(), + }, + ], + }; + + await addOneTenant(delegate); + await addOneTenant(delegator); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(tenantIsNotIPAError(delegator, "Delegator")); + }); + + it("should throw an tenantIsNotIPAError error if delegate has externalId origin different from IPA", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + externalId: { + origin: "NOT_IPA", + value: "test", + }, + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: new Date(), + }, + ], + }; + + await addOneTenant(delegate); + await addOneTenant(delegator); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(tenantIsNotIPAError(delegate, "Delegate")); + }); + + it("should throw an eserviceNotFound error if Eservice does not exist", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedConsumer" as const, + availabilityTimestamp: new Date(), + }, + ], + }; + const eserviceId = generateId(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + id: generateId(), + delegatorId, + delegateId: delegate.id, + }); + + await addOneTenant(delegate); + await addOneTenant(delegator); + await addOneDelegation(delegation); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId, + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(eserviceNotFound(eserviceId)); + }); + + it("should throw a tenantNotAllowedToDelegation error if delegate tenant has no DelegatedConsumer feature", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", + }, + }; + + const delegate = getMockTenant(); + + await addOneTenant(delegate); + await addOneTenant(delegator); + + await expect( + delegationConsumerService.createConsumerDelegation( + { + delegateId: delegate.id, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError( + tenantNotAllowedToDelegation( + delegate.id, + delegationKind.delegatedConsumer + ) + ); + }); +}); diff --git a/packages/delegation-process/test/createProducerDelegation.test.ts b/packages/delegation-process/test/createProducerDelegation.test.ts index 97b0cec3c2..1463fba985 100644 --- a/packages/delegation-process/test/createProducerDelegation.test.ts +++ b/packages/delegation-process/test/createProducerDelegation.test.ts @@ -2,7 +2,7 @@ import { fail } from "assert"; import { genericLogger } from "pagopa-interop-commons"; import { decodeProtobufPayload, - getMockDelegationProducer, + getMockDelegation, getMockEService, getMockTenant, getRandomAuthData, @@ -25,14 +25,14 @@ import { delegatorAndDelegateSameIdError, differentEServiceProducer, eserviceNotFound, - invalidExternalOriginError, + tenantIsNotIPAError, tenantNotAllowedToDelegation, tenantNotFound, } from "../src/model/domain/errors.js"; import { activeDelegationStates, - delegationNotActivableStates, + inactiveDelegationStates, } from "../src/services/validators.js"; import { addOneDelegation, @@ -81,8 +81,8 @@ const expectedDelegationCreation = async ( ); }; -describe("create delegation", () => { - it("should create a delegation if not exists", async () => { +describe("create producer delegation", () => { + it("should create a delegation if it does not exist", async () => { const currentExecutionTime = new Date(); vi.useFakeTimers(); vi.setSystemTime(currentExecutionTime); @@ -93,7 +93,7 @@ describe("create delegation", () => { ...getMockTenant(delegatorId), externalId: { origin: "IPA", - value: "anythings", + value: "test", }, }; @@ -147,83 +147,87 @@ describe("create delegation", () => { vi.useRealTimers(); }); - it("should create a delegation if already exists the same delegation in status Rejected or Revoked", async () => { - const currentExecutionTime = new Date(); - vi.useFakeTimers(); - vi.setSystemTime(currentExecutionTime); + it.each(inactiveDelegationStates)( + "should create a delegation the same delegation exists and is in state %s", + async (inactiveDelegationState) => { + const currentExecutionTime = new Date(); + vi.useFakeTimers(); + vi.setSystemTime(currentExecutionTime); - const delegatorId = generateId(); - const authData = getRandomAuthData(delegatorId); - const delegator = { - ...getMockTenant(delegatorId), - externalId: { - origin: "IPA", - value: "anythings", - }, - }; - - const delegate = { - ...getMockTenant(), - features: [ - { - type: "DelegatedProducer" as const, - availabilityTimestamp: currentExecutionTime, + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", }, - ], - }; - const eservice = getMockEService(generateId(), delegatorId); - - const existentDelegation = { - ...getMockDelegationProducer({ - id: generateId(), - delegatorId, - delegateId: delegate.id, - eserviceId: eservice.id, - }), - state: randomArrayItem(delegationNotActivableStates), - }; + }; - await addOneTenant(delegator); - await addOneTenant(delegate); - await addOneEservice(eservice); - await addOneDelegation(existentDelegation); + const delegate = { + ...getMockTenant(), + features: [ + { + type: "DelegatedProducer" as const, + availabilityTimestamp: currentExecutionTime, + }, + ], + }; + const eservice = getMockEService(generateId(), delegatorId); - const actualDelegation = - await delegationProducerService.createProducerDelegation( - { + const existentDelegation = { + ...getMockDelegation({ + kind: delegationKind.delegatedProducer, + id: generateId(), + delegatorId, delegateId: delegate.id, eserviceId: eservice.id, - }, - { - authData, - logger: genericLogger, - correlationId: generateId(), - serviceName: "DelegationServiceTest", - } - ); + }), + state: inactiveDelegationState, + }; - const expectedDelegation: Delegation = { - id: actualDelegation.id, - delegatorId, - delegateId: delegate.id, - eserviceId: eservice.id, - kind: delegationKind.delegatedProducer, - state: delegationState.waitingForApproval, - createdAt: currentExecutionTime, - submittedAt: currentExecutionTime, - stamps: { - submission: { - who: delegatorId, - when: currentExecutionTime, + await addOneTenant(delegator); + await addOneTenant(delegate); + await addOneEservice(eservice); + await addOneDelegation(existentDelegation); + + const actualDelegation = + await delegationProducerService.createProducerDelegation( + { + delegateId: delegate.id, + eserviceId: eservice.id, + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ); + + const expectedDelegation: Delegation = { + id: actualDelegation.id, + delegatorId, + delegateId: delegate.id, + eserviceId: eservice.id, + kind: delegationKind.delegatedProducer, + state: delegationState.waitingForApproval, + createdAt: currentExecutionTime, + submittedAt: currentExecutionTime, + stamps: { + submission: { + who: delegatorId, + when: currentExecutionTime, + }, }, - }, - }; + }; - await expectedDelegationCreation(actualDelegation, expectedDelegation); - vi.useRealTimers(); - }); + await expectedDelegationCreation(actualDelegation, expectedDelegation); + vi.useRealTimers(); + } + ); - it("should throw an differentEServiceProducer error if requester is not Eservice producer", async () => { + it("should throw a differentEServiceProducer error if requester is not Eservice producer", async () => { const currentExecutionTime = new Date(); vi.useFakeTimers(); vi.setSystemTime(currentExecutionTime); @@ -234,7 +238,7 @@ describe("create delegation", () => { ...getMockTenant(delegatorId), externalId: { origin: "IPA", - value: "anythings", + value: "test", }, }; @@ -272,7 +276,7 @@ describe("create delegation", () => { }); it.each(activeDelegationStates)( - "should throw an delegationAlreadyExists error when Delegation for eservice producer already exists with for same delegator, delegate and eserivce ", + "should throw a delegationAlreadyExists error when a producer Delegation in state %s already exists with for same delegator, delegate and eservice", async (validDelegationState) => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); @@ -280,7 +284,7 @@ describe("create delegation", () => { ...getMockTenant(delegatorId), externalId: { origin: "IPA", - value: "anythings", + value: "test", }, }; @@ -294,8 +298,9 @@ describe("create delegation", () => { ], }; const eservice = getMockEService(generateId(), delegatorId); - const existentValidDelegation = { - ...getMockDelegationProducer({ + const existentActiveDelegation = { + ...getMockDelegation({ + kind: delegationKind.delegatedProducer, id: generateId(), delegatorId, delegateId: delegate.id, @@ -307,35 +312,40 @@ describe("create delegation", () => { await addOneTenant(delegate); await addOneTenant(delegator); await addOneEservice(eservice); - // Add existent valid delegation for the same delegator, delegate and eservice - await addOneDelegation(existentValidDelegation); - // Add existent invalid delegation for the same delegator, delegate and eservice + // Add existent active delegation for the same delegator, delegate and eservice + await addOneDelegation(existentActiveDelegation); + // Add existent inactive delegation for the same delegator, delegate and eservice await addOneDelegation({ - ...existentValidDelegation, + ...existentActiveDelegation, id: generateId(), - state: validDelegationState, + state: randomArrayItem(inactiveDelegationStates), }); // Add another generic delegation - await addOneDelegation(getMockDelegationProducer()); + await addOneDelegation( + getMockDelegation({ kind: delegationKind.delegatedProducer }) + ); // Add another delegation with same delegator await addOneDelegation( - getMockDelegationProducer({ + getMockDelegation({ + kind: delegationKind.delegatedProducer, delegatorId, }) ); // Add another delegation with same delegate await addOneDelegation( - getMockDelegationProducer({ + getMockDelegation({ + kind: delegationKind.delegatedProducer, delegateId: delegate.id, }) ); // Add another delegation for the same eservice await addOneDelegation( - getMockDelegationProducer({ + getMockDelegation({ + kind: delegationKind.delegatedProducer, eserviceId: eservice.id, }) ); @@ -356,14 +366,14 @@ describe("create delegation", () => { ).rejects.toThrowError( delegationAlreadyExists( delegatorId, - existentValidDelegation.eserviceId, + existentActiveDelegation.eserviceId, delegationKind.delegatedProducer ) ); } ); - it("should throw an tenantNotFound error if delegated tenant not exists", async () => { + it("should throw a tenantNotFound error if delegated tenant does not exist", async () => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); const delegator = getMockTenant(delegatorId); @@ -388,7 +398,7 @@ describe("create delegation", () => { ).rejects.toThrowError(tenantNotFound(delegateId)); }); - it("should throw an tenantNotFound error if delegator tenant not exists", async () => { + it("should throw a tenantNotFound error if delegator tenant does not exist", async () => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); @@ -441,14 +451,14 @@ describe("create delegation", () => { ).rejects.toThrowError(delegatorAndDelegateSameIdError()); }); - it("should throw an invalidExternalOriginError error if delegator has externalId origin different from IPA", async () => { + it("should throw an tenantIsNotIPAError error if delegator has externalId origin different from IPA", async () => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); const delegator = { ...getMockTenant(delegatorId), externalId: { origin: "NOT_IPA", - value: "anythings", + value: "test", }, }; @@ -478,19 +488,61 @@ describe("create delegation", () => { serviceName: "DelegationServiceTest", } ) - ).rejects.toThrowError( - invalidExternalOriginError(delegator.externalId.origin) - ); + ).rejects.toThrowError(tenantIsNotIPAError(delegator, "Delegator")); }); - it("should throw an eserviceNotFound error if Eservice not exists", async () => { + it("should throw an tenantIsNotIPAError error if delegate has externalId origin different from IPA", async () => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); const delegator = { ...getMockTenant(delegatorId), externalId: { origin: "IPA", - value: "anythings", + value: "test", + }, + }; + + const delegate = { + ...getMockTenant(), + externalId: { + origin: "NOT_IPA", + value: "test", + }, + features: [ + { + type: "DelegatedProducer" as const, + availabilityTimestamp: new Date(), + }, + ], + }; + + await addOneTenant(delegate); + await addOneTenant(delegator); + + await expect( + delegationProducerService.createProducerDelegation( + { + delegateId: delegate.id, + eserviceId: generateId(), + }, + { + authData, + logger: genericLogger, + correlationId: generateId(), + serviceName: "DelegationServiceTest", + } + ) + ).rejects.toThrowError(tenantIsNotIPAError(delegate, "Delegate")); + }); + + it("should throw an eserviceNotFound error if Eservice does not exist", async () => { + const delegatorId = generateId(); + const authData = getRandomAuthData(delegatorId); + const delegator = { + ...getMockTenant(delegatorId), + externalId: { + origin: "IPA", + value: "test", }, }; @@ -504,7 +556,8 @@ describe("create delegation", () => { ], }; const eserviceId = generateId(); - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, id: generateId(), delegatorId, delegateId: delegate.id, @@ -530,14 +583,14 @@ describe("create delegation", () => { ).rejects.toThrowError(eserviceNotFound(eserviceId)); }); - it("should throw an invalidExternalOriginError error if delegator has externalId origin different from IPA", async () => { + it("should throw a tenantNotAllowedToDelegation error if delegate tenant has no DelegatedProducer feature", async () => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); const delegator = { ...getMockTenant(delegatorId), externalId: { origin: "IPA", - value: "anythings", + value: "test", }, }; @@ -559,6 +612,11 @@ describe("create delegation", () => { serviceName: "DelegationServiceTest", } ) - ).rejects.toThrowError(tenantNotAllowedToDelegation(delegate.id)); + ).rejects.toThrowError( + tenantNotAllowedToDelegation( + delegate.id, + delegationKind.delegatedProducer + ) + ); }); }); diff --git a/packages/delegation-process/test/getDelegationById.test.ts b/packages/delegation-process/test/getDelegationById.test.ts index ddaf026c79..9a323bbaec 100644 --- a/packages/delegation-process/test/getDelegationById.test.ts +++ b/packages/delegation-process/test/getDelegationById.test.ts @@ -1,6 +1,10 @@ /* eslint-disable functional/no-let */ -import { getMockDelegationProducer } from "pagopa-interop-commons-test/index.js"; -import { DelegationId, generateId } from "pagopa-interop-models"; +import { getMockDelegation } from "pagopa-interop-commons-test/index.js"; +import { + DelegationId, + delegationKind, + generateId, +} from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; import { genericLogger } from "pagopa-interop-commons"; import { delegationNotFound } from "../src/model/domain/errors.js"; @@ -8,7 +12,9 @@ import { addOneDelegation, delegationService } from "./utils.js"; describe("get delegation by id", () => { it("should get the delegation if it exists", async () => { - const delegation = getMockDelegationProducer(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + }); await addOneDelegation(delegation); @@ -21,7 +27,9 @@ describe("get delegation by id", () => { }); it("should fail with delegationNotFound", async () => { - const delegation = getMockDelegationProducer(); + const delegation = getMockDelegation({ + kind: delegationKind.delegatedConsumer, + }); await addOneDelegation(delegation); diff --git a/packages/delegation-process/test/getDelegations.test.ts b/packages/delegation-process/test/getDelegations.test.ts index e7ba0c8381..0a11027266 100644 --- a/packages/delegation-process/test/getDelegations.test.ts +++ b/packages/delegation-process/test/getDelegations.test.ts @@ -1,13 +1,19 @@ /* eslint-disable functional/no-let */ -import { getMockDelegationProducer } from "pagopa-interop-commons-test/index.js"; +import { getMockDelegation } from "pagopa-interop-commons-test/index.js"; import { describe, expect, it } from "vitest"; import { genericLogger } from "pagopa-interop-commons"; +import { delegationKind } from "pagopa-interop-models"; import { addOneDelegation, delegationService } from "./utils.js"; describe("get delegations", () => { it("should get delegations", async () => { - const delegation1 = getMockDelegationProducer({ state: "Active" }); - const delegation2 = getMockDelegationProducer(); + const delegation1 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + state: "Active", + }); + const delegation2 = getMockDelegation({ + kind: delegationKind.delegatedProducer, + }); await addOneDelegation(delegation1); await addOneDelegation(delegation2); diff --git a/packages/delegation-process/test/rejectProducerDelegation.test.ts b/packages/delegation-process/test/rejectProducerDelegation.test.ts index f24401386d..cf5afe2063 100644 --- a/packages/delegation-process/test/rejectProducerDelegation.test.ts +++ b/packages/delegation-process/test/rejectProducerDelegation.test.ts @@ -2,13 +2,14 @@ import { decodeProtobufPayload, getMockAuthData, - getMockDelegationProducer, + getMockDelegation, getMockTenant, } from "pagopa-interop-commons-test/index.js"; import { describe, expect, it, vi } from "vitest"; import { DelegationId, ProducerDelegationRejectedV2, + delegationKind, generateId, toDelegationV2, unsafeBrandId, @@ -26,14 +27,15 @@ import { readLastDelegationEvent, } from "./utils.js"; -describe("reject delegation", () => { +describe("reject producer delegation", () => { it("should reject delegation if all validations succed", async () => { const currentExecutionTime = new Date(); vi.useFakeTimers(); vi.setSystemTime(currentExecutionTime); const delegate = getMockTenant(); - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, state: "WaitingForApproval", delegateId: delegate.id, }); @@ -93,7 +95,8 @@ describe("reject delegation", () => { it("should throw operationRestrictedToDelegate when rejecter is not the delegate", async () => { const delegate = getMockTenant(); const wrongDelegate = getMockTenant(); - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, state: "WaitingForApproval", delegateId: delegate.id, }); @@ -113,7 +116,8 @@ describe("reject delegation", () => { it("should throw incorrectState when delegation is not in WaitingForApproval state", async () => { const delegate = getMockTenant(); - const delegation = getMockDelegationProducer({ + const delegation = getMockDelegation({ + kind: delegationKind.delegatedProducer, state: "Active", delegateId: delegate.id, }); diff --git a/packages/delegation-process/test/revokeProducerDelegation.test.ts b/packages/delegation-process/test/revokeProducerDelegation.test.ts index ebff03f0cb..ffc5c3c287 100644 --- a/packages/delegation-process/test/revokeProducerDelegation.test.ts +++ b/packages/delegation-process/test/revokeProducerDelegation.test.ts @@ -1,7 +1,7 @@ import { fail } from "assert"; import { decodeProtobufPayload, - getMockDelegationProducer, + getMockDelegation, getMockEService, getMockTenant, getRandomAuthData, @@ -17,6 +17,7 @@ import { EServiceId, unsafeBrandId, DelegationContractId, + delegationKind, } from "pagopa-interop-models"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import { @@ -102,7 +103,7 @@ const getNotRevocableStateSeeds = (): DelegationStateSeed[] => { ]; }; -describe("revoke delegation", () => { +describe("revoke producer delegation", () => { const TEST_EXECUTION_DATE = new Date(); beforeAll(() => { @@ -138,7 +139,8 @@ describe("revoke delegation", () => { await addOneEservice(eservice); const existentDelegation: Delegation = { - ...getMockDelegationProducer({ + ...getMockDelegation({ + kind: delegationKind.delegatedProducer, delegatorId, delegateId, }), @@ -257,7 +259,7 @@ describe("revoke delegation", () => { expect(delegationFromLastEvent).toMatchObject(expectedDelegation); }); - it("should throw an delegationNotFound if Delegation not exists", async () => { + it("should throw a delegationNotFound if Delegation does not exist", async () => { const delegatorId = generateId(); const authData = getRandomAuthData(delegatorId); const delegationId = generateId(); @@ -271,7 +273,7 @@ describe("revoke delegation", () => { ).rejects.toThrow(delegationNotFound(delegationId)); }); - it("should throw an delegatorNotAllowToRevoke if Requester Id and DelegatorId are differents", async () => { + it("should throw a delegatorNotAllowToRevoke if Requester Id and DelegatorId are differents", async () => { const currentExecutionTime = new Date(); const delegatorId = generateId(); @@ -286,7 +288,8 @@ describe("revoke delegation", () => { delegationApprovalDate.setMonth(currentExecutionTime.getMonth() - 1); const existentDelegation = { - ...getMockDelegationProducer({ + ...getMockDelegation({ + kind: delegationKind.delegatedProducer, id: delegationId, delegateId, }), @@ -318,7 +321,7 @@ describe("revoke delegation", () => { }); it.each(notRevocableDelegationState)( - "should throw an delegatorNotAllowToRevoke if delegation doesn't have revocable one of revocable states [Rejected,Revoked]", + "should throw a delegatorNotAllowToRevoke if delegation doesn't have revocable one of revocable states [Rejected,Revoked]", async (notRevocableDelegationState: DelegationStateSeed) => { const currentExecutionTime = new Date(); @@ -333,7 +336,8 @@ describe("revoke delegation", () => { delegationActivationDate.setMonth(currentExecutionTime.getMonth() - 1); const existentDelegation: Delegation = { - ...getMockDelegationProducer({ + ...getMockDelegation({ + kind: delegationKind.delegatedProducer, delegatorId, delegateId, }), diff --git a/packages/delegation-process/test/utils.ts b/packages/delegation-process/test/utils.ts index 94fbca3251..8e975f19bb 100644 --- a/packages/delegation-process/test/utils.ts +++ b/packages/delegation-process/test/utils.ts @@ -26,6 +26,7 @@ import { import puppeteer, { Browser } from "puppeteer"; import { PDFDocument } from "pdf-lib"; import { delegationProducerServiceBuilder } from "../src/services/delegationProducerService.js"; +import { delegationConsumerServiceBuilder } from "../src/services/delegationConsumerService.js"; import { delegationServiceBuilder } from "../src/services/delegationService.js"; import { readModelServiceBuilder } from "../src/services/readModelService.js"; @@ -67,6 +68,11 @@ export const delegationProducerService = delegationProducerServiceBuilder( fileManager ); +export const delegationConsumerService = delegationConsumerServiceBuilder( + postgresDB, + readModelService +); + export const delegationService = delegationServiceBuilder(readModelService); export const writeSubmitDelegationInEventstore = async ( diff --git a/packages/delegation-readmodel-writer/src/delegationConsumerServiceV2.ts b/packages/delegation-readmodel-writer/src/delegationConsumerServiceV2.ts index 65cdc3c701..b8b4208203 100644 --- a/packages/delegation-readmodel-writer/src/delegationConsumerServiceV2.ts +++ b/packages/delegation-readmodel-writer/src/delegationConsumerServiceV2.ts @@ -15,6 +15,7 @@ export async function handleMessageV2( { type: "ProducerDelegationRejected" }, { type: "ProducerDelegationRevoked" }, { type: "ProducerDelegationSubmitted" }, + { type: "ConsumerDelegationSubmitted" }, async (message) => { const delegation = message.data.delegation; await delegations.updateOne( diff --git a/packages/delegation-readmodel-writer/test/consumerServiceV2.test.ts b/packages/delegation-readmodel-writer/test/consumerServiceV2.test.ts index 48348a453f..c99206b13a 100644 --- a/packages/delegation-readmodel-writer/test/consumerServiceV2.test.ts +++ b/packages/delegation-readmodel-writer/test/consumerServiceV2.test.ts @@ -1,4 +1,7 @@ -import { getMockDelegationProducer } from "pagopa-interop-commons-test/index.js"; +import { + getMockDelegation, + randomArrayItem, +} from "pagopa-interop-commons-test/index.js"; import { DelegationEventEnvelopeV2, toDelegationV2, @@ -6,13 +9,17 @@ import { ProducerDelegationRejectedV2, ProducerDelegationRevokedV2, ProducerDelegationSubmittedV2, + delegationKind, + ConsumerDelegationSubmittedV2, } from "pagopa-interop-models"; import { describe, expect, it } from "vitest"; import { handleMessageV2 } from "../src/delegationConsumerServiceV2.js"; import { delegations } from "./utils.js"; describe("Events V2", async () => { - const mockDelegation = getMockDelegationProducer(); + const mockDelegation = getMockDelegation({ + kind: randomArrayItem(Object.values(delegationKind)), + }); const mockMessage: DelegationEventEnvelopeV2 = { event_version: 2, stream_id: mockDelegation.id, @@ -118,4 +125,28 @@ describe("Events V2", async () => { version: 1, }); }); + + it("ConsumerDelegationSubmitted", async () => { + const payload: ConsumerDelegationSubmittedV2 = { + delegation: toDelegationV2(mockDelegation), + }; + + const message: DelegationEventEnvelopeV2 = { + ...mockMessage, + type: "ConsumerDelegationSubmitted", + data: payload, + }; + + await handleMessageV2(message, delegations); + + const retrievedDelegation = await delegations.findOne({ + "data.id": mockDelegation.id, + }); + + expect(retrievedDelegation?.data).toEqual(mockDelegation); + + expect(retrievedDelegation?.metadata).toEqual({ + version: 1, + }); + }); }); diff --git a/packages/models/proto/v2/delegation/events.proto b/packages/models/proto/v2/delegation/events.proto index ef551774ff..19e50f85ca 100644 --- a/packages/models/proto/v2/delegation/events.proto +++ b/packages/models/proto/v2/delegation/events.proto @@ -19,3 +19,7 @@ message ProducerDelegationRejectedV2 { message ProducerDelegationRevokedV2 { DelegationV2 delegation = 1; } + +message ConsumerDelegationSubmittedV2 { + DelegationV2 delegation = 1; +} diff --git a/packages/models/src/delegation/delegationEvents.ts b/packages/models/src/delegation/delegationEvents.ts index 89206416b6..68f952bff3 100644 --- a/packages/models/src/delegation/delegationEvents.ts +++ b/packages/models/src/delegation/delegationEvents.ts @@ -6,6 +6,7 @@ import { ProducerDelegationApprovedV2, ProducerDelegationRejectedV2, ProducerDelegationRevokedV2, + ConsumerDelegationSubmittedV2, } from "../gen/v2/delegation/events.js"; import { protobufDecoder } from "../protobuf/protobuf.js"; import { EventEnvelope } from "../events/events.js"; @@ -31,6 +32,11 @@ export const DelegationEventV2 = z.discriminatedUnion("type", [ type: z.literal("ProducerDelegationRevoked"), data: protobufDecoder(ProducerDelegationRevokedV2), }), + z.object({ + event_version: z.literal(2), + type: z.literal("ConsumerDelegationSubmitted"), + data: protobufDecoder(ConsumerDelegationSubmittedV2), + }), ]); export type DelegationEventV2 = z.infer; @@ -51,6 +57,9 @@ export function delegationEventToBinaryDataV2( .with({ type: "ProducerDelegationRevoked" }, ({ data }) => ProducerDelegationRevokedV2.toBinary(data) ) + .with({ type: "ConsumerDelegationSubmitted" }, ({ data }) => + ConsumerDelegationSubmittedV2.toBinary(data) + ) .exhaustive(); }