Skip to content

Commit

Permalink
Merge pull request #4120 from novuhq/nv-2771-api-e2e-tests-for-the-in…
Browse files Browse the repository at this point in the history
…tegration-conditions

Nv 2771 api e2e tests for the integration conditions
  • Loading branch information
davidsoderberg authored Sep 8, 2023
2 parents 3e8cf76 + b22e39f commit f399b56
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 41 deletions.
68 changes: 67 additions & 1 deletion apps/api/src/app/events/e2e/trigger-event.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -65,6 +66,69 @@ 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: [
{
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);

await 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 return correct status 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: 'test1', 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, {}, {}, 'test1');

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}`,
Expand Down Expand Up @@ -1848,7 +1912,8 @@ export async function sendTrigger(
template,
newSubscriberIdInAppNotification: string,
payload: Record<string, unknown> = {},
overrides: Record<string, unknown> = {}
overrides: Record<string, unknown> = {},
tenant?: string
): Promise<AxiosResponse> {
return await axiosInstance.post(
`${session.serverUrl}${eventTriggerPath}`,
Expand All @@ -1861,6 +1926,7 @@ export async function sendTrigger(
...payload,
},
overrides,
tenant,
},
{
headers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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<boolean> {
for (const subscriber of to) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -58,6 +67,8 @@ export class CreateIntegrationRequestDto implements ICreateIntegrationBodyDto {
@ApiPropertyOptional({
type: [StepFilter],
})
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
conditions?: StepFilter[];
}
4 changes: 3 additions & 1 deletion apps/api/src/app/integrations/dtos/update-integration.dto.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -45,6 +45,8 @@ export class UpdateIntegrationRequestDto implements IUpdateIntegrationBodyDto {
@ApiPropertyOptional({
type: [StepFilter],
})
@IsArray()
@IsOptional()
@ValidateNested({ each: true })
conditions?: StepFilter[];
}
44 changes: 44 additions & 0 deletions apps/api/src/app/integrations/e2e/create-integration.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,50 @@ 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: [
{
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 () {
const payload = {
providerId: EmailProviderIdEnum.SendGrid,
Expand Down
28 changes: 28 additions & 0 deletions apps/api/src/app/integrations/e2e/set-itegration-as-primary.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,34 @@ 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([]);
expect(found?.primary).to.equal(true);
});

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,
Expand Down
63 changes: 63 additions & 0 deletions apps/api/src/app/integrations/e2e/update-integration.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ChatProviderIdEnum,
EmailProviderIdEnum,
InAppProviderIdEnum,
ITenantFilterPart,
PushProviderIdEnum,
} from '@novu/shared';

Expand Down Expand Up @@ -66,6 +67,68 @@ 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: 'SG.123', secretKey: 'abc' },
active: true,
check: false,
conditions: [
{
children: [{ field: 'identifier', value: 'test', operator: 'EQUAL', on: 'tenant' }],
},
],
};

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 integrationRepository.findOne({
_id: integration._id,
_organizationId: session.organization._id,
});

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 () {
const identifier2 = 'identifier2';
const integrationOne = await integrationRepository.create({
Expand Down
Loading

0 comments on commit f399b56

Please sign in to comment.