api/src/__tests__/widgetMutations.test.ts

Summary

Maintainability
F
1 wk
Test Coverage
import * as faker from 'faker';
import * as Random from 'meteor-random';
import * as sinon from 'sinon';
import { MESSAGE_KINDS } from '../data/constants';
import { IntegrationsAPI } from '../data/dataSources';
import * as logUtils from '../data/logUtils';
import widgetMutations, {
  getMessengerData
} from '../data/resolvers/mutations/widgets';
import * as utils from '../data/utils';
import {
  brandFactory,
  companyFactory,
  conversationFactory,
  conversationMessageFactory,
  customerFactory,
  engageMessageFactory,
  fieldFactory,
  fieldGroupFactory,
  formFactory,
  integrationFactory,
  knowledgeBaseArticleFactory,
  messengerAppFactory,
  productFactory,
  skillFactor,
  userFactory
} from '../db/factories';
import {
  Brands,
  Companies,
  ConversationMessages,
  Conversations,
  Customers,
  FormSubmissions,
  Integrations,
  KnowledgeBaseArticles,
  MessengerApps
} from '../db/models';
import { IBrandDocument } from '../db/models/definitions/brands';
import {
  CONVERSATION_OPERATOR_STATUS,
  CONVERSATION_STATUSES,
  MESSAGE_TYPES,
  METHODS
} from '../db/models/definitions/constants';
import { ICustomerDocument } from '../db/models/definitions/customers';
import { IIntegrationDocument } from '../db/models/definitions/integrations';
import * as elk from '../elasticsearch';
import './setup.ts';

describe('messenger connect', () => {
  let _brand: IBrandDocument;
  let _integration: IIntegrationDocument;
  let _customer: ICustomerDocument;

  beforeEach(async () => {
    // Creating test data
    _brand = await brandFactory();
    _integration = await integrationFactory({
      brandId: _brand._id,
      kind: 'messenger'
    });
    _customer = await customerFactory({
      integrationId: _integration._id,
      primaryEmail: 'test@gmail.com',
      emails: ['test@gmail.com'],
      primaryPhone: '96221050',
      deviceTokens: ['111']
    });
  });

  afterEach(async () => {
    // Clearing test data
    await Brands.deleteMany({});
    await Integrations.deleteMany({});
    await Customers.deleteMany({});
    await MessengerApps.deleteMany({});
  });

  test('brand not found', async () => {
    try {
      await widgetMutations.widgetsMessengerConnect(
        {},
        { brandCode: 'invalidCode' }
      );
    } catch (e) {
      expect(e.message).toBe('Invalid configuration');
    }
  });

  test('integration not found', async () => {
    const brand = await brandFactory({});

    try {
      await widgetMutations.widgetsMessengerConnect(
        {},
        { brandCode: brand.code || '' }
      );
    } catch (e) {
      expect(e.message).toBe('Integration not found');
    }
  });

  test('Test inactive integration', async () => {
    const brand = await brandFactory({});
    const integration = await integrationFactory({
      isActive: false,
      brandId: brand._id
    });

    try {
      await widgetMutations.widgetsMessengerConnect(
        {},
        { brandCode: brand.code || '' }
      );
    } catch (e) {
      expect(e.message).toBe(`Integration "${integration.name}" is not active`);
    }
  });

  test('Company data (Not a valid enum value for path `industry`)', async () => {
    const brand = await brandFactory();
    await integrationFactory({ brandId: brand._id });

    const companyName = faker.name.findName();

    await widgetMutations.widgetsMessengerConnect(
      {},
      {
        brandCode: brand.code || '',
        companyData: { name: companyName, industry: 'үйлчилгээ' }
      }
    );

    // company isn't created because industry is not a valid
    expect(await Companies.findOne({ name: companyName })).toBeNull();
  });

  test('with company error`)', async () => {
    const mock = sinon.stub(Companies, 'createCompany').callsFake(() => {
      throw new Error('fake error');
    });

    const brand = await brandFactory();
    await integrationFactory({ brandId: brand._id });

    const companyName = faker.name.findName();

    await widgetMutations.widgetsMessengerConnect(
      {},
      {
        brandCode: brand.code || '',
        companyData: { name: companyName, industry: 'үйлчилгээ' }
      }
    );

    // company isn't created because industry is not a valid
    expect(await Companies.findOne({ name: companyName })).toBeNull();
    mock.restore();
  });

  test('returns proper integrationId', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });

    await messengerAppFactory({
      kind: 'knowledgebase',
      name: 'kb',
      credentials: {
        integrationId: _integration._id,
        topicId: 'topicId'
      }
    });

    await messengerAppFactory({
      kind: 'lead',
      name: 'lead',
      credentials: {
        integrationId: _integration._id,
        formCode: 'formCode'
      }
    });

    const {
      integrationId,
      brand,
      messengerData
    } = await widgetMutations.widgetsMessengerConnect(
      {},
      { brandCode: _brand.code || '', email: faker.internet.email() }
    );

    expect(integrationId).toBe(_integration._id);
    expect(brand.code).toBe(_brand.code);
    expect(messengerData.formCodes[0]).toBe('formCode');
    expect(messengerData.knowledgeBaseTopicId).toBe('topicId');

    mock.restore();
  });

  test('creates new customer', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });
    const email = 'newCustomer@gmail.com';
    const now = new Date();

    await fieldFactory({
      contentType: 'customer',
      validation: 'date',
      text: 'dateField'
    });

    const { customerId } = await widgetMutations.widgetsMessengerConnect(
      {},
      {
        brandCode: _brand.code || '',
        email,
        data: { dateField: new Date() },
        companyData: {
          name: 'company',
          email: 'mail@company.com',
          phone: '123456789'
        },
        deviceToken: '111'
      }
    );

    expect(customerId).toBeDefined();

    const customer = await Customers.findById(customerId);

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

    expect(customer._id).toBeDefined();
    expect(customer.primaryEmail).toBe(email);
    expect(customer.emails).toContain(email);
    expect(customer.integrationId).toBe(_integration._id);
    expect((customer.deviceTokens || []).length).toBe(1);
    expect(customer.deviceTokens).toContain('111');
    expect(customer.createdAt >= now).toBeTruthy();
    expect(customer.sessionCount).toBe(1);
    mock.restore();
  });

  test('updates company', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });
    const email = 'newCustomer@gmail.com';
    const now = new Date();

    await fieldFactory({
      contentType: 'customer',
      validation: 'date',
      text: 'dateField'
    });

    await companyFactory({ primaryName: 'test company' });

    const { customerId } = await widgetMutations.widgetsMessengerConnect(
      {},
      {
        brandCode: _brand.code || '',
        email,
        data: { dateField: new Date() },
        companyData: {
          name: 'test company',
          email: 'testmail@company.com',
          phone: '0987654321',
          code: '1234'
        },
        deviceToken: '111'
      }
    );

    expect(customerId).toBeDefined();

    const customer = await Customers.findById(customerId);

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

    expect(customer._id).toBeDefined();
    expect(customer.primaryEmail).toBe(email);
    expect(customer.emails).toContain(email);
    expect(customer.integrationId).toBe(_integration._id);
    expect((customer.deviceTokens || []).length).toBe(1);
    expect(customer.deviceTokens).toContain('111');
    expect(customer.createdAt >= now).toBeTruthy();
    expect(customer.sessionCount).toBe(1);
    mock.restore();
  });

  test('creates new visitor log', async () => {
    const logUtilsMock = sinon.stub(logUtils, 'sendToLog').callsFake(() => {
      return Promise.resolve('ok');
    });

    const response = await widgetMutations.widgetsMessengerConnect(
      {},
      {
        brandCode: _brand.code || '',
        visitorId: '123'
      }
    );

    expect(response.customerId).toBeUndefined();
    expect(response.visitorId).toBe('123');

    logUtilsMock.restore();
  });

  test('updates existing customer', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });

    const now = new Date();

    const { customerId } = await widgetMutations.widgetsMessengerConnect(
      {},
      {
        brandCode: _brand.code || '',
        email: _customer.primaryEmail,
        isUser: true,
        deviceToken: '222'
      }
    );

    expect(customerId).toBeDefined();

    const customer = await Customers.findById(customerId);

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

    expect(customer).toBeDefined();
    expect(customer.integrationId).toBe(_integration._id);
    expect(customer.createdAt < now).toBeTruthy();

    // must be updated
    expect((customer.deviceTokens || []).length).toBe(2);
    expect(customer.deviceTokens).toContain('111');
    expect(customer.deviceTokens).toContain('222');
    mock.restore();
  });
});

