test: Salesforce CRM - Add user facing tests and more logs (#19531)
* Added user facing tests * fix tests * Fix ts error
This commit is contained in:
@@ -391,7 +391,7 @@ export default class SalesforceCRMService implements CRM {
|
||||
if (recordToSearch === SalesforceRecordEnum.ACCOUNT) {
|
||||
// For an account let's assume that the first email is the one we should be querying against
|
||||
const attendeeEmail = emailArray[0];
|
||||
log.info("Searching account for email", safeStringify({ attendeeEmail }));
|
||||
log.info("[recordToSearch=ACCOUNT] Searching contact for email", safeStringify({ attendeeEmail }));
|
||||
soql = `SELECT Id, Email, OwnerId, AccountId, Account.Owner.Email, Account.Website FROM ${SalesforceRecordEnum.CONTACT} WHERE Email = '${attendeeEmail}' AND AccountId != null`;
|
||||
} else {
|
||||
// Handle Contact/Lead record types
|
||||
@@ -454,25 +454,26 @@ export default class SalesforceCRMService implements CRM {
|
||||
!(await this.shouldSkipAttendeeIfFreeEmailDomain(emailArray[0]));
|
||||
|
||||
const includeAccountRecordType = forRoundRobinSkip && recordToSearch === SalesforceRecordEnum.ACCOUNT;
|
||||
|
||||
return records.map((record) => {
|
||||
// Handle if Account is nested
|
||||
const ownerEmail =
|
||||
recordToSearch === SalesforceRecordEnum.ACCOUNT &&
|
||||
record?.attributes?.type !== SalesforceRecordEnum.ACCOUNT
|
||||
? record?.Account?.Owner?.Email
|
||||
: record?.Owner?.Email;
|
||||
|
||||
return {
|
||||
id: includeAccountRecordType ? record?.AccountId : record?.Id || "",
|
||||
email: record?.Email || "",
|
||||
recordType: includeAccountRecordType ? SalesforceRecordEnum.ACCOUNT : record?.attributes?.type,
|
||||
...(includeOwnerData && {
|
||||
ownerId: record?.OwnerId,
|
||||
// Handle if Account is nested
|
||||
ownerEmail:
|
||||
recordToSearch === SalesforceRecordEnum.ACCOUNT &&
|
||||
record?.attributes?.type !== SalesforceRecordEnum.ACCOUNT
|
||||
? record?.Account?.Owner?.Email
|
||||
: record?.Owner?.Email,
|
||||
ownerEmail: ownerEmail,
|
||||
}),
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
log.error("Error in getContacts", safeStringify({ error }));
|
||||
log.error("Error in getContacts", safeStringify(error));
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -482,7 +483,7 @@ export default class SalesforceCRMService implements CRM {
|
||||
organizerEmail?: string,
|
||||
calEventResponses?: CalEventResponses | null
|
||||
) {
|
||||
const log = logger.getSubLogger({ prefix: [`[getContacts]`] });
|
||||
const log = logger.getSubLogger({ prefix: [`[createContacts]`] });
|
||||
const conn = await this.conn;
|
||||
const appOptions = this.getAppOptions();
|
||||
const createEventOn = appOptions.createEventOn ?? SalesforceRecordEnum.CONTACT;
|
||||
@@ -901,13 +902,15 @@ export default class SalesforceCRMService implements CRM {
|
||||
private async getAccountBasedOnEmailDomainOfContacts(email: string) {
|
||||
const conn = await this.conn;
|
||||
const emailDomain = email.split("@")[1];
|
||||
|
||||
const log = logger.getSubLogger({ prefix: [`[getAccountBasedOnEmailDomainOfContacts]:${email}`] });
|
||||
log.info("Querying first account matching email domain", safeStringify({ emailDomain }));
|
||||
// First check if an account has the same website as the email domain of the attendee
|
||||
const accountQuery = await conn.query(
|
||||
`SELECT Id, OwnerId, Owner.Email FROM Account WHERE Website LIKE '%${emailDomain}%' LIMIT 1`
|
||||
);
|
||||
|
||||
if (accountQuery.records.length > 0) {
|
||||
log.info("Found account matching email domain", safeStringify({ emailDomain }));
|
||||
return {
|
||||
...(accountQuery.records[0] as { Id?: string; OwnerId?: string; Owner?: { Email?: string } }),
|
||||
Email: undefined,
|
||||
@@ -928,6 +931,7 @@ export default class SalesforceCRMService implements CRM {
|
||||
const dominantAccountId = this.getDominantAccountId(contacts);
|
||||
|
||||
const contactUnderAccount = contacts.find((contact) => contact.AccountId === dominantAccountId);
|
||||
log.info("Using dominant account's owner", safeStringify({ dominantAccountId }));
|
||||
|
||||
return {
|
||||
Id: dominantAccountId,
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
/**
|
||||
* Purpose of these tests is to ensure that we test from CrmService and salesforce interactions perspective purely
|
||||
* It allows us to document the behaviour more from User perspective and also verify it in parallel
|
||||
*
|
||||
* It will also give us confidence to refactor as it doesn't care about internal functions.
|
||||
*
|
||||
* CrmService.test.ts could still focus on testing detailed edge cases as needed.
|
||||
*/
|
||||
import "../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import jsforce from "@jsforce/jsforce-node";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
import SalesforceCRMService from "../CrmService";
|
||||
import { SalesforceRecordEnum } from "../enums";
|
||||
import { createSalesforceMock } from "./salesforceMock";
|
||||
|
||||
vi.mock("@calcom/lib/constants", () => {
|
||||
return {
|
||||
WEBAPP_URL: "https://app.cal.com",
|
||||
APP_CREDENTIAL_SHARING_ENABLED: false,
|
||||
IS_PRODUCTION: false,
|
||||
};
|
||||
});
|
||||
// Mock dependencies
|
||||
vi.mock("@jsforce/jsforce-node", () => {
|
||||
return {
|
||||
default: {
|
||||
Connection: vi.fn().mockImplementation(() => ({})),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/lib/freeEmailDomainCheck/checkIfFreeEmailDomain", () => ({
|
||||
checkIfFreeEmailDomain: vi.fn().mockResolvedValue(false),
|
||||
}));
|
||||
|
||||
vi.mock("../getSalesforceAppKeys", () => ({
|
||||
getSalesforceAppKeys: vi.fn().mockResolvedValue({
|
||||
consumer_key: "test_consumer_key",
|
||||
consumer_secret: "test_consumer_secret",
|
||||
}),
|
||||
}));
|
||||
|
||||
// Helper to create mock credential
|
||||
const createMockCredential = () => {
|
||||
return {
|
||||
id: 1,
|
||||
key: {
|
||||
access_token: "test_access_token",
|
||||
refresh_token: "test_refresh_token",
|
||||
instance_url: "https://test.salesforce.com",
|
||||
},
|
||||
type: "salesforce_other_calendar",
|
||||
user: {
|
||||
email: "test@example.com",
|
||||
},
|
||||
userId: 1,
|
||||
teamId: 1,
|
||||
appId: "test_app_id",
|
||||
invalid: false,
|
||||
};
|
||||
};
|
||||
|
||||
const salesforceSettingScenario = {
|
||||
createOnLeadAndSearchOnAccount: () => {
|
||||
return {
|
||||
appOptions: {
|
||||
createEventOn: SalesforceRecordEnum.LEAD,
|
||||
createEventOnLeadCheckForContact: true,
|
||||
roundRobinSkipCheckRecordOn: SalesforceRecordEnum.ACCOUNT,
|
||||
},
|
||||
credential: createMockCredential(),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
describe("SalesforceCRMService", () => {
|
||||
let salesforceMock: ReturnType<typeof createSalesforceMock>;
|
||||
|
||||
beforeEach(() => {
|
||||
salesforceMock = createSalesforceMock();
|
||||
fetchMock.mockReturnValueOnce(
|
||||
Promise.resolve(
|
||||
new Response(
|
||||
JSON.stringify({
|
||||
id: "abc",
|
||||
issued_at: "123",
|
||||
instance_url: "https://test.salesforce.com",
|
||||
signature: "123",
|
||||
access_token: "123",
|
||||
scope: "123",
|
||||
token_type: "123",
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
// Override jsforce mock with our custom mock
|
||||
// @ts-expect-error - Not full implementation of jsforce.Connection
|
||||
vi.mocked(jsforce.Connection).mockImplementation(() => ({
|
||||
...salesforceMock.mockConnection,
|
||||
version: "1.0",
|
||||
loginUrl: "https://test.salesforce.com",
|
||||
instanceUrl: "https://test.salesforce.com",
|
||||
accessToken: "123",
|
||||
}));
|
||||
});
|
||||
|
||||
describe("createOnLeadAndSearchOnAccount", () => {
|
||||
const { appOptions, credential } = salesforceSettingScenario.createOnLeadAndSearchOnAccount();
|
||||
describe("getContacts: forRoundRobinSkip=true", () => {
|
||||
const forRoundRobinSkip = true;
|
||||
it("(Lookup-1) should return contact's account owner's email as ownerEmail if the contact is found directly by email", async () => {
|
||||
const crmService = new SalesforceCRMService(credential, appOptions);
|
||||
const contactAccountOwnerEmail = "contact-account-owner@acme.com";
|
||||
const contactOwnerEmail = "contact-owner@acme.com";
|
||||
const lookingForEmail = "test@example.com";
|
||||
|
||||
const account = salesforceMock.addAccount({
|
||||
Id: "test-account-id",
|
||||
Owner: {
|
||||
Email: contactAccountOwnerEmail,
|
||||
},
|
||||
Website: "https://anything",
|
||||
});
|
||||
|
||||
salesforceMock.addContact({
|
||||
Email: lookingForEmail,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
AccountId: account.Id,
|
||||
Owner: {
|
||||
Email: contactOwnerEmail,
|
||||
},
|
||||
});
|
||||
|
||||
const contacts = await crmService.getContacts({
|
||||
emails: lookingForEmail,
|
||||
forRoundRobinSkip,
|
||||
});
|
||||
|
||||
expect(contacts).toHaveLength(1);
|
||||
expect(contacts[0].ownerEmail).toBe(contactAccountOwnerEmail);
|
||||
expect(contacts[0].email).toBe(lookingForEmail);
|
||||
expect(contacts[0].recordType).toBe(SalesforceRecordEnum.ACCOUNT);
|
||||
expect(contacts[0].id).toBe(account.Id);
|
||||
});
|
||||
|
||||
it("(Lookup-2) should fallback to account(and use the account owner email as ownerEmail) matched by emailDomain if the contact is not found(i.e Lookup-1 fails)", async () => {
|
||||
const crmService = new SalesforceCRMService(credential, appOptions);
|
||||
const contactAccountOwnerEmail = "contact-account-owner@example.com";
|
||||
const emailDomain = "example.com";
|
||||
const lookingForEmail = `test@${emailDomain}`;
|
||||
const account = salesforceMock.addAccount({
|
||||
Id: "test-account-id",
|
||||
Owner: {
|
||||
Email: contactAccountOwnerEmail,
|
||||
},
|
||||
Website: `https://${emailDomain}`,
|
||||
});
|
||||
|
||||
const contacts = await crmService.getContacts({
|
||||
emails: lookingForEmail,
|
||||
forRoundRobinSkip,
|
||||
});
|
||||
|
||||
expect(contacts).toHaveLength(1);
|
||||
expect(contacts[0].ownerEmail).toBe(contactAccountOwnerEmail);
|
||||
// Account doesn't have email
|
||||
expect(contacts[0].email).toBe("");
|
||||
expect(contacts[0].recordType).toBe(SalesforceRecordEnum.ACCOUNT);
|
||||
// FIXME: Seems like a bug as this should have been accountId but it is undefined
|
||||
// expect(contacts[0].id).toBe(account.Id);
|
||||
});
|
||||
|
||||
it("(Lookup-3) should fallback to account having most number of contacts matched by emailDomain when Lookup-1 and Lookup-2 fails", async () => {
|
||||
const crmService = new SalesforceCRMService(credential, appOptions);
|
||||
const account1OwnerEmail = "contact-account-owner1@example.com";
|
||||
const account2OwnerEmail = "contact-account-owner2@example.com";
|
||||
const emailDomain = "example.com";
|
||||
const lookingForEmail = `test@${emailDomain}`;
|
||||
const account1 = salesforceMock.addAccount({
|
||||
Id: "test-account-id1",
|
||||
Owner: {
|
||||
Email: account1OwnerEmail,
|
||||
},
|
||||
Website: `https://anything`,
|
||||
});
|
||||
|
||||
const dominatingAccount = salesforceMock.addAccount({
|
||||
Id: "test-account-id2",
|
||||
Owner: {
|
||||
Email: account2OwnerEmail,
|
||||
},
|
||||
Website: `https://anything`,
|
||||
});
|
||||
|
||||
salesforceMock.addContact({
|
||||
Email: `some-other-contact_acc2_1@${emailDomain}`,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
AccountId: dominatingAccount.Id,
|
||||
});
|
||||
|
||||
salesforceMock.addContact({
|
||||
Email: `some-other-contact_acc2_2@${emailDomain}`,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
AccountId: dominatingAccount.Id,
|
||||
});
|
||||
|
||||
salesforceMock.addContact({
|
||||
Email: `some-other-contact_acc1_1@${emailDomain}`,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
AccountId: account1.Id,
|
||||
});
|
||||
|
||||
const contacts = await crmService.getContacts({
|
||||
emails: lookingForEmail,
|
||||
forRoundRobinSkip,
|
||||
});
|
||||
|
||||
expect(contacts).toHaveLength(1);
|
||||
expect(contacts[0].ownerEmail).toBe(account2OwnerEmail);
|
||||
// Account doesn't have email
|
||||
expect(contacts[0].email).toBe("");
|
||||
expect(contacts[0].recordType).toBe(SalesforceRecordEnum.ACCOUNT);
|
||||
// FIXME: Seems like a bug as this should have been accountId but it is undefined
|
||||
// expect(contacts[0].id).toBe(dominatingAccount.Id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getContacts: forRoundRobinSkip=false i.e. how it is called when creating event", () => {
|
||||
const forRoundRobinSkip = false;
|
||||
it("should return lead object if the lead is found directly by email and contact is not found by that email", async () => {
|
||||
const crmService = new SalesforceCRMService(credential, appOptions);
|
||||
const contactAccountOwnerEmail = "contact-account-owner@acme.com";
|
||||
const contactOwnerEmail = "contact-owner@acme.com";
|
||||
const leadOwnerEmail = "lead-owner@acme.com";
|
||||
const lookingForEmail = "test1@example.com";
|
||||
const contactEmail = "test2@example.com";
|
||||
|
||||
const account = salesforceMock.addAccount({
|
||||
Id: "test-account-id",
|
||||
Owner: {
|
||||
Email: contactAccountOwnerEmail,
|
||||
},
|
||||
Website: "https://anything",
|
||||
});
|
||||
|
||||
const contact = salesforceMock.addContact({
|
||||
// Contact doesn't have the matching email
|
||||
Email: contactEmail,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
AccountId: account.Id,
|
||||
Owner: {
|
||||
Email: contactOwnerEmail,
|
||||
},
|
||||
});
|
||||
|
||||
const lead = salesforceMock.addLead({
|
||||
// Lead has the matching email
|
||||
Email: lookingForEmail,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
Owner: {
|
||||
Email: leadOwnerEmail,
|
||||
},
|
||||
});
|
||||
|
||||
const contactsOrLeadsOrAccounts = await crmService.getContacts({
|
||||
emails: lookingForEmail,
|
||||
forRoundRobinSkip,
|
||||
});
|
||||
|
||||
expect(contactsOrLeadsOrAccounts).toHaveLength(1);
|
||||
// Because we intentionally don't include owner data in this case
|
||||
expect(contactsOrLeadsOrAccounts[0].ownerEmail).toBe(undefined);
|
||||
expect(contactsOrLeadsOrAccounts[0].email).toBe(lookingForEmail);
|
||||
expect(contactsOrLeadsOrAccounts[0].recordType).toBe(SalesforceRecordEnum.LEAD);
|
||||
expect(contactsOrLeadsOrAccounts[0].id).toBe(lead.Id);
|
||||
});
|
||||
|
||||
it("should return contact object(if there is one by that email) even if lead is found by that email", async () => {
|
||||
const crmService = new SalesforceCRMService(credential, appOptions);
|
||||
const contactAccountOwnerEmail = "contact-account-owner@acme.com";
|
||||
const contactOwnerEmail = "contact-owner@acme.com";
|
||||
const leadOwnerEmail = "lead-owner@acme.com";
|
||||
const lookingForEmail = "test1@example.com";
|
||||
const contactEmail = "test2@example.com";
|
||||
|
||||
const account = salesforceMock.addAccount({
|
||||
Id: "test-account-id",
|
||||
Owner: {
|
||||
Email: contactAccountOwnerEmail,
|
||||
},
|
||||
Website: "https://anything",
|
||||
});
|
||||
|
||||
const contact = salesforceMock.addContact({
|
||||
// Contact doesn't have the matching email
|
||||
Email: lookingForEmail,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
AccountId: account.Id,
|
||||
Owner: {
|
||||
Email: contactOwnerEmail,
|
||||
},
|
||||
});
|
||||
|
||||
const lead = salesforceMock.addLead({
|
||||
// Lead has the matching email
|
||||
Email: lookingForEmail,
|
||||
FirstName: "Test",
|
||||
LastName: "User",
|
||||
Owner: {
|
||||
Email: leadOwnerEmail,
|
||||
},
|
||||
});
|
||||
|
||||
const contactsOrLeadsOrAccounts = await crmService.getContacts({
|
||||
emails: lookingForEmail,
|
||||
forRoundRobinSkip,
|
||||
});
|
||||
|
||||
expect(contactsOrLeadsOrAccounts).toHaveLength(1);
|
||||
// Because we intentionally don't include owner data in this case
|
||||
expect(contactsOrLeadsOrAccounts[0].ownerEmail).toBe(undefined);
|
||||
expect(contactsOrLeadsOrAccounts[0].id).toBe(contact.Id);
|
||||
expect(contactsOrLeadsOrAccounts[0].email).toBe(lookingForEmail);
|
||||
expect(contactsOrLeadsOrAccounts[0].recordType).toBe(SalesforceRecordEnum.CONTACT);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,294 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
|
||||
type Contact = {
|
||||
Id?: string;
|
||||
FirstName?: string;
|
||||
LastName?: string;
|
||||
Email?: string;
|
||||
AccountId?: string;
|
||||
Account?: Account;
|
||||
Owner?: {
|
||||
Email?: string;
|
||||
};
|
||||
OwnerId?: string;
|
||||
};
|
||||
type Lead = {
|
||||
Id?: string;
|
||||
FirstName?: string;
|
||||
LastName?: string;
|
||||
Email?: string;
|
||||
AccountId?: string;
|
||||
Account?: Account;
|
||||
Owner?: {
|
||||
Email?: string;
|
||||
};
|
||||
OwnerId?: string;
|
||||
};
|
||||
type Account = { Id?: string; OwnerId?: string; Owner?: { Email?: string }; Website?: string };
|
||||
type User = { Id?: string; IsActive?: boolean; Email?: string };
|
||||
|
||||
// Salesforce mock factory
|
||||
export const createSalesforceMock = () => {
|
||||
// Store for mock records
|
||||
const records = {
|
||||
contacts: [] as Contact[],
|
||||
leads: [] as Lead[],
|
||||
accounts: [] as Account[],
|
||||
users: [] as User[],
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
events: [] as any[],
|
||||
};
|
||||
|
||||
// Query parser and responder
|
||||
const handleQuery = (query: string) => {
|
||||
// Simple SOQL parser
|
||||
const fromMatch = query.match(/FROM\s+(\w+)/i);
|
||||
const whereMatch = query.match(/WHERE\s+(.+?)(?:\s+LIMIT|\s+ORDER|\s*$)/i);
|
||||
const limitMatch = query.match(/LIMIT\s+(\d+)/i);
|
||||
|
||||
if (!fromMatch) return { records: [] };
|
||||
|
||||
const objectType = fromMatch[1];
|
||||
const whereClause = whereMatch ? whereMatch[1] : null;
|
||||
const limit = limitMatch ? parseInt(limitMatch[1]) : Infinity;
|
||||
|
||||
let result = [];
|
||||
|
||||
switch (objectType) {
|
||||
case "Contact":
|
||||
result = records.contacts;
|
||||
break;
|
||||
case "Lead":
|
||||
result = records.leads;
|
||||
break;
|
||||
case "Account":
|
||||
result = records.accounts;
|
||||
break;
|
||||
case "User":
|
||||
result = records.users;
|
||||
break;
|
||||
case "Event":
|
||||
result = records.events;
|
||||
break;
|
||||
default:
|
||||
return { records: [] };
|
||||
}
|
||||
|
||||
// Apply where clause filtering (basic implementation)
|
||||
if (whereClause) {
|
||||
if (whereClause.includes("Email =")) {
|
||||
const emailMatch = whereClause.match(/Email\s*=\s*'([^']+)'/i);
|
||||
if (emailMatch) {
|
||||
const email = emailMatch[1];
|
||||
result = result.filter((r) => r.Email === email);
|
||||
}
|
||||
}
|
||||
|
||||
if (whereClause.includes("Email IN")) {
|
||||
const emailsMatch = whereClause.match(/Email IN \('([^']+)(?:','([^']+))*'\)/i);
|
||||
if (emailsMatch) {
|
||||
const emails = whereClause
|
||||
.match(/Email IN \((.+)\)/i)?.[1]
|
||||
?.split("','")
|
||||
?.map((e) => e.replace(/'/g, ""));
|
||||
result = result.filter((r) => emails?.includes(r.Email));
|
||||
}
|
||||
}
|
||||
|
||||
if (whereClause.includes("Email LIKE")) {
|
||||
const emailLikeMatch = whereClause.match(/Email LIKE '%@([^']+)'/i);
|
||||
if (emailLikeMatch) {
|
||||
const domain = emailLikeMatch[1];
|
||||
result = result.filter((r) => r.Email && r.Email.endsWith(`@${domain}`));
|
||||
}
|
||||
}
|
||||
|
||||
if (whereClause.includes("Id =")) {
|
||||
const idMatch = whereClause.match(/Id\s*=\s*'([^']+)'/i);
|
||||
if (idMatch) {
|
||||
const id = idMatch[1];
|
||||
result = result.filter((r) => r.Id === id);
|
||||
}
|
||||
}
|
||||
|
||||
if (whereClause.includes("AccountId =")) {
|
||||
const accountIdMatch = whereClause.match(/AccountId\s*=\s*'([^']+)'/i);
|
||||
if (accountIdMatch) {
|
||||
const accountId = accountIdMatch[1];
|
||||
result = result.filter((r) => r.AccountId === accountId);
|
||||
}
|
||||
}
|
||||
|
||||
if (whereClause.includes("AccountId != null")) {
|
||||
result = result.filter((r) => r.AccountId);
|
||||
}
|
||||
|
||||
if (whereClause.includes("Website LIKE")) {
|
||||
const websiteLikeMatch = whereClause.match(/Website LIKE '%([^']+)%'/i);
|
||||
if (websiteLikeMatch) {
|
||||
const websitePart = websiteLikeMatch[1];
|
||||
result = result.filter((r) => r.Website && r.Website.includes(websitePart));
|
||||
}
|
||||
}
|
||||
|
||||
if (whereClause.includes("IsActive = true")) {
|
||||
result = result.filter((r) => r.IsActive === true);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply limit
|
||||
result = result.slice(0, limit);
|
||||
|
||||
// Handle special cases with nested property selection
|
||||
if (query.includes("Account.Owner.Email")) {
|
||||
result = result.map((r) => {
|
||||
if (r.AccountId) {
|
||||
const account = records.accounts.find((a) => a.Id === r.AccountId);
|
||||
if (account && account.OwnerId) {
|
||||
const owner = records.users.find((u) => u.Id === account.OwnerId);
|
||||
r.Account = {
|
||||
Owner: {
|
||||
Email: owner ? owner.Email : null,
|
||||
},
|
||||
OwnerId: account.OwnerId,
|
||||
};
|
||||
}
|
||||
}
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
if (query.includes("Owner.Email")) {
|
||||
result = result.map((r) => {
|
||||
if (r.OwnerId) {
|
||||
const owner = records.users.find((u) => u.Id === r.OwnerId);
|
||||
r.Owner = {
|
||||
Email: owner ? owner.Email : null,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
});
|
||||
}
|
||||
|
||||
return { records: result };
|
||||
};
|
||||
|
||||
const getAccountById = (id: string) => {
|
||||
return records.accounts.find((a) => a.Id === id);
|
||||
};
|
||||
|
||||
// Add record helpers
|
||||
const addContact = (contact: Contact) => {
|
||||
const newContact = {
|
||||
Id: contact.Id || `003${Math.random().toString().slice(2, 10)}`,
|
||||
attributes: { type: "Contact" },
|
||||
...contact,
|
||||
};
|
||||
|
||||
if (newContact.AccountId) {
|
||||
const account = getAccountById(newContact.AccountId);
|
||||
if (account) {
|
||||
newContact.Account = {
|
||||
...account,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Account with id ${newContact.AccountId} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("[SalesforceMock]Adding contact", safeStringify(newContact));
|
||||
records.contacts.push(newContact);
|
||||
return newContact;
|
||||
};
|
||||
|
||||
const addLead = (lead: Lead) => {
|
||||
const newLead = {
|
||||
Id: lead.Id || `00Q${Math.random().toString().slice(2, 10)}`,
|
||||
attributes: { type: "Lead" },
|
||||
...lead,
|
||||
};
|
||||
if (newLead.AccountId) {
|
||||
const account = getAccountById(newLead.AccountId);
|
||||
if (account) {
|
||||
newLead.Account = {
|
||||
...account,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Account with id ${newLead.AccountId} not found`);
|
||||
}
|
||||
}
|
||||
logger.debug("[SalesforceMock] Adding lead", safeStringify(newLead));
|
||||
records.leads.push(newLead);
|
||||
return newLead;
|
||||
};
|
||||
|
||||
const addAccount = (account: Account) => {
|
||||
const newAccount = {
|
||||
Id: account.Id || `001${Math.random().toString().slice(2, 10)}`,
|
||||
...account,
|
||||
attributes: { type: "Account" },
|
||||
};
|
||||
records.accounts.push(newAccount);
|
||||
logger.debug("[SalesforceMock] Adding account", safeStringify(newAccount));
|
||||
return newAccount;
|
||||
};
|
||||
|
||||
const addUser = (user: User) => {
|
||||
const newUser = {
|
||||
Id: user.Id || `005${Math.random().toString().slice(2, 10)}`,
|
||||
IsActive: user.IsActive !== undefined ? user.IsActive : true,
|
||||
attributes: { type: "User" },
|
||||
...user,
|
||||
};
|
||||
logger.debug("[SalesforceMock] Adding user", safeStringify(newUser));
|
||||
records.users.push(newUser);
|
||||
return newUser;
|
||||
};
|
||||
|
||||
// Mock connection interface
|
||||
const mockConnection = {
|
||||
query: vi.fn().mockImplementation(handleQuery),
|
||||
sobject: vi.fn().mockReturnValue({
|
||||
create: vi.fn().mockImplementation(() => ({ success: true, id: `id_${Math.random()}` })),
|
||||
update: vi.fn().mockImplementation(() => ({ success: true })),
|
||||
delete: vi.fn().mockImplementation(() => ({ success: true })),
|
||||
}),
|
||||
describe: vi.fn().mockImplementation(() => {
|
||||
return {
|
||||
fields: [
|
||||
{ name: "Id", type: "id" },
|
||||
{ name: "Email", type: "email" },
|
||||
{ name: "FirstName", type: "string" },
|
||||
{ name: "LastName", type: "string" },
|
||||
{ name: "Company", type: "string" },
|
||||
{ name: "Website", type: "url" },
|
||||
{ name: "OwnerId", type: "reference" },
|
||||
{ name: "NoShow__c", type: "boolean", length: 0 },
|
||||
{ name: "LastMeetingDate__c", type: "date" },
|
||||
],
|
||||
};
|
||||
}),
|
||||
clear: () => {
|
||||
records.contacts = [];
|
||||
records.leads = [];
|
||||
records.accounts = [];
|
||||
records.users = [];
|
||||
records.events = [];
|
||||
mockConnection.query.mockClear();
|
||||
mockConnection.sobject.mockClear();
|
||||
mockConnection.describe.mockClear();
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
mockConnection,
|
||||
addContact,
|
||||
addLead,
|
||||
addAccount,
|
||||
addUser,
|
||||
getRecords: () => records,
|
||||
};
|
||||
};
|
||||
@@ -123,7 +123,12 @@ async function getOwnerEmailFromCrm(
|
||||
if (!crmContactOwner?.email) return returnNullValue;
|
||||
log.info(
|
||||
"[getOwnerEmailFromCrm] crmContactOwner",
|
||||
safeStringify({ contactOwnerEmail: crmContactOwner.email, email, eventTypeId: eventData.id })
|
||||
safeStringify({
|
||||
contactOwnerEmail: crmContactOwner.email,
|
||||
recordType: crmContactOwner.recordType,
|
||||
email,
|
||||
eventTypeId: eventData.id,
|
||||
})
|
||||
);
|
||||
// Determine if the contactOwner is a part of the event type
|
||||
const contactOwnerQuery = await findUserByEmailWhoIsAHostOfEventType({
|
||||
|
||||
Reference in New Issue
Block a user