Files
calendar/apps/web/lib/daily-webhook/tests/recorded-daily-video.test.ts
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ab21c7f805 refactor: Cal.diy (#28903)
* 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>
2026-04-15 09:52:36 -03:00

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
);
});