describe('insertMessage()', () => {
  let _integration: IIntegrationDocument;
  let _customer: ICustomerDocument;
  let _integrationBot: IIntegrationDocument;
  let context;

  beforeEach(async () => {
    // Creating test data
    _integration = await integrationFactory({
      brandId: Random.id(),
      kind: 'messenger'
    });
    _integrationBot = await integrationFactory({
      brandId: Random.id(),
      kind: 'messenger',
      messengerData: {
        botEndpointUrl: 'botEndpointUrl'
      }
    });
    _customer = await customerFactory({ integrationId: _integration._id });

    context = {
      dataSources: {
        IntegrationsAPI: new IntegrationsAPI()
      }
    };
  });

  afterEach(async () => {
    // Clearing test data
    await Integrations.deleteMany({});
    await Customers.deleteMany({});
  });

  test('without conversationId', async () => {
    const now = new Date();

    const message = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integration._id,
        customerId: _customer._id,
        message: faker.lorem.sentence()
      },
      context
    );

    // check message ==========
    expect(message).toBeDefined();
    expect(message.createdAt >= now).toBeTruthy();

    // check conversation =========
    const conversation = await Conversations.findById(message.conversationId);

    if (!conversation) {
      throw new Error('conversation is not found');
    }

    expect(conversation.status).toBe(CONVERSATION_STATUSES.OPEN);
    expect((conversation.readUserIds || []).length).toBe(0);

    // check customer =========
    const customer = await Customers.findOne({ _id: _customer._id });

    if (!customer) {
      throw new Error('customer is not found');
    }

    expect(customer.isOnline).toBeTruthy();

    const user = await userFactory({ code: '123 ' });
    const skill = await skillFactor({ memberIds: [user._id] });

    const message2 = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integration._id,
        customerId: _customer._id,
        message: faker.lorem.sentence(),
        skillId: skill._id
      },
      context
    );

    const conversation2 = await Conversations.findById(
      message2.conversationId
    ).lean();

    if (conversation2) {
      expect(conversation2.userRelevance).toBe(`${user.code}SS`);
    }
  });

  test('with conversationId', async () => {
    const conversation = await conversationFactory({});

    const message = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integration._id,
        customerId: _customer._id,
        message: 'withConversationId',
        conversationId: conversation._id
      },
      context
    );

    expect(message.content).toBe('withConversationId');
  });

  test('with visitorId', async () => {
    const logUtilsMock = sinon.stub(logUtils, 'sendToLog').callsFake(() => {
      return Promise.resolve('ok');
    });

    const conversation = await conversationFactory({});

    const message = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integration._id,
        visitorId: '123',
        message: 'withConversationId',
        conversationId: conversation._id
      },
      context
    );

    expect(message.content).toBe('withConversationId');

    logUtilsMock.restore();
  });

  test('Widget bot message with conversationId', async () => {
    const conversation = await conversationFactory({
      operatorStatus: CONVERSATION_OPERATOR_STATUS.BOT
    });

    const conversation2 = await conversationFactory({
      operatorStatus: CONVERSATION_OPERATOR_STATUS.OPERATOR
    });

    const sendRequestMock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve({
        responses: [
          {
            type: 'text',
            text: 'Bot message'
          }
        ]
      });
    });

    const message = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integrationBot._id,
        message: 'User message',
        customerId: _customer._id,
        conversationId: conversation._id
      },
      context
    );

    expect(message.content).toBe('User message');

    const botMessage = await ConversationMessages.findOne({
      conversationId: conversation._id,
      botData: { $exists: true }
    });

    if (botMessage) {
      expect(botMessage.botData).toEqual([
        {
          type: 'text',
          text: 'Bot message'
        }
      ]);
    } else {
      fail('Bot message not found');
    }

    const message2 = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integrationBot._id,
        message: 'User message 2',
        customerId: _customer._id,
        conversationId: conversation2._id
      },
      context
    );

    expect(message2.content).toBe('User message 2');

    sendRequestMock.restore();
  });

  test('Bot message without conversationId', async () => {
    const sendRequestMock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve({
        responses: [
          {
            type: 'text',
            text: 'Bot message'
          }
        ]
      });
    });

    const message = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integrationBot._id,
        message: 'User message',
        customerId: _customer._id
      },
      context
    );

    expect(message.content).toBe('User message');

    const botMessage = await ConversationMessages.findOne({
      conversationId: message.conversationId,
      botData: { $exists: true }
    });

    if (botMessage) {
      expect(botMessage.botData).toEqual([
        {
          type: 'text',
          text: 'Bot message'
        }
      ]);
    } else {
      fail('Bot message not found');
    }

    sendRequestMock.restore();
  });

  test('Bot message: Failed to connect to BOTPRESS', async () => {
    const sendRequestMock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve(new Error('Failed to connect to BOTPRESS'));
    });

    const conversation = await conversationFactory({
      operatorStatus: CONVERSATION_OPERATOR_STATUS.BOT
    });

    _integrationBot = await integrationFactory({
      brandId: Random.id(),
      kind: 'messenger',
      messengerData: {
        botEndpointUrl: 'botEndpointUrl',
        botShowInitialMessage: false
      }
    });

    try {
      await widgetMutations.widgetsInsertMessage(
        {},
        {
          contentType: MESSAGE_TYPES.TEXT,
          integrationId: _integrationBot._id,
          message: 'User message',
          customerId: _customer._id,
          conversationId: conversation._id
        },
        context
      );
    } catch (e) {
      expect(e.message).toBe('Failed to connect to BOTPRESS');
    }

    sendRequestMock.restore();
  });

  test('Bot show initial message', async () => {
    const mock = sinon.stub(utils, 'sendRequest').returns(
      Promise.resolve({
        responses: [
          {
            type: 'text',
            text: 'Greeting bot message'
          }
        ]
      })
    );

    const message = await widgetMutations.widgetGetBotInitialMessage(
      {},
      {
        integrationId: _integrationBot._id
      }
    );

    expect(message.botData[0].text).toBe('Greeting bot message');

    mock.restore();
  });

  test('Bot widget post request', async () => {
    const conversation1 = await conversationFactory({
      operatorStatus: CONVERSATION_OPERATOR_STATUS.BOT
    });
    const conversation2 = await conversationFactory({
      operatorStatus: CONVERSATION_OPERATOR_STATUS.BOT
    });

    const sendRequestMock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve({
        responses: [
          {
            type: 'text',
            text: 'Response of quick reply'
          }
        ]
      });
    });

    const botMessage1 = await widgetMutations.widgetBotRequest(
      {},
      {
        integrationId: _integrationBot._id,
        conversationId: conversation1._id,
        customerId: _customer._id,
        message: 'Reply message',
        payload: 'Response of reply',
        type: 'postback'
      }
    );

    const message1 = await ConversationMessages.findOne({
      conversationId: conversation1._id,
      botData: { $exists: false }
    });

    if (message1) {
      expect(message1.content).toBe('Reply message');
    } else {
      fail('Message not found');
    }

    expect(botMessage1.botData).toEqual([
      {
        type: 'text',
        text: 'Response of quick reply'
      }
    ]);

    const botMessage2 = await widgetMutations.widgetBotRequest(
      {},
      {
        integrationId: _integrationBot._id,
        conversationId: conversation2._id,
        customerId: _customer._id,
        message: 'Reply message 2',
        payload: 'Response of reply',
        type: 'say_something'
      }
    );

    const message2 = await ConversationMessages.findOne({
      conversationId: conversation2._id,
      botData: { $exists: false }
    });

    if (message2) {
      expect(message2.content).toBe('Reply message 2');
    } else {
      fail('Message not found');
    }

    expect(botMessage2.botData).toEqual([
      {
        type: 'text',
        text: 'Response of reply'
      }
    ]);

    const sendToVisitorLogMock = sinon
      .stub(logUtils, 'sendToLog')
      .callsFake(() => {
        return Promise.resolve({
          visitorId: '123',
          integrationId: _integration._id
        });
      });

    const botMessage3 = await widgetMutations.widgetBotRequest(
      {},
      {
        integrationId: _integrationBot._id,
        visitorId: '123',
        message: 'Reply message',
        payload: 'Response of reply',
        type: 'postback'
      }
    );

    expect(botMessage3.botData).toEqual([
      {
        type: 'text',
        text: 'Response of quick reply'
      }
    ]);

    sendToVisitorLogMock.restore();

    sendRequestMock.restore();
  });

  test('Video call request', async () => {
    const conversation = await conversationFactory();

    // When first video call request
    const vcrMessage = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.VIDEO_CALL_REQUEST,
        integrationId: _integration._id,
        customerId: _customer._id,
        conversationId: conversation._id,
        message: ''
      },
      context
    );

    expect(vcrMessage).toBeDefined();

    // When not connected with Integration API
    const vcrMessage2 = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.VIDEO_CALL_REQUEST,
        integrationId: _integration._id,
        customerId: _customer._id,
        conversationId: conversation._id,
        message: ''
      },
      context
    );

    expect(vcrMessage2).toBeDefined();

    // When occured error
    const spy = jest.spyOn(context.dataSources.IntegrationsAPI, 'fetchApi');

    spy.mockImplementation(() =>
      Promise.resolve([
        { code: 'VIDEO_CALL_TIME_DELAY_BETWEEN_REQUESTS', value: 10 },
        {
          code: 'VIDEO_CALL_MESSAGE_FOR_TIME_DELAY',
          value: 'Video call request has already sent'
        }
      ])
    );

    try {
      await widgetMutations.widgetsInsertMessage(
        {},
        {
          contentType: MESSAGE_TYPES.VIDEO_CALL_REQUEST,
          integrationId: _integration._id,
          customerId: _customer._id,
          conversationId: conversation._id,
          message: ''
        },
        context
      );
    } catch (e) {
      expect(e.message).toBe('Video call request has already sent');
    }
  });

  test('Failed to send notification to mobile', async () => {
    const spy = jest.spyOn(utils, 'sendMobileNotification');
    spy.mockImplementation(() => {
      throw new Error('error');
    });

    const conversation = await conversationFactory();

    const message = await widgetMutations.widgetsInsertMessage(
      {},
      {
        contentType: MESSAGE_TYPES.TEXT,
        integrationId: _integration._id,
        customerId: _customer._id,
        conversationId: conversation._id,
        message: 'message'
      },
      context
    );

    expect(message.content).toBe('message');

    spy.mockRestore();
  });
});

