* 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>
167 lines
4.8 KiB
TypeScript
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,
|
|
});
|
|
});
|
|
});
|
|
});
|