Files
calendar/packages/trpc/server/routers/viewer/workflows/delete.handler.test.ts
T
Benny JooandGitHub ff38d6c7db refactor: Remove circular deps between @calcom/lib and @calcom/features [1] (#24399)
* add eslint config

* migrate autoLock to features

* migrate teamService to features

* migrate userCreationService

* migrate insights services to features

* migrate ProfileRepository

* update imports

* migrate filter segmen tests

* migrate filter segment repository

* migrate getBusyTimes

* migrate getLocaleFromRequest

* refactor csvUtils

* make filename clearer

* migrate getLuckyUser integration test

* migrate autoLock test to features

* wip

* refactors

* migrate useBookerUrl

* migrate more

* wip

* Migrate eventTypeRepository

* membership repository

* update imports

* update imports

* migrate

* move organization repository

* update imports

* update imports

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix tests

* fix type checks

* fix

* fix

* migrate

* update imports

* fix tests

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix

* fix
2025-10-13 12:01:02 -03:00

288 lines
8.5 KiB
TypeScript

import { prisma } from "@calcom/prisma/__mocks__/prisma";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { createDefaultAIPhoneServiceProvider } from "@calcom/features/calAIPhone";
import { WorkflowRepository } from "@calcom/features/ee/workflows/repositories/WorkflowRepository";
import { WorkflowActions } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
import { deleteHandler } from "./delete.handler";
import {
isAuthorized,
removeSmsReminderFieldForEventTypes,
removeAIAgentCallPhoneNumberFieldForEventTypes,
} from "./util";
vi.mock("@calcom/prisma", () => ({
prisma,
}));
vi.mock("@calcom/features/calAIPhone", () => ({
createDefaultAIPhoneServiceProvider: vi.fn(),
}));
vi.mock("@calcom/features/ee/workflows/repositories/WorkflowRepository", () => ({
WorkflowRepository: {
deleteAllWorkflowReminders: vi.fn(),
},
}));
vi.mock("./util", () => ({
isAuthorized: vi.fn(),
removeSmsReminderFieldForEventTypes: vi.fn(),
removeAIAgentCallPhoneNumberFieldForEventTypes: vi.fn(),
}));
describe("deleteHandler", () => {
const mockCreateDefaultAIPhoneServiceProvider = vi.mocked(createDefaultAIPhoneServiceProvider);
const mockIsAuthorized = vi.mocked(isAuthorized);
const mockRemoveSmsReminderFieldForEventTypes = vi.mocked(removeSmsReminderFieldForEventTypes);
const mockRemoveAIAgentCallPhoneNumberFieldForEventTypes = vi.mocked(
removeAIAgentCallPhoneNumberFieldForEventTypes
);
const mockDeleteAllWorkflowReminders = vi.mocked(WorkflowRepository.deleteAllWorkflowReminders);
const mockUser = {
id: 123,
name: "Test User",
email: "test@example.com",
};
const mockCtx = {
user: mockUser,
};
beforeEach(() => {
vi.clearAllMocks();
});
describe("Authorization", () => {
it("should throw UNAUTHORIZED when user is not authorized or workflow not found", async () => {
const workflowId = 1;
prisma.workflow.findUnique.mockResolvedValue({
id: workflowId,
teamId: 456,
userId: 789,
activeOn: [],
activeOnTeams: [],
steps: [],
team: null,
});
mockIsAuthorized.mockResolvedValue(false);
await expect(deleteHandler({ ctx: mockCtx, input: { id: workflowId } })).rejects.toThrow(
new TRPCError({ code: "UNAUTHORIZED" })
);
prisma.workflow.findUnique.mockResolvedValue(null);
await expect(deleteHandler({ ctx: mockCtx, input: { id: workflowId } })).rejects.toThrow(
new TRPCError({ code: "UNAUTHORIZED" })
);
});
});
describe("Booking field cleanup", () => {
it("should remove both SMS reminder and AI agent phone number fields", async () => {
const workflowId = 1;
const eventTypeIds = [10, 20];
const mockWorkflow = {
id: workflowId,
teamId: null,
userId: mockUser.id,
activeOn: eventTypeIds.map((id) => ({ eventTypeId: id })),
activeOnTeams: [],
steps: [],
team: null,
};
prisma.workflow.findUnique.mockResolvedValue(mockWorkflow);
mockIsAuthorized.mockResolvedValue(true);
prisma.workflowReminder.findMany.mockResolvedValue([]);
prisma.workflow.deleteMany.mockResolvedValue({ count: 1 });
await deleteHandler({ ctx: mockCtx, input: { id: workflowId } });
expect(mockRemoveSmsReminderFieldForEventTypes).toHaveBeenCalledWith({
activeOnToRemove: eventTypeIds,
workflowId: workflowId,
isOrg: false,
});
expect(mockRemoveAIAgentCallPhoneNumberFieldForEventTypes).toHaveBeenCalledWith({
activeOnToRemove: eventTypeIds,
workflowId: workflowId,
isOrg: false,
});
});
it("should handle organization workflows correctly", async () => {
const workflowId = 1;
const teamIds = [100, 200];
const mockWorkflow = {
id: workflowId,
teamId: 456,
userId: mockUser.id,
activeOn: [],
activeOnTeams: teamIds.map((id) => ({ teamId: id })),
steps: [],
team: {
isOrganization: true,
},
};
prisma.workflow.findUnique.mockResolvedValue(mockWorkflow);
mockIsAuthorized.mockResolvedValue(true);
prisma.workflowReminder.findMany.mockResolvedValue([]);
prisma.workflow.deleteMany.mockResolvedValue({ count: 1 });
await deleteHandler({ ctx: mockCtx, input: { id: workflowId } });
expect(mockRemoveSmsReminderFieldForEventTypes).toHaveBeenCalledWith({
activeOnToRemove: teamIds,
workflowId: workflowId,
isOrg: true,
});
expect(mockRemoveAIAgentCallPhoneNumberFieldForEventTypes).toHaveBeenCalledWith({
activeOnToRemove: teamIds,
workflowId: workflowId,
isOrg: true,
});
});
});
describe("CAL AI phone call cleanup", () => {
let mockAIPhoneService: {
cancelPhoneNumberSubscription: ReturnType<typeof vi.fn>;
deletePhoneNumber: ReturnType<typeof vi.fn>;
deleteAgent: ReturnType<typeof vi.fn>;
};
beforeEach(() => {
mockAIPhoneService = {
cancelPhoneNumberSubscription: vi.fn(),
deletePhoneNumber: vi.fn(),
deleteAgent: vi.fn(),
};
mockCreateDefaultAIPhoneServiceProvider.mockReturnValue(mockAIPhoneService);
});
it("should cleanup AI phone resources based on subscription status", async () => {
const workflowId = 1;
const mockWorkflow = {
id: workflowId,
teamId: null,
userId: mockUser.id,
activeOn: [],
activeOnTeams: [],
steps: [
{
action: WorkflowActions.CAL_AI_PHONE_CALL,
agent: {
id: "agent-1",
outboundPhoneNumbers: [
{
id: "phone-active",
phoneNumber: "+1111111111",
subscriptionStatus: "ACTIVE",
},
{
id: "phone-null",
phoneNumber: "+2222222222",
subscriptionStatus: null,
},
],
},
},
],
team: null,
};
prisma.workflow.findUnique.mockResolvedValue(mockWorkflow);
mockIsAuthorized.mockResolvedValue(true);
prisma.workflowReminder.findMany.mockResolvedValue([]);
prisma.workflow.deleteMany.mockResolvedValue({ count: 1 });
await deleteHandler({ ctx: mockCtx, input: { id: workflowId } });
expect(mockAIPhoneService.cancelPhoneNumberSubscription).toHaveBeenCalledWith({
phoneNumberId: "phone-active",
userId: mockUser.id,
});
expect(mockAIPhoneService.deletePhoneNumber).toHaveBeenCalledWith({
phoneNumber: "+2222222222",
userId: mockUser.id,
deleteFromDB: true,
});
expect(mockAIPhoneService.deleteAgent).toHaveBeenCalledWith({
id: "agent-1",
userId: mockUser.id,
teamId: undefined,
});
expect(mockRemoveAIAgentCallPhoneNumberFieldForEventTypes).toHaveBeenCalled();
});
});
describe("Workflow deletion flow", () => {
it("should complete full deletion flow successfully", async () => {
const workflowId = 1;
const mockReminders = [
{
id: 1,
workflowStepId: 1,
scheduled: true,
referenceId: "ref-1",
},
];
const mockWorkflow = {
id: workflowId,
teamId: null,
userId: mockUser.id,
activeOn: [{ eventTypeId: 10 }],
activeOnTeams: [],
steps: [],
team: null,
};
prisma.workflow.findUnique.mockResolvedValue(mockWorkflow);
mockIsAuthorized.mockResolvedValue(true);
prisma.workflowReminder.findMany.mockResolvedValue(mockReminders);
prisma.workflow.deleteMany.mockResolvedValue({ count: 1 });
const result = await deleteHandler({ ctx: mockCtx, input: { id: workflowId } });
expect(prisma.workflowReminder.findMany).toHaveBeenCalledWith({
where: {
workflowStep: {
workflowId: workflowId,
},
scheduled: true,
NOT: {
referenceId: null,
},
},
});
expect(mockDeleteAllWorkflowReminders).toHaveBeenCalledWith(mockReminders);
expect(mockRemoveSmsReminderFieldForEventTypes).toHaveBeenCalled();
expect(mockRemoveAIAgentCallPhoneNumberFieldForEventTypes).toHaveBeenCalled();
expect(prisma.workflow.deleteMany).toHaveBeenCalledWith({
where: {
id: workflowId,
},
});
expect(result).toEqual({ id: workflowId });
});
});
});