feat: add comprehensive validation tests for event-types/[id] GET endpoint (#23809)
* feat: add comprehensive validation tests for event-types/[id] GET endpoint - Add integration tests for complex validation scenarios - Test locations, booking fields, metadata, and seats validation - Test edge cases that could cause validation mismatches between validators and DB results - Ensure API response structure matches schemaEventTypeReadPublic schema - Successfully catch validation discrepancies like missing displayLocationPublicly and assignAllTeamMembers fields Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: add explicit type annotations for booking fields find callbacks - Fix TypeScript errors on lines 388 and 395 - Provide proper type annotations for find method callback parameters - Replace any[] with specific { label: string; value: string }[] type - Ensure CI type checking passes Co-Authored-By: alex@cal.com <me@alexvanandel.com> * feat: convert event-types integration test to use real database - Replace prismaMock with real Prisma client operations - Create actual database records for comprehensive test scenarios - Add proper setup/teardown with unique timestamps for test isolation - Maintain all existing validation test coverage - Follow Cal.com integration test patterns for database operations Co-Authored-By: alex@cal.com <me@alexvanandel.com> * feat: restore original mock-based test alongside integration test - Restored original _get.test.ts with prismaMock for unit testing - Kept _get.integration.test.ts with real database for integration testing - Both tests provide complementary coverage: mocks for fast unit tests, real DB for validation testing - Fixed type annotations in find callbacks to maintain type safety Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: rename integration test to use proper naming convention - Rename _get.integration.test.ts to _get.integration-test.ts - Follows Cal.com vitest workspace pattern for integration tests - Ensures integration test runs in proper CI workflow with database setup Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: update tests to use handler return value instead of res._getData() Co-Authored-By: keith@cal.com <keithwillcode@gmail.com> * fix: add proper type assertions for locations and bookingFields in tests Co-Authored-By: unknown <> * fix: create schedule for test user to fix foreign key constraint in integration tests Co-Authored-By: unknown <> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: keith@cal.com <keithwillcode@gmail.com>
This commit is contained in:
co-authored by
keith@cal.com
unknown
Devin AI
parent
b6df61d287
commit
6fb42b63e0
@@ -0,0 +1,545 @@
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, expect, test, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { User, Team, EventType, Schedule } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import handler from "../../../../pages/api/event-types/[id]/_get";
|
||||
|
||||
type CustomNextApiRequest = NextApiRequest & Request;
|
||||
type CustomNextApiResponse = NextApiResponse & Response;
|
||||
|
||||
describe("GET /api/event-types/[id]", () => {
|
||||
let testUser: User;
|
||||
let adminUser: User;
|
||||
let teamUser: User;
|
||||
let testTeam: Team;
|
||||
let testSchedule: Schedule;
|
||||
let complexEventType: EventType;
|
||||
let seatsEventType: EventType;
|
||||
let maxSeatsEventType: EventType;
|
||||
let nullSeatsEventType: EventType;
|
||||
let customFieldsEventType: EventType;
|
||||
let teamEventType: EventType;
|
||||
let metadataEventType: EventType;
|
||||
|
||||
beforeAll(async () => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
const createTestUser = prisma.user.create({
|
||||
data: {
|
||||
username: `test-user-${timestamp}`,
|
||||
name: "Test User",
|
||||
email: `test-user-${timestamp}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
const createAdminUser = prisma.user.create({
|
||||
data: {
|
||||
username: `admin-user-${timestamp}`,
|
||||
name: "Admin User",
|
||||
email: `admin-user-${timestamp}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
const createTeamUser = prisma.user.create({
|
||||
data: {
|
||||
username: `team-user-${timestamp}`,
|
||||
name: "Team User",
|
||||
email: `team-user-${timestamp}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
[testUser, adminUser, teamUser] = await Promise.all([createTestUser, createAdminUser, createTeamUser]);
|
||||
|
||||
// Create a schedule for the test user (required for event types with scheduleId)
|
||||
testSchedule = await prisma.schedule.create({
|
||||
data: {
|
||||
name: `Test Schedule ${timestamp}`,
|
||||
userId: testUser.id,
|
||||
timeZone: "UTC",
|
||||
},
|
||||
});
|
||||
|
||||
testTeam = await prisma.team.create({
|
||||
data: {
|
||||
name: `Test Team ${timestamp}`,
|
||||
slug: `test-team-${timestamp}`,
|
||||
members: {
|
||||
createMany: {
|
||||
data: [
|
||||
{
|
||||
userId: teamUser.id,
|
||||
role: MembershipRole.MEMBER,
|
||||
accepted: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createComplexEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Complex Event Type",
|
||||
slug: `complex-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
locations: [
|
||||
{
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
{
|
||||
type: "attendeeInPerson",
|
||||
address: "123 Main St, City, State 12345",
|
||||
displayLocationPublicly: false,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
hostPhoneNumber: "+1234567890",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
editable: "system-but-optional",
|
||||
defaultLabel: "email_address",
|
||||
},
|
||||
{
|
||||
name: "customField",
|
||||
type: "text",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Custom Question",
|
||||
placeholder: "Enter your answer",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createSeatsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Seats Event Type",
|
||||
slug: `seats-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
seatsPerTimeSlot: 10,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: false,
|
||||
},
|
||||
});
|
||||
|
||||
const createMaxSeatsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Max Seats Event Type",
|
||||
slug: `max-seats-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
seatsPerTimeSlot: 1000,
|
||||
seatsShowAttendees: false,
|
||||
seatsShowAvailabilityCount: true,
|
||||
},
|
||||
});
|
||||
|
||||
const createNullSeatsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Null Seats Event Type",
|
||||
slug: `null-seats-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
seatsPerTimeSlot: null,
|
||||
seatsShowAttendees: null,
|
||||
seatsShowAvailabilityCount: null,
|
||||
},
|
||||
});
|
||||
|
||||
const createCustomFieldsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Custom Fields Event Type",
|
||||
slug: `custom-fields-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
variant: "fullName",
|
||||
},
|
||||
{
|
||||
name: "customSelect",
|
||||
type: "select",
|
||||
required: true,
|
||||
editable: "user",
|
||||
label: "Preferred Time",
|
||||
options: [
|
||||
{ label: "Morning", value: "morning" },
|
||||
{ label: "Afternoon", value: "afternoon" },
|
||||
{ label: "Evening", value: "evening" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "customTextarea",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Additional Information",
|
||||
placeholder: "Please provide any additional details",
|
||||
maxLength: 500,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const createTeamEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Team Event Type",
|
||||
slug: `team-event-${timestamp}`,
|
||||
length: 60,
|
||||
teamId: testTeam.id,
|
||||
schedulingType: "COLLECTIVE",
|
||||
assignAllTeamMembers: true,
|
||||
},
|
||||
});
|
||||
|
||||
const createMetadataEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Metadata Event Type",
|
||||
slug: `metadata-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
disableStandardEmails: false,
|
||||
requiresConfirmationThreshold: {
|
||||
time: 30,
|
||||
unit: "minutes",
|
||||
},
|
||||
apps: {
|
||||
stripe: {
|
||||
price: 2500,
|
||||
currency: "usd",
|
||||
},
|
||||
},
|
||||
},
|
||||
recurringEvent: {
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
},
|
||||
bookingLimits: {
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
},
|
||||
durationLimits: {
|
||||
PER_DAY: 480,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
[
|
||||
complexEventType,
|
||||
seatsEventType,
|
||||
maxSeatsEventType,
|
||||
nullSeatsEventType,
|
||||
customFieldsEventType,
|
||||
teamEventType,
|
||||
metadataEventType,
|
||||
] = await Promise.all([
|
||||
createComplexEventType,
|
||||
createSeatsEventType,
|
||||
createMaxSeatsEventType,
|
||||
createNullSeatsEventType,
|
||||
createCustomFieldsEventType,
|
||||
createTeamEventType,
|
||||
createMetadataEventType,
|
||||
]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.eventType.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: [
|
||||
complexEventType.id,
|
||||
seatsEventType.id,
|
||||
maxSeatsEventType.id,
|
||||
nullSeatsEventType.id,
|
||||
customFieldsEventType.id,
|
||||
teamEventType.id,
|
||||
metadataEventType.id,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.team.delete({
|
||||
where: { id: testTeam.id },
|
||||
});
|
||||
|
||||
await prisma.schedule.delete({
|
||||
where: { id: testSchedule.id },
|
||||
});
|
||||
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: [testUser.id, adminUser.id, teamUser.id],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("Errors", () => {
|
||||
test("Returns 403 if user not admin/team member/event owner", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = adminUser.id;
|
||||
|
||||
await expect(handler(req)).rejects.toThrowError(
|
||||
expect.objectContaining({
|
||||
statusCode: 403,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Success", async () => {
|
||||
test("Returns event type if user is admin", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = true;
|
||||
req.userId = adminUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.id).toEqual(complexEventType.id);
|
||||
});
|
||||
|
||||
test("Returns event type if user is in team associated with event type", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: teamEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = teamUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.id).toEqual(teamEventType.id);
|
||||
});
|
||||
|
||||
test("Returns event type if user is the event type owner", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.id).toEqual(complexEventType.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Data Validation", () => {
|
||||
test("Returns properly validated event type with complex locations", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type).toBeDefined();
|
||||
expect(responseData.event_type.id).toBe(complexEventType.id);
|
||||
expect(responseData.event_type.locations).toHaveLength(3);
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
|
||||
const locations = responseData.event_type.locations as Array<Record<string, unknown>>;
|
||||
expect(locations[0]).toMatchObject({
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
});
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<Record<string, unknown>>;
|
||||
expect(bookingFields[0]).toMatchObject({
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
});
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with seats configuration", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: seatsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(10);
|
||||
expect(responseData.event_type.seatsShowAttendees).toBe(true);
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBe(false);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with maximum seats", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: maxSeatsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(1000);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with null seats", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: nullSeatsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAttendees).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBeNull();
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with custom inputs and booking fields", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: customFieldsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<{
|
||||
name: string;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
maxLength?: number;
|
||||
}>;
|
||||
const selectField = bookingFields.find((f) => f.name === "customSelect");
|
||||
expect(selectField?.options).toHaveLength(3);
|
||||
expect(selectField?.options?.[0]).toMatchObject({
|
||||
label: "Morning",
|
||||
value: "morning",
|
||||
});
|
||||
|
||||
const textareaField = bookingFields.find((f) => f.name === "customTextarea");
|
||||
expect(textareaField?.maxLength).toBe(500);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with team configuration", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: teamEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = teamUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.teamId).toBe(testTeam.id);
|
||||
expect(responseData.event_type.schedulingType).toBe("COLLECTIVE");
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with complex metadata", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: metadataEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
expect(responseData.event_type.recurringEvent).toMatchObject({
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
});
|
||||
expect(responseData.event_type.bookingLimits).toMatchObject({
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
});
|
||||
expect(responseData.event_type.durationLimits).toMatchObject({
|
||||
PER_DAY: 480,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -165,4 +165,353 @@ describe("GET /api/event-types/[id]", () => {
|
||||
expect(prismaMock.team.findFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Data Validation", () => {
|
||||
test("Returns properly validated event type with complex locations", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const complexEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
locations: [
|
||||
{
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
{
|
||||
type: "attendeeInPerson",
|
||||
address: "123 Main St, City, State 12345",
|
||||
displayLocationPublicly: false,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
hostPhoneNumber: "+1234567890",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
editable: "system-but-optional",
|
||||
defaultLabel: "email_address",
|
||||
},
|
||||
{
|
||||
name: "customField",
|
||||
type: "text",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Custom Question",
|
||||
placeholder: "Enter your answer",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
},
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(complexEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type).toBeDefined();
|
||||
expect(responseData.event_type.id).toBe(eventTypeId);
|
||||
expect(responseData.event_type.locations).toHaveLength(3);
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
|
||||
const locations = responseData.event_type.locations as Array<Record<string, unknown>>;
|
||||
expect(locations[0]).toMatchObject({
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
});
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<Record<string, unknown>>;
|
||||
expect(bookingFields[0]).toMatchObject({
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
});
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with seats configuration", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const seatsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
seatsPerTimeSlot: 10,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: false,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(seatsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(10);
|
||||
expect(responseData.event_type.seatsShowAttendees).toBe(true);
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBe(false);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with maximum seats", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const maxSeatsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
seatsPerTimeSlot: 1000,
|
||||
seatsShowAttendees: false,
|
||||
seatsShowAvailabilityCount: true,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(maxSeatsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(1000);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with null seats", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const nullSeatsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
seatsPerTimeSlot: null,
|
||||
seatsShowAttendees: null,
|
||||
seatsShowAvailabilityCount: null,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(nullSeatsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAttendees).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBeNull();
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with custom inputs and booking fields", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const customFieldsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
variant: "fullName",
|
||||
},
|
||||
{
|
||||
name: "customSelect",
|
||||
type: "select",
|
||||
required: true,
|
||||
editable: "user",
|
||||
label: "Preferred Time",
|
||||
options: [
|
||||
{ label: "Morning", value: "morning" },
|
||||
{ label: "Afternoon", value: "afternoon" },
|
||||
{ label: "Evening", value: "evening" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "customTextarea",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Additional Information",
|
||||
placeholder: "Please provide any additional details",
|
||||
maxLength: 500,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(customFieldsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<{
|
||||
name: string;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
maxLength?: number;
|
||||
}>;
|
||||
const selectField = bookingFields.find((f) => f.name === "customSelect");
|
||||
expect(selectField?.options).toHaveLength(3);
|
||||
expect(selectField?.options?.[0]).toMatchObject({
|
||||
label: "Morning",
|
||||
value: "morning",
|
||||
});
|
||||
|
||||
const textareaField = bookingFields.find((f) => f.name === "customTextarea");
|
||||
expect(textareaField?.maxLength).toBe(500);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with team configuration", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const teamId = 9999;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const teamEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
teamId,
|
||||
userId: null,
|
||||
schedulingType: "COLLECTIVE",
|
||||
assignAllTeamMembers: true,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(teamEventType);
|
||||
prismaMock.team.findFirst.mockResolvedValue({
|
||||
id: teamId,
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
members: [
|
||||
{
|
||||
userId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.teamId).toBe(teamId);
|
||||
expect(responseData.event_type.schedulingType).toBe("COLLECTIVE");
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with complex metadata", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const metadataEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
disableStandardEmails: false,
|
||||
requiresConfirmationThreshold: {
|
||||
time: 30,
|
||||
unit: "minutes",
|
||||
},
|
||||
apps: {
|
||||
stripe: {
|
||||
price: 2500,
|
||||
currency: "usd",
|
||||
},
|
||||
},
|
||||
},
|
||||
recurringEvent: {
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
},
|
||||
bookingLimits: {
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
},
|
||||
durationLimits: {
|
||||
PER_DAY: 480,
|
||||
},
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(metadataEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
expect(responseData.event_type.recurringEvent).toMatchObject({
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
});
|
||||
expect(responseData.event_type.bookingLimits).toMatchObject({
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
});
|
||||
expect(responseData.event_type.durationLimits).toMatchObject({
|
||||
PER_DAY: 480,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user