Files
calendar/packages/features/credentials/handleDeleteCredential.integration-test.ts
T

528 lines
15 KiB
TypeScript

import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest";
import handleDeleteCredential from "@calcom/features/credentials/handleDeleteCredential";
import prisma from "@calcom/prisma";
import type { Credential, EventType, User } from "@calcom/prisma/client";
import { BookingStatus } from "@calcom/prisma/enums";
describe("handleDeleteCredential Integration Tests - BookingReference Soft Delete", () => {
let testUser: User;
let testCredential: Credential;
let testEventType: EventType;
const createdBookingReferenceIds: number[] = [];
const createdBookingIds: number[] = [];
async function deleteCredentialWithMetadata(credentialId: number, userId: number) {
const user = await prisma.user.findUnique({
where: { id: userId },
select: { metadata: true },
});
return handleDeleteCredential({
userId,
userMetadata: user?.metadata ?? {},
credentialId,
});
}
beforeAll(async () => {
testUser = await prisma.user.create({
data: {
email: "delete-credential-test@example.com",
username: "delete-credential-test",
},
});
testCredential = await prisma.credential.create({
data: {
type: "stripe_payment",
key: { test_key: "test_value" },
userId: testUser.id,
appId: "stripe",
},
});
testEventType = await prisma.eventType.create({
data: {
title: "Paid Event",
slug: "paid-event-test",
length: 30,
userId: testUser.id,
price: 1000, // $10
currency: "usd",
},
});
});
afterEach(async () => {
if (createdBookingReferenceIds.length > 0) {
await prisma.bookingReference.deleteMany({
where: {
id: {
in: createdBookingReferenceIds,
},
},
});
createdBookingReferenceIds.splice(0, createdBookingReferenceIds.length);
}
if (createdBookingIds.length > 0) {
await prisma.attendee.deleteMany({
where: {
bookingId: {
in: createdBookingIds,
},
},
});
await prisma.booking.deleteMany({
where: {
id: {
in: createdBookingIds,
},
},
});
createdBookingIds.splice(0, createdBookingIds.length);
}
});
afterAll(async () => {
await prisma.eventType.delete({
where: {
id: testEventType.id,
},
});
try {
await prisma.credential.delete({
where: {
id: testCredential.id,
},
});
} catch (error) {
// Credential already deleted by test
console.log("Credential deletion error:", error);
}
await prisma.user.delete({
where: {
id: testUser.id,
},
});
});
describe("Soft-delete bookingReferences when deleting payment credential", () => {
it("should soft-delete booking references for unpaid bookings when credential is deleted", async () => {
const unpaidBooking = await prisma.booking.create({
data: {
uid: "unpaid-booking-uid-123",
title: "Unpaid Booking",
startTime: new Date(Date.now() + 86400000), // Tomorrow
endTime: new Date(Date.now() + 90000000),
userId: testUser.id,
eventTypeId: testEventType.id,
status: BookingStatus.PENDING,
paid: false,
attendees: {
create: [
{
email: "attendee@test.com",
name: "Test Attendee",
timeZone: "America/New_York",
},
],
},
payment: {
create: {
uid: "payment-uid-123",
appId: "stripe",
amount: 1000,
fee: 0,
currency: "usd",
success: false,
refunded: false,
data: {},
externalId: "ext-payment-123",
},
},
},
});
createdBookingIds.push(unpaidBooking.id);
const bookingRef1 = await prisma.bookingReference.create({
data: {
type: "stripe_payment",
uid: "stripe-ref-123",
meetingId: "stripe-meeting-123",
credentialId: testCredential.id,
bookingId: unpaidBooking.id,
},
});
createdBookingReferenceIds.push(bookingRef1.id);
const bookingRef2 = await prisma.bookingReference.create({
data: {
type: "google_calendar",
uid: "gcal-ref-123",
meetingId: "gcal-meeting-123",
credentialId: testCredential.id,
bookingId: unpaidBooking.id,
},
});
createdBookingReferenceIds.push(bookingRef2.id);
const refsBeforeDeletion = await prisma.bookingReference.findMany({
where: {
bookingId: unpaidBooking.id,
},
});
expect(refsBeforeDeletion).toHaveLength(2);
expect(refsBeforeDeletion.every((ref) => ref.deleted === null)).toBe(true);
await deleteCredentialWithMetadata(testCredential.id, testUser.id);
const refsAfterDeletion = await prisma.bookingReference.findMany({
where: {
bookingId: unpaidBooking.id,
},
});
expect(refsAfterDeletion).toHaveLength(2);
expect(refsAfterDeletion.every((ref) => ref.deleted === true)).toBe(true);
const cancelledBooking = await prisma.booking.findUnique({
where: { id: unpaidBooking.id },
});
expect(cancelledBooking?.status).toBe(BookingStatus.CANCELLED);
});
it("should persist soft-deleted references in database", async () => {
const newCredential = await prisma.credential.create({
data: {
type: "stripe_payment",
key: { test_key: "test_value" },
userId: testUser.id,
appId: "stripe",
},
});
const unpaidBooking = await prisma.booking.create({
data: {
uid: "unpaid-booking-uid-456",
title: "Another Unpaid Booking",
startTime: new Date(Date.now() + 86400000),
endTime: new Date(Date.now() + 90000000),
userId: testUser.id,
eventTypeId: testEventType.id,
status: BookingStatus.PENDING,
paid: false,
attendees: {
create: [
{
email: "attendee2@test.com",
name: "Test Attendee 2",
timeZone: "America/New_York",
},
],
},
payment: {
create: {
uid: "payment-uid-456",
appId: "stripe",
amount: 1000,
fee: 0,
currency: "usd",
success: false,
refunded: false,
data: {},
externalId: "ext-payment-456",
},
},
},
});
createdBookingIds.push(unpaidBooking.id);
const bookingRef = await prisma.bookingReference.create({
data: {
type: "stripe_payment",
uid: "stripe-ref-456",
meetingId: "stripe-meeting-456",
meetingUrl: "https://example.com/meeting",
credentialId: newCredential.id,
bookingId: unpaidBooking.id,
},
});
createdBookingReferenceIds.push(bookingRef.id);
await deleteCredentialWithMetadata(newCredential.id, testUser.id);
const deletedRef = await prisma.bookingReference.findUnique({
where: { id: bookingRef.id },
});
expect(deletedRef).toBeDefined();
expect(deletedRef?.deleted).toBe(true);
expect(deletedRef?.uid).toBe("stripe-ref-456");
expect(deletedRef?.meetingUrl).toBe("https://example.com/meeting");
expect(deletedRef?.type).toBe("stripe_payment");
});
it("should soft-delete references for multiple unpaid bookings when credential is deleted", async () => {
const newCredential = await prisma.credential.create({
data: {
type: "stripe_payment",
key: { test_key: "test_value" },
userId: testUser.id,
appId: "stripe",
},
});
const booking1 = await prisma.booking.create({
data: {
uid: "booking-1-uid",
title: "Booking 1",
startTime: new Date(Date.now() + 86400000),
endTime: new Date(Date.now() + 90000000),
userId: testUser.id,
eventTypeId: testEventType.id,
status: BookingStatus.PENDING,
paid: false,
attendees: {
create: [
{
email: "attendee-b1@test.com",
name: "Attendee B1",
timeZone: "America/New_York",
},
],
},
payment: {
create: {
uid: "payment-uid-b1",
appId: "stripe",
amount: 1000,
fee: 0,
currency: "usd",
success: false,
refunded: false,
data: {},
externalId: "ext-payment-b1",
},
},
},
});
createdBookingIds.push(booking1.id);
const booking2 = await prisma.booking.create({
data: {
uid: "booking-2-uid",
title: "Booking 2",
startTime: new Date(Date.now() + 172800000), // 2 days from now
endTime: new Date(Date.now() + 176400000),
userId: testUser.id,
eventTypeId: testEventType.id,
status: BookingStatus.PENDING,
paid: false,
attendees: {
create: [
{
email: "attendee-b2@test.com",
name: "Attendee B2",
timeZone: "America/New_York",
},
],
},
payment: {
create: {
uid: "payment-uid-b2",
appId: "stripe",
amount: 1000,
fee: 0,
currency: "usd",
success: false,
refunded: false,
data: {},
externalId: "ext-payment-b2",
},
},
},
});
createdBookingIds.push(booking2.id);
const ref1 = await prisma.bookingReference.create({
data: {
type: "stripe_payment",
uid: "stripe-ref-b1",
meetingId: "meeting-b1",
credentialId: newCredential.id,
bookingId: booking1.id,
},
});
createdBookingReferenceIds.push(ref1.id);
const ref2 = await prisma.bookingReference.create({
data: {
type: "stripe_payment",
uid: "stripe-ref-b2",
meetingId: "meeting-b2",
credentialId: newCredential.id,
bookingId: booking2.id,
},
});
createdBookingReferenceIds.push(ref2.id);
await deleteCredentialWithMetadata(newCredential.id, testUser.id);
const allRefsAfter = await prisma.bookingReference.findMany({
where: {
id: {
in: [ref1.id, ref2.id],
},
},
});
expect(allRefsAfter).toHaveLength(2);
expect(allRefsAfter.every((ref) => ref.deleted === true)).toBe(true);
});
it("should not affect references of paid bookings when credential is deleted", async () => {
const newCredential = await prisma.credential.create({
data: {
type: "stripe_payment",
key: { test_key: "test_value" },
userId: testUser.id,
appId: "stripe",
},
});
const paidBooking = await prisma.booking.create({
data: {
uid: "paid-booking-uid",
title: "Paid Booking",
startTime: new Date(Date.now() + 86400000),
endTime: new Date(Date.now() + 90000000),
userId: testUser.id,
eventTypeId: testEventType.id,
status: BookingStatus.ACCEPTED,
paid: true,
attendees: {
create: [
{
email: "paid-attendee@test.com",
name: "Paid Attendee",
timeZone: "America/New_York",
},
],
},
},
});
createdBookingIds.push(paidBooking.id);
const paidBookingRef = await prisma.bookingReference.create({
data: {
type: "stripe_payment",
uid: "stripe-paid-ref",
meetingId: "stripe-paid-meeting",
credentialId: newCredential.id,
bookingId: paidBooking.id,
},
});
createdBookingReferenceIds.push(paidBookingRef.id);
await deleteCredentialWithMetadata(newCredential.id, testUser.id);
const refAfter = await prisma.bookingReference.findUnique({
where: { id: paidBookingRef.id },
});
expect(refAfter).toBeDefined();
expect(refAfter?.deleted).toBe(null);
const bookingAfter = await prisma.booking.findUnique({
where: { id: paidBooking.id },
});
expect(bookingAfter?.status).toBe(BookingStatus.ACCEPTED);
});
});
describe("Soft-delete behavior with queries after credential deletion", () => {
it("should exclude soft-deleted references from regular queries", async () => {
const newCredential = await prisma.credential.create({
data: {
type: "stripe_payment",
key: { test_key: "test_value" },
userId: testUser.id,
appId: "stripe",
},
});
const unpaidBooking = await prisma.booking.create({
data: {
uid: "query-test-booking-uid",
title: "Query Test Booking",
startTime: new Date(Date.now() + 86400000),
endTime: new Date(Date.now() + 90000000),
userId: testUser.id,
eventTypeId: testEventType.id,
status: BookingStatus.PENDING,
paid: false,
attendees: {
create: [
{
email: "query-test@test.com",
name: "Query Test",
timeZone: "America/New_York",
},
],
},
payment: {
create: {
uid: "payment-uid-query-test",
appId: "stripe",
amount: 1000,
fee: 0,
currency: "usd",
success: false,
refunded: false,
data: {},
externalId: "ext-payment-query-test",
},
},
},
});
createdBookingIds.push(unpaidBooking.id);
const ref = await prisma.bookingReference.create({
data: {
type: "stripe_payment",
uid: "query-test-ref",
meetingId: "query-test-meeting",
credentialId: newCredential.id,
bookingId: unpaidBooking.id,
},
});
createdBookingReferenceIds.push(ref.id);
await deleteCredentialWithMetadata(newCredential.id, testUser.id);
const activeRefs = await prisma.bookingReference.findMany({
where: {
bookingId: unpaidBooking.id,
deleted: null,
},
});
expect(activeRefs).toHaveLength(0);
const allRefs = await prisma.bookingReference.findMany({
where: {
bookingId: unpaidBooking.id,
},
});
expect(allRefs).toHaveLength(1);
expect(allRefs[0].deleted).toBe(true);
});
});
});