* 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 * fix: Add CHECK constraint to ensure at least one response ID is set in routing trace tables Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: Address PR review comments for routing trace feature - Add checkedFallback to routing trace step data (Comment 11) - Extract hardcoded domain/step strings to constants (Comment 13) - Use @default(now()) for createdAt in PendingRoutingTrace and RoutingTrace (Comment 15) - Add DEFAULT CURRENT_TIMESTAMP to migration for createdAt fields Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: Use UTC-safe timezone expression for createdAt defaults in routing trace tables Addresses Cubic AI review feedback (confidence 9/10) to prevent timezone issues by using dbgenerated("timezone('UTC', now())") instead of bare now() for the createdAt fields in PendingRoutingTrace and RoutingTrace models. Co-Authored-By: unknown <> * fix: Align migration SQL with Prisma's expected timezone syntax Use 'UTC'::text cast in timezone() function to match Prisma's generated SQL. Co-Authored-By: unknown <> * fix: Revert migration SQL to match Prisma schema timezone syntax Remove ::text cast from timezone() function to match what Prisma generates from the schema definition. Co-Authored-By: unknown <> * fix: Use explicit ::text cast in timezone() for PostgreSQL compatibility PostgreSQL normalizes timezone('UTC', now()) to timezone('UTC'::text, now()) internally. Update both schema and migration to use the explicit cast to ensure they match and pass the migration check. Co-Authored-By: unknown <> * fix: Use CURRENT_TIMESTAMP for createdAt defaults (standard Cal.com pattern) Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com> * fix: Use @default(now()) for createdAt in routing trace tables to match migration 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: Pedro Castro <pedro@cal.com>
664 lines
22 KiB
TypeScript
664 lines
22 KiB
TypeScript
import type { AssignmentReasonRepository } from "@calcom/features/assignment-reason/repositories/AssignmentReasonRepository";
|
|
import { AssignmentReasonEnum } from "@calcom/prisma/enums";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import type {
|
|
IPendingRoutingTraceRepository,
|
|
PendingRoutingTraceRecord,
|
|
} from "../repositories/PendingRoutingTraceRepository.interface";
|
|
import type { IRoutingTraceRepository } from "../repositories/RoutingTraceRepository.interface";
|
|
import { RoutingTraceService } from "./RoutingTraceService";
|
|
|
|
describe("RoutingTraceService", () => {
|
|
let service: RoutingTraceService;
|
|
let mockPendingRoutingTraceRepository: IPendingRoutingTraceRepository;
|
|
let mockRoutingTraceRepository: IRoutingTraceRepository;
|
|
let mockAssignmentReasonRepository: Pick<AssignmentReasonRepository, "createAssignmentReason">;
|
|
|
|
function createMockPendingRoutingTraceRepository(): IPendingRoutingTraceRepository {
|
|
return {
|
|
create: vi.fn(),
|
|
findByFormResponseId: vi.fn(),
|
|
findByQueuedFormResponseId: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function createMockRoutingTraceRepository(): IRoutingTraceRepository {
|
|
return {
|
|
create: vi.fn(),
|
|
};
|
|
}
|
|
|
|
function createMockAssignmentReasonRepository(): Pick<AssignmentReasonRepository, "createAssignmentReason"> {
|
|
return {
|
|
createAssignmentReason: vi.fn(),
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockPendingRoutingTraceRepository = createMockPendingRoutingTraceRepository();
|
|
mockRoutingTraceRepository = createMockRoutingTraceRepository();
|
|
mockAssignmentReasonRepository = createMockAssignmentReasonRepository();
|
|
service = new RoutingTraceService({
|
|
pendingRoutingTraceRepository: mockPendingRoutingTraceRepository,
|
|
routingTraceRepository: mockRoutingTraceRepository,
|
|
assignmentReasonRepository: mockAssignmentReasonRepository as AssignmentReasonRepository,
|
|
});
|
|
});
|
|
|
|
describe("addStep", () => {
|
|
it("should add a step to the routing trace", () => {
|
|
service.addStep({
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
data: { routeIsFallback: false },
|
|
});
|
|
|
|
expect(service.getStepsCount()).toBe(1);
|
|
});
|
|
|
|
it("should add multiple steps to the routing trace", () => {
|
|
service.addStep({
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
data: { routeIsFallback: false },
|
|
});
|
|
|
|
service.addStep({
|
|
domain: "salesforce",
|
|
step: "salesforce_assignment",
|
|
data: { email: "owner@example.com" },
|
|
});
|
|
|
|
expect(service.getStepsCount()).toBe(2);
|
|
});
|
|
|
|
it("should add step with empty data by default", () => {
|
|
service.addStep({
|
|
domain: "test_domain",
|
|
step: "test_step",
|
|
});
|
|
|
|
expect(service.getStepsCount()).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe("getStepsCount", () => {
|
|
it("should return 0 when no steps have been added", () => {
|
|
expect(service.getStepsCount()).toBe(0);
|
|
});
|
|
|
|
it("should return correct count after adding steps", () => {
|
|
service.addStep({ domain: "test", step: "step1" });
|
|
service.addStep({ domain: "test", step: "step2" });
|
|
service.addStep({ domain: "test", step: "step3" });
|
|
|
|
expect(service.getStepsCount()).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe("savePendingRoutingTrace", () => {
|
|
it("should save pending trace with formResponseId", async () => {
|
|
service.addStep({
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
data: { routeIsFallback: false },
|
|
});
|
|
|
|
await service.savePendingRoutingTrace({ formResponseId: 123 });
|
|
|
|
expect(mockPendingRoutingTraceRepository.create).toHaveBeenCalledWith({
|
|
trace: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
data: { routeIsFallback: false },
|
|
}),
|
|
]),
|
|
formResponseId: 123,
|
|
});
|
|
});
|
|
|
|
it("should save pending trace with queuedFormResponseId", async () => {
|
|
service.addStep({
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
data: { routeIsFallback: true },
|
|
});
|
|
|
|
await service.savePendingRoutingTrace({ queuedFormResponseId: "queued-123" });
|
|
|
|
expect(mockPendingRoutingTraceRepository.create).toHaveBeenCalledWith({
|
|
trace: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
data: { routeIsFallback: true },
|
|
}),
|
|
]),
|
|
queuedFormResponseId: "queued-123",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("processForBooking", () => {
|
|
const baseProcessArgs = {
|
|
bookingId: 1,
|
|
bookingUid: "booking-uid-123",
|
|
organizerEmail: "organizer@example.com",
|
|
isRerouting: false,
|
|
};
|
|
|
|
it("should return null if no pending trace is found", async () => {
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(null);
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should return null if no pending trace is found for queuedFormResponseId", async () => {
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByQueuedFormResponseId).mockResolvedValue(null);
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
queuedFormResponseId: "queued-123",
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
describe("routing form assignment", () => {
|
|
it("should extract ROUTING_FORM_ROUTING assignment reason", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: {
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
},
|
|
});
|
|
expect(mockAssignmentReasonRepository.createAssignmentReason).toHaveBeenCalledWith({
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
});
|
|
});
|
|
|
|
it("should extract ROUTING_FORM_ROUTING_FALLBACK assignment reason when routeIsFallback is true", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: true },
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING_FALLBACK,
|
|
reasonString: "",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: {
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING_FALLBACK,
|
|
reasonString: "",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should extract REROUTED assignment reason when isRerouting is true", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.REROUTED,
|
|
reasonString: "Rerouted by admin@example.com",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
isRerouting: true,
|
|
reroutedByEmail: "admin@example.com",
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: {
|
|
reasonEnum: AssignmentReasonEnum.REROUTED,
|
|
reasonString: "Rerouted by admin@example.com",
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("salesforce assignment", () => {
|
|
it("should extract SALESFORCE_ASSIGNMENT when organizer matches CRM contact owner", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "salesforce",
|
|
step: "salesforce_assignment",
|
|
timestamp: Date.now(),
|
|
data: {
|
|
email: "organizer@example.com",
|
|
recordType: "Contact",
|
|
recordId: "003ABC123",
|
|
},
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT,
|
|
reasonString: "Salesforce contact owner: organizer@example.com (Contact ID: 003ABC123)",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: {
|
|
reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT,
|
|
reasonString: "Salesforce contact owner: organizer@example.com (Contact ID: 003ABC123)",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should use account lookup field reason string when rrSkipToAccountLookupField is true", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "salesforce",
|
|
step: "salesforce_assignment",
|
|
timestamp: Date.now(),
|
|
data: {
|
|
email: "organizer@example.com",
|
|
recordType: "Account",
|
|
recordId: "001ABC123",
|
|
rrSkipToAccountLookupField: true,
|
|
rrSKipToAccountLookupFieldName: "Account_Owner__c",
|
|
},
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT,
|
|
reasonString:
|
|
"Salesforce account lookup field: Account_Owner__c - organizer@example.com (Account ID: 001ABC123)",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: {
|
|
reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT,
|
|
reasonString:
|
|
"Salesforce account lookup field: Account_Owner__c - organizer@example.com (Account ID: 001ABC123)",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should fall back to routing form assignment when organizer does not match CRM contact owner", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "salesforce",
|
|
step: "salesforce_assignment",
|
|
timestamp: Date.now(),
|
|
data: {
|
|
email: "different-owner@example.com",
|
|
recordType: "Contact",
|
|
recordId: "003ABC123",
|
|
},
|
|
},
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: {
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should handle case-insensitive email matching for CRM assignment", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "salesforce",
|
|
step: "salesforce_assignment",
|
|
timestamp: Date.now(),
|
|
data: {
|
|
email: "ORGANIZER@EXAMPLE.COM",
|
|
recordType: "Contact",
|
|
recordId: "003ABC123",
|
|
},
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.SALESFORCE_ASSIGNMENT,
|
|
reasonString: "Salesforce contact owner: ORGANIZER@EXAMPLE.COM (Contact ID: 003ABC123)",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result?.assignmentReason?.reasonEnum).toBe(AssignmentReasonEnum.SALESFORCE_ASSIGNMENT);
|
|
});
|
|
});
|
|
|
|
it("should create permanent routing trace with assignment reason", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 42,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 42,
|
|
});
|
|
|
|
await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(mockRoutingTraceRepository.create).toHaveBeenCalledWith({
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: undefined,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 42,
|
|
});
|
|
});
|
|
|
|
it("should handle pending trace without assignment reason data", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "other_domain",
|
|
step: "some_step",
|
|
timestamp: Date.now(),
|
|
data: {},
|
|
},
|
|
],
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: null,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
assignmentReason: undefined,
|
|
});
|
|
expect(mockAssignmentReasonRepository.createAssignmentReason).not.toHaveBeenCalled();
|
|
expect(mockRoutingTraceRepository.create).toHaveBeenCalledWith({
|
|
trace: pendingTrace.trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: undefined,
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: undefined,
|
|
});
|
|
});
|
|
|
|
it("should process pending trace found by queuedFormResponseId", async () => {
|
|
const pendingTrace: PendingRoutingTraceRecord = {
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace: [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
],
|
|
formResponseId: null,
|
|
queuedFormResponseId: "queued-123",
|
|
};
|
|
|
|
vi.mocked(mockPendingRoutingTraceRepository.findByQueuedFormResponseId).mockResolvedValue(pendingTrace);
|
|
vi.mocked(mockAssignmentReasonRepository.createAssignmentReason).mockResolvedValue({
|
|
id: 1,
|
|
bookingId: 1,
|
|
reasonEnum: AssignmentReasonEnum.ROUTING_FORM_ROUTING,
|
|
reasonString: "",
|
|
});
|
|
vi.mocked(mockRoutingTraceRepository.create).mockResolvedValue({
|
|
id: "trace-1",
|
|
createdAt: new Date(),
|
|
trace: pendingTrace.trace,
|
|
formResponseId: null,
|
|
queuedFormResponseId: "queued-123",
|
|
bookingUid: "booking-uid-123",
|
|
assignmentReasonId: 1,
|
|
});
|
|
|
|
const result = await service.processForBooking({
|
|
...baseProcessArgs,
|
|
queuedFormResponseId: "queued-123",
|
|
});
|
|
|
|
expect(mockPendingRoutingTraceRepository.findByQueuedFormResponseId).toHaveBeenCalledWith("queued-123");
|
|
expect(result?.assignmentReason?.reasonEnum).toBe(AssignmentReasonEnum.ROUTING_FORM_ROUTING);
|
|
});
|
|
});
|
|
});
|