cleanup(test): remove org booking integration coverage (#29019)

This commit is contained in:
Romit
2026-04-28 17:43:57 +05:30
committed by GitHub
parent de1ffb08d6
commit c2c95b371a
7 changed files with 0 additions and 1841 deletions
@@ -1,257 +0,0 @@
import { BookingStatus } from "@calcom/prisma/enums";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import { makeUserActor } from "../../makeActor";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import {
cleanupTestData,
createTestBooking,
createTestEventType,
createTestMembership,
createTestOrganization,
createTestUser,
enableFeatureForOrganization,
} from "./integration-utils";
describe("Accepted Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
const additionalBookingUids: string[] = [];
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
const owner = await createTestUser({ name: "Test Audit User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
status: BookingStatus.PENDING,
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
for (const bookingUid of additionalBookingUids) {
await cleanupTestData({
bookingUid,
});
}
additionalBookingUids.length = 0; // Clear the array for next test
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: testData.attendee?.email ? [testData.attendee.email] : [],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
});
describe("when single booking is accepted", () => {
it("should create audit record and retrieve it with correct data formatting", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "ACCEPTED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
status: { old: BookingStatus.PENDING, new: BookingStatus.ACCEPTED },
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.bookingUid).toBe(testData.booking.uid);
expect(auditLog.action).toBe("ACCEPTED");
expect(auditLog.type).toBe("RECORD_UPDATED");
const displayData = auditLog.displayJson as Record<string, unknown>;
expect(displayData).toBeDefined();
expect(displayData.previousStatus).toBe(BookingStatus.PENDING);
expect(displayData.newStatus).toBe(BookingStatus.ACCEPTED);
});
it("should enrich actor information with user details from database", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "ACCEPTED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
status: { old: BookingStatus.PENDING, new: BookingStatus.ACCEPTED },
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const auditLog = result.auditLogs[0];
expect(auditLog.actor.displayName).toBe("Test Audit User");
expect(auditLog.actor.displayEmail).toBe(testData.owner.email);
expect(auditLog.actor.userUuid).toBe(testData.owner.uuid);
});
});
describe("when multiple bookings are accepted in bulk", () => {
it("should create audit records for all bookings with same operation ID", async () => {
const booking2 = await createTestBooking(testData.owner.id, testData.eventType.id, {
status: BookingStatus.PENDING,
attendees: [
{
email: testData.attendee.email,
name: "Test Attendee",
timeZone: "UTC",
},
],
});
additionalBookingUids.push(booking2.uid);
const booking3 = await createTestBooking(testData.owner.id, testData.eventType.id, {
status: BookingStatus.PENDING,
attendees: [
{
email: testData.attendee.email,
name: "Test Attendee",
timeZone: "UTC",
},
],
});
additionalBookingUids.push(booking3.uid);
const actor = makeUserActor(testData.owner.uuid);
const operationId = `bulk-op-${Date.now()}`;
const timestamp = Date.now();
await bookingAuditTaskConsumer.processBulkAuditTask(
{
isBulk: true,
bookings: [
{
bookingUid: testData.booking.uid,
data: {
status: { old: BookingStatus.PENDING, new: BookingStatus.ACCEPTED },
},
},
{
bookingUid: booking2.uid,
data: {
status: { old: BookingStatus.PENDING, new: BookingStatus.ACCEPTED },
},
},
{
bookingUid: booking3.uid,
data: {
status: { old: BookingStatus.PENDING, new: BookingStatus.ACCEPTED },
},
},
],
actor,
action: "ACCEPTED",
source: "WEBAPP",
operationId,
timestamp,
organizationId: testData.organization.id,
},
"test-task-id"
);
const result1 = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const result2 = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: booking2.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const result3 = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: booking3.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result1.auditLogs).toHaveLength(1);
expect(result2.auditLogs).toHaveLength(1);
expect(result3.auditLogs).toHaveLength(1);
expect(result1.auditLogs[0].action).toBe("ACCEPTED");
expect(result2.auditLogs[0].action).toBe("ACCEPTED");
expect(result3.auditLogs[0].action).toBe("ACCEPTED");
// Verify all bookings share the same operationId
expect(result1.auditLogs[0].operationId).toBe(operationId);
expect(result2.auditLogs[0].operationId).toBe(operationId);
expect(result3.auditLogs[0].operationId).toBe(operationId);
});
});
});
@@ -1,122 +0,0 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import type { BookingStatus } from "@calcom/prisma/enums";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import { makeUserActor } from "../../makeActor";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import {
createTestUser,
createTestOrganization,
createTestMembership,
createTestEventType,
createTestBooking,
enableFeatureForOrganization,
cleanupTestData,
} from "./integration-utils";
describe("Attendee Added Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
const owner = await createTestUser({ name: "Test Audit User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: testData.attendee?.email ? [testData.attendee.email] : [],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
});
describe("when guests are added to a booking", () => {
it("should create audit record with correct added attendees data", async () => {
const actor = makeUserActor(testData.owner.uuid);
const newGuestEmails = ["guest1@example.com", "guest2@example.com"];
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "ATTENDEE_ADDED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
added: newGuestEmails,
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.bookingUid).toBe(testData.booking.uid);
expect(auditLog.action).toBe("ATTENDEE_ADDED");
expect(auditLog.type).toBe("RECORD_UPDATED");
const displayData = auditLog.displayJson as { addedAttendees: string[] };
expect(displayData).toBeDefined();
expect(displayData.addedAttendees).toEqual(newGuestEmails);
expect(auditLog.actor.displayName).toBe("Test Audit User");
expect(auditLog.actor.displayEmail).toBe(testData.owner.email);
expect(auditLog.actor.userUuid).toBe(testData.owner.uuid);
});
});
});
@@ -1,125 +0,0 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import { BookingStatus } from "@calcom/prisma/enums";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import { makeUserActor } from "../../makeActor";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import {
createTestUser,
createTestOrganization,
createTestMembership,
createTestEventType,
createTestBooking,
enableFeatureForOrganization,
cleanupTestData,
} from "./integration-utils";
describe("Cancelled Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
const owner = await createTestUser({ name: "Test Audit User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: testData.attendee?.email ? [testData.attendee.email] : [],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
});
describe("when booking is cancelled", () => {
it("should create audit record with cancellation details and retrieve it correctly", async () => {
const actor = makeUserActor(testData.owner.uuid);
const cancellationReason = "Schedule conflict";
const cancelledBy = "owner";
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "CANCELLED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
cancellationReason,
cancelledBy,
status: {
old: BookingStatus.ACCEPTED,
new: BookingStatus.CANCELLED,
},
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.action).toBe("CANCELLED");
expect(auditLog.type).toBe("RECORD_UPDATED");
const displayData = auditLog.displayJson as Record<string, unknown>;
expect(displayData.cancellationReason).toBe(cancellationReason);
expect(displayData.cancelledBy).toBe(cancelledBy);
expect(displayData.previousStatus).toBe(BookingStatus.ACCEPTED);
expect(displayData.newStatus).toBe(BookingStatus.CANCELLED);
});
});
});
@@ -1,359 +0,0 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import type { BookingStatus } from "@calcom/prisma/enums";
import prisma from "@calcom/prisma";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import { makeUserActor } from "../../makeActor";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import {
createTestUser,
createTestOrganization,
createTestMembership,
createTestEventType,
createTestBooking,
enableFeatureForOrganization,
cleanupTestData,
} from "./integration-utils";
describe("Created Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
const owner = await createTestUser({ name: "Test Audit User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: testData.attendee?.email ? [testData.attendee.email] : [],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
});
describe("when single booking is created", () => {
it("should create audit record and retrieve it with correct data formatting", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "CREATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
startTime: testData.booking.startTime.getTime(),
endTime: testData.booking.endTime.getTime(),
status: testData.booking.status,
hostUserUuid: testData.owner.uuid,
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.bookingUid).toBe(testData.booking.uid);
expect(auditLog.action).toBe("CREATED");
expect(auditLog.type).toBe("RECORD_CREATED");
const displayData = auditLog.displayJson as Record<string, unknown>;
expect(displayData).toBeDefined();
expect(displayData.startTime).toBeDefined();
expect(typeof displayData.startTime).toBe("string");
expect(displayData.startTime).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
expect(displayData.endTime).toBeDefined();
expect(typeof displayData.endTime).toBe("string");
expect(displayData.endTime).toMatch(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/);
expect(displayData.status).toBe(testData.booking.status);
});
it("should enrich actor information with user details from database", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "CREATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
startTime: testData.booking.startTime.getTime(),
endTime: testData.booking.endTime.getTime(),
status: testData.booking.status,
hostUserUuid: testData.owner.uuid,
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const auditLog = result.auditLogs[0];
expect(auditLog.actor.displayName).toBe("Test Audit User");
expect(auditLog.actor.displayEmail).toBe(testData.owner.email);
expect(auditLog.actor.userUuid).toBe(testData.owner.uuid);
});
it("should include impersonator details when context has impersonatedBy", async () => {
// Create a second user to act as impersonator
const impersonator = await createTestUser({ name: "Admin Impersonator" });
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "CREATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
startTime: testData.booking.startTime.getTime(),
endTime: testData.booking.endTime.getTime(),
status: testData.booking.status,
hostUserUuid: testData.owner.uuid,
},
timestamp: Date.now(),
context: {
impersonatedBy: impersonator.uuid,
},
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.impersonatedBy).toBeDefined();
expect(auditLog.impersonatedBy?.displayName).toBe("Admin Impersonator");
expect(auditLog.impersonatedBy?.displayEmail).toBe(impersonator.email);
// Cleanup impersonator user
await prisma.user.delete({ where: { id: impersonator.id } });
});
it.skip("should deny access to unauthorized users viewing audit logs", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "CREATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
startTime: testData.booking.startTime.getTime(),
endTime: testData.booking.endTime.getTime(),
status: testData.booking.status,
hostUserUuid: testData.owner.uuid,
},
timestamp: Date.now(),
});
const ownerResult = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(ownerResult.auditLogs).toHaveLength(1);
const unauthorizedUserId = 999999;
const unauthorizedEmail = "unauthorized@example.com";
await expect(
bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: unauthorizedUserId,
userEmail: unauthorizedEmail,
userTimeZone: "UTC",
organizationId: testData.organization.id,
})
).rejects.toThrow();
});
});
describe("when multiple bookings are created in bulk", () => {
it("should create audit records for all bookings with same operation ID", async () => {
const booking2 = await createTestBooking(testData.owner.id, testData.eventType.id, {
attendees: [
{
email: testData.attendee.email,
name: "Test Attendee",
timeZone: "UTC",
},
],
});
const booking3 = await createTestBooking(testData.owner.id, testData.eventType.id, {
attendees: [
{
email: testData.attendee.email,
name: "Test Attendee",
timeZone: "UTC",
},
],
});
const actor = makeUserActor(testData.owner.uuid);
const operationId = `bulk-op-${Date.now()}`;
const timestamp = Date.now();
await bookingAuditTaskConsumer.processBulkAuditTask(
{
isBulk: true,
bookings: [
{
bookingUid: testData.booking.uid,
data: {
startTime: testData.booking.startTime.getTime(),
endTime: testData.booking.endTime.getTime(),
status: testData.booking.status,
hostUserUuid: testData.owner.uuid,
},
},
{
bookingUid: booking2.uid,
data: {
startTime: booking2.startTime.getTime(),
endTime: booking2.endTime.getTime(),
status: booking2.status,
hostUserUuid: testData.owner.uuid,
},
},
{
bookingUid: booking3.uid,
data: {
startTime: booking3.startTime.getTime(),
endTime: booking3.endTime.getTime(),
status: booking3.status,
hostUserUuid: testData.owner.uuid,
},
},
],
actor,
action: "CREATED",
source: "WEBAPP",
operationId,
timestamp,
organizationId: testData.organization.id,
},
"test-task-id"
);
const result1 = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const result2 = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: booking2.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const result3 = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: booking3.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result1.auditLogs).toHaveLength(1);
expect(result2.auditLogs).toHaveLength(1);
expect(result3.auditLogs).toHaveLength(1);
expect(result1.auditLogs[0].action).toBe("CREATED");
expect(result2.auditLogs[0].action).toBe("CREATED");
expect(result3.auditLogs[0].action).toBe("CREATED");
// Verify all bookings share the same operationId
expect(result1.auditLogs[0].operationId).toBe(operationId);
expect(result2.auditLogs[0].operationId).toBe(operationId);
expect(result3.auditLogs[0].operationId).toBe(operationId);
await cleanupTestData({
bookingUid: booking2.uid,
});
await cleanupTestData({
bookingUid: booking3.uid,
});
});
});
});
@@ -1,173 +0,0 @@
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import { makeUserActor } from "../../makeActor";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import {
createTestUser,
createTestOrganization,
createTestMembership,
createTestEventType,
createTestBooking,
enableFeatureForOrganization,
cleanupTestData,
} from "./integration-utils";
import { BookingStatus } from "@calcom/prisma/enums";
describe("Location Changed Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
// Setup basic test data using Test Data Builder pattern
const owner = await createTestUser({ name: "Location Audit User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: testData.attendee?.email ? [testData.attendee.email] : [],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
});
describe("when location is changed", () => {
it("should create audit record and retrieve it with correct location data", async () => {
const actor = makeUserActor(testData.owner.uuid);
const operationId = `op-${Date.now()}`;
// Simulate LOCATION_CHANGED action
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "LOCATION_CHANGED",
source: "WEBAPP",
operationId,
data: {
location: {
old: "Zoom",
new: "Google Meet",
},
},
timestamp: Date.now(),
});
// Retrieve logs
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
// Find the specific log we just created
const auditLog = result.auditLogs.find((log) => log.operationId === operationId);
expect(auditLog).toBeDefined();
if (!auditLog) throw new Error("Audit log not found");
expect(auditLog.action).toBe("LOCATION_CHANGED");
// The type identifier from the service
expect(auditLog.type).toBe("RECORD_UPDATED");
expect(auditLog.displayJson).toBeNull();
// Verify the title params are correct
expect(auditLog.actionDisplayTitle).toBeDefined();
expect(auditLog.actionDisplayTitle.key).toBe("booking_audit_action.location_changed_from_to");
expect(auditLog.actionDisplayTitle.params).toEqual({
fromLocation: "Zoom",
toLocation: "Google Meet",
});
});
it("should handle location change where old value was null", async () => {
const actor = makeUserActor(testData.owner.uuid);
const operationId = `op-initial-${Date.now()}`;
// Simulate setting location for the first time (old is null)
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "LOCATION_CHANGED",
source: "WEBAPP",
operationId,
data: {
location: {
old: null,
new: "In Person",
},
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
const auditLog = result.auditLogs.find((log) => log.operationId === operationId);
expect(auditLog).toBeDefined();
expect(auditLog!.displayJson).toBeNull();
expect(auditLog!.actionDisplayTitle.params).toEqual({
fromLocation: "No location defined",
toLocation: "In Person",
});
});
});
});
@@ -1,320 +0,0 @@
import type { BookingStatus } from "@calcom/prisma/enums";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { getBookingAuditTaskConsumer } from "../../../di/BookingAuditTaskConsumer.container";
import { getBookingAuditViewerService } from "../../../di/BookingAuditViewerService.container";
import { makeUserActor } from "../../makeActor";
import type { BookingAuditTaskConsumer } from "../BookingAuditTaskConsumer";
import type { BookingAuditViewerService } from "../BookingAuditViewerService";
import {
cleanupTestData,
createTestBooking,
createTestEventType,
createTestMembership,
createTestOrganization,
createTestUser,
enableFeatureForOrganization,
} from "./integration-utils";
describe("No-Show Updated Action Integration", () => {
let bookingAuditTaskConsumer: BookingAuditTaskConsumer;
let bookingAuditViewerService: BookingAuditViewerService;
let testData: {
owner: { id: number; uuid: string; email: string };
attendee: { id: number; email: string };
organization: { id: number };
eventType: { id: number };
booking: { id: number; uid: string; startTime: Date; endTime: Date; status: BookingStatus };
};
const additionalAttendeeEmails: string[] = [];
beforeEach(async () => {
bookingAuditTaskConsumer = getBookingAuditTaskConsumer();
bookingAuditViewerService = getBookingAuditViewerService();
const owner = await createTestUser({ name: "Test Host User" });
const organization = await createTestOrganization();
await createTestMembership(owner.id, organization.id);
await enableFeatureForOrganization(organization.id, "booking-audit");
const eventType = await createTestEventType(owner.id);
const attendee = await createTestUser({ name: "Test Attendee" });
const booking = await createTestBooking(owner.id, eventType.id, {
attendees: [
{
email: attendee.email,
name: attendee.name || "Test Attendee",
timeZone: "UTC",
},
],
});
testData = {
owner: { id: owner.id, uuid: owner.uuid, email: owner.email },
attendee: { id: attendee.id, email: attendee.email },
organization: { id: organization.id },
eventType: { id: eventType.id },
booking: {
id: booking.id,
uid: booking.uid,
startTime: booking.startTime,
endTime: booking.endTime,
status: booking.status,
},
};
});
afterEach(async () => {
if (!testData) return;
await cleanupTestData({
bookingUid: testData.booking?.uid,
userUuids: testData.owner?.uuid ? [testData.owner.uuid] : [],
attendeeEmails: [
...(testData.attendee?.email ? [testData.attendee.email] : []),
...additionalAttendeeEmails,
],
eventTypeId: testData.eventType?.id,
organizationId: testData.organization?.id,
userIds: [testData.owner?.id, testData.attendee?.id].filter((id): id is number => id !== undefined),
featureSlug: "booking-audit",
});
additionalAttendeeEmails.length = 0;
});
describe("when host is marked as no-show", () => {
it("should create audit record with host field containing userUuid and noShow", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "NO_SHOW_UPDATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
// New schema: host contains userUuid and noShow change
host: {
userUuid: testData.owner.uuid,
noShow: { old: null, new: true },
},
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.bookingUid).toBe(testData.booking.uid);
expect(auditLog.action).toBe("NO_SHOW_UPDATED");
expect(auditLog.type).toBe("RECORD_UPDATED");
const displayData = auditLog.displayJson as Record<string, unknown>;
expect(displayData).toBeDefined();
expect(displayData.hostNoShow).toBe(true);
expect(displayData.previousHostNoShow).toBe(null);
});
});
describe("when attendees are marked as no-show", () => {
it("should create audit record with attendeesNoShow array", async () => {
const actor = makeUserActor(testData.owner.uuid);
const attendeesNoShow = [{ attendeeEmail: testData.attendee.email, noShow: { old: null, new: true } }];
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "NO_SHOW_UPDATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
attendeesNoShow,
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.bookingUid).toBe(testData.booking.uid);
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.action).toBe("NO_SHOW_UPDATED");
expect(auditLog.type).toBe("RECORD_UPDATED");
const displayData = auditLog.displayJson as Record<string, unknown>;
expect(displayData).toBeDefined();
expect(displayData.attendeesNoShow).toBeDefined();
const storedAttendeesNoShow = displayData.attendeesNoShow as Array<{
attendeeEmail: string;
noShow: { old: boolean | null; new: boolean };
}>;
expect(storedAttendeesNoShow).toHaveLength(1);
expect(storedAttendeesNoShow[0].attendeeEmail).toBe(testData.attendee.email);
expect(storedAttendeesNoShow[0].noShow.old).toBe(null);
expect(storedAttendeesNoShow[0].noShow.new).toBe(true);
});
it("should handle multiple attendees marked as no-show", async () => {
const { prisma } = await import("@calcom/prisma");
const secondAttendeeEmail = `second-attendee-${Date.now()}@example.com`;
additionalAttendeeEmails.push(secondAttendeeEmail);
await prisma.attendee.create({
data: {
email: secondAttendeeEmail,
name: "Second Attendee",
timeZone: "UTC",
bookingId: testData.booking.id,
},
});
const actor = makeUserActor(testData.owner.uuid);
const attendeesNoShow = [
{ attendeeEmail: testData.attendee.email, noShow: { old: null, new: true } },
{ attendeeEmail: secondAttendeeEmail, noShow: { old: false, new: true } },
];
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "NO_SHOW_UPDATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: {
attendeesNoShow,
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.auditLogs).toHaveLength(1);
const displayData = result.auditLogs[0].displayJson as Record<string, unknown>;
const storedAttendeesNoShow = displayData.attendeesNoShow as Array<{
attendeeEmail: string;
noShow: { old: boolean | null; new: boolean };
}>;
expect(storedAttendeesNoShow).toHaveLength(2);
const firstAttendee = storedAttendeesNoShow.find((a) => a.attendeeEmail === testData.attendee.email);
const secondAttendeeData = storedAttendeesNoShow.find((a) => a.attendeeEmail === secondAttendeeEmail);
expect(firstAttendee?.noShow).toEqual({ old: null, new: true });
expect(secondAttendeeData?.noShow).toEqual({ old: false, new: true });
});
});
describe("when both host and attendees are marked as no-show", () => {
it("should create single audit record with both host and attendeesNoShow fields", async () => {
const actor = makeUserActor(testData.owner.uuid);
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "NO_SHOW_UPDATED",
source: "API_V2",
operationId: `op-${Date.now()}`,
data: {
host: {
userUuid: testData.owner.uuid,
noShow: { old: null, new: true },
},
attendeesNoShow: [{ attendeeEmail: testData.attendee.email, noShow: { old: null, new: true } }],
},
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.auditLogs).toHaveLength(1);
const auditLog = result.auditLogs[0];
expect(auditLog.action).toBe("NO_SHOW_UPDATED");
expect(auditLog.source).toBe("API_V2");
const displayData = auditLog.displayJson as Record<string, unknown>;
expect(displayData.hostNoShow).toBe(true);
expect(displayData.previousHostNoShow).toBe(null);
const storedAttendeesNoShow = displayData.attendeesNoShow as Array<{
attendeeEmail: string;
noShow: { old: boolean | null; new: boolean };
}>;
expect(storedAttendeesNoShow).toHaveLength(1);
expect(storedAttendeesNoShow[0].attendeeEmail).toBe(testData.attendee.email);
expect(storedAttendeesNoShow[0].noShow).toEqual({ old: null, new: true });
});
});
describe("schema validation with array format", () => {
it("should accept attendeesNoShow data with array format", async () => {
const actor = makeUserActor(testData.owner.uuid);
const dataWithArrayFormat = {
attendeesNoShow: [{ attendeeEmail: testData.attendee.email, noShow: { old: null, new: true } }],
};
await bookingAuditTaskConsumer.onBookingAction({
bookingUid: testData.booking.uid,
actor,
action: "NO_SHOW_UPDATED",
source: "WEBAPP",
operationId: `op-${Date.now()}`,
data: dataWithArrayFormat,
timestamp: Date.now(),
});
const result = await bookingAuditViewerService.getAuditLogsForBooking({
bookingUid: testData.booking.uid,
userId: testData.owner.id,
userEmail: testData.owner.email,
userTimeZone: "UTC",
organizationId: testData.organization.id,
});
expect(result.auditLogs).toHaveLength(1);
const displayData = result.auditLogs[0].displayJson as Record<string, unknown>;
expect(displayData.attendeesNoShow).toBeDefined();
const storedAttendeesNoShow = displayData.attendeesNoShow as Array<{
attendeeEmail: string;
noShow: { old: boolean | null; new: boolean };
}>;
expect(storedAttendeesNoShow).toHaveLength(1);
expect(storedAttendeesNoShow[0].attendeeEmail).toBe(testData.attendee.email);
});
});
});
@@ -8,7 +8,6 @@ import {
mockCalendarToHaveNoBusySlots,
mockCalendarToCrashOnGetAvailability,
BookingLocations,
createOrganization,
} from "@calcom/testing/lib/bookingScenario/bookingScenario";
import { getMockRequestDataForBooking } from "@calcom/testing/lib/bookingScenario/getMockRequestDataForBooking";
import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTeardown";
@@ -54,22 +53,6 @@ const createGlobalWatchlistEntry = async (overrides: {
});
};
const createOrganizationWatchlistEntry = async (
organizationId: number,
overrides: {
type: WatchlistType;
value: string;
action: "BLOCK" | "REPORT";
}
) => {
return createTestWatchlistEntry({
type: overrides.type,
value: overrides.value,
action: overrides.action,
organizationId,
});
};
const expectDecoyBookingResponse = (booking: Record<string, unknown>) => {
expect(booking).toHaveProperty("isShortCircuitedBooking", true);
expect(booking).toHaveProperty("uid");
@@ -402,472 +385,4 @@ describe("handleNewBooking - Spam Detection", () => {
);
});
describe("Organization Watchlist Blocking:", () => {
test(
"should block booking when email is in organization watchlist and return decoy response",
async () => {
const handleNewBooking = getNewBookingHandler();
const blockedEmail = "org-spammer@example.com";
// Create organization with a team
const org = await createOrganization({
name: "Test Org",
slug: "test-org",
withTeam: true,
});
const booker = getBooker({
email: blockedEmail,
name: "Org Blocked Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
organizationId: org.id,
});
await createOrganizationWatchlistEntry(org.id, {
type: WatchlistType.EMAIL,
value: blockedEmail,
action: "BLOCK",
});
// Use the child team ID for the eventType
const teamId = org.children && org.children.length > 0 ? org.children[0].id : null;
await createBookingScenario(
getScenarioData(
{
eventTypes: [
{
id: 1,
slotInterval: 30,
length: 30,
teamId,
users: [
{
id: 101,
},
],
},
],
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
},
{ id: org.id }
)
);
await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expectDecoyBookingResponse(createdBooking);
expect(createdBooking.attendees[0].email).toBe(blockedEmail);
await expectNoBookingInDatabase(blockedEmail);
},
timeout
);
test(
"should block booking when domain is in organization watchlist and return decoy response",
async () => {
const handleNewBooking = getNewBookingHandler();
const blockedDomain = "spammydomain.com";
const blockedEmail = `user@${blockedDomain}`;
// Create organization with a team
const org = await createOrganization({
name: "Test Org",
slug: "test-org",
withTeam: true,
});
const booker = getBooker({
email: blockedEmail,
name: "Domain Blocked Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
organizationId: org.id,
});
await createOrganizationWatchlistEntry(org.id, {
type: WatchlistType.DOMAIN,
value: blockedDomain,
action: "BLOCK",
});
// Use the child team ID for the eventType
const teamId = org.children && org.children.length > 0 ? org.children[0].id : null;
await createBookingScenario(
getScenarioData(
{
eventTypes: [
{
id: 1,
slotInterval: 30,
length: 30,
teamId,
users: [
{
id: 101,
},
],
},
],
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
},
{ id: org.id }
)
);
await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expectDecoyBookingResponse(createdBooking);
expect(createdBooking.attendees[0].email).toBe(blockedEmail);
await expectNoBookingInDatabase(blockedEmail);
},
timeout
);
test(
"should NOT block booking when email is in a different organization's watchlist",
async () => {
const handleNewBooking = getNewBookingHandler();
const blockedEmail = "different-org-user@example.com";
// Create two different organizations with teams
const organizerOrg = await createOrganization({
name: "Organizer Org",
slug: "organizer-org",
withTeam: true,
});
const differentOrg = await createOrganization({
name: "Different Org",
slug: "different-org",
withTeam: true,
});
const booker = getBooker({
email: blockedEmail,
name: "Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
organizationId: organizerOrg.id,
});
// Block email in a DIFFERENT organization
await createOrganizationWatchlistEntry(differentOrg.id, {
type: WatchlistType.EMAIL,
value: blockedEmail,
action: "BLOCK",
});
// Use the child team ID for the eventType
const teamId =
organizerOrg.children && organizerOrg.children.length > 0 ? organizerOrg.children[0].id : null;
await createBookingScenario(
getScenarioData(
{
eventTypes: [
{
id: 1,
slotInterval: 30,
length: 30,
teamId,
users: [
{
id: 101,
},
],
},
],
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
},
{ id: organizerOrg.id }
)
);
await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
// Should NOT be a decoy response - booking should succeed
expect(createdBooking).not.toHaveProperty("isShortCircuitedBooking");
expect(createdBooking.status).toBe(BookingStatus.ACCEPTED);
expect(createdBooking.id).not.toBe(0);
expect(createdBooking.attendees[0].email).toBe(blockedEmail);
},
timeout
);
test(
"should block booking for managed event when email is in organization watchlist",
async () => {
const handleNewBooking = getNewBookingHandler();
const blockedEmail = "managed-event-spammer@example.com";
// Create organization with a team
const org = await createOrganization({
name: "Managed Event Org",
slug: "managed-event-org",
withTeam: true,
});
const booker = getBooker({
email: blockedEmail,
name: "Managed Event Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
organizationId: org.id,
});
await createOrganizationWatchlistEntry(org.id, {
type: WatchlistType.EMAIL,
value: blockedEmail,
action: "BLOCK",
});
// Get the child team ID
const teamId = org.children && org.children.length > 0 ? org.children[0].id : null;
// Create a parent event type and a managed (child) event type
await createBookingScenario(
getScenarioData(
{
eventTypes: [
// Parent event type
{
id: 1,
slotInterval: 30,
length: 30,
teamId,
users: [
{
id: 101,
},
],
},
// Managed event type (child) - has parent but no teamId
{
id: 2,
slotInterval: 30,
length: 30,
parent: { id: 1 }, // References parent event type
users: [
{
id: 101,
},
],
},
],
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
},
{ id: org.id }
)
);
await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
});
// Try to book the managed event (id: 2)
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 2, // Booking the managed event
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
// Should return a decoy response since email is blocked in the organization
expectDecoyBookingResponse(createdBooking);
expect(createdBooking.attendees[0].email).toBe(blockedEmail);
await expectNoBookingInDatabase(blockedEmail);
},
timeout
);
test(
"should block booking for user event in organization when email is in organization watchlist",
async () => {
const handleNewBooking = getNewBookingHandler();
const blockedEmail = "user-event-spammer@example.com";
// Create organization
const org = await createOrganization({
name: "User Event Org",
slug: "user-event-org",
withTeam: false,
});
const booker = getBooker({
email: blockedEmail,
name: "User Event Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
organizationId: org.id,
});
await createOrganizationWatchlistEntry(org.id, {
type: WatchlistType.EMAIL,
value: blockedEmail,
action: "BLOCK",
});
// Create a user event (no teamId) but with a profile linking to the organization
await createBookingScenario(
getScenarioData(
{
eventTypes: [
{
id: 1,
slotInterval: 30,
length: 30,
// User Event Type has userId set
userId: 101,
},
],
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
},
{ id: org.id }
)
);
await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
// Should return a decoy response since email is blocked in the organization
expectDecoyBookingResponse(createdBooking);
expect(createdBooking.attendees[0].email).toBe(blockedEmail);
await expectNoBookingInDatabase(blockedEmail);
},
timeout
);
});
});