From 79d51a49893db8b99a71de7374ec1a0b12e17a1e Mon Sep 17 00:00:00 2001 From: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:14:34 -0500 Subject: [PATCH] chore: Add Salesforce tests to `getContacts` method (#18225) --- .../salesforce/lib/CrmService.test.ts | 441 ++++++++++++++---- .../app-store/salesforce/lib/CrmService.ts | 32 +- 2 files changed, 366 insertions(+), 107 deletions(-) diff --git a/packages/app-store/salesforce/lib/CrmService.test.ts b/packages/app-store/salesforce/lib/CrmService.test.ts index e47ca4df23..c1a9afc1fa 100644 --- a/packages/app-store/salesforce/lib/CrmService.test.ts +++ b/packages/app-store/salesforce/lib/CrmService.test.ts @@ -1,12 +1,71 @@ import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown"; import { describe, expect, it, beforeEach, vi } from "vitest"; +import type { z } from "zod"; import type { CredentialPayload } from "@calcom/types/Credential"; +import type { appDataSchema } from "../zod"; import SalesforceCRMService from "./CrmService"; import { SalesforceRecordEnum } from "./enums"; +type AppOptions = z.infer; + +const contactQueryResponse = { + records: [ + { + Id: "001", + Email: "test@example.com", + OwnerId: "owner001", + attributes: { type: "Contact" }, + }, + ], +}; + +const contactUnderAccountQueryResponse = { + records: [ + { + Id: "001", + Email: "test@example.com", + OwnerId: "owner001", + AccountId: "acc001", + attributes: { type: "Contact" }, + }, + ], +}; + +const leadQueryResponse = { + records: [ + { + Id: "001", + Email: "test@example.com", + OwnerId: "owner001", + attributes: { type: "Lead" }, + }, + ], +}; + +const ownerQueryResponse = { + records: [ + { + Id: "owner001", + Email: "owner@example.com", + Name: "Test Owner", + }, + ], +}; + +const accountQueryResponse = { + records: [ + { + Id: "acc001", + OwnerId: "owner001", + Email: "test@example.com", + attributes: { type: "Account" }, + }, + ], +}; + describe("SalesforceCRMService", () => { let service: SalesforceCRMService; let mockConnection: { query: any }; @@ -41,6 +100,11 @@ describe("SalesforceCRMService", () => { service.conn = Promise.resolve(mockConnection); }); + const mockAppOptions = (appOptions: AppOptions) => { + const appOptionsSpy = vi.spyOn(service, "getAppOptions"); + appOptionsSpy.mockReturnValue(appOptions); + }; + describe("getContacts", () => { it("should return empty array when no records found", async () => { // Setup mock response @@ -130,26 +194,25 @@ describe("SalesforceCRMService", () => { ); }); - it("should include owner information when includeOwner is true", async () => { + it("should return contacts when includeOwner is true but no owner is found", async () => { + mockAppOptions({ + roundRobinSkipCheckRecordOn: SalesforceRecordEnum.CONTACT, + createEventOn: SalesforceRecordEnum.CONTACT, + }); + const contactQueryResponse = { records: [ { Id: "001", Email: "test@example.com", - OwnerId: "owner001", + // OwnerId: "owner001", attributes: { type: "Contact" }, }, ], }; const ownerQueryResponse = { - records: [ - { - Id: "owner001", - Email: "owner@example.com", - Name: "Test Owner", - }, - ], + records: [], }; // Setup query spy with specific responses @@ -167,8 +230,8 @@ describe("SalesforceCRMService", () => { { id: "001", email: "test@example.com", - ownerId: "owner001", - ownerEmail: "owner@example.com", + ownerId: undefined, + ownerEmail: undefined, recordType: "Contact", }, ]); @@ -177,89 +240,297 @@ describe("SalesforceCRMService", () => { 1, "SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')" ); - expect(querySpy).toHaveBeenNthCalledWith( - 2, - expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") - ); }); - it("should handle account record type with round robin skip", async () => { - service = new SalesforceCRMService( - {} as CredentialPayload, - { - createEventOn: SalesforceRecordEnum.ACCOUNT, - roundRobinSkipCheckRecordOn: SalesforceRecordEnum.ACCOUNT, - }, - true - ); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - service.conn = Promise.resolve(mockConnection); + describe("no additional params are passed", () => { + it("when createEventOn is contact", async () => { + mockAppOptions({ + createEventOn: SalesforceRecordEnum.CONTACT, + }); - const initialContactQueryResponse = { - records: [ + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy.mockResolvedValueOnce(contactQueryResponse); + + const result = await service.getContacts({ + emails: "test@example.com", + }); + + expect(result).toEqual([ { - Id: "001", - Email: "test@example.com", - AccountId: "acc001", - OwnerId: "owner001", - attributes: { type: "Contact" }, + id: "001", + email: "test@example.com", + recordType: "Contact", }, - ], - }; + ]); - const accountQueryResponse = { - records: [ - { - Id: "acc001", - OwnerId: "owner001", - Email: "test@example.com", - attributes: { type: "Account" }, - }, - ], - }; - - const ownerQueryResponse = { - records: [ - { - Id: "owner001", - Email: "owner@example.com", - Name: "Test Owner", - }, - ], - }; - - const querySpy = vi.spyOn(mockConnection, "query"); - querySpy - .mockResolvedValueOnce(initialContactQueryResponse) - .mockResolvedValueOnce(accountQueryResponse) - .mockResolvedValueOnce(ownerQueryResponse); - - const result = await service.getContacts({ - emails: "test@example.com", - forRoundRobinSkip: true, - includeOwner: true, + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')" + ); }); + it("when createEventOn is lead", async () => { + mockAppOptions({ + createEventOn: SalesforceRecordEnum.LEAD, + }); - expect(result).toEqual([ - { - id: "acc001", - email: "test@example.com", - ownerId: "owner001", - ownerEmail: "owner@example.com", - recordType: "Account", - }, - ]); + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy.mockResolvedValueOnce(leadQueryResponse); - expect(querySpy).toHaveBeenNthCalledWith( - 1, - "SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null" - ); - expect(querySpy).toHaveBeenNthCalledWith(2, "SELECT Id, OwnerId FROM Account WHERE Id = 'acc001'"); - expect(querySpy).toHaveBeenNthCalledWith( - 3, - expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") - ); + const result = await service.getContacts({ + emails: "test@example.com", + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + recordType: "Lead", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId FROM Lead WHERE Email IN ('test@example.com')" + ); + }); + it("when createEventOn is contact under an account", async () => { + mockAppOptions({ + createEventOn: SalesforceRecordEnum.ACCOUNT, + }); + + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy.mockResolvedValueOnce(contactUnderAccountQueryResponse); + + const result = await service.getContacts({ + emails: "test@example.com", + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + recordType: "Contact", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null" + ); + }); + }); + + describe("includeOwner param is passed", () => { + it("when createEventOn is contact", async () => { + mockAppOptions({ + createEventOn: SalesforceRecordEnum.CONTACT, + }); + + // Setup query spy with specific responses + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy + .mockResolvedValueOnce(contactQueryResponse) // Contact query + .mockResolvedValueOnce(ownerQueryResponse); // Owner query + + const result = await service.getContacts({ + emails: "test@example.com", + includeOwner: true, + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + ownerId: "owner001", + ownerEmail: "owner@example.com", + recordType: "Contact", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')" + ); + expect(querySpy).toHaveBeenNthCalledWith( + 2, + expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") + ); + }); + it("when createEventOn is lead", async () => { + mockAppOptions({ + createEventOn: SalesforceRecordEnum.LEAD, + }); + + // Setup query spy with specific responses + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy.mockResolvedValueOnce(leadQueryResponse).mockResolvedValueOnce(ownerQueryResponse); + + const result = await service.getContacts({ + emails: "test@example.com", + includeOwner: true, + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + ownerId: "owner001", + ownerEmail: "owner@example.com", + recordType: "Lead", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId FROM Lead WHERE Email IN ('test@example.com')" + ); + expect(querySpy).toHaveBeenNthCalledWith( + 2, + expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") + ); + }); + it("when createEventOn is contact under an account", async () => { + mockAppOptions({ + createEventOn: SalesforceRecordEnum.ACCOUNT, + }); + + // Setup query spy with specific responses + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy + .mockResolvedValueOnce(contactUnderAccountQueryResponse) + .mockResolvedValueOnce(ownerQueryResponse); + + const result = await service.getContacts({ + emails: "test@example.com", + includeOwner: true, + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + ownerId: "owner001", + ownerEmail: "owner@example.com", + recordType: "Account", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null" + ); + expect(querySpy).toHaveBeenNthCalledWith( + 2, + expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") + ); + }); + }); + + describe("forRoundRobinSkip param is passed", () => { + it("checking against contacts", async () => { + mockAppOptions({ + roundRobinSkipCheckRecordOn: SalesforceRecordEnum.CONTACT, + createEventOn: SalesforceRecordEnum.CONTACT, + }); + + // Setup query spy with specific responses + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy + .mockResolvedValueOnce(contactQueryResponse) // Contact query + .mockResolvedValueOnce(ownerQueryResponse); // Owner query + + const result = await service.getContacts({ + emails: "test@example.com", + forRoundRobinSkip: true, + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + ownerId: "owner001", + ownerEmail: "owner@example.com", + recordType: "Contact", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId FROM Contact WHERE Email IN ('test@example.com')" + ); + expect(querySpy).toHaveBeenNthCalledWith( + 2, + expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") + ); + }); + it("checking against leads", async () => { + mockAppOptions({ + roundRobinSkipCheckRecordOn: SalesforceRecordEnum.LEAD, + }); + + // Setup query spy with specific responses + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy.mockResolvedValueOnce(leadQueryResponse).mockResolvedValueOnce(ownerQueryResponse); + + const result = await service.getContacts({ + emails: "test@example.com", + forRoundRobinSkip: true, + }); + + expect(result).toEqual([ + { + id: "001", + email: "test@example.com", + ownerId: "owner001", + ownerEmail: "owner@example.com", + recordType: "Lead", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId FROM Lead WHERE Email IN ('test@example.com')" + ); + expect(querySpy).toHaveBeenNthCalledWith( + 2, + expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") + ); + }); + it("checking against contacts under an account", async () => { + mockAppOptions({ + roundRobinSkipCheckRecordOn: SalesforceRecordEnum.ACCOUNT, + }); + const querySpy = vi.spyOn(mockConnection, "query"); + querySpy + .mockResolvedValueOnce(contactUnderAccountQueryResponse) + .mockResolvedValueOnce(accountQueryResponse) + .mockResolvedValueOnce(ownerQueryResponse); + + const result = await service.getContacts({ + emails: "test@example.com", + forRoundRobinSkip: true, + }); + + expect(result).toEqual([ + { + id: "acc001", + email: "test@example.com", + ownerId: "owner001", + ownerEmail: "owner@example.com", + recordType: "Account", + }, + ]); + + expect(querySpy).toHaveBeenNthCalledWith( + 1, + "SELECT Id, Email, OwnerId, AccountId FROM Contact WHERE Email = 'test@example.com' AND AccountId != null" + ); + expect(querySpy).toHaveBeenNthCalledWith(2, "SELECT Id, OwnerId FROM Account WHERE Id = 'acc001'"); + expect(querySpy).toHaveBeenNthCalledWith( + 3, + expect.stringContaining("SELECT Id, Email, Name FROM User WHERE Id = 'owner001'") + ); + }); }); }); }); diff --git a/packages/app-store/salesforce/lib/CrmService.ts b/packages/app-store/salesforce/lib/CrmService.ts index adc0e9ba30..46253f6644 100644 --- a/packages/app-store/salesforce/lib/CrmService.ts +++ b/packages/app-store/salesforce/lib/CrmService.ts @@ -458,17 +458,15 @@ export default class SalesforceCRMService implements CRM { }); } - if (ownerIds.size === 0) { - log.warn("No owner IDs found for records"); - return []; - } - - const ownersQuery = await Promise.all( - Array.from(ownerIds).map(async (ownerId) => { - const result = await this.getSalesforceUserFromUserId(ownerId); - return result; - }) - ); + const ownersQuery = + ownerIds.size > 0 + ? await Promise.all( + Array.from(ownerIds).map(async (ownerId) => { + const result = await this.getSalesforceUserFromUserId(ownerId); + return result; + }) + ) + : []; // Filter out any undefined results and ensure records exist const validOwnersQuery = ownersQuery.filter((query): query is jsforce.QueryResult => { @@ -480,14 +478,9 @@ export default class SalesforceCRMService implements CRM { ); }); - if (validOwnersQuery.length === 0) { - log.warn("No valid owner records found"); - return []; - } - return records .map((record) => { - if (!record?.Id || !record?.OwnerId) { + if (!record?.Id) { log.warn("Invalid record data", { record }); return null; } @@ -496,11 +489,6 @@ export default class SalesforceCRMService implements CRM { ? validOwnersQuery[0]?.records[0]?.Email : validOwnersQuery.find((user) => user.records[0]?.Id === record.OwnerId)?.records[0]?.Email; - if (!ownerEmail) { - log.warn("Could not find owner email", { recordId: record.Id, ownerId: record.OwnerId }); - return null; - } - return { id: record.Id, email: record.Email,