mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main: - Rebrand Cal.com to Cal.diy across the entire codebase - Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions - Switch license from AGPL-3.0 to MIT - Remove docs/ directory (migrated to Nextra at cal.diy) - Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc. - Clean up .env.example for self-hosted Cal.diy - Update Docker image references to calcom/cal.diy - Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork - Add PR welcome bot for Cal.diy contributors - Fix API v2 breaking changes oasdiff ignore entries - Replace Blacksmith CI runners with default GitHub Actions 3893 files changed, 20789 insertions(+), 411020 deletions(-) Co-Authored-By: [email protected] <[email protected]> * refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * rip out org related comments in api v2 --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
302 lines
8.4 KiB
TypeScript
302 lines
8.4 KiB
TypeScript
import {
|
|
createBookingScenario,
|
|
getBooker,
|
|
getDate,
|
|
getMockBookingAttendee,
|
|
getOrganizer,
|
|
getScenarioData,
|
|
TestData,
|
|
} from "@calcom/testing/lib/bookingScenario/bookingScenario";
|
|
import process from "node:process";
|
|
import { appStoreMetadata } from "@calcom/app-store/apps.metadata.generated";
|
|
import { getBatchProcessorJobAccessLink, getRoomNameFromRecordingId } from "@calcom/app-store/dailyvideo/lib";
|
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
|
import prisma from "@calcom/prisma";
|
|
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";
|
|
import { expectWebhookToHaveBeenCalledWith } from "@calcom/testing/lib/bookingScenario/expects";
|
|
import * as recordedDailyVideoRoute from "@calcom/web/app/api/recorded-daily-video/route";
|
|
import { NextRequest } from "next/server";
|
|
import { createMocks } from "node-mocks-http";
|
|
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
|
|
|
// Mock the next/headers module before importing the handler
|
|
vi.mock("next/headers", () => ({
|
|
headers: () => new Headers({ "content-type": "application/json" }),
|
|
cookies: () => ({
|
|
get: () => null,
|
|
getAll: () => [],
|
|
has: () => false,
|
|
}),
|
|
}));
|
|
|
|
// Mock NextResponse to handle the response properly
|
|
vi.mock("next/server", async () => {
|
|
const actual = (await vi.importActual("next/server")) as any;
|
|
return {
|
|
...actual,
|
|
NextResponse: {
|
|
json: (data: any, init?: ResponseInit) => {
|
|
return new Response(JSON.stringify(data), {
|
|
...init,
|
|
headers: {
|
|
...init?.headers,
|
|
"content-type": "application/json",
|
|
},
|
|
});
|
|
},
|
|
// Add other methods you might need
|
|
redirect: actual.NextResponse.redirect,
|
|
next: actual.NextResponse.next,
|
|
rewrite: actual.NextResponse.rewrite,
|
|
},
|
|
};
|
|
});
|
|
|
|
// Now import the handler after mocking
|
|
const { postHandler } = recordedDailyVideoRoute;
|
|
|
|
beforeAll(() => {
|
|
// Setup env vars
|
|
vi.stubEnv("SENDGRID_API_KEY", "FAKE_SENDGRID_API_KEY");
|
|
vi.stubEnv("SENDGRID_EMAIL", "FAKE_SENDGRID_EMAIL");
|
|
});
|
|
|
|
vi.mock("@calcom/app-store/dailyvideo/lib", () => {
|
|
return {
|
|
getRoomNameFromRecordingId: vi.fn(),
|
|
getBatchProcessorJobAccessLink: vi.fn(),
|
|
};
|
|
});
|
|
|
|
vi.mock("@calcom/lib/videoTokens", () => {
|
|
return {
|
|
generateVideoToken: vi.fn().mockReturnValue("MOCK_TOKEN"),
|
|
};
|
|
});
|
|
|
|
vi.mock("app/api/defaultResponderForAppDir", () => {
|
|
return {
|
|
defaultResponderForAppDir: vi.fn(),
|
|
};
|
|
});
|
|
const BATCH_PROCESSOR_JOB_FINSISHED_PAYLOAD = {
|
|
version: "1.1.0",
|
|
type: "batch-processor.job-finished",
|
|
id: "77b1cb9e-cd79-43cd-bad6-3ccaccba26be",
|
|
payload: {
|
|
id: "77b1cb9e-cd79-43cd-bad6-3ccaccba26be",
|
|
status: "finished",
|
|
input: {
|
|
sourceType: "recordingId",
|
|
recordingId: "eb9e84de-783e-4e14-875d-94700ee4b976",
|
|
},
|
|
output: {
|
|
transcription: [
|
|
{
|
|
format: "json",
|
|
s3Config: {
|
|
key: "transcript.json",
|
|
bucket: "daily-bucket",
|
|
region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
format: "srt",
|
|
s3Config: {
|
|
key: "transcript.srt",
|
|
bucket: "daily-bucket",
|
|
region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
format: "txt",
|
|
s3Config: {
|
|
key: "transcript.txt",
|
|
bucket: "daily-bucket",
|
|
region: "us-west-2",
|
|
},
|
|
},
|
|
{
|
|
format: "vtt",
|
|
s3Config: {
|
|
key: "transcript.vtt",
|
|
bucket: "daily-bucket",
|
|
region: "us-west-2",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
event_ts: 1717688213.803,
|
|
};
|
|
|
|
const timeout = process.env.CI ? 5000 : 20000;
|
|
|
|
const TRANSCRIPTION_ACCESS_LINK = {
|
|
id: "MOCK_ID",
|
|
preset: "transcript",
|
|
status: "finished",
|
|
transcription: [
|
|
{
|
|
format: "json",
|
|
link: "https://download.json",
|
|
},
|
|
{
|
|
format: "srt",
|
|
link: "https://download.srt",
|
|
},
|
|
],
|
|
};
|
|
|
|
// We may need to make this more globally available. Will move if we need it elsewhere
|
|
function createNextRequest(mockReq: ReturnType<typeof createMocks>["req"]): NextRequest {
|
|
// Create a Request object that NextRequest can wrap
|
|
const request = new Request("https://example.com/api/recorded-daily-video", {
|
|
method: mockReq.method,
|
|
headers: new Headers(mockReq.headers as Record<string, string>),
|
|
body: mockReq.body ? JSON.stringify(mockReq.body) : undefined,
|
|
});
|
|
|
|
// Create a NextRequest from the Request
|
|
const nextRequest = new NextRequest(request);
|
|
|
|
return nextRequest;
|
|
}
|
|
|
|
describe("Handler: /api/recorded-daily-video", () => {
|
|
beforeEach(() => {
|
|
fetchMock.resetMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.resetAllMocks();
|
|
fetchMock.resetMocks();
|
|
});
|
|
|
|
test(
|
|
`Batch Processor Job finished triggers RECORDING_TRANSCRIPTION_GENERATED webhooks`,
|
|
async () => {
|
|
const organizer = getOrganizer({
|
|
name: "Organizer",
|
|
email: "[email protected]",
|
|
id: 101,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
});
|
|
|
|
const bookingUid = "n5Wv3eHgconAED2j4gcVhP";
|
|
const iCalUID = `${bookingUid}@Cal.diy`;
|
|
const subscriberUrl = "http://my-webhook.example.com";
|
|
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
const booker = getBooker({
|
|
email: "[email protected]",
|
|
name: "Booker",
|
|
});
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
webhooks: [
|
|
{
|
|
userId: organizer.id,
|
|
eventTriggers: [WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED],
|
|
subscriberUrl,
|
|
active: true,
|
|
eventTypeId: 1,
|
|
appId: null,
|
|
},
|
|
],
|
|
eventTypes: [
|
|
{
|
|
id: 1,
|
|
slotInterval: 15,
|
|
length: 15,
|
|
users: [
|
|
{
|
|
id: 101,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
bookings: [
|
|
{
|
|
uid: bookingUid,
|
|
eventTypeId: 1,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime: `${plus1DateString}T05:00:00.000Z`,
|
|
endTime: `${plus1DateString}T05:15:00.000Z`,
|
|
userId: organizer.id,
|
|
metadata: {
|
|
videoCallUrl: "https://existing-daily-video-call-url.example.com",
|
|
},
|
|
references: [
|
|
{
|
|
type: appStoreMetadata.dailyvideo.type,
|
|
uid: "MOCK_ID",
|
|
meetingId: "MOCK_ID",
|
|
meetingPassword: "MOCK_PASS",
|
|
meetingUrl: "http://mock-dailyvideo.example.com",
|
|
credentialId: null,
|
|
},
|
|
],
|
|
attendees: [
|
|
getMockBookingAttendee({
|
|
id: 2,
|
|
name: booker.name,
|
|
email: booker.email,
|
|
locale: "en",
|
|
timeZone: "Asia/Kolkata",
|
|
noShow: false,
|
|
}),
|
|
],
|
|
iCalUID,
|
|
},
|
|
],
|
|
organizer,
|
|
apps: [TestData.apps["daily-video"]],
|
|
})
|
|
);
|
|
|
|
vi.mocked(getRoomNameFromRecordingId).mockResolvedValue("MOCK_ID");
|
|
vi.mocked(getBatchProcessorJobAccessLink).mockResolvedValue(TRANSCRIPTION_ACCESS_LINK);
|
|
|
|
const { req } = createMocks({
|
|
method: "POST",
|
|
body: BATCH_PROCESSOR_JOB_FINSISHED_PAYLOAD,
|
|
prisma,
|
|
});
|
|
|
|
const nextReq = createNextRequest(req);
|
|
|
|
// Handle the response differently
|
|
try {
|
|
const response = await postHandler(nextReq);
|
|
// For App Router, we just need to check if the response exists
|
|
expect(response).toBeDefined();
|
|
} catch (error) {
|
|
console.error("Handler error:", error);
|
|
throw error;
|
|
}
|
|
|
|
await expectWebhookToHaveBeenCalledWith(subscriberUrl, {
|
|
triggerEvent: WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED,
|
|
payload: {
|
|
type: "Test Booking Title",
|
|
uid: bookingUid,
|
|
downloadLinks: {
|
|
transcription: TRANSCRIPTION_ACCESS_LINK.transcription,
|
|
recording: `${WEBAPP_URL}/api/video/recording?token=MOCK_TOKEN`,
|
|
},
|
|
organizer: {
|
|
email: organizer.email,
|
|
name: organizer.name,
|
|
timeZone: organizer.timeZone,
|
|
language: { locale: "en" },
|
|
utcOffset: 330,
|
|
},
|
|
},
|
|
});
|
|
},
|
|
timeout
|
|
);
|
|
});
|