Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(api): Echo server mvp test suite #5584

Merged
merged 11 commits into from
May 28, 2024
2 changes: 1 addition & 1 deletion .source
288 changes: 264 additions & 24 deletions apps/api/src/app/events/e2e/echo-trigger.e2e-ee.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import axios from 'axios';
import { expect } from 'chai';
import { UserSession, SubscribersService } from '@novu/testing';
import { MessageRepository, SubscriberEntity, NotificationTemplateRepository } from '@novu/dal';
import { StepTypeEnum } from '@novu/shared';
import {
MessageRepository,
SubscriberEntity,
NotificationTemplateRepository,
ExecutionDetailsRepository,
} from '@novu/dal';
import { ExecutionDetailsStatusEnum, MarkMessagesAsEnum, StepTypeEnum } from '@novu/shared';
import { echoServer } from '../../../../e2e/echo.server';

const eventTriggerPath = '/v1/events/trigger';

describe('Echo Trigger ', async () => {
let session: UserSession;
const messageRepository = new MessageRepository();
const workflowsRepository = new NotificationTemplateRepository();
const executionDetailsRepository = new ExecutionDetailsRepository();

let subscriber: SubscriberEntity;
let subscriberService: SubscribersService;

const triggerEvent = async (workflowId: string, payload): Promise<void> => {
await axios.post(
`${session.serverUrl}/v1/events/trigger`,
{
name: workflowId,
to: [subscriber.subscriberId],
payload,
},
{
headers: {
authorization: `ApiKey ${session.apiKey}`,
},
}
);
};

beforeEach(async () => {
session = new UserSession();
await session.initialize();
Expand Down Expand Up @@ -56,47 +77,266 @@ describe('Echo Trigger ', async () => {
}
);

const resultDiscover = await axios.get(echoServer.serverPath + '/echo?action=discover');
await discoverAndSyncEcho(session);

await session.testAgent.post(`/v1/echo/sync`).send({
chimeraUrl: echoServer.serverPath + '/echo',
workflows: resultDiscover.data.workflows,
const workflow = await workflowsRepository.findByTriggerIdentifier(session.environment._id, workflowId);
expect(workflow).to.be.ok;

if (!workflow) {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, { name: 'test_name' });

await session.awaitRunningJobs(workflow._id);

const messagesAfter = await messageRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
channel: StepTypeEnum.EMAIL,
});

expect(messagesAfter.length).to.be.eq(1);
expect(messagesAfter[0].subject).to.include('This is an email subject TEST');
});

it('should have execution detail errors for invalid trigger payload', async () => {
const workflowId = 'missing-payload-var';
await echoServer.echo.workflow(
workflowId,
async ({ step, payload }) => {
await step.email('send-email', async () => {
return {
subject: 'This is an email subject',
body: 'Body result',
};
});
},
{
payloadSchema: {
type: 'object',
properties: {
name: { type: 'string' },
},
required: ['name'],
additionalProperties: false,
} as const,
}
);

await discoverAndSyncEcho(session);

const workflow = await workflowsRepository.findByTriggerIdentifier(session.environment._id, workflowId);
expect(workflow).to.be.ok;

if (!workflow) {
throw new Error('Workflow not found');
}

await axios.post(
`${session.serverUrl}${eventTriggerPath}`,
{
name: workflowId,
to: {
subscriberId: subscriber.subscriberId,
email: 'test@subscriber.com',
},
payload: {
name: 'test_name',
await triggerEvent(workflowId, {});

await session.awaitRunningJobs(workflow._id);

const messagesAfter = await messageRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
channel: StepTypeEnum.EMAIL,
});

expect(messagesAfter.length).to.be.eq(0);
const executionDetailsRequired = await executionDetailsRepository.find({
_environmentId: session.environment._id,
_notificationTemplateId: workflow._id,
status: ExecutionDetailsStatusEnum.FAILED,
});

let raw = JSON.parse(executionDetailsRequired[0]?.raw ?? '');
let error = raw.raw.data[0].message;

expect(error).to.include("must have required property 'name'");

await executionDetailsRepository.delete({ _environmentId: session.environment._id });

await triggerEvent(workflowId, { name: 4 });
await session.awaitRunningJobs(workflow._id);

const executionDetailsInvalidType = await executionDetailsRepository.find({
_environmentId: session.environment._id,
_notificationTemplateId: workflow._id,
status: ExecutionDetailsStatusEnum.FAILED,
});
raw = JSON.parse(executionDetailsInvalidType[0]?.raw ?? '');
error = raw.raw.data[0].message;

expect(error).to.include('must be string');
});

it('should use custom step', async () => {
const workflowId = 'with-custom-step';
await echoServer.echo.workflow(workflowId, async ({ step }) => {
const resInApp = await step.inApp('send-in-app', async () => {
return {
body: `Hello There`,
};
});

const resCustom = await step.custom(
'custom',
async () => {
await markAllSubscriberMessagesAs(session, subscriber.subscriberId, MarkMessagesAsEnum.READ);

return { readString: 'Read', unReadString: 'Unread' };
},
{
outputSchema: {
type: 'object',
properties: {
readString: { type: 'string' },
unReadString: { type: 'string' },
},
required: [],
additionalProperties: false,
} as const,
}
);

await step.email('send-email', async () => {
const emailSubject = resInApp.read ? resCustom?.readString : resCustom?.unReadString;

return {
subject: `${emailSubject}`,
body: 'Email Body',
};
});
});

await discoverAndSyncEcho(session);

const workflow = await workflowsRepository.findByTriggerIdentifier(session.environment._id, workflowId);
expect(workflow).to.be.ok;

if (!workflow) {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, {});

await session.awaitRunningJobs(workflow._id);

const messagesAfterInApp = await messageRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
channel: StepTypeEnum.IN_APP,
});

expect(messagesAfterInApp.length).to.be.eq(1);

const messagesAfterEmail = await messageRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
channel: StepTypeEnum.EMAIL,
});
expect(messagesAfterEmail.length).to.be.eq(1);
expect(messagesAfterEmail[0].subject).to.include('Read');
});

