From bbc314c4d72138bf3d84e8024da5af5bf6896520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Wed, 6 Sep 2023 09:16:57 +0200 Subject: [PATCH 1/8] feat: add tests for create and update integration with conditions --- .../e2e/create-integration.e2e.ts | 15 +++++++++++++ .../e2e/update-integration.e2e.ts | 21 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/apps/api/src/app/integrations/e2e/create-integration.e2e.ts b/apps/api/src/app/integrations/e2e/create-integration.e2e.ts index 72c87d6401c..7abead71b53 100644 --- a/apps/api/src/app/integrations/e2e/create-integration.e2e.ts +++ b/apps/api/src/app/integrations/e2e/create-integration.e2e.ts @@ -104,6 +104,21 @@ describe('Create Integration - /integration (POST)', function () { } }); + it('should create integration with conditions', async function () { + const payload = { + providerId: EmailProviderIdEnum.SendGrid, + channel: ChannelTypeEnum.EMAIL, + identifier: 'identifier-conditions', + active: false, + check: false, + conditions: [{}], + }; + + const { body } = await session.testAgent.post('/v1/integrations').send(payload); + + expect(body.data.conditions.length).to.equal(1); + }); + it('should not allow to create integration with same identifier', async function () { const payload = { providerId: EmailProviderIdEnum.SendGrid, diff --git a/apps/api/src/app/integrations/e2e/update-integration.e2e.ts b/apps/api/src/app/integrations/e2e/update-integration.e2e.ts index 45768f84476..4459a8e89fa 100644 --- a/apps/api/src/app/integrations/e2e/update-integration.e2e.ts +++ b/apps/api/src/app/integrations/e2e/update-integration.e2e.ts @@ -66,6 +66,27 @@ describe('Update Integration - /integrations/:integrationId (PUT)', function () expect(integration.credentials.secretKey).to.equal(payload.credentials.secretKey); }); + it('should update conditions on integration', async function () { + const payload = { + providerId: EmailProviderIdEnum.SendGrid, + channel: ChannelTypeEnum.EMAIL, + credentials: { apiKey: 'new_key', secretKey: 'new_secret' }, + active: true, + check: false, + conditions: [{}], + }; + + const integration = (await session.testAgent.get(`/v1/integrations`)).body.data.find((i) => i.channel === 'email'); + + expect(integration.conditions.length).to.equal(0); + + await session.testAgent.put(`/v1/integrations/${integration._id}`).send(payload); + + const result = (await session.testAgent.get(`/v1/integrations`)).body.data[0]; + + expect(result.conditions.length).to.equal(1); + }); + it('should not allow to update the integration with same identifier', async function () { const identifier2 = 'identifier2'; const integrationOne = await integrationRepository.create({ From f689ba3c4dd3395ceefdd74f0ba5e22611140f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Wed, 6 Sep 2023 09:17:40 +0200 Subject: [PATCH 2/8] feat: add test for set as primary integration with conditions --- .../e2e/set-itegration-as-primary.e2e.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts b/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts index db3805efa7a..ccae3a3d11f 100644 --- a/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts +++ b/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts @@ -65,6 +65,33 @@ describe('Set Integration As Primary - /integrations/:integrationId/set-primary expect(body.message).to.equal(`Channel ${inAppIntegration.channel} does not support primary`); }); + it('clears conditions when set as primary', async () => { + await integrationRepository.deleteMany({ + _organizationId: session.organization._id, + _environmentId: session.environment._id, + }); + + const integration = await integrationRepository.create({ + name: 'Email with conditions', + identifier: 'identifier1', + providerId: EmailProviderIdEnum.SendGrid, + channel: ChannelTypeEnum.EMAIL, + active: false, + _organizationId: session.organization._id, + _environmentId: session.environment._id, + conditions: [{}], + }); + + await session.testAgent.post(`/v1/integrations/${integration._id}/set-primary`).send({}); + + const found = await integrationRepository.findOne({ + _id: integration._id, + _organizationId: session.organization._id, + }); + + expect(found?.conditions).to.deep.equal([]); + }); + it('push channel does not support primary flag, then for integration it should throw bad request exception', async () => { await integrationRepository.deleteMany({ _organizationId: session.organization._id, From 6d3da19400241e450d338e26ed1f695ad845da5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Wed, 6 Sep 2023 11:52:59 +0200 Subject: [PATCH 3/8] feat: add more update integration tests --- .../app/integrations/e2e/update-integration.e2e.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/api/src/app/integrations/e2e/update-integration.e2e.ts b/apps/api/src/app/integrations/e2e/update-integration.e2e.ts index 4459a8e89fa..5fe346a2a75 100644 --- a/apps/api/src/app/integrations/e2e/update-integration.e2e.ts +++ b/apps/api/src/app/integrations/e2e/update-integration.e2e.ts @@ -70,21 +70,27 @@ describe('Update Integration - /integrations/:integrationId (PUT)', function () const payload = { providerId: EmailProviderIdEnum.SendGrid, channel: ChannelTypeEnum.EMAIL, - credentials: { apiKey: 'new_key', secretKey: 'new_secret' }, + credentials: { apiKey: 'SG.123', secretKey: 'abc' }, active: true, check: false, conditions: [{}], }; - const integration = (await session.testAgent.get(`/v1/integrations`)).body.data.find((i) => i.channel === 'email'); + const data = (await session.testAgent.get(`/v1/integrations`)).body.data; + + const integration = data.find((i) => i.primary && i.channel === 'email'); expect(integration.conditions.length).to.equal(0); await session.testAgent.put(`/v1/integrations/${integration._id}`).send(payload); - const result = (await session.testAgent.get(`/v1/integrations`)).body.data[0]; + const result = await integrationRepository.findOne({ + _id: integration._id, + _organizationId: session.organization._id, + }); - expect(result.conditions.length).to.equal(1); + expect(result?.conditions?.length).to.equal(1); + expect(result?.primary).to.equal(false); }); it('should not allow to update the integration with same identifier', async function () { From 3c1fade02dec7d5441f8621de77d6e429cad5c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Thu, 7 Sep 2023 15:47:24 +0200 Subject: [PATCH 4/8] feat: add e2e test for trigger event with condition --- .../src/app/events/e2e/trigger-event.e2e.ts | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/apps/api/src/app/events/e2e/trigger-event.e2e.ts b/apps/api/src/app/events/e2e/trigger-event.e2e.ts index 091602705d4..ab99e0cf045 100644 --- a/apps/api/src/app/events/e2e/trigger-event.e2e.ts +++ b/apps/api/src/app/events/e2e/trigger-event.e2e.ts @@ -34,6 +34,7 @@ import { MESSAGE_GENERIC_RETENTION_DAYS, } from '@novu/shared'; import { EmailEventStatusEnum } from '@novu/stateless'; +import { createTenant } from '../../tenant/e2e/create-tenant.e2e'; const axiosInstance = axios.create(); @@ -65,6 +66,45 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { subscriber = await subscriberService.createSubscriber(); }); + it('should use conditions to select integration', async function () { + const payload = { + providerId: EmailProviderIdEnum.Mailgun, + channel: 'email', + credentials: { apiKey: '123', secretKey: 'abc' }, + _environmentId: session.environment._id, + conditions: [ + { + cildren: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], + }, + ], + active: true, + check: false, + }; + + await session.testAgent.post('/v1/integrations').send(payload); + + template = await createTemplate(session, ChannelTypeEnum.EMAIL); + + createTenant({ session, identifier: 'test', name: 'test' }); + + await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test'); + + await session.awaitRunningJobs(template._id); + + const createdSubscriber = await subscriberRepository.findBySubscriberId( + session.environment._id, + subscriber.subscriberId + ); + + const message = await messageRepository.findOne({ + _environmentId: session.environment._id, + _subscriberId: createdSubscriber?._id, + channel: ChannelTypeEnum.EMAIL, + }); + + expect(message?.providerId).to.equal(payload.providerId); + }); + it('should trigger an event successfully', async function () { const response = await axiosInstance.post( `${session.serverUrl}${eventTriggerPath}`, @@ -1848,7 +1888,8 @@ export async function sendTrigger( template, newSubscriberIdInAppNotification: string, payload: Record = {}, - overrides: Record = {} + overrides: Record = {}, + tenant?: string ): Promise { return await axiosInstance.post( `${session.serverUrl}${eventTriggerPath}`, @@ -1861,6 +1902,7 @@ export async function sendTrigger( ...payload, }, overrides, + tenant, }, { headers: { From f74afb7deccb28a154855acbf02f1d6dcf1e5244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Thu, 7 Sep 2023 15:50:43 +0200 Subject: [PATCH 5/8] fix: csspell error --- apps/api/src/app/events/e2e/trigger-event.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/app/events/e2e/trigger-event.e2e.ts b/apps/api/src/app/events/e2e/trigger-event.e2e.ts index ab99e0cf045..dcb0eded402 100644 --- a/apps/api/src/app/events/e2e/trigger-event.e2e.ts +++ b/apps/api/src/app/events/e2e/trigger-event.e2e.ts @@ -74,7 +74,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { _environmentId: session.environment._id, conditions: [ { - cildren: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], + children: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], }, ], active: true, From c5952cbeb549e3b91e7c626099277d20f901c939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Fri, 8 Sep 2023 05:22:05 +0200 Subject: [PATCH 6/8] feat: add more tests and fixes --- .../src/app/events/e2e/trigger-event.e2e.ts | 26 +++++- .../parse-event-request.usecase.ts | 25 +++++- .../dtos/create-integration-request.dto.ts | 13 ++- .../dtos/update-integration.dto.ts | 4 +- .../e2e/create-integration.e2e.ts | 31 +++++++- .../e2e/set-itegration-as-primary.e2e.ts | 1 + .../e2e/update-integration.e2e.ts | 38 ++++++++- .../integrations/integrations.controller.ts | 79 +++++++++++-------- .../create-integration.command.ts | 8 +- .../update-integration.command.ts | 7 +- 10 files changed, 189 insertions(+), 43 deletions(-) diff --git a/apps/api/src/app/events/e2e/trigger-event.e2e.ts b/apps/api/src/app/events/e2e/trigger-event.e2e.ts index dcb0eded402..506c5ff191e 100644 --- a/apps/api/src/app/events/e2e/trigger-event.e2e.ts +++ b/apps/api/src/app/events/e2e/trigger-event.e2e.ts @@ -85,7 +85,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { template = await createTemplate(session, ChannelTypeEnum.EMAIL); - createTenant({ session, identifier: 'test', name: 'test' }); + await createTenant({ session, identifier: 'test', name: 'test' }); await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test'); @@ -105,6 +105,30 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(message?.providerId).to.equal(payload.providerId); }); + it('should use throw when using a non existing tenant', async function () { + const payload = { + providerId: EmailProviderIdEnum.Mailgun, + channel: 'email', + credentials: { apiKey: '123', secretKey: 'abc' }, + _environmentId: session.environment._id, + conditions: [ + { + children: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], + }, + ], + active: true, + check: false, + }; + + await session.testAgent.post('/v1/integrations').send(payload); + + template = await createTemplate(session, ChannelTypeEnum.EMAIL); + + const result = await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test'); + + expect(result.data.data.status).to.equal('no_tenant_found'); + }); + it('should trigger an event successfully', async function () { const response = await axiosInstance.post( `${session.serverUrl}${eventTriggerPath}`, diff --git a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts index ec3655cc283..78cefae31d0 100644 --- a/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts +++ b/apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts @@ -11,7 +11,7 @@ import { StorageHelperService, WorkflowQueueService, } from '@novu/application-generic'; -import { NotificationTemplateRepository, NotificationTemplateEntity } from '@novu/dal'; +import { NotificationTemplateRepository, NotificationTemplateEntity, TenantRepository } from '@novu/dal'; import { ISubscribersDefine, ITenantDefine, @@ -35,7 +35,8 @@ export class ParseEventRequest { private verifyPayload: VerifyPayload, private storageHelperService: StorageHelperService, private workflowQueueService: WorkflowQueueService, - private mapTriggerRecipients: MapTriggerRecipients + private mapTriggerRecipients: MapTriggerRecipients, + private tenantRepository: TenantRepository ) {} @InstrumentUsecase() @@ -90,6 +91,17 @@ export class ParseEventRequest { }; } + if (command.tenant) { + try { + await this.validateTenant(typeof command.tenant === 'string' ? command.tenant : command.tenant.identifier); + } catch (e) { + return { + acknowledged: true, + status: 'no_tenant_found', + }; + } + } + Sentry.addBreadcrumb({ message: 'Sending trigger', data: { @@ -146,6 +158,15 @@ export class ParseEventRequest { ); } + private async validateTenant(identifier: string) { + const found = await this.tenantRepository.findOne({ + identifier, + }); + if (!found) { + throw new ApiException(`Tenant with identifier ${identifier} cound not be found`); + } + } + @Instrument() private async validateSubscriberIdProperty(to: ISubscribersDefine[]): Promise { for (const subscriber of to) { diff --git a/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts b/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts index a407877d889..324735cb258 100644 --- a/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts +++ b/apps/api/src/app/integrations/dtos/create-integration-request.dto.ts @@ -1,4 +1,13 @@ -import { IsBoolean, IsDefined, IsEnum, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { + IsArray, + IsBoolean, + IsDefined, + IsEnum, + IsMongoId, + IsOptional, + IsString, + ValidateNested, +} from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { ChannelTypeEnum, ICreateIntegrationBodyDto } from '@novu/shared'; @@ -58,6 +67,8 @@ export class CreateIntegrationRequestDto implements ICreateIntegrationBodyDto { @ApiPropertyOptional({ type: [StepFilter], }) + @IsArray() + @IsOptional() @ValidateNested({ each: true }) conditions?: StepFilter[]; } diff --git a/apps/api/src/app/integrations/dtos/update-integration.dto.ts b/apps/api/src/app/integrations/dtos/update-integration.dto.ts index a26ee3a7290..4ccc2b59beb 100644 --- a/apps/api/src/app/integrations/dtos/update-integration.dto.ts +++ b/apps/api/src/app/integrations/dtos/update-integration.dto.ts @@ -1,6 +1,6 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; import { IUpdateIntegrationBodyDto } from '@novu/shared'; -import { IsBoolean, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; import { CredentialsDto } from './credentials.dto'; import { Type } from 'class-transformer'; import { StepFilter } from '../../shared/dtos/step-filter'; @@ -45,6 +45,8 @@ export class UpdateIntegrationRequestDto implements IUpdateIntegrationBodyDto { @ApiPropertyOptional({ type: [StepFilter], }) + @IsArray() + @IsOptional() @ValidateNested({ each: true }) conditions?: StepFilter[]; } diff --git a/apps/api/src/app/integrations/e2e/create-integration.e2e.ts b/apps/api/src/app/integrations/e2e/create-integration.e2e.ts index 7abead71b53..e64e8cce195 100644 --- a/apps/api/src/app/integrations/e2e/create-integration.e2e.ts +++ b/apps/api/src/app/integrations/e2e/create-integration.e2e.ts @@ -111,12 +111,41 @@ describe('Create Integration - /integration (POST)', function () { identifier: 'identifier-conditions', active: false, check: false, - conditions: [{}], + conditions: [ + { + children: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], + }, + ], }; const { body } = await session.testAgent.post('/v1/integrations').send(payload); expect(body.data.conditions.length).to.equal(1); + expect(body.data.conditions[0].children.length).to.equal(1); + expect(body.data.conditions[0].children[0].on).to.equal('tenant'); + expect(body.data.conditions[0].children[0].field).to.equal('identifier'); + expect(body.data.conditions[0].children[0].value).to.equal('test'); + expect(body.data.conditions[0].children[0].operator).to.equal('EQUAL'); + }); + + it('should return error with malformed conditions', async function () { + const payload = { + providerId: EmailProviderIdEnum.SendGrid, + channel: ChannelTypeEnum.EMAIL, + identifier: 'identifier-conditions', + active: false, + check: false, + conditions: [ + { + children: 'test', + }, + ], + }; + + const { body } = await session.testAgent.post('/v1/integrations').send(payload); + + expect(body.statusCode).to.equal(400); + expect(body.error).to.equal('Bad Request'); }); it('should not allow to create integration with same identifier', async function () { diff --git a/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts b/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts index ccae3a3d11f..1e32cde3ee0 100644 --- a/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts +++ b/apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts @@ -90,6 +90,7 @@ describe('Set Integration As Primary - /integrations/:integrationId/set-primary }); expect(found?.conditions).to.deep.equal([]); + expect(found?.primary).to.equal(true); }); it('push channel does not support primary flag, then for integration it should throw bad request exception', async () => { diff --git a/apps/api/src/app/integrations/e2e/update-integration.e2e.ts b/apps/api/src/app/integrations/e2e/update-integration.e2e.ts index 5fe346a2a75..4cbc2ec7c42 100644 --- a/apps/api/src/app/integrations/e2e/update-integration.e2e.ts +++ b/apps/api/src/app/integrations/e2e/update-integration.e2e.ts @@ -6,6 +6,7 @@ import { ChatProviderIdEnum, EmailProviderIdEnum, InAppProviderIdEnum, + ITenantFilterPart, PushProviderIdEnum, } from '@novu/shared'; @@ -73,7 +74,11 @@ describe('Update Integration - /integrations/:integrationId (PUT)', function () credentials: { apiKey: 'SG.123', secretKey: 'abc' }, active: true, check: false, - conditions: [{}], + conditions: [ + { + children: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], + }, + ], }; const data = (await session.testAgent.get(`/v1/integrations`)).body.data; @@ -91,6 +96,37 @@ describe('Update Integration - /integrations/:integrationId (PUT)', function () expect(result?.conditions?.length).to.equal(1); expect(result?.primary).to.equal(false); + expect(result?.conditions?.at(0)?.children.length).to.equal(1); + expect(result?.conditions?.at(0)?.children.at(0)?.on).to.equal('tenant'); + expect((result?.conditions?.at(0)?.children.at(0) as ITenantFilterPart)?.field).to.equal('identifier'); + expect((result?.conditions?.at(0)?.children.at(0) as ITenantFilterPart)?.value).to.equal('test'); + expect((result?.conditions?.at(0)?.children.at(0) as ITenantFilterPart)?.operator).to.equal('EQUAL'); + }); + + it('should return error with malformed conditions', async function () { + const payload = { + providerId: EmailProviderIdEnum.SendGrid, + channel: ChannelTypeEnum.EMAIL, + credentials: { apiKey: 'SG.123', secretKey: 'abc' }, + active: true, + check: false, + conditions: [ + { + children: 'test', + }, + ], + }; + + const data = (await session.testAgent.get(`/v1/integrations`)).body.data; + + const integration = data.find((i) => i.primary && i.channel === 'email'); + + expect(integration.conditions.length).to.equal(0); + + const { body } = await session.testAgent.put(`/v1/integrations/${integration._id}`).send(payload); + + expect(body.statusCode).to.equal(400); + expect(body.error).to.equal('Bad Request'); }); it('should not allow to update the integration with same identifier', async function () { diff --git a/apps/api/src/app/integrations/integrations.controller.ts b/apps/api/src/app/integrations/integrations.controller.ts index 0d654dc269e..0b3ecb9043a 100644 --- a/apps/api/src/app/integrations/integrations.controller.ts +++ b/apps/api/src/app/integrations/integrations.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, ClassSerializerInterceptor, Controller, @@ -135,21 +136,29 @@ export class IntegrationsController { @UserSession() user: IJwtPayload, @Body() body: CreateIntegrationRequestDto ): Promise { - return await this.createIntegrationUsecase.execute( - CreateIntegrationCommand.create({ - userId: user._id, - name: body.name, - identifier: body.identifier, - environmentId: body._environmentId ?? user.environmentId, - organizationId: user.organizationId, - providerId: body.providerId, - channel: body.channel, - credentials: body.credentials, - active: body.active ?? false, - check: body.check ?? true, - conditions: body.conditions, - }) - ); + try { + return await this.createIntegrationUsecase.execute( + CreateIntegrationCommand.create({ + userId: user._id, + name: body.name, + identifier: body.identifier, + environmentId: body._environmentId ?? user.environmentId, + organizationId: user.organizationId, + providerId: body.providerId, + channel: body.channel, + credentials: body.credentials, + active: body.active ?? false, + check: body.check ?? true, + conditions: body.conditions, + }) + ); + } catch (e) { + if (e.message.includes('Integration validation failed') || e.message.includes('Cast to embedded')) { + throw new BadRequestException(e.message); + } + + throw e; + } } @Put('/:integrationId') @@ -162,26 +171,34 @@ export class IntegrationsController { summary: 'Update integration', }) @ExternalApiAccessible() - updateIntegrationById( + async updateIntegrationById( @UserSession() user: IJwtPayload, @Param('integrationId') integrationId: string, @Body() body: UpdateIntegrationRequestDto ): Promise { - return this.updateIntegrationUsecase.execute( - UpdateIntegrationCommand.create({ - userId: user._id, - name: body.name, - identifier: body.identifier, - environmentId: body._environmentId, - userEnvironmentId: user.environmentId, - organizationId: user.organizationId, - integrationId, - credentials: body.credentials, - active: body.active, - check: body.check ?? true, - conditions: body.conditions, - }) - ); + try { + return await this.updateIntegrationUsecase.execute( + UpdateIntegrationCommand.create({ + userId: user._id, + name: body.name, + identifier: body.identifier, + environmentId: body._environmentId, + userEnvironmentId: user.environmentId, + organizationId: user.organizationId, + integrationId, + credentials: body.credentials, + active: body.active, + check: body.check ?? true, + conditions: body.conditions, + }) + ); + } catch (e) { + if (e.message.includes('Integration validation failed') || e.message.includes('Cast to embedded')) { + throw new BadRequestException(e.message); + } + + throw e; + } } @Post('/:integrationId/set-primary') diff --git a/apps/api/src/app/integrations/usecases/create-integration/create-integration.command.ts b/apps/api/src/app/integrations/usecases/create-integration/create-integration.command.ts index af2fb5c048a..d425a29ea21 100644 --- a/apps/api/src/app/integrations/usecases/create-integration/create-integration.command.ts +++ b/apps/api/src/app/integrations/usecases/create-integration/create-integration.command.ts @@ -1,8 +1,8 @@ -import { IsDefined, IsEnum, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsDefined, IsEnum, IsOptional, IsString, ValidateNested } from 'class-validator'; import { ChannelTypeEnum, ICredentialsDto } from '@novu/shared'; import { EnvironmentCommand } from '../../../shared/commands/project.command'; -import { StepFilter } from '../../../shared/dtos/step-filter'; +import { MessageFilter } from '../../../workflows/usecases/create-notification-template'; export class CreateIntegrationCommand extends EnvironmentCommand { @IsOptional() @@ -34,5 +34,7 @@ export class CreateIntegrationCommand extends EnvironmentCommand { userId: string; @IsOptional() - conditions?: StepFilter[]; + @IsArray() + @ValidateNested({ each: true }) + conditions?: MessageFilter[]; } diff --git a/apps/api/src/app/integrations/usecases/update-integration/update-integration.command.ts b/apps/api/src/app/integrations/usecases/update-integration/update-integration.command.ts index e43a1d02aa7..9b8d2a2db38 100644 --- a/apps/api/src/app/integrations/usecases/update-integration/update-integration.command.ts +++ b/apps/api/src/app/integrations/usecases/update-integration/update-integration.command.ts @@ -1,8 +1,9 @@ -import { IsDefined, IsMongoId, IsOptional, IsString } from 'class-validator'; +import { IsArray, IsDefined, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; import { ICredentialsDto } from '@novu/shared'; import { OrganizationCommand } from '../../../shared/commands/organization.command'; import { StepFilter } from '../../../shared/dtos/step-filter'; +import { MessageFilter } from '../../../workflows/usecases/create-notification-template'; export class UpdateIntegrationCommand extends OrganizationCommand { @IsOptional() @@ -34,5 +35,7 @@ export class UpdateIntegrationCommand extends OrganizationCommand { check?: boolean; @IsOptional() - conditions?: StepFilter[]; + @IsArray() + @ValidateNested({ each: true }) + conditions?: MessageFilter[]; } From 331b7418da934f5651ee26911b8ef87f9b2e3b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Fri, 8 Sep 2023 05:24:04 +0200 Subject: [PATCH 7/8] fix: test description --- apps/api/src/app/events/e2e/trigger-event.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/app/events/e2e/trigger-event.e2e.ts b/apps/api/src/app/events/e2e/trigger-event.e2e.ts index 506c5ff191e..44ef3552b24 100644 --- a/apps/api/src/app/events/e2e/trigger-event.e2e.ts +++ b/apps/api/src/app/events/e2e/trigger-event.e2e.ts @@ -105,7 +105,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { expect(message?.providerId).to.equal(payload.providerId); }); - it('should use throw when using a non existing tenant', async function () { + it('should return correct status when using a non existing tenant', async function () { const payload = { providerId: EmailProviderIdEnum.Mailgun, channel: 'email', From b22e39f225f00c6bab0ad19d8ec65130d921e5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=B6derberg?= Date: Fri, 8 Sep 2023 07:05:07 +0200 Subject: [PATCH 8/8] fix: test --- apps/api/src/app/events/e2e/trigger-event.e2e.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/events/e2e/trigger-event.e2e.ts b/apps/api/src/app/events/e2e/trigger-event.e2e.ts index 44ef3552b24..1426af9d07e 100644 --- a/apps/api/src/app/events/e2e/trigger-event.e2e.ts +++ b/apps/api/src/app/events/e2e/trigger-event.e2e.ts @@ -113,7 +113,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { _environmentId: session.environment._id, conditions: [ { - children: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }], + children: [{ field: 'identifier', value: 'test1', operator: 'EQUAL', on: 'tenant' }], }, ], active: true, @@ -124,7 +124,7 @@ describe(`Trigger event - ${eventTriggerPath} (POST)`, function () { template = await createTemplate(session, ChannelTypeEnum.EMAIL); - const result = await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test'); + const result = await sendTrigger(session, template, subscriber.subscriberId, {}, {}, 'test1'); expect(result.data.data.status).to.equal('no_tenant_found'); });