describe('saveBrowserInfo()', () => {
  const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
    return Promise.resolve('success');
  });

  afterEach(async () => {
    // Clearing test data
    await Integrations.deleteMany({});
    await Customers.deleteMany({});
    await Brands.deleteMany({});
  });

  test('not found', async () => {
    let customer = await customerFactory({});

    // integration not found
    try {
      await widgetMutations.widgetsSaveBrowserInfo(
        {},
        {
          customerId: customer._id,
          browserInfo: {}
        }
      );
    } catch (e) {
      expect(e.message).toBe('Integration not found');
    }

    const integration = await integrationFactory({});
    customer = await customerFactory({ integrationId: integration._id });

    try {
      await widgetMutations.widgetsSaveBrowserInfo(
        {},
        {
          customerId: customer._id,
          browserInfo: {}
        }
      );
    } catch (e) {
      expect(e.message).toBe('Brand not found');
    }
  });

  test('success', async () => {
    const user = await userFactory({});
    const brand = await brandFactory({});
    const integration = await integrationFactory({ brandId: brand._id });

    const customer = await customerFactory({ integrationId: integration._id });

    await engageMessageFactory({
      userId: user._id,
      messenger: {
        brandId: brand._id,
        content: 'engageMessage',
        rules: [
          {
            text: 'text',
            kind: 'currentPageUrl',
            condition: 'is',
            value: '/page'
          }
        ]
      },
      kind: MESSAGE_KINDS.VISITOR_AUTO,
      method: 'messenger',
      isLive: true
    });

    const response = await widgetMutations.widgetsSaveBrowserInfo(
      {},
      {
        customerId: customer._id,
        browserInfo: { url: '/page' }
      }
    );

    expect(response).toBe(null);
  });

  test('with visitorId', async () => {
    const user = await userFactory({});
    const brand = await brandFactory({});
    const integration = await integrationFactory({ brandId: brand._id });

    const sendToVisitorLogMock = sinon
      .stub(logUtils, 'sendToLog')
      .callsFake(() => {
        return Promise.resolve({
          visitorId: '1234',
          integrationId: integration._id
        });
      });

    await engageMessageFactory({
      userId: user._id,
      messenger: {
        brandId: brand._id,
        content: 'engageMessage',
        rules: [
          {
            text: 'text',
            kind: 'currentPageUrl',
            condition: 'is',
            value: '/page'
          }
        ]
      },
      kind: MESSAGE_KINDS.VISITOR_AUTO,
      method: METHODS.MESSENGER,
      isLive: true
    });

    const response = await widgetMutations.widgetsSaveBrowserInfo(
      {},
      {
        visitorId: '1234',
        browserInfo: { url: '/page' }
      }
    );

    expect(response).toBe(null);
    sendToVisitorLogMock.restore();
  });

  mock.restore();
});