it('should trigger the echo workflow with digest', async () => {
const workflowId = 'digest-workflow';
await echoServer.echo.workflow(
workflowId,
async ({ step }) => {
const digestResponse = await step.digest(
'digest',
async (inputs) => {
return {
amount: inputs.amount,
unit: inputs.unit,
};
},
{
inputSchema: {
type: 'object',
properties: {
amount: {
type: 'number',
default: 2,
},
unit: {
type: 'string',
enum: ['seconds', 'minutes', 'hours', 'days', 'weeks', 'months'],
default: 'seconds',
},
},
},
}
);

await step.sms('send-sms', async () => {
const events = digestResponse.events.length;

return {
body: `${events} people liked your post`,
};
});
},
{
headers: {
authorization: `ApiKey ${session.apiKey}`,
},
payloadSchema: {
type: 'object',
properties: {
name: { type: 'string', default: 'default_name' },
},
required: [],
additionalProperties: false,
} as const,
}
);
await session.awaitRunningJobs(workflow._id);

await discoverAndSyncEcho(session);

const workflow = await workflowsRepository.findByTriggerIdentifier(session.environment._id, workflowId);
expect(workflow).to.be.ok;

if (!workflow) {
throw new Error('Workflow not found');
}

await triggerEvent(workflowId, { name: 'John' });
await triggerEvent(workflowId, { name: 'Bela' });

await session.awaitRunningJobs(workflow?._id, false, 0);

const messagesAfter = await messageRepository.find({
_environmentId: session.environment._id,
_subscriberId: subscriber._id,
channel: StepTypeEnum.EMAIL,
channel: StepTypeEnum.SMS,
});

expect(messagesAfter.length).to.be.eq(1);
expect(messagesAfter[0].subject).to.include('This is an email subject TEST');
expect(messagesAfter[0].content).to.include('2 people liked your post');
});
});

async function discoverAndSyncEcho(session: UserSession) {
const resultDiscover = await axios.get(echoServer.serverPath + '/echo?action=discover');

await session.testAgent.post(`/v1/echo/sync`).send({
chimeraUrl: echoServer.serverPath + '/echo',
workflows: resultDiscover.data.workflows,
});
}

async function markAllSubscriberMessagesAs(session: UserSession, subscriberId: string, markAs: MarkMessagesAsEnum) {
const response = await axios.post(
`${session.serverUrl}/v1/subscribers/${subscriberId}/messages/mark-all`,
{
markAs,
},
{
headers: {
authorization: `ApiKey ${session.apiKey}`,
},
}
);

return response.data;
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export class SendMessageSms extends SendMessageBase {
step.template = template;
}

let content: string | null = '';
const chimeraBody = command.chimeraData?.outputs.body;
let content: string = chimeraBody || '';

try {
if (!command.chimeraData) {
Expand Down
Loading