* Add DB schema * Init zod schema * Init RoutingTrace and PendingRoutingTrace repository interfaces * Create PrismaPendingRoutingTraceRepository * Init RoutingTraceService * Create RoutingTraceService container * User routing trace service in routing * Create RoutingTraceRepository and PrismaRoutingTraceRepoistory * Add findByFormResponseId and findByQueuedFormResponseId to PendingRoutingTraceRepository * Update DI containers * RoutingTraceService create process booking method * Use pending routing trace rather than URL params * Fix schema * Fix writing assignment reason for routed booking * Remove from service * Refactor RoutingTraceService to not rely on async local storage * Pass RoutingTraceService through routing call * Add attribute-logic-evaluated to routing trace step * Add routing trace to trpc endpoint * Add CRM routing trace step * Fix extracting routing trace to assignment reason * Add back CRM params to prevent refetching * test: Add unit tests for RoutingTraceService and repositories Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * test: Add missing mock for RoutingTraceService in getRoutedUrl tests Also fix pre-existing lint issues in the test file: - Add explicit types to mockForm and mockSerializableForm variables - Add explicit type to url parameter in mockContext - Replace 'as any' with 'as unknown as InstanceType<typeof UserRepository>' Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * Link pending form submission to routing trace * Clean up * Add lookup field assignment reasons * Rename to PendingRoutingTrace * Add migration file * fix: Update RoutingTraceService tests to use assignmentReasonRepository mock - Add getStepsCount() method back to RoutingTraceService - Add queuedFormResponseId support to processForBooking method - Update tests to use mockAssignmentReasonRepository instead of prisma mock - Remove test for missing routingTraceRepository (all deps now required) Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: Remove PII (organizer email) from log payload in RoutingTraceService Addresses Cubic AI review feedback with confidence 9/10. Logging PII violates sensitive information logging rules. Co-Authored-By: unknown <> * Write field values at the time of routing * Write attributes used to route * feat: add CRM routing trace service Add CrmRoutingTraceService as a reusable wrapper around RoutingTraceService for CRM-specific tracing. Also adds CrmRoutingTraceServiceInterface type to support passing trace services through the CRM call chain. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add Salesforce routing trace infrastructure Add SalesforceRoutingTrace static class with 19 trace methods covering: - Account resolution (SOQL path): searching by website, contact domain - Lookup field queries - Owner lookups (contact, lead, account) - Validation and skip scenarios - GraphQL three-tier resolution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: wire CRM trace service through call chain Pass crmTrace parameter through the CRM call chain: - routerGetCrmContactOwnerEmail creates CrmRoutingTraceService - Passes to app booking form handlers and CRM round robin skip - CrmManager.getContacts accepts and forwards crmTrace - All handlers accept optional crmTrace parameter Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add trace instrumentation to Salesforce CRM service Instrument Salesforce CRM methods with routing trace steps: - getContacts: trace owner lookups for contact/lead/account - getAccountIdBasedOnEmailDomainOfContacts: trace website and domain searches - findUserEmailFromLookupField: trace lookup field queries - GetAccountRecordsForRRSkip (GraphQL): trace three-tier resolution Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: rename SalesforceRoutingTrace to SalesforceRoutingTraceService Consistent naming with CrmRoutingTraceService. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test: add unit tests for CrmRoutingTraceService and SalesforceRoutingTraceService Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * refactor: use AsyncLocalStorage for CRM routing trace Replace explicit crmTrace parameter passing with AsyncLocalStorage: - Add AsyncLocalStorage to RoutingTraceService with getCurrent() and runAsync() - Update SalesforceRoutingTraceService to auto-resolve trace from AsyncLocalStorage - Remove CrmRoutingTraceService wrapper (no longer needed) - Remove crmTrace parameter from all CRM method signatures - Wrap CRM operations in routingTraceService.runAsync() context This is cleaner than threading crmTrace through 5+ function layers. The trace context is available within the withReporting wrapper. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Use async local storage for `CrmRoutingTraceService` * fix: correct template literal syntax in RoutingTraceService Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: use narrowed eventTypeId variable in nested async function Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: update SalesforceRoutingTraceService tests to use AsyncLocalStorage API Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * refactor: rename crmTrace to crmRoutingTraceService for consistency Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Volnei Munhoz <volnei@cal.com>
96 lines
3.1 KiB
TypeScript
96 lines
3.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { CrmRoutingTraceService } from "./CrmRoutingTraceService";
|
|
import type { RoutingTraceService } from "./RoutingTraceService";
|
|
|
|
describe("CrmRoutingTraceService", () => {
|
|
let mockParentTraceService: Pick<RoutingTraceService, "addStep">;
|
|
|
|
function createMockRoutingTraceService(): Pick<RoutingTraceService, "addStep"> {
|
|
return {
|
|
addStep: vi.fn(),
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockParentTraceService = createMockRoutingTraceService();
|
|
});
|
|
|
|
describe("create", () => {
|
|
it("should return undefined when parent is undefined", () => {
|
|
const result = CrmRoutingTraceService.create(undefined);
|
|
|
|
expect(result).toBeUndefined();
|
|
});
|
|
|
|
it("should return a CrmRoutingTraceService instance when parent is provided", () => {
|
|
const result = CrmRoutingTraceService.create(mockParentTraceService as RoutingTraceService);
|
|
|
|
expect(result).toBeInstanceOf(CrmRoutingTraceService);
|
|
});
|
|
});
|
|
|
|
describe("addStep", () => {
|
|
it("should delegate to parent trace service with correct parameters", () => {
|
|
const crmTrace = new CrmRoutingTraceService(mockParentTraceService as RoutingTraceService);
|
|
|
|
crmTrace.addStep("salesforce", "account_lookup", { accountId: "123" });
|
|
|
|
expect(mockParentTraceService.addStep).toHaveBeenCalledWith({
|
|
domain: "salesforce",
|
|
step: "account_lookup",
|
|
data: { accountId: "123" },
|
|
});
|
|
});
|
|
|
|
it("should use empty object as default data when not provided", () => {
|
|
const crmTrace = new CrmRoutingTraceService(mockParentTraceService as RoutingTraceService);
|
|
|
|
crmTrace.addStep("hubspot", "contact_search");
|
|
|
|
expect(mockParentTraceService.addStep).toHaveBeenCalledWith({
|
|
domain: "hubspot",
|
|
step: "contact_search",
|
|
data: {},
|
|
});
|
|
});
|
|
|
|
it("should pass complex data objects correctly", () => {
|
|
const crmTrace = new CrmRoutingTraceService(mockParentTraceService as RoutingTraceService);
|
|
const complexData = {
|
|
email: "test@example.com",
|
|
recordType: "Contact",
|
|
recordId: "003ABC123",
|
|
nested: { key: "value" },
|
|
};
|
|
|
|
crmTrace.addStep("salesforce", "contact_owner_lookup", complexData);
|
|
|
|
expect(mockParentTraceService.addStep).toHaveBeenCalledWith({
|
|
domain: "salesforce",
|
|
step: "contact_owner_lookup",
|
|
data: complexData,
|
|
});
|
|
});
|
|
|
|
it("should allow multiple steps to be added", () => {
|
|
const crmTrace = new CrmRoutingTraceService(mockParentTraceService as RoutingTraceService);
|
|
|
|
crmTrace.addStep("salesforce", "step1", { data: 1 });
|
|
crmTrace.addStep("salesforce", "step2", { data: 2 });
|
|
crmTrace.addStep("salesforce", "step3", { data: 3 });
|
|
|
|
expect(mockParentTraceService.addStep).toHaveBeenCalledTimes(3);
|
|
});
|
|
});
|
|
|
|
describe("constructor", () => {
|
|
it("should create instance with parent trace service", () => {
|
|
const crmTrace = new CrmRoutingTraceService(mockParentTraceService as RoutingTraceService);
|
|
|
|
expect(crmTrace).toBeInstanceOf(CrmRoutingTraceService);
|
|
});
|
|
});
|
|
});
|