describe('rest', () => {
  test('widgetsSaveCustomerGetNotified', async () => {
    let customer = await customerFactory({});

    customer = await widgetMutations.widgetsSaveCustomerGetNotified(
      {},
      {
        customerId: customer._id,
        type: 'email',
        value: 'email'
      }
    );

    expect(
      customer.visitorContactInfo && customer.visitorContactInfo.email
    ).toBe('email');
  });

  test('widgetsSaveCustomerGetNotified without customerId', async () => {
    const logUtilsMock = sinon.stub(logUtils, 'sendToLog').callsFake(() => {
      return Promise.resolve('ok');
    });

    const customer = await widgetMutations.widgetsSaveCustomerGetNotified(
      {},
      {
        visitorId: '123',
        customerId: '',
        type: 'email',
        value: 'email'
      }
    );

    expect(
      customer.visitorContactInfo && customer.visitorContactInfo.email
    ).toBe('email');

    logUtilsMock.restore();
  });

  test('widgetsSendTypingInfo', async () => {
    const conversation = await conversationFactory({});

    const response = await widgetMutations.widgetsSendTypingInfo(
      {},
      {
        conversationId: conversation._id
      }
    );

    expect(response).toBe('ok');
  });

  test('widgetsReadConversationMessages', async () => {
    const user = await userFactory({});
    const conversation = await conversationFactory({});

    const message = await conversationMessageFactory({
      conversationId: conversation._id,
      userId: user._id,
      isCustomerRead: false
    });

    expect(message.isCustomerRead).toBe(false);

    await widgetMutations.widgetsReadConversationMessages(
      {},
      {
        conversationId: conversation._id
      }
    );

    const updatedMessage = await ConversationMessages.findOne({
      _id: message._id
    });

    expect(updatedMessage && updatedMessage.isCustomerRead).toBe(true);
  });

  test('getMessengerData', async () => {
    const integration = await integrationFactory({
      languageCode: 'en',
      messengerData: {
        messages: {
          en: {
            welcome: 'welcome'
          }
        }
      }
    });

    const response = await getMessengerData(integration);

    expect(response.messages && response.messages.welcome).toBe('welcome');
  });
});

