From ac0930a11fd75a626c0d4f10a978b8096529211c Mon Sep 17 00:00:00 2001 From: Lauris Skraucis Date: Fri, 9 Aug 2024 15:14:28 +0200 Subject: [PATCH] test: v2 new event-types endpoints handle legacy event-types (#16144) * test: v2 new event-types endpoints return legacy event-types * Update event-types.repository.fixture.ts --- .../event-types.controller.e2e-spec.ts | 327 ++++++++++++++++++ .../event-types.repository.fixture.ts | 11 +- 2 files changed, 333 insertions(+), 5 deletions(-) diff --git a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts index 58bc495d8c..5485fad919 100644 --- a/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts +++ b/apps/api/v2/src/ee/event-types/event-types_2024_06_14/controllers/event-types.controller.e2e-spec.ts @@ -1,5 +1,6 @@ import { bootstrap } from "@/app"; import { AppModule } from "@/app.module"; +import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input"; import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module"; import { HttpExceptionFilter } from "@/filters/http-exception.filter"; import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; @@ -25,6 +26,7 @@ import { EventTypeOutput_2024_06_14, UpdateEventTypeInput_2024_06_14, } from "@calcom/platform-types"; +import { SchedulingType } from "@calcom/prisma/enums"; describe("Event types Endpoints", () => { describe("Not authenticated", () => { @@ -380,4 +382,329 @@ describe("Event types Endpoints", () => { await app.close(); }); }); + + describe("Handle legacy event-types booking fields", () => { + let app: INestApplication; + + let oAuthClient: PlatformOAuthClient; + let organization: Team; + let userRepositoryFixture: UserRepositoryFixture; + let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; + let teamRepositoryFixture: TeamRepositoryFixture; + let eventTypesRepositoryFixture: EventTypesRepositoryFixture; + + const userEmail = "legacy-event-types-test-e2e@api.com"; + const name = "bob-the-builder"; + const username = name; + let user: User; + let legacyEventTypeId1: number; + let legacyEventTypeId2: number; + + beforeAll(async () => { + const moduleRef = await withApiAuth( + userEmail, + Test.createTestingModule({ + providers: [PrismaExceptionFilter, HttpExceptionFilter], + imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule], + }) + ) + .overrideGuard(PermissionsGuard) + .useValue({ + canActivate: () => true, + }) + .compile(); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); + eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef); + + organization = await teamRepositoryFixture.create({ name: "organization" }); + oAuthClient = await createOAuthClient(organization.id); + user = await userRepositoryFixture.create({ + email: userEmail, + name, + username, + }); + + await app.init(); + }); + + async function createOAuthClient(organizationId: number) { + const data = { + logo: "logo-url", + name: "name", + redirectUris: ["redirect-uri"], + permissions: 32, + }; + const secret = "secret"; + + const client = await oauthClientRepositoryFixture.create(organizationId, data, secret); + return client; + } + + it("should be defined", () => { + expect(oauthClientRepositoryFixture).toBeDefined(); + expect(userRepositoryFixture).toBeDefined(); + expect(oAuthClient).toBeDefined(); + expect(user).toBeDefined(); + }); + + it("should not allow creating an event type with input of event-types version 2024_04_15", async () => { + const body: CreateEventTypeInput_2024_04_15 = { + title: "Coding class", + slug: "coding-class", + description: "Let's learn how to code like a pro.", + length: 60, + locations: [{ type: "integrations:daily" }], + }; + + return request(app.getHttpServer()) + .post("/api/v2/event-types") + .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) + .send(body) + .expect(400); + }); + + it("should return empty bookingFields if system fields are the only one in database", async () => { + const legacyEventTypeInput = { + title: "legacy event type", + description: "legacy event type description", + length: 40, + hidden: false, + slug: "legacy-event-type", + locations: [], + schedulingType: SchedulingType.ROUND_ROBIN, + bookingFields: [ + { + name: "name", + type: "name", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system", + required: true, + defaultLabel: "your_name", + }, + { + name: "email", + type: "email", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system", + required: true, + defaultLabel: "email_address", + }, + { + name: "location", + type: "radioInput", + label: "", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system", + required: false, + placeholder: "", + defaultLabel: "location", + getOptionsAt: "locations", + optionsInputs: { + phone: { type: "phone", required: true, placeholder: "" }, + attendeeInPerson: { type: "address", required: true, placeholder: "" }, + }, + hideWhenJustOneOption: true, + }, + { + name: "title", + type: "text", + hidden: true, + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: true, + defaultLabel: "what_is_this_meeting_about", + defaultPlaceholder: "", + }, + { + name: "notes", + type: "textarea", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: false, + defaultLabel: "additional_notes", + defaultPlaceholder: "share_additional_notes", + }, + { + name: "guests", + type: "multiemail", + hidden: false, + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: false, + defaultLabel: "additional_guests", + defaultPlaceholder: "email", + }, + { + name: "rescheduleReason", + type: "textarea", + views: [{ id: "reschedule", label: "Reschedule View" }], + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: false, + defaultLabel: "reason_for_reschedule", + defaultPlaceholder: "reschedule_placeholder", + }, + ], + }; + const legacyEventType = await eventTypesRepositoryFixture.create(legacyEventTypeInput, user.id); + legacyEventTypeId1 = legacyEventType.id; + + return request(app.getHttpServer()) + .get(`/api/v2/event-types/${legacyEventTypeId1}`) + .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) + .expect(200) + .then(async (response) => { + const responseBody: ApiSuccessResponse = response.body; + const fetchedEventType = responseBody.data; + expect(fetchedEventType.bookingFields).toEqual([]); + }); + }); + + it("should return user created bookingFields among system fields in the database", async () => { + const userDefinedBookingField = { + name: "team", + type: "textarea", + label: "your team", + sources: [ + { + id: "user", + type: "user", + label: "User", + fieldRequired: true, + }, + ], + editable: "user", + required: true, + placeholder: "FC Barcelona", + }; + + const legacyEventTypeInput = { + title: "legacy event type two", + description: "legacy event type description two", + length: 40, + hidden: false, + slug: "legacy-event-type-two", + locations: [], + schedulingType: SchedulingType.ROUND_ROBIN, + bookingFields: [ + userDefinedBookingField, + { + name: "name", + type: "name", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system", + required: true, + defaultLabel: "your_name", + }, + { + name: "email", + type: "email", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system", + required: true, + defaultLabel: "email_address", + }, + { + name: "location", + type: "radioInput", + label: "", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system", + required: false, + placeholder: "", + defaultLabel: "location", + getOptionsAt: "locations", + optionsInputs: { + phone: { type: "phone", required: true, placeholder: "" }, + attendeeInPerson: { type: "address", required: true, placeholder: "" }, + }, + hideWhenJustOneOption: true, + }, + { + name: "title", + type: "text", + hidden: true, + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: true, + defaultLabel: "what_is_this_meeting_about", + defaultPlaceholder: "", + }, + { + name: "notes", + type: "textarea", + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: false, + defaultLabel: "additional_notes", + defaultPlaceholder: "share_additional_notes", + }, + { + name: "guests", + type: "multiemail", + hidden: false, + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: false, + defaultLabel: "additional_guests", + defaultPlaceholder: "email", + }, + { + name: "rescheduleReason", + type: "textarea", + views: [{ id: "reschedule", label: "Reschedule View" }], + sources: [{ id: "default", type: "default", label: "Default" }], + editable: "system-but-optional", + required: false, + defaultLabel: "reason_for_reschedule", + defaultPlaceholder: "reschedule_placeholder", + }, + ], + }; + const legacyEventType = await eventTypesRepositoryFixture.create(legacyEventTypeInput, user.id); + legacyEventTypeId2 = legacyEventType.id; + + return request(app.getHttpServer()) + .get(`/api/v2/event-types/${legacyEventTypeId2}`) + .set(CAL_API_VERSION_HEADER, VERSION_2024_06_14) + .expect(200) + .then(async (response) => { + const responseBody: ApiSuccessResponse = response.body; + const fetchedEventType = responseBody.data; + + expect(fetchedEventType.bookingFields).toEqual([ + { + type: userDefinedBookingField.type, + slug: userDefinedBookingField.name, + label: userDefinedBookingField.label, + required: userDefinedBookingField.required, + placeholder: userDefinedBookingField.placeholder, + }, + ]); + }); + }); + + afterAll(async () => { + await oauthClientRepositoryFixture.delete(oAuthClient.id); + await teamRepositoryFixture.delete(organization.id); + try { + await eventTypesRepositoryFixture.delete(legacyEventTypeId1); + await eventTypesRepositoryFixture.delete(legacyEventTypeId2); + } catch (e) { + // Event type might have been deleted by the test + } + try { + await userRepositoryFixture.delete(user.id); + } catch (e) { + // User might have been deleted by the test + } + await app.close(); + }); + }); }); diff --git a/apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts b/apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts index 160d002f90..2d0e57c20d 100644 --- a/apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts +++ b/apps/api/v2/test/fixtures/repository/event-types.repository.fixture.ts @@ -34,14 +34,15 @@ export class EventTypesRepositoryFixture { }); } - async create( - data: Pick, - userId: number - ) { + async create(data: Prisma.EventTypeCreateInput, userId: number) { return this.prismaWriteClient.eventType.create({ data: { ...data, - userId, + owner: { + connect: { + id: userId, + }, + }, }, }); }