539214b164
The integration tests were flaky because they relied on a shared booking created in beforeAll, which could cause race conditions when tests run in parallel or when cleanup from previous runs interfered. Changes: - Use seed user (member0-acme@example.com) when available, fallback to creating a test user for local development - Create a fresh booking in beforeEach instead of sharing one across tests - Track created bookings and clean them up properly in afterEach - Fix import organization to satisfy linter Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
413 lines
12 KiB
TypeScript
413 lines
12 KiB
TypeScript
import { prisma } from "@calcom/prisma";
|
|
import type { Booking, Credential } from "@calcom/prisma/client";
|
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
|
|
import { BookingReferenceRepository } from "./BookingReferenceRepository";
|
|
|
|
const testRunId = `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
|
|
let testUserId: number;
|
|
let testCredential: Credential;
|
|
let testBooking: Booking;
|
|
const createdBookingReferenceIds: number[] = [];
|
|
const createdBookingIds: number[] = [];
|
|
|
|
async function clearTestBookingReferences() {
|
|
if (createdBookingReferenceIds.length > 0) {
|
|
await prisma.bookingReference.deleteMany({
|
|
where: {
|
|
id: {
|
|
in: createdBookingReferenceIds,
|
|
},
|
|
},
|
|
});
|
|
createdBookingReferenceIds.length = 0;
|
|
}
|
|
}
|
|
|
|
async function clearTestBookings() {
|
|
if (createdBookingIds.length > 0) {
|
|
await prisma.bookingReference.deleteMany({
|
|
where: {
|
|
bookingId: {
|
|
in: createdBookingIds,
|
|
},
|
|
},
|
|
});
|
|
await prisma.booking.deleteMany({
|
|
where: {
|
|
id: {
|
|
in: createdBookingIds,
|
|
},
|
|
},
|
|
});
|
|
createdBookingIds.length = 0;
|
|
}
|
|
}
|
|
|
|
describe("BookingReferenceRepository Integration Tests", () => {
|
|
beforeAll(async () => {
|
|
let testUser = await prisma.user.findFirst({
|
|
where: { email: "member0-acme@example.com" },
|
|
});
|
|
|
|
if (!testUser) {
|
|
testUser = await prisma.user.create({
|
|
data: {
|
|
email: `bookingreference-test-${testRunId}@example.com`,
|
|
username: `bookingreference-test-${testRunId}`,
|
|
},
|
|
});
|
|
}
|
|
testUserId = testUser.id;
|
|
|
|
let credential = await prisma.credential.findFirst({
|
|
where: { userId: testUserId, type: "google_calendar" },
|
|
});
|
|
|
|
if (!credential) {
|
|
credential = await prisma.credential.create({
|
|
data: {
|
|
type: "google_calendar",
|
|
key: {},
|
|
userId: testUserId,
|
|
},
|
|
});
|
|
}
|
|
testCredential = credential;
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
testBooking = await prisma.booking.create({
|
|
data: {
|
|
uid: `test-booking-uid-${testRunId}-${Date.now()}`,
|
|
title: "Test Booking",
|
|
startTime: new Date(),
|
|
endTime: new Date(),
|
|
userId: testUserId,
|
|
},
|
|
});
|
|
createdBookingIds.push(testBooking.id);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await clearTestBookingReferences();
|
|
await clearTestBookings();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await clearTestBookingReferences();
|
|
await clearTestBookings();
|
|
});
|
|
|
|
describe("replaceBookingReferences", () => {
|
|
it("should soft-delete existing references and create new ones", async () => {
|
|
const existingReference1 = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "google_calendar",
|
|
uid: "old-ref-1",
|
|
meetingId: "old-meeting-1",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(existingReference1.id);
|
|
|
|
const existingReference2 = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "google_calendar",
|
|
uid: "old-ref-2",
|
|
meetingId: "old-meeting-2",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(existingReference2.id);
|
|
|
|
const newReferences = [
|
|
{
|
|
type: "google_calendar",
|
|
uid: "new-ref-1",
|
|
meetingId: "new-meeting-1",
|
|
credentialId: testCredential.id,
|
|
},
|
|
{
|
|
type: "google_calendar",
|
|
uid: "new-ref-2",
|
|
meetingId: "new-meeting-2",
|
|
credentialId: testCredential.id,
|
|
},
|
|
];
|
|
|
|
await BookingReferenceRepository.replaceBookingReferences({
|
|
bookingId: testBooking.id,
|
|
newReferencesToCreate: newReferences,
|
|
});
|
|
|
|
const allReferences = await prisma.bookingReference.findMany({
|
|
where: {
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
|
|
const oldReferences = allReferences.filter((ref) => ref.uid === "old-ref-1" || ref.uid === "old-ref-2");
|
|
const newlyCreatedReferences = allReferences.filter(
|
|
(ref) => ref.uid === "new-ref-1" || ref.uid === "new-ref-2"
|
|
);
|
|
|
|
expect(oldReferences).toHaveLength(2);
|
|
expect(oldReferences.every((ref) => ref.deleted === true)).toBe(true);
|
|
|
|
expect(newlyCreatedReferences).toHaveLength(2);
|
|
expect(newlyCreatedReferences.every((ref) => ref.deleted === null)).toBe(true);
|
|
|
|
newlyCreatedReferences.forEach((ref) => {
|
|
createdBookingReferenceIds.push(ref.id);
|
|
});
|
|
});
|
|
|
|
it("should only soft-delete references of the same type as new references being created", async () => {
|
|
const googleRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "google_calendar",
|
|
uid: "google-ref",
|
|
meetingId: "google-meeting",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(googleRef.id);
|
|
|
|
const zoomRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "zoom_video",
|
|
uid: "zoom-ref",
|
|
meetingId: "zoom-meeting",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(zoomRef.id);
|
|
|
|
const newGoogleReferences = [
|
|
{
|
|
type: "google_calendar",
|
|
uid: "new-google-ref",
|
|
meetingId: "new-google-meeting",
|
|
credentialId: testCredential.id,
|
|
},
|
|
];
|
|
|
|
await BookingReferenceRepository.replaceBookingReferences({
|
|
bookingId: testBooking.id,
|
|
newReferencesToCreate: newGoogleReferences,
|
|
});
|
|
|
|
const googleRefAfter = await prisma.bookingReference.findUnique({
|
|
where: { id: googleRef.id },
|
|
});
|
|
expect(googleRefAfter?.deleted).toBe(true);
|
|
|
|
const zoomRefAfter = await prisma.bookingReference.findUnique({
|
|
where: { id: zoomRef.id },
|
|
});
|
|
expect(zoomRefAfter?.deleted).toBe(null);
|
|
|
|
const newRef = await prisma.bookingReference.findFirst({
|
|
where: {
|
|
uid: "new-google-ref",
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
expect(newRef).toBeDefined();
|
|
expect(newRef?.deleted).toBe(null);
|
|
if (newRef) {
|
|
createdBookingReferenceIds.push(newRef.id);
|
|
}
|
|
});
|
|
|
|
it("should persist soft-deleted records in database", async () => {
|
|
const reference = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "daily_video",
|
|
uid: "daily-ref",
|
|
meetingId: "daily-meeting",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(reference.id);
|
|
|
|
const newReferences = [
|
|
{
|
|
type: "daily_video",
|
|
uid: "new-daily-ref",
|
|
meetingId: "new-daily-meeting",
|
|
credentialId: testCredential.id,
|
|
},
|
|
];
|
|
|
|
await BookingReferenceRepository.replaceBookingReferences({
|
|
bookingId: testBooking.id,
|
|
newReferencesToCreate: newReferences,
|
|
});
|
|
|
|
const deletedRefInDb = await prisma.bookingReference.findUnique({
|
|
where: { id: reference.id },
|
|
});
|
|
expect(deletedRefInDb).toBeDefined();
|
|
expect(deletedRefInDb?.deleted).toBe(true);
|
|
expect(deletedRefInDb?.uid).toBe("daily-ref");
|
|
|
|
const newRef = await prisma.bookingReference.findFirst({
|
|
where: {
|
|
uid: "new-daily-ref",
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
if (newRef) {
|
|
createdBookingReferenceIds.push(newRef.id);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("findDailyVideoReferenceByRoomName", () => {
|
|
it("should find active daily video references", async () => {
|
|
const roomName = "test-daily-room-123";
|
|
const dailyRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "daily_video",
|
|
uid: roomName,
|
|
meetingId: roomName,
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(dailyRef.id);
|
|
|
|
const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
|
|
roomName,
|
|
});
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result?.id).toBe(dailyRef.id);
|
|
expect(result?.uid).toBe(roomName);
|
|
expect(result?.deleted).toBe(null);
|
|
});
|
|
|
|
it("should exclude soft-deleted daily video references", async () => {
|
|
const roomName = "test-daily-room-deleted-456";
|
|
const dailyRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "daily_video",
|
|
uid: roomName,
|
|
meetingId: roomName,
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
deleted: true,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(dailyRef.id);
|
|
|
|
const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
|
|
roomName,
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should return null when room name does not exist", async () => {
|
|
const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
|
|
roomName: "non-existent-room",
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("should not return daily video references without a booking", async () => {
|
|
const roomName = "test-daily-room-no-booking";
|
|
const dailyRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "daily_video",
|
|
uid: roomName,
|
|
meetingId: roomName,
|
|
credentialId: testCredential.id,
|
|
bookingId: null,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(dailyRef.id);
|
|
|
|
const result = await BookingReferenceRepository.findDailyVideoReferenceByRoomName({
|
|
roomName,
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Soft-delete behavior verification", () => {
|
|
it("should not return soft-deleted references in regular queries", async () => {
|
|
const activeRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "zoom_video",
|
|
uid: "active-zoom",
|
|
meetingId: "active-zoom-meeting",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(activeRef.id);
|
|
|
|
const deletedRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "zoom_video",
|
|
uid: "deleted-zoom",
|
|
meetingId: "deleted-zoom-meeting",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
deleted: true,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(deletedRef.id);
|
|
|
|
const foundReferences = await prisma.bookingReference.findMany({
|
|
where: {
|
|
bookingId: testBooking.id,
|
|
type: "zoom_video",
|
|
deleted: null,
|
|
},
|
|
});
|
|
|
|
expect(foundReferences).toHaveLength(1);
|
|
expect(foundReferences[0].id).toBe(activeRef.id);
|
|
expect(foundReferences[0].uid).toBe("active-zoom");
|
|
});
|
|
|
|
it("should allow querying soft-deleted references explicitly", async () => {
|
|
const deletedRef = await prisma.bookingReference.create({
|
|
data: {
|
|
type: "office365_calendar",
|
|
uid: "deleted-office365",
|
|
meetingId: "deleted-office365-meeting",
|
|
credentialId: testCredential.id,
|
|
bookingId: testBooking.id,
|
|
deleted: true,
|
|
},
|
|
});
|
|
createdBookingReferenceIds.push(deletedRef.id);
|
|
|
|
const foundDeletedReferences = await prisma.bookingReference.findMany({
|
|
where: {
|
|
bookingId: testBooking.id,
|
|
deleted: true,
|
|
},
|
|
});
|
|
|
|
expect(foundDeletedReferences.length).toBeGreaterThan(0);
|
|
const ourDeletedRef = foundDeletedReferences.find((ref) => ref.id === deletedRef.id);
|
|
expect(ourDeletedRef).toBeDefined();
|
|
expect(ourDeletedRef?.uid).toBe("deleted-office365");
|
|
});
|
|
});
|
|
});
|