From 0e8418752983ae2c72b0ff0d7f9c3be7029153c9 Mon Sep 17 00:00:00 2001 From: GalT <39020298+tatarco@users.noreply.github.com> Date: Thu, 7 Nov 2024 21:15:09 +0100 Subject: [PATCH] bug(api): added status persistence and testing for it --- .../app/workflows-v2/generate-preview.e2e.ts | 24 +- .../src/app/workflows-v2/maily-test-data.ts | 900 +++++++++--------- .../mappers/notification-template-mapper.ts | 2 +- ...ate-and-persist-workflow-issues.usecase.ts | 17 +- .../workflows-v2/workflow.controller.e2e.ts | 45 +- .../notification-template.entity.ts | 3 + .../notification-template.schema.ts | 3 + 7 files changed, 509 insertions(+), 485 deletions(-) diff --git a/apps/api/src/app/workflows-v2/generate-preview.e2e.ts b/apps/api/src/app/workflows-v2/generate-preview.e2e.ts index 985206f2d90..04dec02caa5 100644 --- a/apps/api/src/app/workflows-v2/generate-preview.e2e.ts +++ b/apps/api/src/app/workflows-v2/generate-preview.e2e.ts @@ -84,7 +84,7 @@ describe('Generate Preview', () => { .exist; if (type !== StepTypeEnum.EMAIL) { - expect(previewResponseDto.result!.preview).to.deep.equal(getControlValues(stepId)[type]); + expect(previewResponseDto.result!.preview).to.deep.equal(getTestControlValues(stepId)[type]); } else { assertEmail(previewResponseDto); } @@ -99,7 +99,7 @@ describe('Generate Preview', () => { workflowId, stepDatabaseId, { - controlValues: getControlValues(stepId)[StepTypeEnum.EMAIL], + controlValues: getTestControlValues(stepId)[StepTypeEnum.EMAIL], previewPayload: { payload: { params: { isPayedUser: 'false' } } }, }, 'email' @@ -115,7 +115,7 @@ describe('Generate Preview', () => { workflowId, stepDatabaseId, { - controlValues: getControlValues(stepId)[StepTypeEnum.EMAIL], + controlValues: getTestControlValues(stepId)[StepTypeEnum.EMAIL], previewPayload: { payload: { params: { isPayedUser: 'true' } } }, }, 'email' @@ -131,7 +131,7 @@ describe('Generate Preview', () => { workflowId, stepDatabaseId, { - controlValues: getControlValues(stepId)[StepTypeEnum.EMAIL], + controlValues: getTestControlValues(stepId)[StepTypeEnum.EMAIL], previewPayload: { payload: { params: { isPayedUser: true } } }, }, 'email' @@ -147,7 +147,7 @@ describe('Generate Preview', () => { workflowId, stepDatabaseId, { - controlValues: getControlValues(stepId)[StepTypeEnum.EMAIL], + controlValues: getTestControlValues(stepId)[StepTypeEnum.EMAIL], previewPayload: { payload: { params: { isPayedUser: 'true' } } }, }, 'email' @@ -269,19 +269,19 @@ describe('Generate Preview', () => { function buildDtoNoPayload(stepTypeEnum: StepTypeEnum, stepId: string): GeneratePreviewRequestDto { return { - controlValues: getControlValues(stepId)[stepTypeEnum], + controlValues: getTestControlValues(stepId)[stepTypeEnum], }; } function buildDtoWithPayload(stepTypeEnum: StepTypeEnum, stepId: string): GeneratePreviewRequestDto { return { - controlValues: getControlValues(stepId)[stepTypeEnum], + controlValues: getTestControlValues(stepId)[stepTypeEnum], previewPayload: { payload: { subject: PLACEHOLDER_SUBJECT_INAPP_PAYLOAD_VALUE } }, }; } function buildDtoWithMissingControlValues(stepTypeEnum: StepTypeEnum, stepId: string): GeneratePreviewRequestDto { - const stepTypeToElement = getControlValues(stepId)[stepTypeEnum]; + const stepTypeToElement = getTestControlValues(stepId)[stepTypeEnum]; if (stepTypeEnum === StepTypeEnum.EMAIL) { delete stepTypeToElement.subject; } else { @@ -294,7 +294,7 @@ function buildDtoWithMissingControlValues(stepTypeEnum: StepTypeEnum, stepId: st }; } -function buildEmailControlValuesPayload(stepId: string): EmailStepControlSchemaDto { +function buildEmailControlValuesPayload(stepId?: string): EmailStepControlSchemaDto { return { subject: `Hello, World! ${SUBJECT_TEST_PAYLOAD}`, emailEditor: JSON.stringify(fullCodeSnippet(stepId)), @@ -306,10 +306,10 @@ function buildSimpleForEmail(): EmailStepControlSchemaDto { emailEditor: JSON.stringify(forSnippet), }; } -function buildInAppControlValues(stepId: string) { +function buildInAppControlValues(stepId?: string) { return { subject: `{{subscriber.firstName}} Hello, World! ${PLACEHOLDER_SUBJECT_INAPP}`, - body: 'Hello, World! {{payload.placeholder.body}}', + body: `${stepId ? `steps.${stepId}.origins` : '{{payload.origins}}'} Hello, World! {{payload.placeholder.body}}`, avatar: 'https://www.example.com/avatar.png', primaryAction: { label: '{{payload.secondaryUrl}}', @@ -354,7 +354,7 @@ function buildChatControlValuesPayload() { }; } -const getControlValues = (stepId: string) => ({ +export const getTestControlValues = (stepId?: string) => ({ [StepTypeEnum.SMS]: buildSmsControlValuesPayload(), [StepTypeEnum.EMAIL]: buildEmailControlValuesPayload(stepId) as unknown as Record, [StepTypeEnum.PUSH]: buildPushControlValuesPayload(), diff --git a/apps/api/src/app/workflows-v2/maily-test-data.ts b/apps/api/src/app/workflows-v2/maily-test-data.ts index c0452241f44..555122376f1 100644 --- a/apps/api/src/app/workflows-v2/maily-test-data.ts +++ b/apps/api/src/app/workflows-v2/maily-test-data.ts @@ -77,494 +77,496 @@ export const forSnippet = { ], }; -export const fullCodeSnippet = (stepId) => ({ - type: 'doc', - content: [ - { - type: 'logo', - attrs: { - src: 'https://maily.to/brand/logo.png', - alt: null, - title: null, - 'maily-component': 'logo', - size: 'md', - alignment: 'left', +export function fullCodeSnippet(stepId?: string) { + return { + type: 'doc', + content: [ + { + type: 'logo', + attrs: { + src: 'https://maily.to/brand/logo.png', + alt: null, + title: null, + 'maily-component': 'logo', + size: 'md', + alignment: 'left', + }, }, - }, - { - type: 'spacer', - attrs: { - height: 'xl', + { + type: 'spacer', + attrs: { + height: 'xl', + }, }, - }, - { - type: 'heading', - attrs: { - textAlign: 'left', - level: 2, + { + type: 'heading', + attrs: { + textAlign: 'left', + level: 2, + }, + content: [ + { + type: 'text', + marks: [ + { + type: 'bold', + }, + ], + text: 'Discover Maily', + }, + ], }, - content: [ - { - type: 'text', - marks: [ - { - type: 'bold', - }, - ], - text: 'Discover Maily', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + content: [ + { + type: 'text', + text: 'Are you ready to transform your email communication? Introducing Maily, the powerful emaly.', + }, + ], }, - content: [ - { - type: 'text', - text: 'Are you ready to transform your email communication? Introducing Maily, the powerful emaly.', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + content: [ + { + type: 'text', + text: 'Elevate your email communication with Maily! Click below to try it out:', + }, + ], }, - content: [ - { - type: 'text', - text: 'Elevate your email communication with Maily! Click below to try it out:', + { + type: 'button', + attrs: { + text: 'Try Maily Now →', + url: '', + alignment: 'left', + variant: 'filled', + borderRadius: 'round', + buttonColor: '#000000', + textColor: '#ffffff', }, - ], - }, - { - type: 'button', - attrs: { - text: 'Try Maily Now →', - url: '', - alignment: 'left', - variant: 'filled', - borderRadius: 'round', - buttonColor: '#000000', - textColor: '#ffffff', }, - }, - { - type: 'section', - attrs: { - show: 'payload.params.isPayedUser', - borderRadius: 0, - backgroundColor: '#f7f7f7', - align: 'left', - borderWidth: 1, - borderColor: '#e2e2e2', - paddingTop: 5, - paddingRight: 5, - paddingBottom: 5, - paddingLeft: 5, - marginTop: 0, - marginRight: 0, - marginBottom: 0, - marginLeft: 0, + { + type: 'section', + attrs: { + show: 'payload.params.isPayedUser', + borderRadius: 0, + backgroundColor: '#f7f7f7', + align: 'left', + borderWidth: 1, + borderColor: '#e2e2e2', + paddingTop: 5, + paddingRight: 5, + paddingBottom: 5, + paddingLeft: 5, + marginTop: 0, + marginRight: 0, + marginBottom: 0, + marginLeft: 0, + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + }, + content: [ + { + type: 'variable', + attrs: { + id: 'payload.hidden.section', + label: null, + fallback: 'should be the fallback value', + }, + }, + { + type: 'text', + text: ' ', + }, + { + type: 'variable', + attrs: { + id: 'subscriber.fullName', + label: null, + fallback: 'should be the fallback value', + }, + }, + ], + }, + ], }, - content: [ - { - type: 'paragraph', - attrs: { - textAlign: 'left', + { + type: 'paragraph', + attrs: { + textAlign: 'left', + }, + content: [ + { + type: 'text', + text: 'Join our vibrant community of users and developers on GitHub, where Maily is an ', }, - content: [ - { - type: 'variable', - attrs: { - id: 'payload.hidden.section', - label: null, - fallback: 'should be the fallback value', + { + type: 'text', + marks: [ + { + type: 'link', + attrs: { + href: 'https://github.com/arikchakma/maily.to', + target: '_blank', + rel: 'noopener noreferrer nofollow', + class: null, + }, }, - }, - { - type: 'text', - text: ' ', - }, - { - type: 'variable', - attrs: { - id: 'subscriber.fullName', - label: null, - fallback: 'should be the fallback value', + { + type: 'italic', }, - }, - ], - }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + ], + text: 'open-source', + }, + { + type: 'text', + text: " project. Together, we'll shape the future of email editing.", + }, + ], }, - content: [ - { - type: 'text', - text: 'Join our vibrant community of users and developers on GitHub, where Maily is an ', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - { - type: 'text', - marks: [ - { - type: 'link', - attrs: { - href: 'https://github.com/arikchakma/maily.to', - target: '_blank', - rel: 'noopener noreferrer nofollow', - class: null, - }, - }, - { - type: 'italic', + content: [ + { + type: 'text', + text: '@this is a placeholder value of name payload.body|| ', + }, + { + type: 'variable', + attrs: { + id: 'payload.body', + label: null, + fallback: null, }, - ], - text: 'open-source', - }, - { - type: 'text', - text: " project. Together, we'll shape the future of email editing.", - }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + }, + { + type: 'text', + text: ' |||the value should have been here', + }, + ], }, - content: [ - { - type: 'text', - text: '@this is a placeholder value of name payload.body|| ', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - { - type: 'variable', - attrs: { - id: 'payload.body', - label: null, - fallback: null, + content: [ + { + type: 'text', + text: 'this is a regular for block showing multiple comments:', }, - }, - { - type: 'text', - text: ' |||the value should have been here', - }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + ], }, - content: [ - { - type: 'text', - text: 'this is a regular for block showing multiple comments:', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + content: [ + { + type: 'text', + text: 'This will be two for each one in another column: ', + }, + ], }, - content: [ - { - type: 'text', - text: 'This will be two for each one in another column: ', + { + type: 'columns', + attrs: { + width: '100%', }, - ], - }, - { - type: 'columns', - attrs: { - width: '100%', - }, - content: [ - { - type: 'column', - attrs: { - columnId: '394bcc6f-c674-4d56-aced-f3f54434482e', - width: 50, - verticalAlign: 'top', - borderRadius: 0, - backgroundColor: 'transparent', - borderWidth: 0, - borderColor: 'transparent', - paddingTop: 0, - paddingRight: 0, - paddingBottom: 0, - paddingLeft: 0, - }, - content: [ - { - type: 'for', - attrs: { - each: `steps.${stepId}.origins`, - isUpdatingKey: false, - }, - content: [ - { - type: 'orderedList', - attrs: { - start: 1, - }, - content: [ - { - type: 'listItem', - attrs: { - color: null, - }, - content: [ - { - type: 'paragraph', - attrs: { - textAlign: 'left', - }, - content: [ - { - type: 'text', - text: 'a list item: ', + content: [ + { + type: 'column', + attrs: { + columnId: '394bcc6f-c674-4d56-aced-f3f54434482e', + width: 50, + verticalAlign: 'top', + borderRadius: 0, + backgroundColor: 'transparent', + borderWidth: 0, + borderColor: 'transparent', + paddingTop: 0, + paddingRight: 0, + paddingBottom: 0, + paddingLeft: 0, + }, + content: [ + { + type: 'for', + attrs: { + each: stepId ? `steps.${stepId}.origins` : 'payload.origins', + isUpdatingKey: false, + }, + content: [ + { + type: 'orderedList', + attrs: { + start: 1, + }, + content: [ + { + type: 'listItem', + attrs: { + color: null, + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - { - type: 'payloadValue', - attrs: { - id: 'origin.country', - label: null, + content: [ + { + type: 'text', + text: 'a list item: ', }, - }, - { - type: 'text', - text: ' ', - }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - type: 'column', - attrs: { - columnId: 'a61ae45e-ea27-4a2b-a356-bfad769ea50f', - width: 50, - verticalAlign: 'top', - borderRadius: 0, - backgroundColor: 'transparent', - borderWidth: 0, - borderColor: 'transparent', - paddingTop: 0, - paddingRight: 0, - paddingBottom: 0, - paddingLeft: 0, - }, - content: [ - { - type: 'for', - attrs: { - each: 'payload.students', - isUpdatingKey: false, - }, - content: [ - { - type: 'bulletList', - content: [ - { - type: 'listItem', - attrs: { - color: null, - }, - content: [ - { - type: 'paragraph', - attrs: { - textAlign: 'left', + { + type: 'payloadValue', + attrs: { + id: 'origin.country', + label: null, + }, + }, + { + type: 'text', + text: ' ', + }, + ], }, - content: [ - { - type: 'text', - text: 'bulleted list item: ', + ], + }, + ], + }, + ], + }, + ], + }, + { + type: 'column', + attrs: { + columnId: 'a61ae45e-ea27-4a2b-a356-bfad769ea50f', + width: 50, + verticalAlign: 'top', + borderRadius: 0, + backgroundColor: 'transparent', + borderWidth: 0, + borderColor: 'transparent', + paddingTop: 0, + paddingRight: 0, + paddingBottom: 0, + paddingLeft: 0, + }, + content: [ + { + type: 'for', + attrs: { + each: 'payload.students', + isUpdatingKey: false, + }, + content: [ + { + type: 'bulletList', + content: [ + { + type: 'listItem', + attrs: { + color: null, + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - { - type: 'payloadValue', - attrs: { - id: 'id', - label: null, + content: [ + { + type: 'text', + text: 'bulleted list item: ', }, - }, - { - type: 'text', - text: ' and name: ', - }, - { - type: 'payloadValue', - attrs: { - id: 'name', - label: null, + { + type: 'payloadValue', + attrs: { + id: 'id', + label: null, + }, }, - }, - { - type: 'text', - text: ' ', - }, - ], - }, - ], - }, - { - type: 'listItem', - attrs: { - color: null, - }, - content: [ - { - type: 'paragraph', - attrs: { - textAlign: 'left', + { + type: 'text', + text: ' and name: ', + }, + { + type: 'payloadValue', + attrs: { + id: 'name', + label: null, + }, + }, + { + type: 'text', + text: ' ', + }, + ], }, - content: [ - { - type: 'text', - text: 'buffer bullet item', - }, - ], + ], + }, + { + type: 'listItem', + attrs: { + color: null, }, - ], - }, - ], - }, - ], - }, - ], - }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', - }, - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + }, + content: [ + { + type: 'text', + text: 'buffer bullet item', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], }, - content: [ - { - type: 'text', - text: 'This will be a nested for block', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - ], - }, - { - type: 'for', - attrs: { - each: 'payload.food.items', - isUpdatingKey: false, }, - content: [ - { - type: 'paragraph', - attrs: { - textAlign: 'left', + { + type: 'paragraph', + attrs: { + textAlign: 'left', + }, + content: [ + { + type: 'text', + text: 'This will be a nested for block', }, - content: [ - { - type: 'text', - text: 'this is a food item with name ', + ], + }, + { + type: 'for', + attrs: { + each: 'payload.food.items', + isUpdatingKey: false, + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - { - type: 'payloadValue', - attrs: { - id: 'name', - label: null, + content: [ + { + type: 'text', + text: 'this is a food item with name ', }, - }, - { - type: 'text', - text: ' ', - }, - ], - }, - { - type: 'for', - attrs: { - each: 'payload.food.warnings', - isUpdatingKey: false, - }, - content: [ - { - type: 'bulletList', - content: [ - { - type: 'listItem', - attrs: { - color: null, - }, - content: [ - { - type: 'paragraph', - attrs: { - textAlign: 'left', - }, - content: [ - { - type: 'payloadValue', - attrs: { - id: 'header', - label: null, - }, - }, - { - type: 'text', - text: ' ', - }, - ], - }, - ], + { + type: 'payloadValue', + attrs: { + id: 'name', + label: null, }, - ], + }, + { + type: 'text', + text: ' ', + }, + ], + }, + { + type: 'for', + attrs: { + each: 'payload.food.warnings', + isUpdatingKey: false, }, - ], - }, - ], - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', - }, - }, - { - type: 'paragraph', - attrs: { - textAlign: 'left', + content: [ + { + type: 'bulletList', + content: [ + { + type: 'listItem', + attrs: { + color: null, + }, + content: [ + { + type: 'paragraph', + attrs: { + textAlign: 'left', + }, + content: [ + { + type: 'payloadValue', + attrs: { + id: 'header', + label: null, + }, + }, + { + type: 'text', + text: ' ', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], }, - content: [ - { - type: 'text', - text: 'Regards,', - }, - { - type: 'hardBreak', + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - { - type: 'text', - text: 'Arikko', + }, + { + type: 'paragraph', + attrs: { + textAlign: 'left', }, - ], - }, - ], -}); + content: [ + { + type: 'text', + text: 'Regards,', + }, + { + type: 'hardBreak', + }, + { + type: 'text', + text: 'Arikko', + }, + ], + }, + ], + }; +} diff --git a/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts b/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts index 33d262e5ec4..0c397470099 100644 --- a/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts +++ b/apps/api/src/app/workflows-v2/mappers/notification-template-mapper.ts @@ -40,7 +40,7 @@ export function toResponseWorkflowDto( origin: computeOrigin(template), updatedAt: template.updatedAt || 'Missing Updated At', createdAt: template.createdAt || 'Missing Create At', - status: WorkflowStatusEnum.ACTIVE, + status: template.status || WorkflowStatusEnum.ACTIVE, issues: template.issues as unknown as Record, }; } diff --git a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/validate-and-persist-workflow-issues.usecase.ts b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/validate-and-persist-workflow-issues.usecase.ts index 43dd390ea3f..d00a51bbb56 100644 --- a/apps/api/src/app/workflows-v2/usecases/upsert-workflow/validate-and-persist-workflow-issues.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/upsert-workflow/validate-and-persist-workflow-issues.usecase.ts @@ -36,7 +36,7 @@ export class ValidateAndPersistWorkflowIssuesUsecase { } private async persistWorkflow(command: ValidateWorkflowCommand, workflowWithIssues: NotificationTemplateEntity) { - const isGoodWorkflow = this.isTriggerableWorkflow(workflowWithIssues); + const isGoodWorkflow = this.isWorkflowCompleteAndValid(workflowWithIssues); await this.notificationTemplateRepository.update( { _id: command.workflow._id, @@ -49,15 +49,19 @@ export class ValidateAndPersistWorkflowIssuesUsecase { ); } - private calculateStatus(isGoodWorkflow: boolean, workflowWithIssues) { - if (workflowWithIssues.status === WorkflowStatusEnum.INACTIVE) { + private calculateStatus(isGoodWorkflow: boolean, workflowWithIssues: NotificationTemplateEntity) { + if (workflowWithIssues.active === false) { return WorkflowStatusEnum.INACTIVE; } - return isGoodWorkflow ? WorkflowStatusEnum.ERROR : WorkflowStatusEnum.ACTIVE; + if (isGoodWorkflow) { + return WorkflowStatusEnum.ACTIVE; + } + + return WorkflowStatusEnum.ERROR; } - private isTriggerableWorkflow(workflowWithIssues) { + private isWorkflowCompleteAndValid(workflowWithIssues: NotificationTemplateEntity) { const workflowIssues = workflowWithIssues.issues && Object.keys(workflowWithIssues.issues).length; const hasStepIssues = workflowWithIssues.steps @@ -65,9 +69,8 @@ export class ValidateAndPersistWorkflowIssuesUsecase { .filter((issue) => issue != null) .filter((issue) => issue.body && Object.keys(issue.body).length > 0) .filter((issue) => issue.controls && Object.keys(issue.controls).length > 0).length > 0; - const active = !hasStepIssues && !workflowIssues; - return active; + return !hasStepIssues && !workflowIssues; } private async getWorkflow(command: ValidateWorkflowCommand) { diff --git a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts index 801907b95e7..17ae7d1a0ee 100644 --- a/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts +++ b/apps/api/src/app/workflows-v2/workflow.controller.e2e.ts @@ -26,10 +26,12 @@ import { WorkflowListResponseDto, WorkflowOriginEnum, WorkflowResponseDto, + WorkflowStatusEnum, } from '@novu/shared'; import { encodeBase62 } from '../shared/helpers'; import { stepTypeToDefaultDashboardControlSchema } from './shared'; +import { getTestControlValues } from './generate-preview.e2e'; const v2Prefix = '/v2'; const PARTIAL_UPDATED_NAME = 'Updated'; @@ -71,6 +73,12 @@ describe('Workflow Controller E2E API Testing', () => { }); describe('Error Handling', () => { + describe('Should show status ok when no problems', () => { + it('should show status ok when no problems', async () => { + const workflowCreated = await createWorkflowAndValidate(); + await getWorkflowAndValidate(workflowCreated); + }); + }); describe('Workflow Body Issues', () => { it('should show description issue when too long', async () => { const issues = await createWorkflowAndReturnIssues({ description: LONG_DESCRIPTION }); @@ -84,29 +92,31 @@ describe('Workflow Controller E2E API Testing', () => { }); describe('Workflow Step Body Issues', () => { it('should show name issue when missing', async () => { - const issues = await createWorkflowAndReturnStepIssues({ steps: [{ ...buildEmailStep(), name: '' }] }, 0); - - expect(issues?.body).to.be.ok; - if (issues?.body) { - expect(issues?.body).to.be.ok; - expect(issues?.body.name).to.be.ok; - expect(issues?.body.name?.issueType, JSON.stringify(issues?.body.name)).to.be.equal( - StepIssueEnum.MISSING_REQUIRED_VALUE - ); + const { issues, status } = await createWorkflowAndReturnStepIssues( + { steps: [{ ...buildEmailStep(), name: '' }] }, + 0 + ); + expect(status).to.be.equal(WorkflowStatusEnum.ERROR); + expect(issues).to.be.ok; + if (issues.body) { + expect(issues.body).to.be.ok; + expect(issues.body.name).to.be.ok; + expect(issues.body.name?.issueType, JSON.stringify(issues)).to.be.equal(StepIssueEnum.MISSING_REQUIRED_VALUE); } }); }); describe('Workflow Step content Issues', () => { it('should show control value required when missing', async () => { - const issues = await createWorkflowAndReturnStepIssues( + const { issues, status } = await createWorkflowAndReturnStepIssues( { steps: [{ ...buildEmailStep(), controlValues: {} }] }, 0 ); - expect(issues?.controls).to.be.ok; - if (issues?.controls) { - expect(issues?.controls.emailEditor).to.be.ok; - if (issues.controls.emailEditor) { - expect(issues.controls.emailEditor[0].issueType).to.be.equal(StepContentIssueEnum.MISSING_VALUE); + expect(status).to.equal(WorkflowStatusEnum.ERROR); + expect(issues).to.be.ok; + if (issues.controls) { + expect(issues.controls?.emailEditor).to.be.ok; + if (issues.controls?.emailEditor) { + expect(issues.controls?.emailEditor[0].issueType).to.be.equal(StepContentIssueEnum.MISSING_VALUE); } } }); @@ -718,6 +728,7 @@ describe('Workflow Controller E2E API Testing', () => { expect(workflowResponseDto.createdAt, workflowAsString(workflowResponseDto)).to.be.ok; expect(workflowResponseDto.preferences, workflowAsString(workflowResponseDto)).to.be.ok; expect(workflowResponseDto.status, workflowAsString(workflowResponseDto)).to.be.ok; + expect(workflowResponseDto.status, workflowAsString(workflowResponseDto)).to.equal(WorkflowStatusEnum.ACTIVE); expect(workflowResponseDto.origin, workflowAsString(workflowResponseDto)).to.be.eq(WorkflowOriginEnum.NOVU_CLOUD); expect(Object.keys(workflowResponseDto.issues || {}).length, workflowAsString(workflowResponseDto)).to.be.equal(0); } @@ -771,7 +782,7 @@ describe('Workflow Controller E2E API Testing', () => { const { issues } = step; expect(issues, JSON.stringify(step)).to.be.ok; if (issues) { - return issues; + return { issues, status: workflowCreated.status }; } throw new Error('Issues not found'); } @@ -788,6 +799,7 @@ function buildEmailStep(): StepCreateDto { return { name: 'Email Test Step', type: StepTypeEnum.EMAIL, + controlValues: getTestControlValues()[StepTypeEnum.EMAIL], }; } @@ -795,6 +807,7 @@ function buildInAppStep(): StepCreateDto { return { name: 'In-App Test Step', type: StepTypeEnum.IN_APP, + controlValues: getTestControlValues()[StepTypeEnum.IN_APP], }; } diff --git a/libs/dal/src/repositories/notification-template/notification-template.entity.ts b/libs/dal/src/repositories/notification-template/notification-template.entity.ts index 71e9a6bf707..03d30a7dcbf 100644 --- a/libs/dal/src/repositories/notification-template/notification-template.entity.ts +++ b/libs/dal/src/repositories/notification-template/notification-template.entity.ts @@ -20,6 +20,7 @@ import { StepIssues, TriggerTypeEnum, WorkflowOriginEnum, + WorkflowStatusEnum, WorkflowTypeEnum, } from '@novu/shared'; import { NotificationGroupEntity } from '../notification-group'; @@ -87,6 +88,8 @@ export class NotificationTemplateEntity implements INotificationTemplate { payloadSchema?: any; issues: Record; + + status?: WorkflowStatusEnum; } export type NotificationTemplateDBModel = ChangePropsValueType< diff --git a/libs/dal/src/repositories/notification-template/notification-template.schema.ts b/libs/dal/src/repositories/notification-template/notification-template.schema.ts index 22c61d28e6e..2c9a7454f3a 100644 --- a/libs/dal/src/repositories/notification-template/notification-template.schema.ts +++ b/libs/dal/src/repositories/notification-template/notification-template.schema.ts @@ -201,6 +201,9 @@ const notificationTemplateSchema = new Schema( origin: { type: Schema.Types.String, }, + status: { + type: Schema.Types.String, + }, _environmentId: { type: Schema.Types.ObjectId, ref: 'Environment',