chore: Add Salesforce tests to getContacts method (#18225)
This commit is contained in:
@@ -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<typeof appDataSchema>;
|
||||
|
||||
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'")
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<ContactRecord> => {
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user