describe('knowledgebase', () => {
  test('widgetsKnowledgebaseIncReactionCount', async () => {
    const article = await knowledgeBaseArticleFactory({
      reactionChoices: ['wow']
    });

    await widgetMutations.widgetsKnowledgebaseIncReactionCount(
      {},
      {
        articleId: article._id,
        reactionChoice: 'wow'
      }
    );

    const updatedArticle = await KnowledgeBaseArticles.findOne({
      _id: article._id
    });

    expect(
      updatedArticle &&
        updatedArticle.reactionCounts &&
        updatedArticle.reactionCounts.wow
    ).toBe(1);
  });

  test('widgetsKnowledgebaseDecReactionCount', async () => {
    const article = await knowledgeBaseArticleFactory({
      reactionChoices: ['wow']
    });

    await widgetMutations.widgetsKnowledgebaseDecReactionCount(
      {},
      {
        articleId: article._id,
        reactionChoice: 'wow'
      }
    );

    const updatedArticle = await KnowledgeBaseArticles.findOne({
      _id: article._id
    });

    expect(
      updatedArticle &&
        updatedArticle.reactionCounts &&
        updatedArticle.reactionCounts.wow
    ).toBe(-1);
  });
});

describe('lead', () => {
  afterEach(async () => {
    // Clearing test data
    await Integrations.deleteMany({});
    await Customers.deleteMany({});
    await Conversations.deleteMany({});
    await ConversationMessages.deleteMany({});
    await FormSubmissions.deleteMany({});
  });

  test('widgetsLeadIncreaseViewCount', async () => {
    const form = await formFactory({});
    const integration = await integrationFactory({ formId: form._id });

    await widgetMutations.widgetsLeadIncreaseViewCount(
      {},
      {
        formId: form._id
      }
    );

    const updatedInteg = await Integrations.findOne({ _id: integration._id });

    expect(
      updatedInteg && updatedInteg.leadData && updatedInteg.leadData.viewCount
    ).toBe(1);
  });

  test('leadConnect: not found', async () => {
    // invalid configuration
    try {
      await widgetMutations.widgetsLeadConnect(
        {},
        {
          brandCode: 'code',
          formCode: 'code'
        }
      );
    } catch (e) {
      expect(e.message).toBe('Invalid configuration');
    }

    const brand = await brandFactory({});
    const form = await formFactory({});

    try {
      await widgetMutations.widgetsLeadConnect(
        {},
        {
          brandCode: brand.code || '',
          formCode: form.code || ''
        }
      );
    } catch (e) {
      expect(e.message).toBe('Integration not found');
    }
  });

  test('leadConnect: success', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });

    const brand = await brandFactory({});
    const form = await formFactory({});

    const integration = await integrationFactory({
      brandId: brand._id,
      formId: form._id,
      leadData: {
        loadType: 'embedded'
      }
    });

    const response1 = await widgetMutations.widgetsLeadConnect(
      {},
      {
        brandCode: brand.code || '',
        formCode: form.code || ''
      }
    );

    expect(response1 && response1.integration._id).toBe(integration._id);
    expect(response1 && response1.form._id).toBe(form._id);

    const response = await widgetMutations.widgetsLeadConnect(
      {},
      {
        brandCode: brand.code || '',
        formCode: form.code || ''
      }
    );

    expect(response && response.integration._id).toBe(integration._id);
    expect(response && response.form._id).toBe(form._id);

    mock.restore();
  });

  test('leadConnect: Already filled', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });

    const brand = await brandFactory({});
    const form = await formFactory({});

    const integration = await integrationFactory({
      brandId: brand._id,
      formId: form._id,
      leadData: {
        loadType: 'embedded',
        isRequireOnce: true
      }
    });

    const conversation = await conversationFactory({
      customerId: '123123',
      integrationId: integration._id
    });

    const response = await widgetMutations.widgetsLeadConnect(
      {},
      {
        brandCode: brand.code || '',
        formCode: form.code || '',
        cachedCustomerId: '123123'
      }
    );

    expect(conversation).toBeDefined();
    expect(response).toBeNull();

    mock.restore();
  });

  test('saveLead: form not found', async () => {
    try {
      await widgetMutations.widgetsSaveLead(
        {},
        {
          integrationId: '_id',
          formId: '_id',
          submissions: [{ _id: 'id', value: null }],
          browserInfo: {}
        }
      );
    } catch (e) {
      expect(e.message).toBe('Form not found');
    }
  });

  test('saveLead: invalid', async () => {
    const form = await formFactory({});

    const requiredField = await fieldFactory({
      contentTypeId: form._id,
      isRequired: true
    });

    const integration = await integrationFactory({ formId: form._id });

    const response = await widgetMutations.widgetsSaveLead(
      {},
      {
        integrationId: integration._id,
        formId: form._id,
        submissions: [{ _id: requiredField._id, value: null }],
        browserInfo: {
          currentPageUrl: '/page'
        }
      }
    );

    expect(response && response.status).toBe('error');
  });

  test('saveLead: without company', async () => {
    const form = await formFactory({});
    const fieldsGroup = await fieldGroupFactory({ contentType: 'company' });
    if (!fieldsGroup) {
      fail('fieldsGroup not found');
    }

    const firstNameField = await fieldFactory({
      type: 'firstName',
      contentTypeId: form._id,
      validation: 'text',
      isRequired: true
    });

    const customProperty = await fieldFactory({
      type: 'input',
      validation: 'number',
      isVisible: true,
      contentType: 'company',
      groupId: fieldsGroup._id
    });

    const inputField = await fieldFactory({
      type: customProperty.type,
      validation: customProperty.validation,
      contentTypeId: form._id,
      contentType: 'form',
      associatedFieldId: customProperty._id
    });

    const pronounField = await fieldFactory({
      type: 'pronoun'
    });

    const integration = await integrationFactory({ formId: form._id });

    const response = await widgetMutations.widgetsSaveLead(
      {},
      {
        integrationId: integration._id,
        formId: form._id,
        submissions: [
          { _id: firstNameField._id, value: 'name' },
          {
            _id: inputField._id,
            type: 'input',
            value: 1,
            associatedFieldId: inputField.associatedFieldId
          },
          {
            _id: inputField._id,
            type: 'input',
            value: 1,
            associatedFieldId: 'fake'
          },
          { _id: pronounField._id, type: 'pronoun', value: '' }
        ],
        browserInfo: {
          currentPageUrl: '/page'
        }
      }
    );

    expect(response && response.status).toBe('ok');
  });

  test('saveLead: success', async () => {
    const mock = sinon.stub(utils, 'sendRequest').callsFake(() => {
      return Promise.resolve('success');
    });

    const form = await formFactory({});

    const group = await fieldGroupFactory({ contentType: 'form' });

    const params = {
      validation: 'text',
      isRequired: true,
      contentTypeId: form._id
    };

    const emailField = await fieldFactory({
      ...params,
      type: 'email',
      groupId: (group && group._id) || ''
    });

    const companyEmailField = await fieldFactory({
      ...params,
      type: 'companyEmail',
      groupId: (group && group._id) || ''
    });

    const firstNameField = await fieldFactory({
      ...params,
      type: 'firstName',
      groupId: (group && group._id) || ''
    });

    const lastNameField = await fieldFactory({
      ...params,
      type: 'lastName'
    });

    const phoneField = await fieldFactory({
      type: 'phone',
      contentTypeId: form._id,
      isRequired: true
    });

    const companyPhoneField = await fieldFactory({
      type: 'companyPhone',
      contentTypeId: form._id,
      isRequired: true
    });

    const checkField = await fieldFactory({
      type: 'check',
      contentTypeId: form._id,
      validation: 'text',
      options: ['check1', 'check2']
    });

    const radioField = await fieldFactory({
      type: 'radio',
      contentTypeId: form._id,
      validation: 'text',
      options: ['radio1', 'radio2']
    });

    const customProperty = await fieldFactory({
      type: 'input',
      validation: 'number',
      isVisible: true,
      contentType: 'customer'
    });

    const inputField = await fieldFactory({
      type: customProperty.type,
      validation: customProperty.validation,
      contentTypeId: form._id,
      contentType: 'form',
      associatedFieldId: customProperty._id
    });

    const companyNameField = await fieldFactory({
      ...params,
      type: 'company_primaryName'
    });

    const companyNameField2 = await fieldFactory({
      ...params,
      type: 'company_primaryName',
      groupId: (group && group._id) || ''
    });

    const avatarField = await fieldFactory({
      ...params,
      type: 'avatar'
    });

    const companyAvatarField = await fieldFactory({
      ...params,
      type: 'company_avatar'
    });

    const industryField = await fieldFactory({
      ...params,
      type: 'industry'
    });

    const sizeField = await fieldFactory({
      type: 'size',
      contentTypeId: form._id,
      isRequired: true
    });

    const businessTypeField = await fieldFactory({
      ...params,
      type: 'businessType'
    });

    const pronounField = await fieldFactory({
      ...params,
      type: 'pronoun'
    });

    const pronounField1 = await fieldFactory({
      ...params,
      type: 'pronoun'
    });

    const pronounField2 = await fieldFactory({
      ...params,
      type: 'pronoun'
    });

    const subscribedField = await fieldFactory({
      ...params,
      type: 'isSubscribed'
    });

    const hasAuthorityField = await fieldFactory({
      ...params,
      type: 'hasAuthority'
    });

    const birthDateField = await fieldFactory({
      ...params,
      type: 'birthDate'
    });

    const descriptionField = await fieldFactory({
      ...params,
      type: 'description'
    });

    const departmentField = await fieldFactory({
      ...params,
      type: 'department'
    });

    const positionField = await fieldFactory({
      ...params,
      type: 'position'
    });

    const companyDescriptionField = await fieldFactory({
      ...params,
      type: 'company_description'
    });

    const companyDoNotDisturbField = await fieldFactory({
      ...params,
      type: 'company_isSubscribed'
    });

    const customMapProperty = await fieldFactory({
      type: 'map',
      isVisible: true,
      contentType: 'customer'
    });

    const mapField = await fieldFactory({
      type: customMapProperty.type,
      contentTypeId: form._id,
      contentType: 'form',
      associatedFieldId: customMapProperty._id
    });

    const dateField = await fieldFactory({
      type: 'input',
      validation: 'date'
    });

    const numberField = await fieldFactory({
      type: 'input',
      validation: 'number'
    });

    const integration = await integrationFactory({ formId: form._id });

    const submissions = [
      {
        _id: emailField._id,
        type: 'email',
        value: 'email@yahoo.com',
        groupId: (group && group._id) || ''
      },
      {
        _id: firstNameField._id,
        type: 'firstName',
        value: 'firstName',
        groupId: (group && group._id) || ''
      },
      { _id: lastNameField._id, type: 'lastName', value: 'lastName' },
      { _id: phoneField._id, type: 'phone', value: '+88998833' },
      { _id: radioField._id, type: 'radio', value: 'radio2' },
      { _id: checkField._id, type: 'check', value: 'check1, check2' },
      {
        _id: inputField._id,
        type: 'input',
        value: 1,
        associatedFieldId: inputField.associatedFieldId
      },
      { _id: companyNameField._id, type: 'companyName', value: 'company' },
      {
        _id: companyNameField2._id,
        type: 'company_primaryName',
        value: 'com',
        groupId: (group && group._id) || ''
      },
      {
        _id: companyEmailField._id,
        type: 'company_primaryEmail',
        value: 'info@company.com'
      },
      {
        _id: companyPhoneField._id,
        type: 'company_primaryPhone',
        value: '+99112233'
      },
      {
        _id: avatarField._id,
        type: 'avatar',
        value: [{ url: 'https://i.pravatar.cc/150?u=a042581f4e29026704d' }]
      },
      {
        _id: companyAvatarField._id,
        type: 'company_avatar',
        value: [{ url: 'https://i.pravatar.cc/150?img=63' }]
      },
      { _id: industryField._id, type: 'industry', value: 'Banks' },
      { _id: sizeField._id, type: 'size', value: '10' },
      {
        _id: businessTypeField._id,
        type: 'businessType',
        value: 'Investor'
      },
      { _id: pronounField._id, type: 'pronoun', value: 'Male' },
      { _id: pronounField1._id, type: 'pronoun', value: 'Female' },
      { _id: pronounField2._id, type: 'pronoun', value: 'Not applicable' },
      { _id: subscribedField._id, type: 'isSubscribed', value: 'Yes' },
      { _id: hasAuthorityField._id, type: 'hasAuthority', value: 'No' },
      {
        _id: birthDateField._id,
        type: 'birthDate',
        value: 'Fri Mar 26 2021 18:01:50 GMT+0800'
      },
      {
        _id: descriptionField._id,
        type: 'description',
        value: 'description'
      },
      { _id: departmentField._id, type: 'department', value: 'department' },
      { _id: positionField._id, type: 'position', value: 'position' },
      {
        _id: companyDescriptionField._id,
        type: 'companyDescription',
        value: 'companyDescription'
      },
      {
        _id: companyDoNotDisturbField._id,
        type: 'company_isSubscribed',
        value: 'Yes'
      },
      {
        _id: mapField._id,
        type: 'map',
        value: { lat: 0, lng: 0, description: 'desc' }
      },
      {
        _id: dateField._id,
        type: 'input',
        value: new Date(),
        validation: 'date'
      },
      {
        _id: numberField._id,
        type: 'input',
        value: '100',
        validation: 'number'
      }
    ];

    const response = await widgetMutations.widgetsSaveLead(
      {},
      {
        integrationId: integration._id,
        formId: form._id,
        submissions,
        browserInfo: {
          currentPageUrl: '/page'
        }
      }
    );

    expect(response && response.status).toBe('ok');

    expect(await Conversations.find().countDocuments()).toBe(1);
    expect(await ConversationMessages.find().countDocuments()).toBe(1);
    expect(await Customers.find().countDocuments()).toBe(2);
    expect(await FormSubmissions.find().countDocuments()).toBe(
      submissions.length
    );

    const message = await ConversationMessages.findOne();
    const formData = message ? message.formWidgetData : {};

    if (!message || !formData) {
      throw new Error('Message not found');
    }

    expect(formData[0].value).toBe('email@yahoo.com');
    expect(formData[1].value).toBe('firstName');
    expect(formData[2].value).toBe('lastName');
    expect(formData[3].value).toBe('+88998833');
    expect(formData[4].value).toBe('radio2');
    expect(formData[5].value).toBe('check1, check2');
    mock.restore();
  });

  test('widgetsSendEmail', async () => {
    const customer = await customerFactory({});
    const form = await formFactory({});
    const integration = await integrationFactory({});

    const mock = sinon.stub(elk, 'fetchElk').callsFake(() => {
      return Promise.resolve({
        hits: {
          hits: [{ _id: integration._id, _source: { name: integration.name } }]
        }
      });
    });

    const emailParams = {
      toEmails: ['test-mail@gmail.com'],
      fromEmail: 'admin@erxes.io',
      title: 'Thank you for submitting.',
      content: 'We have received your request',
      customerId: customer._id,
      formId: form._id,
      attachments: [
        {
          name: 'name',
          url: 'url',
          type: 'type',
          size: 100
        }
      ]
    };

    const response = await widgetMutations.widgetsSendEmail(
      {},
      { ...emailParams }
    );

    expect(response).toBe(undefined);
    mock.restore();
  });

  test('widgetsBookingConnect', async () => {
    const integration = await integrationFactory({});

    const response = await widgetMutations.widgetsBookingConnect(
      {},
      { _id: integration._id }
    );

    expect(integration._id).toBe(response._id);
  });

  test('saveBooking: form not found', async () => {
    const product = await productFactory({});
    try {
      await widgetMutations.widgetsSaveBooking(
        {},
        {
          integrationId: '_id',
          formId: '_id',
          submissions: [{ _id: 'id', value: null }],
          browserInfo: {},
          productId: product._id
        }
      );
    } catch (e) {
      expect(e.message).toBe('Form not found');
    }
  });

  test('saveBooking: invalid', async () => {
    const form = await formFactory({});
    const product = await productFactory({});

    const requiredField = await fieldFactory({
      contentTypeId: form._id,
      isRequired: true
    });

    const integration = await integrationFactory({ formId: form._id });

    const response = await widgetMutations.widgetsSaveBooking(
      {},
      {
        integrationId: integration._id,
        formId: form._id,
        submissions: [{ _id: requiredField._id, value: null }],
        browserInfo: {
          currentPageUrl: '/page'
        },
        productId: product._id
      }
    );

    expect(response && response.status).toBe('error');
  });

  test('saveBooking: success', async () => {
    const product = await productFactory({});
    const form = await formFactory({});
    const group = await fieldGroupFactory({ contentType: 'form' });

    const params = {
      validation: 'text',
      isRequired: true,
      contentTypeId: form._id
    };

    const emailField = await fieldFactory({
      ...params,
      type: 'email',
      groupId: (group && group._id) || ''
    });

    const firstNameField = await fieldFactory({
      ...params,
      type: 'firstName',
      groupId: (group && group._id) || ''
    });

    const lastNameField = await fieldFactory({
      ...params,
      type: 'lastName'
    });

    const phoneField = await fieldFactory({
      type: 'phone',
      contentTypeId: form._id,
      isRequired: true
    });

    const descriptionField = await fieldFactory({
      ...params,
      type: 'description'
    });

    const integration = await integrationFactory({ formId: form._id });

    const submissions = [
      {
        _id: emailField._id,
        type: 'email',
        value: 'email@yahoo.com',
        groupId: (group && group._id) || ''
      },
      {
        _id: firstNameField._id,
        type: 'firstName',
        value: 'firstName',
        groupId: (group && group._id) || ''
      },
      { _id: lastNameField._id, type: 'lastName', value: 'lastName' },
      { _id: phoneField._id, type: 'phone', value: '+88998833' },
      {
        _id: descriptionField._id,
        type: 'description',
        value: 'description'
      }
    ];

    const response = await widgetMutations.widgetsSaveBooking(
      {},
      {
        integrationId: integration._id,
        formId: form._id,
        submissions,
        browserInfo: {
          currentPageUrl: '/page'
        },
        productId: product._id
      }
    );

    expect(response && response.status).toBe('ok');

    expect(await Conversations.find().countDocuments()).toBe(1);
    expect(await ConversationMessages.find().countDocuments()).toBe(1);
    expect(await Customers.find().countDocuments()).toBe(2);
    expect(await FormSubmissions.find().countDocuments()).toBe(
      submissions.length
    );
  });
});