Files
calendar/packages/features/ee/workflows/lib/test/twilioWebhook.test.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

167 lines
4.8 KiB
TypeScript

import { describe, beforeEach, vi, test, expect } from "vitest";
import { CreditUsageType } from "@calcom/prisma/enums";
vi.mock("@calcom/lib/constants", async () => {
const actual = await vi.importActual<typeof import("@calcom/lib/constants")>("@calcom/lib/constants");
return {
...actual,
IS_SMS_CREDITS_ENABLED: true,
};
});
vi.mock("../reminders/providers/twilioProvider", () => ({
validateWebhookRequest: vi.fn().mockResolvedValue(true),
getCountryCodeForNumber: vi.fn().mockResolvedValue("US"),
getMessageInfo: vi.fn().mockResolvedValue({ price: null, numSegments: null }),
}));
const mockChargeCredits = vi.fn().mockResolvedValue({ teamId: 1 });
vi.mock("@calcom/features/ee/billing/credit-service", () => ({
CreditService: vi.fn().mockImplementation(function () {
return {
chargeCredits: mockChargeCredits,
calculateCreditsFromPrice: vi.fn().mockReturnValue(1),
};
}),
}));
const mockFindFirst = vi.fn();
vi.mock("@calcom/prisma", () => ({
default: {
membership: {
findFirst: mockFindFirst,
},
team: {
findUnique: vi.fn().mockResolvedValue(null),
},
},
}));
vi.mock("@calcom/lib/getOrgIdFromMemberOrTeamId", () => ({
default: vi.fn().mockResolvedValue(null),
getPublishedOrgIdFromMemberOrTeamId: vi.fn().mockResolvedValue(null),
}));
describe("Twilio Webhook Handler", () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe("SMS to US and CA numbers", () => {
test("should create expense log with 0 credits with a given teamId", async () => {
const webhookHandler = (await import("../../../../../../apps/web/pages/api/twilio/webhook")).default;
const mockRequest = {
method: "POST",
headers: {
"x-twilio-signature": "valid-signature",
},
query: {
teamId: "1",
},
body: {
MessageStatus: "delivered",
To: "+1234567890", // US number
SmsSid: "SM123",
},
};
const mockResponse = {
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
send: vi.fn(),
};
await webhookHandler(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.send).toHaveBeenCalledWith("SMS to US and CA are free for teams. Credits set to 0");
expect(mockChargeCredits).toHaveBeenCalledWith({
teamId: 1,
bookingUid: undefined,
smsSid: "SM123",
credits: 0,
creditFor: CreditUsageType.SMS,
});
});
test("should create expense log with 0 credits if userId is part of a team", async () => {
mockFindFirst.mockResolvedValue({ teamId: 1 });
const webhookHandler = (await import("../../../../../../apps/web/pages/api/twilio/webhook")).default;
const mockRequest = {
method: "POST",
headers: {
"x-twilio-signature": "valid-signature",
},
query: {
userId: "123",
},
body: {
MessageStatus: "delivered",
To: "+1234567890", // US number
SmsSid: "SM123",
},
};
const mockResponse = {
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
send: vi.fn(),
};
await webhookHandler(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockResponse.send).toHaveBeenCalledWith("SMS to US and CA are free for teams. Credits set to 0");
expect(mockChargeCredits).toHaveBeenCalledWith({
teamId: 1,
bookingUid: undefined,
smsSid: "SM123",
credits: 0,
creditFor: CreditUsageType.SMS,
});
});
test("should create expense log with null credits if userId is not part of a team", async () => {
mockFindFirst.mockResolvedValue(null);
const webhookHandler = (await import("../../../../../../apps/web/pages/api/twilio/webhook")).default;
const mockRequest = {
method: "POST",
headers: {
"x-twilio-signature": "valid-signature",
},
query: {
userId: "123",
},
body: {
MessageStatus: "delivered",
To: "+1234567890", // US number
SmsSid: "SM123",
},
};
const mockResponse = {
status: vi.fn().mockReturnThis(),
json: vi.fn().mockReturnThis(),
send: vi.fn(),
};
await webhookHandler(mockRequest, mockResponse);
expect(mockResponse.status).toHaveBeenCalledWith(200);
expect(mockChargeCredits).toHaveBeenCalledWith({
userId: 123,
bookingUid: undefined,
smsSid: "SM123",
credits: null,
creditFor: CreditUsageType.SMS,
smsSegments: undefined,
teamId: undefined,
});
});
});
});