From 07d5594085b52d4d6c6eabdb16d4a0eccd7ac9b5 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:11:20 +0000 Subject: [PATCH] feat: add integration test for RoutingFormResponseField triggers (#21586) * feat: add integration test for RoutingFormResponseField triggers Co-Authored-By: eunjae@cal.com * refactor: remove changing field types test case Co-Authored-By: eunjae@cal.com --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: eunjae@cal.com --- ...ng-form-response-field.integration-test.ts | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 packages/features/insights/__tests__/routing-form-response-field.integration-test.ts diff --git a/packages/features/insights/__tests__/routing-form-response-field.integration-test.ts b/packages/features/insights/__tests__/routing-form-response-field.integration-test.ts new file mode 100644 index 0000000000..d37b0ef603 --- /dev/null +++ b/packages/features/insights/__tests__/routing-form-response-field.integration-test.ts @@ -0,0 +1,451 @@ +import { Prisma } from "@prisma/client"; +import { describe, expect, beforeEach, afterEach, it } from "vitest"; + +import prisma from "@calcom/prisma"; +import { BookingStatus } from "@calcom/prisma/enums"; + +describe("RoutingFormResponseField", () => { + let userId: number; + let formId: string; + let eventTypeId: number; + let bookingId: number; + let responseId: number; + const randomId = Math.floor(Math.random() * 1000000); + const email = `routing-form-response-field-${randomId}@example.com`; + + beforeEach(async () => { + const user = await prisma.user.create({ + data: { + email, + username: `routing-form-response-field-testuser-${randomId}`, + name: "Test User", + }, + }); + userId = user.id; + + const eventType = await prisma.eventType.create({ + data: { + title: "Test Event", + slug: "test-event", + length: 60, + userId: user.id, + }, + }); + eventTypeId = eventType.id; + + const form = await prisma.app_RoutingForms_Form.create({ + data: { + name: "Test Form", + userId: user.id, + fields: [ + { + id: "text-field-id", + type: "text", + label: "Name", + required: true, + }, + { + id: "number-field-id", + type: "number", + label: "Rating", + required: false, + }, + { + id: "multiselect-field-id", + type: "multiselect", + label: "Skills", + required: false, + options: [ + { + id: "skill-1", + label: "JavaScript", + }, + { + id: "skill-2", + label: "TypeScript", + }, + { + id: "skill-3", + label: "React", + }, + ], + }, + { + id: "email-field-id", + type: "email", + label: "Email", + required: true, + }, + ], + }, + }); + formId = form.id; + + const booking = await prisma.booking.create({ + data: { + uid: `routing-form-response-field-${randomId}`, + title: "Test Booking", + startTime: new Date(), + endTime: new Date(Date.now() + 60 * 60 * 1000), + userId: user.id, + eventTypeId: eventType.id, + status: BookingStatus.PENDING, + }, + }); + bookingId = booking.id; + }); + + afterEach(async () => { + if (responseId) { + await prisma.routingFormResponseField.deleteMany({ + where: { responseId }, + }); + await prisma.routingFormResponseDenormalized.deleteMany({ + where: { id: responseId }, + }); + await prisma.app_RoutingForms_FormResponse.deleteMany({ + where: { id: responseId }, + }); + } + if (bookingId) { + await prisma.booking.deleteMany({ + where: { id: bookingId }, + }); + } + if (formId) { + await prisma.app_RoutingForms_Form.deleteMany({ + where: { id: formId }, + }); + } + if (eventTypeId) { + await prisma.eventType.deleteMany({ + where: { id: eventTypeId }, + }); + } + if (userId) { + await prisma.user.deleteMany({ + where: { id: userId }, + }); + } + }); + + describe("INSERT Trigger Tests", () => { + it("should create field entries when form response is created", async () => { + const formResponse = await prisma.app_RoutingForms_FormResponse.create({ + data: { + formFillerId: "test-filler-all-fields", + formId: formId, + response: { + "text-field-id": { + label: "Name", + value: "John Doe", + }, + "number-field-id": { + label: "Rating", + value: 5, + }, + "multiselect-field-id": { + label: "Skills", + value: ["skill-1", "skill-3"], + }, + "email-field-id": { + label: "Email", + value: "john.doe@example.com", + }, + }, + routedToBookingUid: null, + }, + }); + responseId = formResponse.id; + + const denormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(denormalizedResponse).not.toBeNull(); + expect(denormalizedResponse?.fields).toBeDefined(); + expect(denormalizedResponse?.fields).toHaveLength(4); + + const textField = denormalizedResponse?.fields.find((field) => field.fieldId === "text-field-id"); + expect(textField).toBeDefined(); + expect(textField?.valueString).toBe("John Doe"); + expect(textField?.valueNumber).toBeNull(); + expect(textField?.valueStringArray).toEqual([]); + + const numberField = denormalizedResponse?.fields.find((field) => field.fieldId === "number-field-id"); + expect(numberField).toBeDefined(); + expect(numberField?.valueString).toBeNull(); + expect(numberField?.valueNumber?.equals(new Prisma.Decimal(5))).toBe(true); + expect(numberField?.valueStringArray).toEqual([]); + + const multiselectField = denormalizedResponse?.fields.find( + (field) => field.fieldId === "multiselect-field-id" + ); + expect(multiselectField).toBeDefined(); + expect(multiselectField?.valueString).toBeNull(); + expect(multiselectField?.valueNumber).toBeNull(); + expect(multiselectField?.valueStringArray).toContain("skill-1"); + expect(multiselectField?.valueStringArray).toContain("skill-3"); + expect(multiselectField?.valueStringArray).toHaveLength(2); + + const emailField = denormalizedResponse?.fields.find((field) => field.fieldId === "email-field-id"); + expect(emailField).toBeDefined(); + expect(emailField?.valueString).toBe("john.doe@example.com"); + expect(emailField?.valueNumber).toBeNull(); + expect(emailField?.valueStringArray).toEqual([]); + }); + + it("should handle null and undefined field values", async () => { + const formResponse = await prisma.app_RoutingForms_FormResponse.create({ + data: { + formFillerId: "test-filler-null-values", + formId: formId, + response: { + "text-field-id": { + label: "Name", + value: null, + }, + "number-field-id": { + label: "Rating", + value: undefined, + }, + "multiselect-field-id": { + label: "Skills", + value: [], + }, + "email-field-id": { + label: "Email", + value: "", + }, + }, + routedToBookingUid: null, + }, + }); + responseId = formResponse.id; + + const denormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(denormalizedResponse).not.toBeNull(); + expect(denormalizedResponse?.fields).toBeDefined(); + expect(denormalizedResponse?.fields.length).toBeLessThanOrEqual(2); + + const emailField = denormalizedResponse?.fields.find((field) => field.fieldId === "email-field-id"); + if (emailField) { + expect(emailField.valueString).toBe(""); + } + + const multiselectField = denormalizedResponse?.fields.find( + (field) => field.fieldId === "multiselect-field-id" + ); + if (multiselectField) { + expect(multiselectField.valueStringArray).toEqual([]); + } + }); + + it("should handle invalid data types", async () => { + const formResponse = await prisma.app_RoutingForms_FormResponse.create({ + data: { + formFillerId: "test-filler-invalid-types", + formId: formId, + response: { + "text-field-id": { + label: "Name", + value: 123, // Number instead of string + }, + "number-field-id": { + label: "Rating", + value: "five", // String instead of number + }, + "multiselect-field-id": { + label: "Skills", + value: "skill-1", // String instead of array + }, + }, + routedToBookingUid: null, + }, + }); + responseId = formResponse.id; + + const denormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(denormalizedResponse).not.toBeNull(); + expect(denormalizedResponse?.fields).toBeDefined(); + }); + }); + + describe("UPDATE Trigger Tests", () => { + it("should update field entries when form response is updated", async () => { + const formResponse = await prisma.app_RoutingForms_FormResponse.create({ + data: { + formFillerId: "test-filler-update", + formId: formId, + response: { + "text-field-id": { + label: "Name", + value: "Original Name", + }, + "number-field-id": { + label: "Rating", + value: 3, + }, + "multiselect-field-id": { + label: "Skills", + value: ["skill-1"], + }, + }, + routedToBookingUid: null, + }, + }); + responseId = formResponse.id; + + const initialDenormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(initialDenormalizedResponse).not.toBeNull(); + expect(initialDenormalizedResponse?.fields).toBeDefined(); + expect(initialDenormalizedResponse?.fields).toHaveLength(3); + + const initialTextField = initialDenormalizedResponse?.fields.find( + (field) => field.fieldId === "text-field-id" + ); + expect(initialTextField?.valueString).toBe("Original Name"); + + await prisma.app_RoutingForms_FormResponse.update({ + where: { id: responseId }, + data: { + response: { + "text-field-id": { + label: "Name", + value: "Updated Name", + }, + "number-field-id": { + label: "Rating", + value: 5, + }, + "multiselect-field-id": { + label: "Skills", + value: ["skill-1", "skill-2"], + }, + "email-field-id": { + label: "Email", + value: "new@example.com", + }, + }, + }, + }); + + const updatedDenormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(updatedDenormalizedResponse).not.toBeNull(); + expect(updatedDenormalizedResponse?.fields).toBeDefined(); + expect(updatedDenormalizedResponse?.fields).toHaveLength(4); // Now we have 4 fields + + const updatedTextField = updatedDenormalizedResponse?.fields.find( + (field) => field.fieldId === "text-field-id" + ); + expect(updatedTextField).toBeDefined(); + expect(updatedTextField?.valueString).toBe("Updated Name"); + + const updatedNumberField = updatedDenormalizedResponse?.fields.find( + (field) => field.fieldId === "number-field-id" + ); + expect(updatedNumberField).toBeDefined(); + expect(updatedNumberField?.valueNumber?.equals(new Prisma.Decimal(5))).toBe(true); + + const updatedMultiselectField = updatedDenormalizedResponse?.fields.find( + (field) => field.fieldId === "multiselect-field-id" + ); + expect(updatedMultiselectField).toBeDefined(); + expect(updatedMultiselectField?.valueStringArray).toContain("skill-1"); + expect(updatedMultiselectField?.valueStringArray).toContain("skill-2"); + expect(updatedMultiselectField?.valueStringArray).toHaveLength(2); + + const newEmailField = updatedDenormalizedResponse?.fields.find( + (field) => field.fieldId === "email-field-id" + ); + expect(newEmailField).toBeDefined(); + expect(newEmailField?.valueString).toBe("new@example.com"); + }); + + it("should remove field entries when fields are removed from response", async () => { + const formResponse = await prisma.app_RoutingForms_FormResponse.create({ + data: { + formFillerId: "test-filler-remove-fields", + formId: formId, + response: { + "text-field-id": { + label: "Name", + value: "Test Name", + }, + "number-field-id": { + label: "Rating", + value: 4, + }, + "multiselect-field-id": { + label: "Skills", + value: ["skill-1", "skill-2"], + }, + "email-field-id": { + label: "Email", + value: "test@example.com", + }, + }, + routedToBookingUid: null, + }, + }); + responseId = formResponse.id; + + const initialDenormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(initialDenormalizedResponse).not.toBeNull(); + expect(initialDenormalizedResponse?.fields).toBeDefined(); + expect(initialDenormalizedResponse?.fields).toHaveLength(4); + + await prisma.app_RoutingForms_FormResponse.update({ + where: { id: responseId }, + data: { + response: { + "text-field-id": { + label: "Name", + value: "Test Name", + }, + "email-field-id": { + label: "Email", + value: "test@example.com", + }, + }, + }, + }); + + const updatedDenormalizedResponse = await prisma.routingFormResponseDenormalized.findUnique({ + where: { id: responseId }, + include: { fields: true }, + }); + + expect(updatedDenormalizedResponse).not.toBeNull(); + expect(updatedDenormalizedResponse?.fields).toBeDefined(); + expect(updatedDenormalizedResponse?.fields).toHaveLength(2); // Now we have only 2 fields + + const fieldIds = updatedDenormalizedResponse?.fields.map((field) => field.fieldId); + expect(fieldIds).toContain("text-field-id"); + expect(fieldIds).toContain("email-field-id"); + expect(fieldIds).not.toContain("number-field-id"); + expect(fieldIds).not.toContain("multiselect-field-id"); + }); + }); +});