From 7fffe116da90c5784623612ed5dae6a24dfd9daa Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Thu, 5 Feb 2026 13:53:58 +0000 Subject: [PATCH] feat: add URL host validation for action URLs --- .../use-cases/create-action-rule.use.case.ts | 216 +- ...n-rule-with-actions-and-events.use.case.ts | 361 +-- backend/src/exceptions/text/messages.ts | 1 + .../validators/is-action-url-host-allowed.ts | 42 + .../saas-tests/action-rules-e2e.test.ts | 2449 ++++++++--------- 5 files changed, 1567 insertions(+), 1502 deletions(-) create mode 100644 backend/src/helpers/validators/is-action-url-host-allowed.ts diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts index 268358ef3..8d91d17bd 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts @@ -14,118 +14,130 @@ import { buildTableActionWithRule } from '../../table-actions-module/utils/build import { buildActionEventWithRule } from '../../table-action-events-module/utils/build-action-event-with-rule.util.js'; import { buildFoundActionRulesWithActionsAndEventsDTO } from '../utils/build-found-action-rules-with-actions-and-events-dto.util.js'; import { validateStringWithEnum } from '../../../../helpers/validators/validate-string-with-enum.js'; +import { isActionUrlHostAllowed } from '../../../../helpers/validators/is-action-url-host-allowed.js'; @Injectable({ scope: Scope.REQUEST }) export class CreateActionRuleUseCase - extends AbstractUseCase - implements ICreateActionRule + extends AbstractUseCase + implements ICreateActionRule { - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - ) { - super(); - } + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } - protected async implementation(inputData: CreateActionRuleDS): Promise { - const { connection_data, action_events_data, rule_data, table_actions_data } = inputData; - const { connectionId, masterPwd, userId } = connection_data; - const { table_name } = rule_data; + protected async implementation(inputData: CreateActionRuleDS): Promise { + const { connection_data, action_events_data, rule_data, table_actions_data } = inputData; + const { connectionId, masterPwd, userId } = connection_data; + const { table_name } = rule_data; - await this.validateActionEmailsOrThrowException(table_actions_data, userId); - await this.validateTableNameOrThrowException(table_name, connectionId, masterPwd); - await this.validateTableActionDataOrThrowException(table_actions_data); + await this.validateActionEmailsOrThrowException(table_actions_data, userId); + await this.validateTableNameOrThrowException(table_name, connectionId, masterPwd); + await this.validateTableActionDataOrThrowException(table_actions_data); - const foundConnection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); - const newActionRule = buildEmptyActionRule(rule_data, foundConnection); - const savedActionRule = await this._dbContext.actionRulesRepository.saveNewOrUpdatedActionRule(newActionRule); + const foundConnection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); + const newActionRule = buildEmptyActionRule(rule_data, foundConnection); + const savedActionRule = await this._dbContext.actionRulesRepository.saveNewOrUpdatedActionRule(newActionRule); - const savedTableActions = await Promise.all( - table_actions_data.map((tableAction) => { - const newTableAction = buildTableActionWithRule(tableAction, savedActionRule); - return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(newTableAction); - }), - ); + const savedTableActions = await Promise.all( + table_actions_data.map((tableAction) => { + const newTableAction = buildTableActionWithRule(tableAction, savedActionRule); + return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(newTableAction); + }), + ); - const savedActionEvents = await Promise.all( - action_events_data.map((actionEvent) => { - const newActionEvent = buildActionEventWithRule(actionEvent, savedActionRule); - return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(newActionEvent); - }), - ); - savedActionRule.action_events = savedActionEvents; - savedActionRule.table_actions = savedTableActions; - return buildFoundActionRulesWithActionsAndEventsDTO(savedActionRule); - } + const savedActionEvents = await Promise.all( + action_events_data.map((actionEvent) => { + const newActionEvent = buildActionEventWithRule(actionEvent, savedActionRule); + return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(newActionEvent); + }), + ); + savedActionRule.action_events = savedActionEvents; + savedActionRule.table_actions = savedTableActions; + return buildFoundActionRulesWithActionsAndEventsDTO(savedActionRule); + } - private async validateActionEmailsOrThrowException( - table_actions_data: Array, - userId: string, - ): Promise { - const companyWithUsers = await this._dbContext.companyInfoRepository.findUserCompanyWithUsers(userId); - const usersInCompanyEmails = companyWithUsers.users.map((user) => user.email); - const emailsFromEmailActions: Array = table_actions_data.reduce((acc, table_action) => { - if (table_action.action_method === TableActionMethodEnum.EMAIL) { - return acc.concat(table_action.action_emails); - } - return acc; - }, []); - const emailsNotInCompany = emailsFromEmailActions.filter((email) => !usersInCompanyEmails.includes(email)); - if (emailsNotInCompany.length > 0) { - throw new BadRequestException(Messages.EMAILS_NOT_IN_COMPANY(emailsNotInCompany)); - } - const emailsNotVerified = emailsFromEmailActions.filter((email) => { - const foundUser = companyWithUsers.users.find((user) => user.email === email); - if (foundUser.id === userId) { - return false; - } - return !foundUser.isActive; - }); - if (emailsNotVerified.length > 0) { - throw new BadRequestException(Messages.USERS_NOT_VERIFIED(emailsNotVerified)); - } - } + private async validateActionEmailsOrThrowException( + table_actions_data: Array, + userId: string, + ): Promise { + const companyWithUsers = await this._dbContext.companyInfoRepository.findUserCompanyWithUsers(userId); + const usersInCompanyEmails = companyWithUsers.users.map((user) => user.email); + const emailsFromEmailActions: Array = table_actions_data.reduce((acc, table_action) => { + if (table_action.action_method === TableActionMethodEnum.EMAIL) { + return acc.concat(table_action.action_emails); + } + return acc; + }, []); + const emailsNotInCompany = emailsFromEmailActions.filter((email) => !usersInCompanyEmails.includes(email)); + if (emailsNotInCompany.length > 0) { + throw new BadRequestException(Messages.EMAILS_NOT_IN_COMPANY(emailsNotInCompany)); + } + const emailsNotVerified = emailsFromEmailActions.filter((email) => { + const foundUser = companyWithUsers.users.find((user) => user.email === email); + if (foundUser.id === userId) { + return false; + } + return !foundUser.isActive; + }); + if (emailsNotVerified.length > 0) { + throw new BadRequestException(Messages.USERS_NOT_VERIFIED(emailsNotVerified)); + } + } - private async validateTableNameOrThrowException( - tableName: string, - connectionId: string, - masterPwd: string, - ): Promise { - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - masterPwd, - ); - const dao = getDataAccessObject(foundConnection); - const tablesInConnection = await dao.getTablesFromDB(); - const tableNamesInConnection = tablesInConnection.map((table) => table.tableName); - if (!tableNamesInConnection.includes(tableName)) { - throw new BadRequestException(Messages.TABLE_WITH_NAME_NOT_EXISTS(tableName)); - } - } + private async validateTableNameOrThrowException( + tableName: string, + connectionId: string, + masterPwd: string, + ): Promise { + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPwd, + ); + const dao = getDataAccessObject(foundConnection); + const tablesInConnection = await dao.getTablesFromDB(); + const tableNamesInConnection = tablesInConnection.map((table) => table.tableName); + if (!tableNamesInConnection.includes(tableName)) { + throw new BadRequestException(Messages.TABLE_WITH_NAME_NOT_EXISTS(tableName)); + } + } - private async validateTableActionDataOrThrowException(tableActions: Array): Promise { - for (const action of tableActions) { - if (action.action_method === TableActionMethodEnum.EMAIL) { - if (!action.action_emails || action.action_emails.length === 0) { - throw new BadRequestException(Messages.EMAILS_REQUIRED_FOR_EMAIL_ACTION); - } - } - if (action.action_method === TableActionMethodEnum.SLACK) { - if (!action.action_slack_url) { - throw new BadRequestException(Messages.SLACK_URL_MISSING); - } - } - if (!validateStringWithEnum(action.action_method, TableActionMethodEnum)) { - throw new BadRequestException(Messages.INVALID_ACTION_METHOD(action.action_method)); - } - if (action.action_method === TableActionMethodEnum.URL) { - if (process.env.NODE_ENV === 'test') { - return; - } - if (!action.action_url || !ValidationHelper.isValidUrl(action.action_url)) { - throw new BadRequestException(Messages.URL_INVALID); - } - } - } - } + private async validateTableActionDataOrThrowException(tableActions: Array): Promise { + for (const action of tableActions) { + if (action.action_method === TableActionMethodEnum.EMAIL) { + if (!action.action_emails || action.action_emails.length === 0) { + throw new BadRequestException(Messages.EMAILS_REQUIRED_FOR_EMAIL_ACTION); + } + } + if (action.action_method === TableActionMethodEnum.SLACK) { + if (!action.action_slack_url) { + throw new BadRequestException(Messages.SLACK_URL_MISSING); + } + if (process.env.NODE_ENV !== 'test' && !ValidationHelper.isValidUrl(action.action_slack_url)) { + throw new BadRequestException(Messages.URL_INVALID); + } + const isSlackUrlAllowed = await isActionUrlHostAllowed(action.action_slack_url); + if (!isSlackUrlAllowed) { + throw new BadRequestException(Messages.ACTION_URL_HOST_NOT_ALLOWED); + } + } + if (!validateStringWithEnum(action.action_method, TableActionMethodEnum)) { + throw new BadRequestException(Messages.INVALID_ACTION_METHOD(action.action_method)); + } + if (action.action_method === TableActionMethodEnum.URL) { + if ( + process.env.NODE_ENV !== 'test' && + (!action.action_url || !ValidationHelper.isValidUrl(action.action_url)) + ) { + throw new BadRequestException(Messages.URL_INVALID); + } + const isUrlAllowed = await isActionUrlHostAllowed(action.action_url); + if (!isUrlAllowed) { + throw new BadRequestException(Messages.ACTION_URL_HOST_NOT_ALLOWED); + } + } + } + } } diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts index 844ba0354..34acbce7d 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts @@ -14,181 +14,196 @@ import { buildTableActionWithRule } from '../../table-actions-module/utils/build import { buildActionEventWithRule } from '../../table-action-events-module/utils/build-action-event-with-rule.util.js'; import { buildFoundActionRulesWithActionsAndEventsDTO } from '../utils/build-found-action-rules-with-actions-and-events-dto.util.js'; import { validateStringWithEnum } from '../../../../helpers/validators/validate-string-with-enum.js'; +import { isActionUrlHostAllowed } from '../../../../helpers/validators/is-action-url-host-allowed.js'; @Injectable({ scope: Scope.REQUEST }) export class UpdateRuleUseCase - extends AbstractUseCase - implements IUpdateActionRule + extends AbstractUseCase + implements IUpdateActionRule { - constructor( - @Inject(BaseType.GLOBAL_DB_CONTEXT) - protected _dbContext: IGlobalDatabaseContext, - ) { - super(); - } - - public async implementation(inputData: UpdateActionRuleDS): Promise { - const { connection_data, action_events_data, rule_data, table_actions_data } = inputData; - const { connectionId, masterPwd, userId } = connection_data; - const { table_name, rule_id, rule_title } = rule_data; - - const foundRuleToUpdate = await this._dbContext.actionRulesRepository.findOneWithActionsAndEvents( - rule_id, - connectionId, - ); - if (!foundRuleToUpdate) { - throw new BadRequestException(Messages.RULE_NOT_FOUND); - } - - await this.validateActionEmailsOrThrowException(table_actions_data, userId); - await this.validateTableNameOrThrowException(table_name, connectionId, masterPwd); - await this.validateTableActionDataOrThrowException(table_actions_data); - - foundRuleToUpdate.title = rule_title; - foundRuleToUpdate.table_name = table_name; - - await this._dbContext.actionRulesRepository.saveNewOrUpdatedActionRule(foundRuleToUpdate); - - const { action_events, table_actions } = foundRuleToUpdate; - - const tableActionsToCreate = table_actions_data.filter((tableAction) => !tableAction.action_id); - const tableActionsToUpdate = table_actions_data.filter((tableAction) => tableAction.action_id); - const tableActionsToDelete = table_actions.filter( - (tableAction) => !table_actions_data.map((tableAction) => tableAction.action_id).includes(tableAction.id), - ); - - const actionEventsToCreate = action_events_data.filter((actionEvent) => !actionEvent.event_id); - const actionEventsToUpdate = action_events_data.filter((actionEvent) => actionEvent.event_id); - const actionEventsToDelete = action_events.filter( - (actionEvent) => !action_events_data.map((actionEvent) => actionEvent.event_id).includes(actionEvent.id), - ); - - const createdTableActions = await Promise.all( - tableActionsToCreate.map((tableAction) => { - const newTableAction = buildTableActionWithRule(tableAction, foundRuleToUpdate); - return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(newTableAction); - }), - ); - - const updatedTableActions = await Promise.all( - tableActionsToUpdate.map((tableAction) => { - const foundTableAction = table_actions.find((action) => action.id === tableAction.action_id); - foundTableAction.method = tableAction.action_method; - foundTableAction.url = tableAction.action_url; - foundTableAction.slack_url = tableAction.action_slack_url; - foundTableAction.emails = tableAction.action_emails; - return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(foundTableAction); - }), - ); - - await Promise.all( - tableActionsToDelete.map((tableAction) => { - return this._dbContext.tableActionRepository.remove(tableAction); - }), - ); - - const tableActionToResponse = [...createdTableActions, ...updatedTableActions]; - - const createdActionEvents = await Promise.all( - actionEventsToCreate.map((actionEvent) => { - const newActionEvent = buildActionEventWithRule(actionEvent, foundRuleToUpdate); - return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(newActionEvent); - }), - ); - - const updatedActionEvents = await Promise.all( - actionEventsToUpdate.map((actionEvent) => { - const foundActionEvent = action_events.find((event) => event.id === actionEvent.event_id); - foundActionEvent.event = actionEvent.event; - foundActionEvent.title = actionEvent.event_title; - foundActionEvent.icon = actionEvent.icon; - foundActionEvent.type = actionEvent.type; - foundActionEvent.require_confirmation = actionEvent.require_confirmation; - return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(foundActionEvent); - }), - ); - - await Promise.all( - actionEventsToDelete.map((actionEvent) => { - return this._dbContext.actionEventsRepository.remove(actionEvent); - }), - ); - - const actionEventsToResponse = [...createdActionEvents, ...updatedActionEvents]; - - const foundRuleToUpdateCopy = { ...foundRuleToUpdate }; - foundRuleToUpdateCopy.table_actions = tableActionToResponse; - foundRuleToUpdateCopy.action_events = actionEventsToResponse; - return buildFoundActionRulesWithActionsAndEventsDTO(foundRuleToUpdateCopy); - } - - private async validateActionEmailsOrThrowException( - table_actions_data: Array, - userId: string, - ): Promise { - const companyWithUsers = await this._dbContext.companyInfoRepository.findUserCompanyWithUsers(userId); - const usersInCompanyEmails = companyWithUsers.users.map((user) => user.email); - const emailsFromEmailActions: Array = table_actions_data.reduce((acc, table_action) => { - if (table_action.action_method === TableActionMethodEnum.EMAIL) { - return acc.concat(table_action.action_emails); - } - return acc; - }, []); - const emailsNotInCompany = emailsFromEmailActions.filter((email) => !usersInCompanyEmails.includes(email)); - if (emailsNotInCompany.length > 0) { - throw new BadRequestException(Messages.EMAILS_NOT_IN_COMPANY(emailsNotInCompany)); - } - const emailsNotVerified = emailsFromEmailActions.filter((email) => { - const foundUser = companyWithUsers.users.find((user) => user.email === email); - if (foundUser.id === userId) { - return false; - } - return !foundUser.isActive; - }); - if (emailsNotVerified.length > 0) { - throw new BadRequestException(Messages.USERS_NOT_VERIFIED(emailsNotVerified)); - } - } - - private async validateTableNameOrThrowException( - tableName: string, - connectionId: string, - masterPwd: string, - ): Promise { - const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( - connectionId, - masterPwd, - ); - const dao = getDataAccessObject(foundConnection); - const tablesInConnection = await dao.getTablesFromDB(); - const tableNamesInConnection = tablesInConnection.map((table) => table.tableName); - if (!tableNamesInConnection.includes(tableName)) { - throw new BadRequestException(Messages.TABLE_WITH_NAME_NOT_EXISTS(tableName)); - } - } - - private async validateTableActionDataOrThrowException(tableActions: Array): Promise { - for (const action of tableActions) { - if (action.action_method === TableActionMethodEnum.EMAIL) { - if (!action.action_emails || action.action_emails.length === 0) { - throw new BadRequestException(Messages.EMAILS_REQUIRED_FOR_EMAIL_ACTION); - } - } - if (action.action_method === TableActionMethodEnum.SLACK) { - if (!action.action_slack_url) { - throw new BadRequestException(Messages.SLACK_URL_MISSING); - } - } - - if (!validateStringWithEnum(action.action_method, TableActionMethodEnum)) { - throw new BadRequestException(Messages.INVALID_ACTION_METHOD(action.action_method)); - } - - if (action.action_method === TableActionMethodEnum.URL) { - if (!action.action_url || !ValidationHelper.isValidUrl(action.action_url)) { - throw new BadRequestException(Messages.URL_INVALID); - } - } - } - } + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: UpdateActionRuleDS): Promise { + const { connection_data, action_events_data, rule_data, table_actions_data } = inputData; + const { connectionId, masterPwd, userId } = connection_data; + const { table_name, rule_id, rule_title } = rule_data; + + const foundRuleToUpdate = await this._dbContext.actionRulesRepository.findOneWithActionsAndEvents( + rule_id, + connectionId, + ); + if (!foundRuleToUpdate) { + throw new BadRequestException(Messages.RULE_NOT_FOUND); + } + + await this.validateActionEmailsOrThrowException(table_actions_data, userId); + await this.validateTableNameOrThrowException(table_name, connectionId, masterPwd); + await this.validateTableActionDataOrThrowException(table_actions_data); + + foundRuleToUpdate.title = rule_title; + foundRuleToUpdate.table_name = table_name; + + await this._dbContext.actionRulesRepository.saveNewOrUpdatedActionRule(foundRuleToUpdate); + + const { action_events, table_actions } = foundRuleToUpdate; + + const tableActionsToCreate = table_actions_data.filter((tableAction) => !tableAction.action_id); + const tableActionsToUpdate = table_actions_data.filter((tableAction) => tableAction.action_id); + const tableActionsToDelete = table_actions.filter( + (tableAction) => !table_actions_data.map((tableAction) => tableAction.action_id).includes(tableAction.id), + ); + + const actionEventsToCreate = action_events_data.filter((actionEvent) => !actionEvent.event_id); + const actionEventsToUpdate = action_events_data.filter((actionEvent) => actionEvent.event_id); + const actionEventsToDelete = action_events.filter( + (actionEvent) => !action_events_data.map((actionEvent) => actionEvent.event_id).includes(actionEvent.id), + ); + + const createdTableActions = await Promise.all( + tableActionsToCreate.map((tableAction) => { + const newTableAction = buildTableActionWithRule(tableAction, foundRuleToUpdate); + return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(newTableAction); + }), + ); + + const updatedTableActions = await Promise.all( + tableActionsToUpdate.map((tableAction) => { + const foundTableAction = table_actions.find((action) => action.id === tableAction.action_id); + foundTableAction.method = tableAction.action_method; + foundTableAction.url = tableAction.action_url; + foundTableAction.slack_url = tableAction.action_slack_url; + foundTableAction.emails = tableAction.action_emails; + return this._dbContext.tableActionRepository.saveNewOrUpdatedTableAction(foundTableAction); + }), + ); + + await Promise.all( + tableActionsToDelete.map((tableAction) => { + return this._dbContext.tableActionRepository.remove(tableAction); + }), + ); + + const tableActionToResponse = [...createdTableActions, ...updatedTableActions]; + + const createdActionEvents = await Promise.all( + actionEventsToCreate.map((actionEvent) => { + const newActionEvent = buildActionEventWithRule(actionEvent, foundRuleToUpdate); + return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(newActionEvent); + }), + ); + + const updatedActionEvents = await Promise.all( + actionEventsToUpdate.map((actionEvent) => { + const foundActionEvent = action_events.find((event) => event.id === actionEvent.event_id); + foundActionEvent.event = actionEvent.event; + foundActionEvent.title = actionEvent.event_title; + foundActionEvent.icon = actionEvent.icon; + foundActionEvent.type = actionEvent.type; + foundActionEvent.require_confirmation = actionEvent.require_confirmation; + return this._dbContext.actionEventsRepository.saveNewOrUpdatedActionEvent(foundActionEvent); + }), + ); + + await Promise.all( + actionEventsToDelete.map((actionEvent) => { + return this._dbContext.actionEventsRepository.remove(actionEvent); + }), + ); + + const actionEventsToResponse = [...createdActionEvents, ...updatedActionEvents]; + + const foundRuleToUpdateCopy = { ...foundRuleToUpdate }; + foundRuleToUpdateCopy.table_actions = tableActionToResponse; + foundRuleToUpdateCopy.action_events = actionEventsToResponse; + return buildFoundActionRulesWithActionsAndEventsDTO(foundRuleToUpdateCopy); + } + + private async validateActionEmailsOrThrowException( + table_actions_data: Array, + userId: string, + ): Promise { + const companyWithUsers = await this._dbContext.companyInfoRepository.findUserCompanyWithUsers(userId); + const usersInCompanyEmails = companyWithUsers.users.map((user) => user.email); + const emailsFromEmailActions: Array = table_actions_data.reduce((acc, table_action) => { + if (table_action.action_method === TableActionMethodEnum.EMAIL) { + return acc.concat(table_action.action_emails); + } + return acc; + }, []); + const emailsNotInCompany = emailsFromEmailActions.filter((email) => !usersInCompanyEmails.includes(email)); + if (emailsNotInCompany.length > 0) { + throw new BadRequestException(Messages.EMAILS_NOT_IN_COMPANY(emailsNotInCompany)); + } + const emailsNotVerified = emailsFromEmailActions.filter((email) => { + const foundUser = companyWithUsers.users.find((user) => user.email === email); + if (foundUser.id === userId) { + return false; + } + return !foundUser.isActive; + }); + if (emailsNotVerified.length > 0) { + throw new BadRequestException(Messages.USERS_NOT_VERIFIED(emailsNotVerified)); + } + } + + private async validateTableNameOrThrowException( + tableName: string, + connectionId: string, + masterPwd: string, + ): Promise { + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPwd, + ); + const dao = getDataAccessObject(foundConnection); + const tablesInConnection = await dao.getTablesFromDB(); + const tableNamesInConnection = tablesInConnection.map((table) => table.tableName); + if (!tableNamesInConnection.includes(tableName)) { + throw new BadRequestException(Messages.TABLE_WITH_NAME_NOT_EXISTS(tableName)); + } + } + + private async validateTableActionDataOrThrowException(tableActions: Array): Promise { + for (const action of tableActions) { + if (action.action_method === TableActionMethodEnum.EMAIL) { + if (!action.action_emails || action.action_emails.length === 0) { + throw new BadRequestException(Messages.EMAILS_REQUIRED_FOR_EMAIL_ACTION); + } + } + if (action.action_method === TableActionMethodEnum.SLACK) { + if (!action.action_slack_url) { + throw new BadRequestException(Messages.SLACK_URL_MISSING); + } + if (process.env.NODE_ENV !== 'test' && !ValidationHelper.isValidUrl(action.action_slack_url)) { + throw new BadRequestException(Messages.URL_INVALID); + } + const isSlackUrlAllowed = await isActionUrlHostAllowed(action.action_slack_url); + if (!isSlackUrlAllowed) { + throw new BadRequestException(Messages.ACTION_URL_HOST_NOT_ALLOWED); + } + } + + if (!validateStringWithEnum(action.action_method, TableActionMethodEnum)) { + throw new BadRequestException(Messages.INVALID_ACTION_METHOD(action.action_method)); + } + + if (action.action_method === TableActionMethodEnum.URL) { + if ( + process.env.NODE_ENV !== 'test' && + (!action.action_url || !ValidationHelper.isValidUrl(action.action_url)) + ) { + throw new BadRequestException(Messages.URL_INVALID); + } + const isUrlAllowed = await isActionUrlHostAllowed(action.action_url); + if (!isUrlAllowed) { + throw new BadRequestException(Messages.ACTION_URL_HOST_NOT_ALLOWED); + } + } + } + } } diff --git a/backend/src/exceptions/text/messages.ts b/backend/src/exceptions/text/messages.ts index fda07e221..0d21393d8 100644 --- a/backend/src/exceptions/text/messages.ts +++ b/backend/src/exceptions/text/messages.ts @@ -248,6 +248,7 @@ export const Messages = { SAAS_RECOUNT_USERS_IN_COMPANY_FAILED_UNHANDLED_ERROR: `Failed to recount users in company. Please contact our support team.`, SLACK_CREDENTIALS_MISSING: 'Slack credentials are missing', SLACK_URL_MISSING: 'Slack url is missing', + ACTION_URL_HOST_NOT_ALLOWED: 'Action URL cannot target this host', SOMETHING_WENT_WRONG_ROW_ADD: 'Something went wrong on row insertion, check inserted parameters and try again', SOMETHING_WENT_WRONG_AI_THREAD: 'Something went wrong on AI thread creation, check inserted parameters and try again', SOMETHING_WENT_WRONG_AI_THREAD_MESSAGE: diff --git a/backend/src/helpers/validators/is-action-url-host-allowed.ts b/backend/src/helpers/validators/is-action-url-host-allowed.ts new file mode 100644 index 000000000..4cf75cc0b --- /dev/null +++ b/backend/src/helpers/validators/is-action-url-host-allowed.ts @@ -0,0 +1,42 @@ +import dns from 'dns'; +import ipRangeCheck from 'ip-range-check'; +import { Constants } from '../constants/constants.js'; +import { isSaaS } from '../app/is-saas.js'; + +export async function isActionUrlHostAllowed(url: string): Promise { + if (process.env.NODE_ENV === 'test') { + return true; + } + + if (!isSaaS()) { + return true; + } + + try { + const parsedUrl = new URL(url); + const hostname = parsedUrl.hostname; + + if (ipRangeCheck(hostname, Constants.FORBIDDEN_HOSTS)) { + return false; + } + + return await new Promise((resolve) => { + dns.lookup(hostname, (err, address) => { + if (err) { + console.error('DNS lookup error for action URL:', err.message); + resolve(false); + return; + } + + if (ipRangeCheck(address, Constants.FORBIDDEN_HOSTS)) { + resolve(false); + } else { + resolve(true); + } + }); + }); + } catch (e) { + console.error('Invalid URL format for action URL validation:', e.message); + return false; + } +} diff --git a/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts b/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts index 5c69e8004..1bb81349c 100644 --- a/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts +++ b/backend/test/ava-tests/saas-tests/action-rules-e2e.test.ts @@ -21,8 +21,8 @@ import { TableActionEventEnum } from '../../../src/enums/table-action-event-enum import { TableActionTypeEnum } from '../../../src/enums/table-action-type.enum.js'; import { TableActionMethodEnum } from '../../../src/enums/table-action-method-enum.js'; import { - FoundActionEventDTO, - FoundActionRulesWithActionsAndEventsDTO, + FoundActionEventDTO, + FoundActionRulesWithActionsAndEventsDTO, } from '../../../src/entities/table-actions/table-action-rules-module/application/dto/found-action-rules-with-actions-and-events.dto.js'; import { UpdateTableActionRuleBodyDTO } from '../../../src/entities/table-actions/table-action-rules-module/application/dto/update-action-rule-with-actions-and-events.dto.js'; import { ActivatedTableActionsDTO } from '../../../src/entities/table-actions/table-action-rules-module/application/dto/activated-table-actions.dto.js'; @@ -43,1293 +43,1288 @@ const testSearchedUserName = 'Vasia'; const testEntitiesSeedsCount = 42; test.before(async () => { - const moduleFixture = await Test.createTestingModule({ - imports: [ApplicationModule, DatabaseModule], - providers: [DatabaseService, TestUtils], - }).compile(); - app = moduleFixture.createNestApplication() as any; - testUtils = moduleFixture.get(TestUtils); - - app.use(cookieParser()); - app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); - app.useGlobalPipes( - new ValidationPipe({ - exceptionFactory(validationErrors: ValidationError[] = []) { - return new ValidationException(validationErrors); - }, - }), - ); - await app.init(); - app.getHttpServer().listen(0); - newConnection = mockFactory.generateConnectionToTestPostgresDBInDocker(); - await resetPostgresTestDB(); + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication() as any; + testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); + newConnection = mockFactory.generateConnectionToTestPostgresDBInDocker(); + await resetPostgresTestDB(); }); async function resetPostgresTestDB() { - const { host, username, password, database, port, type, ssl, cert } = newConnection; - const Knex = knex({ - client: type, - connection: { - host: host, - user: username, - password: password, - database: database, - port: port, - }, - }); - await Knex.schema.dropTableIfExists('transactions'); - await Knex.schema.dropTableIfExists(testTableName); - await Knex.schema.createTableIfNotExists(testTableName, (table) => { - table.increments(); - table.string(testTableColumnName); - table.string(testTAbleSecondColumnName); - table.timestamps(); - }); - - for (let i = 0; i < testEntitiesSeedsCount; i++) { - if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) { - await Knex(testTableName).insert({ - [testTableColumnName]: testSearchedUserName, - [testTAbleSecondColumnName]: faker.internet.email(), - created_at: new Date(), - updated_at: new Date(), - }); - } else { - await Knex(testTableName).insert({ - [testTableColumnName]: faker.person.firstName(), - [testTAbleSecondColumnName]: faker.internet.email(), - created_at: new Date(), - updated_at: new Date(), - }); - } - } - await Knex.destroy(); + const { host, username, password, database, port, type, ssl, cert } = newConnection; + const Knex = knex({ + client: type, + connection: { + host: host, + user: username, + password: password, + database: database, + port: port, + }, + }); + await Knex.schema.dropTableIfExists('transactions'); + await Knex.schema.dropTableIfExists(testTableName); + await Knex.schema.createTableIfNotExists(testTableName, (table) => { + table.increments(); + table.string(testTableColumnName); + table.string(testTAbleSecondColumnName); + table.timestamps(); + }); + + for (let i = 0; i < testEntitiesSeedsCount; i++) { + if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) { + await Knex(testTableName).insert({ + [testTableColumnName]: testSearchedUserName, + [testTAbleSecondColumnName]: faker.internet.email(), + created_at: new Date(), + updated_at: new Date(), + }); + } else { + await Knex(testTableName).insert({ + [testTableColumnName]: faker.person.firstName(), + [testTAbleSecondColumnName]: faker.internet.email(), + created_at: new Date(), + updated_at: new Date(), + }); + } + } + await Knex.destroy(); } async function _deleteTable(tableName: string): Promise { - const { host, username, password, database, port, type, ssl, cert } = newConnection; - const Knex = knex({ - client: type, - connection: { - host: host, - user: username, - password: password, - database: database, - port: port, - }, - }); - await Knex.schema.dropTableIfExists(tableName); - await Knex.destroy(); + const { host, username, password, database, port, type, ssl, cert } = newConnection; + const Knex = knex({ + client: type, + connection: { + host: host, + user: username, + password: password, + database: database, + port: port, + }, + }); + await Knex.schema.dropTableIfExists(tableName); + await Knex.destroy(); } async function resetPostgresTestDbTableCompositePrimaryKeys( - secondTestTableName: string, - secondTableCompositeKeyName: string, + secondTestTableName: string, + secondTableCompositeKeyName: string, ): Promise { - const { host, username, password, database, port, type, ssl, cert } = newConnection; - const Knex = knex({ - client: type, - connection: { - host: host, - user: username, - password: password, - database: database, - port: port, - }, - }); - await Knex.schema.dropTableIfExists(secondTestTableName); - await Knex.schema.createTableIfNotExists(secondTestTableName, (table) => { - table.increments('id'); - table.string(testTableColumnName); - table.string(testTAbleSecondColumnName); - table.string(secondTableCompositeKeyName); - table.primary(['id', secondTableCompositeKeyName]); - table.timestamps(); - }); - - for (let i = 0; i < testEntitiesSeedsCount; i++) { - if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) { - await Knex(secondTestTableName).insert({ - [testTableColumnName]: testSearchedUserName, - [testTAbleSecondColumnName]: faker.internet.email(), - [secondTableCompositeKeyName]: faker.string.uuid(), - created_at: new Date(), - updated_at: new Date(), - }); - } else { - await Knex(secondTestTableName).insert({ - [testTableColumnName]: faker.person.firstName(), - [testTAbleSecondColumnName]: faker.internet.email(), - [secondTableCompositeKeyName]: faker.string.uuid(), - created_at: new Date(), - updated_at: new Date(), - }); - } - } - await Knex.destroy(); + const { host, username, password, database, port, type, ssl, cert } = newConnection; + const Knex = knex({ + client: type, + connection: { + host: host, + user: username, + password: password, + database: database, + port: port, + }, + }); + await Knex.schema.dropTableIfExists(secondTestTableName); + await Knex.schema.createTableIfNotExists(secondTestTableName, (table) => { + table.increments('id'); + table.string(testTableColumnName); + table.string(testTAbleSecondColumnName); + table.string(secondTableCompositeKeyName); + table.primary(['id', secondTableCompositeKeyName]); + table.timestamps(); + }); + + for (let i = 0; i < testEntitiesSeedsCount; i++) { + if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) { + await Knex(secondTestTableName).insert({ + [testTableColumnName]: testSearchedUserName, + [testTAbleSecondColumnName]: faker.internet.email(), + [secondTableCompositeKeyName]: faker.string.uuid(), + created_at: new Date(), + updated_at: new Date(), + }); + } else { + await Knex(secondTestTableName).insert({ + [testTableColumnName]: faker.person.firstName(), + [testTAbleSecondColumnName]: faker.internet.email(), + [secondTableCompositeKeyName]: faker.string.uuid(), + created_at: new Date(), + updated_at: new Date(), + }); + } + } + await Knex.destroy(); } test.after(async () => { - try { - await Cacher.clearAllCache(); - await app.close(); - } catch (e) { - console.error('After custom field error: ' + e); - } + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After custom field error: ' + e); + } }); let currentTest = `POST /action/rule/:connectionId`; test.serial(`${currentTest} should return created table rule with action and events`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - type: TableActionTypeEnum.single, - icon: 'test-icon', - require_confirmation: false, - }, - { - event: TableActionEventEnum.ADD_ROW, - type: TableActionTypeEnum.multiple, - title: 'Test event 2', - icon: 'test-icon 2', - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 201); - - t.truthy(createTableRuleRO.id); - t.is(createTableRuleRO.title, tableRuleDTO.title); - t.is(createTableRuleRO.table_name, tableRuleDTO.table_name); - t.is(createTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); - t.is(createTableRuleRO.events.length, tableRuleDTO.events.length); - const createdCustomEvent = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); - t.truthy(createdCustomEvent); - t.is(createdCustomEvent.title, tableRuleDTO.events[0].title); - t.is(createdCustomEvent.icon, tableRuleDTO.events[0].icon); - t.is(createdCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); - const createdAddRowEvent = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); - t.truthy(createdAddRowEvent); - t.is(createdAddRowEvent.title, tableRuleDTO.events[1].title); - t.is(createdAddRowEvent.icon, tableRuleDTO.events[1].icon); - t.is(createdAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); - - const createdUrlAction = createTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.URL, - ); - t.truthy(createdUrlAction); - t.is(createdUrlAction.url, tableRuleDTO.table_actions[0].url); - t.is(createdUrlAction.method, tableRuleDTO.table_actions[0].method); - t.is(createdUrlAction.slack_url, null); - t.is(createdUrlAction.emails.length, tableRuleDTO.table_actions[0].emails.length); - const createdSlackAction = createTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.SLACK, - ); - t.truthy(createdSlackAction); - t.is(createdSlackAction.url, null); - t.is(createdSlackAction.method, tableRuleDTO.table_actions[1].method); - t.truthy(createdSlackAction.slack_url); - t.deepEqual(createdSlackAction.emails, []); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + type: TableActionTypeEnum.single, + icon: 'test-icon', + require_confirmation: false, + }, + { + event: TableActionEventEnum.ADD_ROW, + type: TableActionTypeEnum.multiple, + title: 'Test event 2', + icon: 'test-icon 2', + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + t.truthy(createTableRuleRO.id); + t.is(createTableRuleRO.title, tableRuleDTO.title); + t.is(createTableRuleRO.table_name, tableRuleDTO.table_name); + t.is(createTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); + t.is(createTableRuleRO.events.length, tableRuleDTO.events.length); + const createdCustomEvent = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); + t.truthy(createdCustomEvent); + t.is(createdCustomEvent.title, tableRuleDTO.events[0].title); + t.is(createdCustomEvent.icon, tableRuleDTO.events[0].icon); + t.is(createdCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); + const createdAddRowEvent = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); + t.truthy(createdAddRowEvent); + t.is(createdAddRowEvent.title, tableRuleDTO.events[1].title); + t.is(createdAddRowEvent.icon, tableRuleDTO.events[1].icon); + t.is(createdAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); + + const createdUrlAction = createTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.URL, + ); + t.truthy(createdUrlAction); + t.is(createdUrlAction.url, tableRuleDTO.table_actions[0].url); + t.is(createdUrlAction.method, tableRuleDTO.table_actions[0].method); + t.is(createdUrlAction.slack_url, null); + t.is(createdUrlAction.emails.length, tableRuleDTO.table_actions[0].emails.length); + const createdSlackAction = createTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.SLACK, + ); + t.truthy(createdSlackAction); + t.is(createdSlackAction.url, null); + t.is(createdSlackAction.method, tableRuleDTO.table_actions[1].method); + t.truthy(createdSlackAction.slack_url); + t.deepEqual(createdSlackAction.emails, []); }); test.serial(`${currentTest} throw validation exceptions when create dto includes null values`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - null, - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.ADD_ROW, - title: undefined, - icon: null, - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: 'wrong-method' as any, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - null, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 400); - const { message } = createTableRuleRO; - t.truthy(message); - t.truthy(message.includes('each value in table_actions must be an object')); - t.truthy(message.includes('each value in events must be an object')); - t.truthy(message.includes('each value in events should not be empty')); - t.truthy(message.includes('each value in table_actions should not be empty')); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + null, + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.ADD_ROW, + title: undefined, + icon: null, + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: 'wrong-method' as any, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + null, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 400); + const { message } = createTableRuleRO; + t.truthy(message); + t.truthy(message.includes('each value in table_actions must be an object')); + t.truthy(message.includes('each value in events must be an object')); + t.truthy(message.includes('each value in events should not be empty')); + t.truthy(message.includes('each value in table_actions should not be empty')); }); test.serial(`${currentTest} throw validation exceptions when create dto includes wrong values`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.ADD_ROW, - title: undefined, - icon: null, - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: 'wrong-method' as any, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 400); - const { message } = createTableRuleRO; - t.truthy(message); - t.truthy(message.includes('Invalid action method wrong-method')); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.ADD_ROW, + title: undefined, + icon: null, + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: 'wrong-method' as any, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 400); + const { message } = createTableRuleRO; + t.truthy(message); + t.truthy(message.includes('Invalid action method wrong-method')); }); currentTest = `GET /action/rules/:connectionId`; test.serial(`${currentTest} should return found table rules with action and events`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.ADD_ROW, - title: 'Test event 2', - icon: 'test-icon 2', - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(createTableRuleResult.status, 201); - - const findTableRuleResult = await request(app.getHttpServer()) - .get(`/action/rules/${createConnectionRO.id}?tableName=${testTableName}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const findTableRulesRO: FoundTableActionRulesRoDTO = JSON.parse(findTableRuleResult.text); - - t.is(findTableRuleResult.status, 200); - - t.is(findTableRulesRO.action_rules.length, 1); - const findTableRuleRO = findTableRulesRO.action_rules[0]; - - t.truthy(findTableRuleRO.id); - t.is(findTableRuleRO.title, tableRuleDTO.title); - t.is(findTableRuleRO.table_name, tableRuleDTO.table_name); - t.is(findTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); - t.is(findTableRuleRO.events.length, tableRuleDTO.events.length); - const foundCustomEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); - t.truthy(foundCustomEvent); - t.is(foundCustomEvent.title, tableRuleDTO.events[0].title); - t.is(foundCustomEvent.icon, tableRuleDTO.events[0].icon); - t.is(foundCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); - const foundAddRowEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); - t.truthy(foundAddRowEvent); - t.is(foundAddRowEvent.title, tableRuleDTO.events[1].title); - t.is(foundAddRowEvent.icon, tableRuleDTO.events[1].icon); - t.is(foundAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); - const foundUrlAction = findTableRuleRO.table_actions.find((action) => action.method === TableActionMethodEnum.URL); - t.truthy(foundUrlAction); - t.is(foundUrlAction.url, tableRuleDTO.table_actions[0].url); - t.is(foundUrlAction.method, tableRuleDTO.table_actions[0].method); - t.is(foundUrlAction.slack_url, null); - t.is(foundUrlAction.emails.length, tableRuleDTO.table_actions[0].emails.length); - const foundSlackAction = findTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.SLACK, - ); - t.truthy(foundSlackAction); - t.is(foundSlackAction.url, null); - t.is(foundSlackAction.method, tableRuleDTO.table_actions[1].method); - t.truthy(foundSlackAction.slack_url); - t.deepEqual(foundSlackAction.emails, []); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.ADD_ROW, + title: 'Test event 2', + icon: 'test-icon 2', + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createTableRuleResult.status, 201); + + const findTableRuleResult = await request(app.getHttpServer()) + .get(`/action/rules/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const findTableRulesRO: FoundTableActionRulesRoDTO = JSON.parse(findTableRuleResult.text); + + t.is(findTableRuleResult.status, 200); + + t.is(findTableRulesRO.action_rules.length, 1); + const findTableRuleRO = findTableRulesRO.action_rules[0]; + + t.truthy(findTableRuleRO.id); + t.is(findTableRuleRO.title, tableRuleDTO.title); + t.is(findTableRuleRO.table_name, tableRuleDTO.table_name); + t.is(findTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); + t.is(findTableRuleRO.events.length, tableRuleDTO.events.length); + const foundCustomEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); + t.truthy(foundCustomEvent); + t.is(foundCustomEvent.title, tableRuleDTO.events[0].title); + t.is(foundCustomEvent.icon, tableRuleDTO.events[0].icon); + t.is(foundCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); + const foundAddRowEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); + t.truthy(foundAddRowEvent); + t.is(foundAddRowEvent.title, tableRuleDTO.events[1].title); + t.is(foundAddRowEvent.icon, tableRuleDTO.events[1].icon); + t.is(foundAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); + const foundUrlAction = findTableRuleRO.table_actions.find((action) => action.method === TableActionMethodEnum.URL); + t.truthy(foundUrlAction); + t.is(foundUrlAction.url, tableRuleDTO.table_actions[0].url); + t.is(foundUrlAction.method, tableRuleDTO.table_actions[0].method); + t.is(foundUrlAction.slack_url, null); + t.is(foundUrlAction.emails.length, tableRuleDTO.table_actions[0].emails.length); + const foundSlackAction = findTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.SLACK, + ); + t.truthy(foundSlackAction); + t.is(foundSlackAction.url, null); + t.is(foundSlackAction.method, tableRuleDTO.table_actions[1].method); + t.truthy(foundSlackAction.slack_url); + t.deepEqual(foundSlackAction.emails, []); }); currentTest = `/action/events/custom/:connectionId`; test.serial(`${currentTest} should return found table custom action events`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.ADD_ROW, - title: 'Test event 2', - icon: 'test-icon 2', - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(createTableRuleResult.status, 201); - - const findCustomEventsResult = await request(app.getHttpServer()) - .get(`/action/events/custom/${createConnectionRO.id}?tableName=${testTableName}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const findActionCustomEvents: Array = JSON.parse(findCustomEventsResult.text); - t.is(findCustomEventsResult.status, 200); - t.is(findActionCustomEvents.length, 1); - const foundCustomEvent = findActionCustomEvents[0]; - t.truthy(foundCustomEvent.id); - t.is(foundCustomEvent.event, TableActionEventEnum.CUSTOM); - t.is(foundCustomEvent.title, tableRuleDTO.events[0].title); - t.is(foundCustomEvent.icon, tableRuleDTO.events[0].icon); - t.is(foundCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.ADD_ROW, + title: 'Test event 2', + icon: 'test-icon 2', + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createTableRuleResult.status, 201); + + const findCustomEventsResult = await request(app.getHttpServer()) + .get(`/action/events/custom/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const findActionCustomEvents: Array = JSON.parse(findCustomEventsResult.text); + t.is(findCustomEventsResult.status, 200); + t.is(findActionCustomEvents.length, 1); + const foundCustomEvent = findActionCustomEvents[0]; + t.truthy(foundCustomEvent.id); + t.is(foundCustomEvent.event, TableActionEventEnum.CUSTOM); + t.is(foundCustomEvent.title, tableRuleDTO.events[0].title); + t.is(foundCustomEvent.icon, tableRuleDTO.events[0].icon); + t.is(foundCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); }); currentTest = `DELETE /action/rule/:actionId/:connectionId`; test.serial( - `${currentTest} should delete table action rule with action and events and return deleted result`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.ADD_ROW, - title: 'Test event 2', - icon: 'test-icon 2', - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 201); - - const deleteTableRuleResult = await request(app.getHttpServer()) - .delete(`/action/rule/${createTableRuleRO.id}/${createConnectionRO.id}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const deleteTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(deleteTableRuleResult.text); - t.is(deleteTableRuleResult.status, 200); - - t.truthy(deleteTableRuleRO.id); - t.is(deleteTableRuleRO.title, tableRuleDTO.title); - t.is(deleteTableRuleRO.table_name, tableRuleDTO.table_name); - t.is(deleteTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); - t.is(deleteTableRuleRO.events.length, tableRuleDTO.events.length); - const deletedCustomEvent = deleteTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); - t.truthy(deletedCustomEvent); - t.is(deletedCustomEvent.title, tableRuleDTO.events[0].title); - t.is(deletedCustomEvent.icon, tableRuleDTO.events[0].icon); - t.is(deletedCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); - - const deletedAddRowEvent = deleteTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); - t.truthy(deletedAddRowEvent); - t.is(deletedAddRowEvent.title, tableRuleDTO.events[1].title); - t.is(deletedAddRowEvent.icon, tableRuleDTO.events[1].icon); - t.is(deletedAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); - - const deletedSlackAction = deleteTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.URL, - ); - t.truthy(deletedSlackAction); - t.is(deletedSlackAction.url, tableRuleDTO.table_actions[0].url); - t.is(deletedSlackAction.method, tableRuleDTO.table_actions[0].method); - t.is(deletedSlackAction.slack_url, null); - t.is(deletedSlackAction.emails.length, tableRuleDTO.table_actions[0].emails.length); - - const deletedUrlAction = deleteTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.SLACK, - ); - t.truthy(deletedUrlAction); - t.is(deletedUrlAction.url, null); - t.is(deletedUrlAction.method, tableRuleDTO.table_actions[1].method); - t.truthy(deletedUrlAction.slack_url); - t.deepEqual(deletedUrlAction.emails, []); - - // Check if the rule is deleted - const findTableRuleResult = await request(app.getHttpServer()) - .get(`/action/rules/${createConnectionRO.id}?tableName=${testTableName}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const findTableRulesRO: FoundTableActionRulesRoDTO = JSON.parse(findTableRuleResult.text); - - t.is(findTableRuleResult.status, 200); - - t.is(findTableRulesRO.action_rules.length, 0); - }, + `${currentTest} should delete table action rule with action and events and return deleted result`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.ADD_ROW, + title: 'Test event 2', + icon: 'test-icon 2', + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + const deleteTableRuleResult = await request(app.getHttpServer()) + .delete(`/action/rule/${createTableRuleRO.id}/${createConnectionRO.id}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const deleteTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(deleteTableRuleResult.text); + t.is(deleteTableRuleResult.status, 200); + + t.truthy(deleteTableRuleRO.id); + t.is(deleteTableRuleRO.title, tableRuleDTO.title); + t.is(deleteTableRuleRO.table_name, tableRuleDTO.table_name); + t.is(deleteTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); + t.is(deleteTableRuleRO.events.length, tableRuleDTO.events.length); + const deletedCustomEvent = deleteTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); + t.truthy(deletedCustomEvent); + t.is(deletedCustomEvent.title, tableRuleDTO.events[0].title); + t.is(deletedCustomEvent.icon, tableRuleDTO.events[0].icon); + t.is(deletedCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); + + const deletedAddRowEvent = deleteTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); + t.truthy(deletedAddRowEvent); + t.is(deletedAddRowEvent.title, tableRuleDTO.events[1].title); + t.is(deletedAddRowEvent.icon, tableRuleDTO.events[1].icon); + t.is(deletedAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); + + const deletedSlackAction = deleteTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.URL, + ); + t.truthy(deletedSlackAction); + t.is(deletedSlackAction.url, tableRuleDTO.table_actions[0].url); + t.is(deletedSlackAction.method, tableRuleDTO.table_actions[0].method); + t.is(deletedSlackAction.slack_url, null); + t.is(deletedSlackAction.emails.length, tableRuleDTO.table_actions[0].emails.length); + + const deletedUrlAction = deleteTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.SLACK, + ); + t.truthy(deletedUrlAction); + t.is(deletedUrlAction.url, null); + t.is(deletedUrlAction.method, tableRuleDTO.table_actions[1].method); + t.truthy(deletedUrlAction.slack_url); + t.deepEqual(deletedUrlAction.emails, []); + + // Check if the rule is deleted + const findTableRuleResult = await request(app.getHttpServer()) + .get(`/action/rules/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const findTableRulesRO: FoundTableActionRulesRoDTO = JSON.parse(findTableRuleResult.text); + + t.is(findTableRuleResult.status, 200); + + t.is(findTableRulesRO.action_rules.length, 0); + }, ); currentTest = `GET /action/rule/:actionId/:connectionId`; test.serial( - `${currentTest} should delete table action rule with action and events and return deleted result`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.ADD_ROW, - title: 'Test event 2', - icon: 'test-icon 2', - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 201); - - const getTableRuleResult = await request(app.getHttpServer()) - .get(`/action/rule/${createTableRuleRO.id}/${createConnectionRO.id}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const findTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(getTableRuleResult.text); - t.is(getTableRuleResult.status, 200); - - t.truthy(findTableRuleRO.id); - t.is(findTableRuleRO.title, tableRuleDTO.title); - t.is(findTableRuleRO.table_name, tableRuleDTO.table_name); - t.is(findTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); - t.is(findTableRuleRO.events.length, tableRuleDTO.events.length); - const foundCustomEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); - t.truthy(foundCustomEvent); - t.is(foundCustomEvent.title, tableRuleDTO.events[0].title); - t.is(foundCustomEvent.icon, tableRuleDTO.events[0].icon); - t.is(foundCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); - const foundAddRowEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); - t.truthy(foundAddRowEvent); - t.is(foundAddRowEvent.title, tableRuleDTO.events[1].title); - t.is(foundAddRowEvent.icon, tableRuleDTO.events[1].icon); - t.is(foundAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); - const foundUrlAction = findTableRuleRO.table_actions.find((action) => action.method === TableActionMethodEnum.URL); - t.truthy(foundUrlAction); - t.is(foundUrlAction.url, tableRuleDTO.table_actions[0].url); - t.is(foundUrlAction.method, tableRuleDTO.table_actions[0].method); - t.is(foundUrlAction.slack_url, null); - t.is(foundUrlAction.emails.length, tableRuleDTO.table_actions[0].emails.length); - const foundSlackAction = findTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.SLACK, - ); - t.truthy(foundSlackAction); - t.is(foundSlackAction.url, null); - t.is(foundSlackAction.method, tableRuleDTO.table_actions[1].method); - t.truthy(foundSlackAction.slack_url); - t.deepEqual(foundSlackAction.emails, []); - }, + `${currentTest} should delete table action rule with action and events and return deleted result`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.ADD_ROW, + title: 'Test event 2', + icon: 'test-icon 2', + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + const getTableRuleResult = await request(app.getHttpServer()) + .get(`/action/rule/${createTableRuleRO.id}/${createConnectionRO.id}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const findTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(getTableRuleResult.text); + t.is(getTableRuleResult.status, 200); + + t.truthy(findTableRuleRO.id); + t.is(findTableRuleRO.title, tableRuleDTO.title); + t.is(findTableRuleRO.table_name, tableRuleDTO.table_name); + t.is(findTableRuleRO.table_actions.length, tableRuleDTO.table_actions.length); + t.is(findTableRuleRO.events.length, tableRuleDTO.events.length); + const foundCustomEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); + t.truthy(foundCustomEvent); + t.is(foundCustomEvent.title, tableRuleDTO.events[0].title); + t.is(foundCustomEvent.icon, tableRuleDTO.events[0].icon); + t.is(foundCustomEvent.require_confirmation, tableRuleDTO.events[0].require_confirmation); + const foundAddRowEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); + t.truthy(foundAddRowEvent); + t.is(foundAddRowEvent.title, tableRuleDTO.events[1].title); + t.is(foundAddRowEvent.icon, tableRuleDTO.events[1].icon); + t.is(foundAddRowEvent.require_confirmation, tableRuleDTO.events[1].require_confirmation); + const foundUrlAction = findTableRuleRO.table_actions.find((action) => action.method === TableActionMethodEnum.URL); + t.truthy(foundUrlAction); + t.is(foundUrlAction.url, tableRuleDTO.table_actions[0].url); + t.is(foundUrlAction.method, tableRuleDTO.table_actions[0].method); + t.is(foundUrlAction.slack_url, null); + t.is(foundUrlAction.emails.length, tableRuleDTO.table_actions[0].emails.length); + const foundSlackAction = findTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.SLACK, + ); + t.truthy(foundSlackAction); + t.is(foundSlackAction.url, null); + t.is(foundSlackAction.method, tableRuleDTO.table_actions[1].method); + t.truthy(foundSlackAction.slack_url); + t.deepEqual(foundSlackAction.emails, []); + }, ); currentTest = `PUT /action/rule/:actionId/:connectionId`; test.serial(`${currentTest} should return created table rule with action and events`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.ADD_ROW, - title: 'Test event 2', - icon: 'test-icon 2', - require_confirmation: true, - }, - ], - table_actions: [ - { - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.ZAPIER, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(createTableRuleResult.status, 201); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - const customEventId = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM).id; - const _addRowEventId = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW).id; - const urlActionId = createTableRuleRO.table_actions.find((action) => action.method === TableActionMethodEnum.URL).id; - - const createTableRuleROId = createTableRuleRO.id; - const createdZapierAction = createTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.ZAPIER, - ); - t.truthy(createdZapierAction); - - const updateTableRuleDTO: UpdateTableActionRuleBodyDTO = { - title: 'Test rule updated', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - id: customEventId, - event: TableActionEventEnum.CUSTOM, - title: 'Test event updated', - icon: 'test-icon updated', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.DELETE_ROW, - title: 'Test event 3 created', - icon: 'test-icon 3 created', - require_confirmation: false, - }, - ], - table_actions: [ - { - id: urlActionId, - url: faker.internet.url(), - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [faker.internet.email()], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: faker.internet.url(), - emails: undefined, - }, - ], - }; - - const updateTableRuleResult = await request(app.getHttpServer()) - .put(`/action/rule/${createTableRuleROId}/${createConnectionRO.id}`) - .send(updateTableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const updateTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(updateTableRuleResult.text); - t.is(updateTableRuleResult.status, 200); - - t.truthy(updateTableRuleRO.id); - t.is(updateTableRuleRO.title, updateTableRuleDTO.title); - t.is(updateTableRuleRO.table_name, updateTableRuleDTO.table_name); - t.is(updateTableRuleRO.table_actions.length, updateTableRuleDTO.table_actions.length); - t.is(updateTableRuleRO.events.length, updateTableRuleDTO.events.length); - const updatedCustomEvent = updateTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); - t.truthy(updatedCustomEvent); - t.is(updatedCustomEvent.title, updateTableRuleDTO.events[0].title); - t.is(updatedCustomEvent.icon, updateTableRuleDTO.events[0].icon); - t.is(updatedCustomEvent.require_confirmation, updateTableRuleDTO.events[0].require_confirmation); - const deletedAddRowEvent = updateTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); - t.falsy(deletedAddRowEvent); - - const createdDeleteRowEvent = updateTableRuleRO.events.find( - (event) => event.event === TableActionEventEnum.DELETE_ROW, - ); - t.truthy(createdDeleteRowEvent); - t.is(createdDeleteRowEvent.title, updateTableRuleDTO.events[1].title); - t.is(createdDeleteRowEvent.icon, updateTableRuleDTO.events[1].icon); - t.is(createdDeleteRowEvent.require_confirmation, updateTableRuleDTO.events[1].require_confirmation); - const updatedMultipleAction = updateTableRuleRO.table_actions.find((action) => action.id === urlActionId); - t.truthy(updatedMultipleAction); - t.is(updatedMultipleAction.url, updateTableRuleDTO.table_actions[0].url); - t.is(updatedMultipleAction.method, updateTableRuleDTO.table_actions[0].method); - t.is(updatedMultipleAction.slack_url, null); - t.is(updatedMultipleAction.emails.length, updateTableRuleDTO.table_actions[0].emails.length); - - const createdSlackActions = updateTableRuleRO.table_actions.filter( - (action) => action.method === TableActionMethodEnum.SLACK, - ); - t.is(createdSlackActions.length, 2); - t.truthy(createdSlackActions[0].slack_url); - t.deepEqual(createdSlackActions[0].emails, []); - t.truthy(createdSlackActions[1].slack_url); - t.deepEqual(createdSlackActions[1].emails, []); - - const deletedZapierAction = updateTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.ZAPIER, - ); - t.falsy(deletedZapierAction); - - // Check if the rule is updated - - const findTableRuleResult = await request(app.getHttpServer()) - .get(`/action/rules/${createConnectionRO.id}?tableName=${testTableName}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const findTableRulesRO: FoundTableActionRulesRoDTO = JSON.parse(findTableRuleResult.text); - - t.is(findTableRuleResult.status, 200); - t.truthy(findTableRulesRO.action_rules); - t.is(findTableRulesRO.action_rules.length, 1); - const findTableRuleRO = findTableRulesRO.action_rules[0]; - - t.truthy(findTableRuleRO.id); - t.is(findTableRuleRO.title, updateTableRuleDTO.title); - t.is(findTableRuleRO.table_name, updateTableRuleDTO.table_name); - t.is(findTableRuleRO.table_actions.length, updateTableRuleDTO.table_actions.length); - t.is(findTableRuleRO.events.length, updateTableRuleDTO.events.length); - const foundCustomEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); - t.truthy(foundCustomEvent); - t.is(foundCustomEvent.title, updateTableRuleDTO.events[0].title); - t.is(foundCustomEvent.icon, updateTableRuleDTO.events[0].icon); - t.is(foundCustomEvent.require_confirmation, updateTableRuleDTO.events[0].require_confirmation); - const foundDeleteRowEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.DELETE_ROW); - t.truthy(foundDeleteRowEvent); - t.is(foundDeleteRowEvent.title, updateTableRuleDTO.events[1].title); - t.is(foundDeleteRowEvent.icon, updateTableRuleDTO.events[1].icon); - t.is(foundDeleteRowEvent.require_confirmation, updateTableRuleDTO.events[1].require_confirmation); - const foundMultipleAction = findTableRuleRO.table_actions.find((action) => action.id === urlActionId); - t.truthy(foundMultipleAction); - t.is(foundMultipleAction.url, updateTableRuleDTO.table_actions[0].url); - t.is(foundMultipleAction.method, updateTableRuleDTO.table_actions[0].method); - t.is(foundMultipleAction.slack_url, null); - t.is(foundMultipleAction.emails.length, updateTableRuleDTO.table_actions[0].emails.length); - const foundSlackActions = findTableRuleRO.table_actions.filter( - (action) => action.method === TableActionMethodEnum.SLACK, - ); - t.is(foundSlackActions.length, 2); - t.truthy(foundSlackActions[0].slack_url); - t.deepEqual(foundSlackActions[0].emails, []); - t.truthy(foundSlackActions[1].slack_url); - t.deepEqual(foundSlackActions[1].emails, []); - const foundZapierAction = findTableRuleRO.table_actions.find( - (action) => action.method === TableActionMethodEnum.ZAPIER, - ); - t.falsy(foundZapierAction); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.ADD_ROW, + title: 'Test event 2', + icon: 'test-icon 2', + require_confirmation: true, + }, + ], + table_actions: [ + { + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.ZAPIER, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createTableRuleResult.status, 201); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + const customEventId = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM).id; + const _addRowEventId = createTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW).id; + const urlActionId = createTableRuleRO.table_actions.find((action) => action.method === TableActionMethodEnum.URL).id; + + const createTableRuleROId = createTableRuleRO.id; + const createdZapierAction = createTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.ZAPIER, + ); + t.truthy(createdZapierAction); + + const updateTableRuleDTO: UpdateTableActionRuleBodyDTO = { + title: 'Test rule updated', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + id: customEventId, + event: TableActionEventEnum.CUSTOM, + title: 'Test event updated', + icon: 'test-icon updated', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.DELETE_ROW, + title: 'Test event 3 created', + icon: 'test-icon 3 created', + require_confirmation: false, + }, + ], + table_actions: [ + { + id: urlActionId, + url: faker.internet.url(), + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [faker.internet.email()], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: faker.internet.url(), + emails: undefined, + }, + ], + }; + + const updateTableRuleResult = await request(app.getHttpServer()) + .put(`/action/rule/${createTableRuleROId}/${createConnectionRO.id}`) + .send(updateTableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(updateTableRuleResult.text); + t.is(updateTableRuleResult.status, 200); + + t.truthy(updateTableRuleRO.id); + t.is(updateTableRuleRO.title, updateTableRuleDTO.title); + t.is(updateTableRuleRO.table_name, updateTableRuleDTO.table_name); + t.is(updateTableRuleRO.table_actions.length, updateTableRuleDTO.table_actions.length); + t.is(updateTableRuleRO.events.length, updateTableRuleDTO.events.length); + const updatedCustomEvent = updateTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); + t.truthy(updatedCustomEvent); + t.is(updatedCustomEvent.title, updateTableRuleDTO.events[0].title); + t.is(updatedCustomEvent.icon, updateTableRuleDTO.events[0].icon); + t.is(updatedCustomEvent.require_confirmation, updateTableRuleDTO.events[0].require_confirmation); + const deletedAddRowEvent = updateTableRuleRO.events.find((event) => event.event === TableActionEventEnum.ADD_ROW); + t.falsy(deletedAddRowEvent); + + const createdDeleteRowEvent = updateTableRuleRO.events.find( + (event) => event.event === TableActionEventEnum.DELETE_ROW, + ); + t.truthy(createdDeleteRowEvent); + t.is(createdDeleteRowEvent.title, updateTableRuleDTO.events[1].title); + t.is(createdDeleteRowEvent.icon, updateTableRuleDTO.events[1].icon); + t.is(createdDeleteRowEvent.require_confirmation, updateTableRuleDTO.events[1].require_confirmation); + const updatedMultipleAction = updateTableRuleRO.table_actions.find((action) => action.id === urlActionId); + t.truthy(updatedMultipleAction); + t.is(updatedMultipleAction.url, updateTableRuleDTO.table_actions[0].url); + t.is(updatedMultipleAction.method, updateTableRuleDTO.table_actions[0].method); + t.is(updatedMultipleAction.slack_url, null); + t.is(updatedMultipleAction.emails.length, updateTableRuleDTO.table_actions[0].emails.length); + + const createdSlackActions = updateTableRuleRO.table_actions.filter( + (action) => action.method === TableActionMethodEnum.SLACK, + ); + t.is(createdSlackActions.length, 2); + t.truthy(createdSlackActions[0].slack_url); + t.deepEqual(createdSlackActions[0].emails, []); + t.truthy(createdSlackActions[1].slack_url); + t.deepEqual(createdSlackActions[1].emails, []); + + const deletedZapierAction = updateTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.ZAPIER, + ); + t.falsy(deletedZapierAction); + + // Check if the rule is updated + + const findTableRuleResult = await request(app.getHttpServer()) + .get(`/action/rules/${createConnectionRO.id}?tableName=${testTableName}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const findTableRulesRO: FoundTableActionRulesRoDTO = JSON.parse(findTableRuleResult.text); + + t.is(findTableRuleResult.status, 200); + t.truthy(findTableRulesRO.action_rules); + t.is(findTableRulesRO.action_rules.length, 1); + const findTableRuleRO = findTableRulesRO.action_rules[0]; + + t.truthy(findTableRuleRO.id); + t.is(findTableRuleRO.title, updateTableRuleDTO.title); + t.is(findTableRuleRO.table_name, updateTableRuleDTO.table_name); + t.is(findTableRuleRO.table_actions.length, updateTableRuleDTO.table_actions.length); + t.is(findTableRuleRO.events.length, updateTableRuleDTO.events.length); + const foundCustomEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.CUSTOM); + t.truthy(foundCustomEvent); + t.is(foundCustomEvent.title, updateTableRuleDTO.events[0].title); + t.is(foundCustomEvent.icon, updateTableRuleDTO.events[0].icon); + t.is(foundCustomEvent.require_confirmation, updateTableRuleDTO.events[0].require_confirmation); + const foundDeleteRowEvent = findTableRuleRO.events.find((event) => event.event === TableActionEventEnum.DELETE_ROW); + t.truthy(foundDeleteRowEvent); + t.is(foundDeleteRowEvent.title, updateTableRuleDTO.events[1].title); + t.is(foundDeleteRowEvent.icon, updateTableRuleDTO.events[1].icon); + t.is(foundDeleteRowEvent.require_confirmation, updateTableRuleDTO.events[1].require_confirmation); + const foundMultipleAction = findTableRuleRO.table_actions.find((action) => action.id === urlActionId); + t.truthy(foundMultipleAction); + t.is(foundMultipleAction.url, updateTableRuleDTO.table_actions[0].url); + t.is(foundMultipleAction.method, updateTableRuleDTO.table_actions[0].method); + t.is(foundMultipleAction.slack_url, null); + t.is(foundMultipleAction.emails.length, updateTableRuleDTO.table_actions[0].emails.length); + const foundSlackActions = findTableRuleRO.table_actions.filter( + (action) => action.method === TableActionMethodEnum.SLACK, + ); + t.is(foundSlackActions.length, 2); + t.truthy(foundSlackActions[0].slack_url); + t.deepEqual(foundSlackActions[0].emails, []); + t.truthy(foundSlackActions[1].slack_url); + t.deepEqual(foundSlackActions[1].emails, []); + const foundZapierAction = findTableRuleRO.table_actions.find( + (action) => action.method === TableActionMethodEnum.ZAPIER, + ); + t.falsy(foundZapierAction); }); currentTest = 'POST /rule/actions/activate/:ruleId/:connectionId'; test.serial(`${currentTest} should return created table rule with action and events`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const secondPrimaryKeyPart = 'second_id'; - - await resetPostgresTestDbTableCompositePrimaryKeys(testTableName, secondPrimaryKeyPart); - - const fakeUrl = 'http://www.example.com'; - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - ], - table_actions: [ - { - url: fakeUrl, - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: fakeUrl, - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 201); - - const nockBodiesArray = []; - const scope = nock(fakeUrl) - .post('/') - .times(2) - .reply(201, (_uri, requestBody) => { - nockBodiesArray.push(requestBody); - return { - status: 201, - message: 'Table action was triggered', - }; - }); - - const activateTableRuleResult = await request(app.getHttpServer()) - .post(`/event/actions/activate/${createTableRuleRO.events[0].id}/${createConnectionRO.id}`) - .set('Cookie', token) - .send([{ id: 2, second_id: 'test_key' }]) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - const activateTableRuleRO: ActivatedTableActionsDTO = JSON.parse(activateTableRuleResult.text); - t.is(activateTableRuleResult.status, 201); - t.is(Object.hasOwn(activateTableRuleRO, 'activationResults'), true); - t.is(activateTableRuleRO.activationResults.length, 2); - - t.is(nockBodiesArray.length, 2); - const urlActionRequestBody = nockBodiesArray.find((body) => Object.hasOwn(body, `$$_raUserId`)); - t.truthy(urlActionRequestBody); - t.truthy(urlActionRequestBody.$$_raUserId); - t.truthy(urlActionRequestBody.$$_date); - t.truthy(urlActionRequestBody.$$_tableName); - t.truthy(urlActionRequestBody.$$_actionId); - t.is(urlActionRequestBody.$$_tableName, testTableName); - t.is(urlActionRequestBody.primaryKeys[0].id, 2); - - const slackActionRequestBody = nockBodiesArray.find((body) => Object.hasOwn(body, `text`)); - - t.truthy(slackActionRequestBody); - t.truthy(slackActionRequestBody.text); - t.truthy(slackActionRequestBody.text.includes(testTableName)); - t.truthy(slackActionRequestBody.text.includes(`[{"id":2,"second_id":"test_key"}]`)); - scope.done(); - await resetPostgresTestDB(); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const secondPrimaryKeyPart = 'second_id'; + + await resetPostgresTestDbTableCompositePrimaryKeys(testTableName, secondPrimaryKeyPart); + + const fakeUrl = 'http://www.example.com'; + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + ], + table_actions: [ + { + url: fakeUrl, + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: fakeUrl, + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + const nockBodiesArray = []; + const scope = nock(fakeUrl) + .post('/') + .times(2) + .reply(201, (_uri, requestBody) => { + nockBodiesArray.push(requestBody); + return { + status: 201, + message: 'Table action was triggered', + }; + }); + + const activateTableRuleResult = await request(app.getHttpServer()) + .post(`/event/actions/activate/${createTableRuleRO.events[0].id}/${createConnectionRO.id}`) + .set('Cookie', token) + .send([{ id: 2, second_id: 'test_key' }]) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const activateTableRuleRO: ActivatedTableActionsDTO = JSON.parse(activateTableRuleResult.text); + t.is(activateTableRuleResult.status, 201); + t.is(Object.hasOwn(activateTableRuleRO, 'activationResults'), true); + t.is(activateTableRuleRO.activationResults.length, 2); + + t.is(nockBodiesArray.length, 2); + const urlActionRequestBody = nockBodiesArray.find((body) => Object.hasOwn(body, `$$_raUserId`)); + t.truthy(urlActionRequestBody); + t.truthy(urlActionRequestBody.$$_raUserId); + t.truthy(urlActionRequestBody.$$_date); + t.truthy(urlActionRequestBody.$$_tableName); + t.truthy(urlActionRequestBody.$$_actionId); + t.is(urlActionRequestBody.$$_tableName, testTableName); + t.is(urlActionRequestBody.primaryKeys[0].id, 2); + + const slackActionRequestBody = nockBodiesArray.find((body) => Object.hasOwn(body, `text`)); + + t.truthy(slackActionRequestBody); + t.truthy(slackActionRequestBody.text); + t.truthy(slackActionRequestBody.text.includes(testTableName)); + t.truthy(slackActionRequestBody.text.includes(`[{"id":2,"second_id":"test_key"}]`)); + scope.done(); + await resetPostgresTestDB(); }); //test impersonate action test.serial(`${currentTest} should create impersonate action`, async (t) => { - const firstUser = await registerUserAndReturnUserInfo(app); - const secondUser = await registerUserAndReturnUserInfo(app); - - const connectionToSaasDatabase: CreateConnectionDto = { - host: 'rocketadmin-private-microservice-test-database', - username: 'postgres', - password: 'abc987', - database: 'postgres', - port: 5432, - type: ConnectionTypesEnum.postgres, - masterEncryption: false, - ssh: false, - } as any; - - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(connectionToSaasDatabase) - .set('Cookie', firstUser.token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const actionTableName = `user_info`; - - const url = `http://rocketadmin-private-microservice:3001/actions/impersonate/link`; - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test impersonate rule', - table_name: actionTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.CUSTOM, - title: 'Impersonate', - icon: 'test-icon', - require_confirmation: false, - }, - ], - table_actions: [ - { - url: url, - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [], - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', firstUser.token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - t.is(createTableRuleResult.status, 201); - - //get second user id from jwt token - - const secondUserId = testUtils.verifyJwtToken(secondUser.token).sub; - - //activate impersonate action and receive redirection link - - const activateTableActionResult = await request(app.getHttpServer()) - .post(`/event/actions/activate/${createTableRuleRO.events[0].id}/${createConnectionRO.id}`) - .send([{ id: secondUserId }]) - .set('Cookie', firstUser.token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const actionActivationRO = JSON.parse(activateTableActionResult.text); - t.is(activateTableActionResult.status, 201); - t.is(Object.hasOwn(actionActivationRO, 'location'), true); - - const redirectionLink = actionActivationRO.location; - const getLinkResult = await fetch(redirectionLink, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - Cookie: firstUser.token, - }, - redirect: 'manual', - }); - - t.is(getLinkResult.status, 301); + const firstUser = await registerUserAndReturnUserInfo(app); + const secondUser = await registerUserAndReturnUserInfo(app); + + const connectionToSaasDatabase: CreateConnectionDto = { + host: 'rocketadmin-private-microservice-test-database', + username: 'postgres', + password: 'abc987', + database: 'postgres', + port: 5432, + type: ConnectionTypesEnum.postgres, + masterEncryption: false, + ssh: false, + } as any; + + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(connectionToSaasDatabase) + .set('Cookie', firstUser.token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const actionTableName = `user_info`; + + const url = `http://rocketadmin-private-microservice:3001/actions/impersonate/link`; + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test impersonate rule', + table_name: actionTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.CUSTOM, + title: 'Impersonate', + icon: 'test-icon', + require_confirmation: false, + }, + ], + table_actions: [ + { + url: url, + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [], + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', firstUser.token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + const secondUserId = testUtils.verifyJwtToken(secondUser.token).sub; + + const activateTableActionResult = await request(app.getHttpServer()) + .post(`/event/actions/activate/${createTableRuleRO.events[0].id}/${createConnectionRO.id}`) + .send([{ id: secondUserId }]) + .set('Cookie', firstUser.token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const actionActivationRO = JSON.parse(activateTableActionResult.text); + t.is(activateTableActionResult.status, 201); + t.is(Object.hasOwn(actionActivationRO, 'location'), true); + + const redirectionLink = actionActivationRO.location; + const getLinkResult = await fetch(redirectionLink, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Cookie: firstUser.token, + }, + redirect: 'manual', + }); + + t.is(getLinkResult.status, 301); }); currentTest = 'POST/PUT/DELETE /table/row/:slug'; test.serial(`${currentTest} should create trigger and activate http table action on add row`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const createConnectionResult = await request(app.getHttpServer()) - .post('/connection') - .send(newConnection) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResult.text); - t.is(createConnectionResult.status, 201); - - const fakeUrl = 'http://www.example.com'; - const tableRuleDTO: CreateTableActionRuleBodyDTO = { - title: 'Test rule', - table_name: testTableName, - events: [ - { - type: TableActionTypeEnum.single, - event: TableActionEventEnum.ADD_ROW, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.DELETE_ROW, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - { - type: TableActionTypeEnum.multiple, - event: TableActionEventEnum.UPDATE_ROW, - title: 'Test event', - icon: 'test-icon', - require_confirmation: false, - }, - ], - table_actions: [ - { - url: fakeUrl, - method: TableActionMethodEnum.URL, - slack_url: undefined, - emails: [], - }, - { - url: undefined, - method: TableActionMethodEnum.SLACK, - slack_url: fakeUrl, - emails: undefined, - }, - ], - }; - - const createTableRuleResult = await request(app.getHttpServer()) - .post(`/action/rule/${createConnectionRO.id}`) - .send(tableRuleDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); - console.log('🚀 ~ test.serial ~ createTableRuleRO:', createTableRuleRO); - t.is(createTableRuleResult.status, 201); - - const nockBodiesArray = []; - const scope = nock(fakeUrl) - .post('/') - .times(6) - .reply(201, (_uri, requestBody) => { - nockBodiesArray.push(requestBody); - return { - status: 201, - message: 'Table action was triggered', - }; - }); - - const fakeName = faker.person.firstName(); - const fakeMail = faker.internet.email(); - - const row = { - [testTableColumnName]: fakeName, - [testTAbleSecondColumnName]: fakeMail, - }; - - const addRowInTableResponse = await request(app.getHttpServer()) - .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) - .send(JSON.stringify(row)) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - t.is(addRowInTableResponse.status, 201); - - const updatedRow = { - [testTableColumnName]: fakeName, - [testTAbleSecondColumnName]: fakeMail, - }; - - const updateRowInTableResponse = await request(app.getHttpServer()) - .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) - .send(JSON.stringify(updatedRow)) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - - t.is(updateRowInTableResponse.status, 200); - - const idForDeletion = 1; - const deleteRowInTableResponse = await request(app.getHttpServer()) - .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - t.is(deleteRowInTableResponse.status, 200); - - t.is(nockBodiesArray.length, 6); - const userDeletedRowSlackMessageRequestBody = nockBodiesArray.find( - (body) => Object.hasOwn(body, `text`) && body.text.includes('deleted'), - ); - t.truthy(userDeletedRowSlackMessageRequestBody); - t.truthy(userDeletedRowSlackMessageRequestBody.text); - t.truthy(userDeletedRowSlackMessageRequestBody.text.includes(testTableName)); - t.truthy(userDeletedRowSlackMessageRequestBody.text.includes(`[{"id":"${idForDeletion}"}]`)); - - const userAddedRowSlackMessageRequestBody = nockBodiesArray.find( - (body) => Object.hasOwn(body, `text`) && body.text.includes('added'), - ); - - t.truthy(userAddedRowSlackMessageRequestBody); - t.truthy(userAddedRowSlackMessageRequestBody.text); - t.truthy(userAddedRowSlackMessageRequestBody.text.includes(testTableName)); - t.truthy(userAddedRowSlackMessageRequestBody.text.includes(`[{"id":${testEntitiesSeedsCount + 1}}]`)); - - const userUpdatedRowSlackMessageRequestBody = nockBodiesArray.find( - (body) => Object.hasOwn(body, `text`) && body.text.includes('updated'), - ); - t.truthy(userUpdatedRowSlackMessageRequestBody); - t.truthy(userUpdatedRowSlackMessageRequestBody.text); - t.truthy(userUpdatedRowSlackMessageRequestBody.text.includes(testTableName)); - t.truthy(userUpdatedRowSlackMessageRequestBody.text.includes(`[{"id":"1"}]`)); - scope.done(); + const { token } = await registerUserAndReturnUserInfo(app); + const createConnectionResult = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResult.text); + t.is(createConnectionResult.status, 201); + + const fakeUrl = 'http://www.example.com'; + const tableRuleDTO: CreateTableActionRuleBodyDTO = { + title: 'Test rule', + table_name: testTableName, + events: [ + { + type: TableActionTypeEnum.single, + event: TableActionEventEnum.ADD_ROW, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.DELETE_ROW, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + { + type: TableActionTypeEnum.multiple, + event: TableActionEventEnum.UPDATE_ROW, + title: 'Test event', + icon: 'test-icon', + require_confirmation: false, + }, + ], + table_actions: [ + { + url: fakeUrl, + method: TableActionMethodEnum.URL, + slack_url: undefined, + emails: [], + }, + { + url: undefined, + method: TableActionMethodEnum.SLACK, + slack_url: fakeUrl, + emails: undefined, + }, + ], + }; + + const createTableRuleResult = await request(app.getHttpServer()) + .post(`/action/rule/${createConnectionRO.id}`) + .send(tableRuleDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createTableRuleRO: FoundActionRulesWithActionsAndEventsDTO = JSON.parse(createTableRuleResult.text); + t.is(createTableRuleResult.status, 201); + + const nockBodiesArray = []; + const scope = nock(fakeUrl) + .post('/') + .times(6) + .reply(201, (_uri, requestBody) => { + nockBodiesArray.push(requestBody); + return { + status: 201, + message: 'Table action was triggered', + }; + }); + + const fakeName = faker.person.firstName(); + const fakeMail = faker.internet.email(); + + const row = { + [testTableColumnName]: fakeName, + [testTAbleSecondColumnName]: fakeMail, + }; + + const addRowInTableResponse = await request(app.getHttpServer()) + .post(`/table/row/${createConnectionRO.id}?tableName=${testTableName}`) + .send(JSON.stringify(row)) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(addRowInTableResponse.status, 201); + + const updatedRow = { + [testTableColumnName]: fakeName, + [testTAbleSecondColumnName]: fakeMail, + }; + + const updateRowInTableResponse = await request(app.getHttpServer()) + .put(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=1`) + .send(JSON.stringify(updatedRow)) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(updateRowInTableResponse.status, 200); + + const idForDeletion = 1; + const deleteRowInTableResponse = await request(app.getHttpServer()) + .delete(`/table/row/${createConnectionRO.id}?tableName=${testTableName}&id=${idForDeletion}`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + t.is(deleteRowInTableResponse.status, 200); + + t.is(nockBodiesArray.length, 6); + const userDeletedRowSlackMessageRequestBody = nockBodiesArray.find( + (body) => Object.hasOwn(body, `text`) && body.text.includes('deleted'), + ); + t.truthy(userDeletedRowSlackMessageRequestBody); + t.truthy(userDeletedRowSlackMessageRequestBody.text); + t.truthy(userDeletedRowSlackMessageRequestBody.text.includes(testTableName)); + t.truthy(userDeletedRowSlackMessageRequestBody.text.includes(`[{"id":"${idForDeletion}"}]`)); + + const userAddedRowSlackMessageRequestBody = nockBodiesArray.find( + (body) => Object.hasOwn(body, `text`) && body.text.includes('added'), + ); + + t.truthy(userAddedRowSlackMessageRequestBody); + t.truthy(userAddedRowSlackMessageRequestBody.text); + t.truthy(userAddedRowSlackMessageRequestBody.text.includes(testTableName)); + t.truthy(userAddedRowSlackMessageRequestBody.text.includes(`[{"id":${testEntitiesSeedsCount + 1}}]`)); + + const userUpdatedRowSlackMessageRequestBody = nockBodiesArray.find( + (body) => Object.hasOwn(body, `text`) && body.text.includes('updated'), + ); + t.truthy(userUpdatedRowSlackMessageRequestBody); + t.truthy(userUpdatedRowSlackMessageRequestBody.text); + t.truthy(userUpdatedRowSlackMessageRequestBody.text.includes(testTableName)); + t.truthy(userUpdatedRowSlackMessageRequestBody.text.includes(`[{"id":"1"}]`)); + scope.done(); });