mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* 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: [email protected] <[email protected]> * 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: [email protected] <[email protected]> * 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: [email protected] <[email protected]> * 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: [email protected] <[email protected]> * 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: [email protected] <[email protected]> * 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: [email protected] <[email protected]> * fix: Use @default(now()) for createdAt in routing trace tables to match migration Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Pedro Castro <[email protected]>
209 lines
5.9 KiB
TypeScript
209 lines
5.9 KiB
TypeScript
import type { PrismaClient } from "@calcom/prisma";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { PrismaPendingRoutingTraceRepository } from "./PrismaPendingRoutingTraceRepository";
|
|
|
|
describe("PrismaPendingRoutingTraceRepository", () => {
|
|
let repository: PrismaPendingRoutingTraceRepository;
|
|
|
|
const mockPrisma = {
|
|
pendingRoutingTrace: {
|
|
create: vi.fn(),
|
|
findUnique: vi.fn(),
|
|
},
|
|
} as unknown as PrismaClient;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
repository = new PrismaPendingRoutingTraceRepository(mockPrisma);
|
|
});
|
|
|
|
describe("create", () => {
|
|
it("should create a pending routing trace with formResponseId", async () => {
|
|
const trace = [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
];
|
|
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.create).mockResolvedValue({
|
|
id: "pending-1",
|
|
createdAt: new Date(),
|
|
trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
});
|
|
|
|
await repository.create({
|
|
trace,
|
|
formResponseId: 123,
|
|
});
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.create).toHaveBeenCalledWith({
|
|
data: {
|
|
createdAt: expect.any(Date),
|
|
trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: undefined,
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should create a pending routing trace with queuedFormResponseId", async () => {
|
|
const trace = [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: true },
|
|
},
|
|
];
|
|
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.create).mockResolvedValue({
|
|
id: "pending-2",
|
|
createdAt: new Date(),
|
|
trace,
|
|
formResponseId: null,
|
|
queuedFormResponseId: "queued-123",
|
|
});
|
|
|
|
await repository.create({
|
|
trace,
|
|
queuedFormResponseId: "queued-123",
|
|
});
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.create).toHaveBeenCalledWith({
|
|
data: {
|
|
createdAt: expect.any(Date),
|
|
trace,
|
|
formResponseId: undefined,
|
|
queuedFormResponseId: "queued-123",
|
|
},
|
|
});
|
|
});
|
|
|
|
it("should create a pending routing trace with empty trace array", async () => {
|
|
const trace: never[] = [];
|
|
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.create).mockResolvedValue({
|
|
id: "pending-3",
|
|
createdAt: new Date(),
|
|
trace,
|
|
formResponseId: 456,
|
|
queuedFormResponseId: null,
|
|
});
|
|
|
|
await repository.create({
|
|
trace,
|
|
formResponseId: 456,
|
|
});
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.create).toHaveBeenCalledWith({
|
|
data: {
|
|
createdAt: expect.any(Date),
|
|
trace,
|
|
formResponseId: 456,
|
|
queuedFormResponseId: undefined,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("findByFormResponseId", () => {
|
|
it("should find a pending routing trace by formResponseId", async () => {
|
|
const trace = [
|
|
{
|
|
domain: "routing_form",
|
|
step: "attribute-logic-evaluated",
|
|
timestamp: Date.now(),
|
|
data: { routeIsFallback: false },
|
|
},
|
|
];
|
|
|
|
const mockResult = {
|
|
id: "pending-1",
|
|
createdAt: new Date("2025-01-01T10:00:00Z"),
|
|
trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
};
|
|
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.findUnique).mockResolvedValue(mockResult);
|
|
|
|
const result = await repository.findByFormResponseId(123);
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.findUnique).toHaveBeenCalledWith({
|
|
where: { formResponseId: 123 },
|
|
});
|
|
expect(result).toEqual({
|
|
id: "pending-1",
|
|
createdAt: new Date("2025-01-01T10:00:00Z"),
|
|
trace,
|
|
formResponseId: 123,
|
|
queuedFormResponseId: null,
|
|
});
|
|
});
|
|
|
|
it("should return null when no pending routing trace is found", async () => {
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.findUnique).mockResolvedValue(null);
|
|
|
|
const result = await repository.findByFormResponseId(999);
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.findUnique).toHaveBeenCalledWith({
|
|
where: { formResponseId: 999 },
|
|
});
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("findByQueuedFormResponseId", () => {
|
|
it("should find a pending routing trace by queuedFormResponseId", async () => {
|
|
const trace = [
|
|
{
|
|
domain: "salesforce",
|
|
step: "salesforce_assignment",
|
|
timestamp: Date.now(),
|
|
data: { email: "[email protected]" },
|
|
},
|
|
];
|
|
|
|
const mockResult = {
|
|
id: "pending-2",
|
|
createdAt: new Date("2025-01-02T10:00:00Z"),
|
|
trace,
|
|
formResponseId: null,
|
|
queuedFormResponseId: "queued-456",
|
|
};
|
|
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.findUnique).mockResolvedValue(mockResult);
|
|
|
|
const result = await repository.findByQueuedFormResponseId("queued-456");
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.findUnique).toHaveBeenCalledWith({
|
|
where: { queuedFormResponseId: "queued-456" },
|
|
});
|
|
expect(result).toEqual({
|
|
id: "pending-2",
|
|
createdAt: new Date("2025-01-02T10:00:00Z"),
|
|
trace,
|
|
formResponseId: null,
|
|
queuedFormResponseId: "queued-456",
|
|
});
|
|
});
|
|
|
|
it("should return null when no pending routing trace is found by queuedFormResponseId", async () => {
|
|
vi.mocked(mockPrisma.pendingRoutingTrace.findUnique).mockResolvedValue(null);
|
|
|
|
const result = await repository.findByQueuedFormResponseId("non-existent");
|
|
|
|
expect(mockPrisma.pendingRoutingTrace.findUnique).toHaveBeenCalledWith({
|
|
where: { queuedFormResponseId: "non-existent" },
|
|
});
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
});
|