fix: v2 slots not returning attendees and bookingUid info (#18858)
* fix: v2 slots not returning attendees and bookingUid info * rename file * finish merge main
This commit is contained in:
@@ -12,6 +12,8 @@ import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { AttendeeRepositoryFixture } from "test/fixtures/repository/attendee.repository.fixture";
|
||||
import { BookingSeatRepositoryFixture } from "test/fixtures/repository/booking-seat.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { SelectedSlotsRepositoryFixture } from "test/fixtures/repository/selected-slots.repository.fixture";
|
||||
@@ -250,6 +252,8 @@ describe("Slots Endpoints", () => {
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
let selectedSlotsRepositoryFixture: SelectedSlotsRepositoryFixture;
|
||||
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||
let bookingSeatsRepositoryFixture: BookingSeatRepositoryFixture;
|
||||
let attendeesRepositoryFixture: AttendeeRepositoryFixture;
|
||||
|
||||
const userEmail = `slots-${randomString()}-controller-e2e@api.com`;
|
||||
const userName = "bob";
|
||||
@@ -258,6 +262,9 @@ describe("Slots Endpoints", () => {
|
||||
let eventTypeSlug: string;
|
||||
let reservedSlotUid: string;
|
||||
|
||||
const seatedEventTypeSlug = "peer-coding-seated";
|
||||
let seatedEventTypeId: number;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
@@ -283,6 +290,8 @@ describe("Slots Endpoints", () => {
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
selectedSlotsRepositoryFixture = new SelectedSlotsRepositoryFixture(moduleRef);
|
||||
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
|
||||
bookingSeatsRepositoryFixture = new BookingSeatRepositoryFixture(moduleRef);
|
||||
attendeesRepositoryFixture = new AttendeeRepositoryFixture(moduleRef);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -291,7 +300,7 @@ describe("Slots Endpoints", () => {
|
||||
});
|
||||
|
||||
// nxte(Lauris): this creates default schedule monday to friday from 9AM to 5PM in Europe/Rome timezone
|
||||
const userSchedule = await schedulesService.createUserSchedule(user.id, {
|
||||
await schedulesService.createUserSchedule(user.id, {
|
||||
name: `slots-schedule-${randomString()}-slots.controller.e2e-spec`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
@@ -306,9 +315,24 @@ describe("Slots Endpoints", () => {
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
eventTypeId = eventType.id;
|
||||
eventTypeSlug = eventType.slug;
|
||||
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `slots-event-type-seated-${randomString()}-slots.controller.e2e-spec`,
|
||||
slug: `slots-event-type-seated-${randomString()}-slots.controller.e2e-spec`,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
},
|
||||
user.id
|
||||
);
|
||||
seatedEventTypeId = seatedEvent.id;
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
@@ -445,7 +469,7 @@ describe("Slots Endpoints", () => {
|
||||
|
||||
it("should do a booking and slot should not be available at that time", async () => {
|
||||
const startTime = "2050-09-05T11:00:00.000Z";
|
||||
await bookingsRepositoryFixture.create({
|
||||
const booking = await bookingsRepositoryFixture.create({
|
||||
uid: `booking-uid-${eventTypeId}`,
|
||||
title: "booking title",
|
||||
startTime,
|
||||
@@ -486,6 +510,179 @@ describe("Slots Endpoints", () => {
|
||||
expect(slots).toEqual({
|
||||
slots: { ...expectedSlotsUTC.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
|
||||
});
|
||||
|
||||
await bookingsRepositoryFixture.deleteById(booking.id);
|
||||
});
|
||||
|
||||
it("should do a booking for seated event and slot should show attendees count and bookingUid", async () => {
|
||||
const startTime = "2050-09-05T11:00:00.000Z";
|
||||
const booking = await bookingsRepositoryFixture.create({
|
||||
uid: `booking-uid-${seatedEventTypeId}`,
|
||||
title: "booking title",
|
||||
startTime,
|
||||
endTime: "2050-09-05T12:00:00.000Z",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: seatedEventTypeId,
|
||||
},
|
||||
},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "tester",
|
||||
email: "tester@example.com",
|
||||
guests: [],
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const attendee = await attendeesRepositoryFixture.create({
|
||||
name: "tester",
|
||||
email: "tester@example.com",
|
||||
timeZone: "Europe/London",
|
||||
booking: {
|
||||
connect: {
|
||||
id: booking.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
bookingSeatsRepositoryFixture.create({
|
||||
referenceUid: "100",
|
||||
data: {},
|
||||
booking: {
|
||||
connect: {
|
||||
id: booking.id,
|
||||
},
|
||||
},
|
||||
attendee: {
|
||||
connect: {
|
||||
id: attendee.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(
|
||||
`/api/v2/slots/available?eventTypeId=${seatedEventTypeId}&startTime=2050-09-05&endTime=2050-09-10`
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const slots = responseBody.data;
|
||||
|
||||
expect(slots).toBeDefined();
|
||||
const days = Object.keys(slots.slots);
|
||||
expect(days.length).toEqual(5);
|
||||
|
||||
const expectedSlotsUTC2050_09_05 = [
|
||||
{ time: "2050-09-05T07:00:00.000Z" },
|
||||
{ time: "2050-09-05T08:00:00.000Z" },
|
||||
{ time: "2050-09-05T09:00:00.000Z" },
|
||||
{ time: "2050-09-05T10:00:00.000Z" },
|
||||
{ time: "2050-09-05T11:00:00.000Z", attendees: 1, bookingUid: booking.uid },
|
||||
{ time: "2050-09-05T12:00:00.000Z" },
|
||||
{ time: "2050-09-05T13:00:00.000Z" },
|
||||
{ time: "2050-09-05T14:00:00.000Z" },
|
||||
];
|
||||
|
||||
expect(slots).toEqual({
|
||||
slots: { ...expectedSlotsUTC.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
|
||||
});
|
||||
|
||||
await bookingsRepositoryFixture.deleteById(booking.id);
|
||||
});
|
||||
|
||||
it("should do a booking for seated event and slot should show attendees count and bookingUid in range format", async () => {
|
||||
const startTime = "2050-09-05T11:00:00.000Z";
|
||||
const booking = await bookingsRepositoryFixture.create({
|
||||
uid: `booking-uid-${seatedEventTypeId}`,
|
||||
title: "booking title",
|
||||
startTime,
|
||||
endTime: "2050-09-05T12:00:00.000Z",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: seatedEventTypeId,
|
||||
},
|
||||
},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "tester",
|
||||
email: "tester@example.com",
|
||||
guests: [],
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const attendee = await attendeesRepositoryFixture.create({
|
||||
name: "tester",
|
||||
email: "tester@example.com",
|
||||
timeZone: "Europe/London",
|
||||
booking: {
|
||||
connect: {
|
||||
id: booking.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
bookingSeatsRepositoryFixture.create({
|
||||
referenceUid: "100",
|
||||
data: {},
|
||||
booking: {
|
||||
connect: {
|
||||
id: booking.id,
|
||||
},
|
||||
},
|
||||
attendee: {
|
||||
connect: {
|
||||
id: attendee.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(
|
||||
`/api/v2/slots/available?eventTypeId=${seatedEventTypeId}&startTime=2050-09-05&endTime=2050-09-10&slotFormat=range`
|
||||
)
|
||||
.expect(200);
|
||||
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const slots = responseBody.data;
|
||||
|
||||
expect(slots).toBeDefined();
|
||||
const days = Object.keys(slots.slots);
|
||||
expect(days.length).toEqual(5);
|
||||
|
||||
const expectedSlotsUTC2050_09_05 = [
|
||||
{ startTime: "2050-09-05T07:00:00.000Z", endTime: "2050-09-05T08:00:00.000Z" },
|
||||
{ startTime: "2050-09-05T08:00:00.000Z", endTime: "2050-09-05T09:00:00.000Z" },
|
||||
{ startTime: "2050-09-05T09:00:00.000Z", endTime: "2050-09-05T10:00:00.000Z" },
|
||||
{ startTime: "2050-09-05T10:00:00.000Z", endTime: "2050-09-05T11:00:00.000Z" },
|
||||
{
|
||||
startTime: "2050-09-05T11:00:00.000Z",
|
||||
endTime: "2050-09-05T12:00:00.000Z",
|
||||
attendees: 1,
|
||||
bookingUid: booking.uid,
|
||||
},
|
||||
{ startTime: "2050-09-05T12:00:00.000Z", endTime: "2050-09-05T13:00:00.000Z" },
|
||||
{ startTime: "2050-09-05T13:00:00.000Z", endTime: "2050-09-05T14:00:00.000Z" },
|
||||
{ startTime: "2050-09-05T14:00:00.000Z", endTime: "2050-09-05T15:00:00.000Z" },
|
||||
];
|
||||
|
||||
expect(slots).toEqual({
|
||||
slots: { ...expectedSlotsUTCRange.slots, "2050-09-05": expectedSlotsUTC2050_09_05 },
|
||||
});
|
||||
|
||||
await bookingsRepositoryFixture.deleteById(booking.id);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@@ -4,8 +4,10 @@ import { DateTime } from "luxon";
|
||||
|
||||
import { SlotFormat } from "@calcom/platform-enums";
|
||||
|
||||
type TimeSlots = { slots: Record<string, { time: string }[]> };
|
||||
type RangeSlots = { slots: Record<string, { startTime: string; endTime: string }[]> };
|
||||
type TimeSlots = { slots: Record<string, { time: string; attendees?: number; bookingUid?: string }[]> };
|
||||
type RangeSlots = {
|
||||
slots: Record<string, { startTime: string; endTime: string; attendees?: number; bookingUid?: string }[]>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SlotsOutputService {
|
||||
@@ -30,6 +32,8 @@ export class SlotsOutputService {
|
||||
const formattedSlots = Object.entries(slots.slots).reduce((acc, [date, daySlots]) => {
|
||||
acc[date] = daySlots.map((slot) => ({
|
||||
time: DateTime.fromISO(slot.time).setZone(timeZone).toISO() || "unknown-time",
|
||||
...(slot.attendees ? { attendees: slot.attendees } : {}),
|
||||
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
|
||||
}));
|
||||
return acc;
|
||||
}, {} as Record<string, { time: string }[]>);
|
||||
@@ -42,6 +46,8 @@ export class SlotsOutputService {
|
||||
acc[date] = daySlots.map((slot) => ({
|
||||
startTime: DateTime.fromISO(slot.startTime).setZone(timeZone).toISO() || "unknown-start-time",
|
||||
endTime: DateTime.fromISO(slot.endTime).setZone(timeZone).toISO() || "unknown-end-time",
|
||||
...(slot.attendees ? { attendees: slot.attendees } : {}),
|
||||
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
|
||||
}));
|
||||
return acc;
|
||||
}, {} as Record<string, { startTime: string; endTime: string }[]>);
|
||||
@@ -62,14 +68,16 @@ export class SlotsOutputService {
|
||||
const slotDuration = await this.getDuration(duration, eventTypeId);
|
||||
|
||||
const slots = Object.entries(availableSlots.slots).reduce<
|
||||
Record<string, { startTime: string; endTime: string }[]>
|
||||
Record<string, { startTime: string; endTime: string; attendees?: number; bookingUid?: string }[]>
|
||||
>((acc, [date, slots]) => {
|
||||
acc[date] = (slots as { time: string }[]).map((slot) => {
|
||||
acc[date] = (slots as { time: string; attendees?: number; bookingUid?: string }[]).map((slot) => {
|
||||
const startTime = new Date(slot.time);
|
||||
const endTime = new Date(startTime.getTime() + slotDuration * 60000);
|
||||
return {
|
||||
startTime: startTime.toISOString(),
|
||||
endTime: endTime.toISOString(),
|
||||
...(slot.attendees ? { attendees: slot.attendees } : {}),
|
||||
...(slot.bookingUid ? { bookingUid: slot.bookingUid } : {}),
|
||||
};
|
||||
});
|
||||
return acc;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { TestingModule } from "@nestjs/testing";
|
||||
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
export class AttendeeRepositoryFixture {
|
||||
private prismaReadClient: PrismaReadService["prisma"];
|
||||
private prismaWriteClient: PrismaWriteService["prisma"];
|
||||
|
||||
constructor(private readonly module: TestingModule) {
|
||||
this.prismaReadClient = module.get(PrismaReadService).prisma;
|
||||
this.prismaWriteClient = module.get(PrismaWriteService).prisma;
|
||||
}
|
||||
|
||||
async create(attendee: Prisma.AttendeeCreateInput) {
|
||||
return this.prismaWriteClient.attendee.create({ data: attendee });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { TestingModule } from "@nestjs/testing";
|
||||
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
export class BookingSeatRepositoryFixture {
|
||||
private prismaReadClient: PrismaReadService["prisma"];
|
||||
private prismaWriteClient: PrismaWriteService["prisma"];
|
||||
|
||||
constructor(private readonly module: TestingModule) {
|
||||
this.prismaReadClient = module.get(PrismaReadService).prisma;
|
||||
this.prismaWriteClient = module.get(PrismaWriteService).prisma;
|
||||
}
|
||||
|
||||
async create(bookingSeat: Prisma.BookingSeatCreateInput) {
|
||||
return this.prismaWriteClient.bookingSeat.create({ data: bookingSeat });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user