refactor: convert RoutingFormResponseRepository to use dependency injection pattern (#22408)
* refactor: convert RoutingFormResponseRepository to use dependency injection pattern - Add constructor with PrismaClient dependency injection - Convert all 5 static methods to instance methods using this.prismaClient - Convert private static helper method generateCreateFormResponseData to instance method - Update all usage sites to use two-step instantiation pattern: const formResponseRepo = new RoutingFormResponseRepository(prisma); formResponseRepo.method(...) - Update test files to use proper mock implementation with vi.mocked pattern - Reuse repository instances within same function scope where multiple calls are made - Follow same dependency injection pattern as UserRepository, TeamRepository, SelectedSlotsRepository, BookingRepository, and EventTypeRepository Co-Authored-By: morgan@cal.com <morgan@cal.com> * fix: resolve inconsistent dependency injection in AvailableSlotsService - Use two-step instantiation pattern for UserRepository calls - Continue fixing remaining repository instantiation inconsistencies Co-Authored-By: morgan@cal.com <morgan@cal.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
co-authored by
morgan@cal.com
Morgan
parent
1c9778e667
commit
c9a47dd0ce
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { beforeEach, describe, it, expect, vi } from "vitest";
|
||||
|
||||
import { onSubmissionOfFormResponse } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
|
||||
import { getResponseToStore } from "@calcom/app-store/routing-forms/lib/getResponseToStore";
|
||||
@@ -8,6 +8,11 @@ import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/for
|
||||
import { queuedResponseHandler } from "../route";
|
||||
|
||||
vi.mock("@calcom/lib/server/repository/formResponse");
|
||||
|
||||
const mockRoutingFormResponseRepository = {
|
||||
getQueuedFormResponseFromId: vi.fn(),
|
||||
recordFormResponse: vi.fn(),
|
||||
};
|
||||
vi.mock("@calcom/app-store/routing-forms/lib/getSerializableForm");
|
||||
vi.mock("@calcom/app-store/routing-forms/lib/getResponseToStore");
|
||||
vi.mock("@calcom/app-store/routing-forms/lib/formSubmissionUtils");
|
||||
@@ -43,8 +48,14 @@ const mockQueuedFormResponse = {
|
||||
};
|
||||
|
||||
describe("queuedResponseHandler", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(RoutingFormResponseRepository).mockImplementation(
|
||||
() => mockRoutingFormResponseRepository as any
|
||||
);
|
||||
});
|
||||
|
||||
it("should process a queued form response", async () => {
|
||||
vi.mocked(RoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
|
||||
vi.mocked(mockRoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
|
||||
mockQueuedFormResponse
|
||||
);
|
||||
|
||||
@@ -90,7 +101,7 @@ describe("queuedResponseHandler", () => {
|
||||
team: null,
|
||||
} as unknown as Awaited<ReturnType<typeof onSubmissionOfFormResponse>>);
|
||||
|
||||
vi.mocked(RoutingFormResponseRepository.recordFormResponse).mockResolvedValue({
|
||||
vi.mocked(mockRoutingFormResponseRepository.recordFormResponse).mockResolvedValue({
|
||||
id: "mock-form-id",
|
||||
name: "Test Form",
|
||||
description: "Test Form Description",
|
||||
@@ -102,7 +113,7 @@ describe("queuedResponseHandler", () => {
|
||||
},
|
||||
team: null,
|
||||
chosenRouteId: "mock-chosen-route-id",
|
||||
} as unknown as Awaited<ReturnType<typeof RoutingFormResponseRepository.recordFormResponse>>);
|
||||
} as unknown as Awaited<ReturnType<typeof mockRoutingFormResponseRepository.recordFormResponse>>);
|
||||
|
||||
const response = await queuedResponseHandler({
|
||||
queuedFormResponseId: "1",
|
||||
@@ -115,7 +126,7 @@ describe("queuedResponseHandler", () => {
|
||||
});
|
||||
|
||||
it("if no queued form response is found, should return early", async () => {
|
||||
vi.mocked(RoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(null);
|
||||
vi.mocked(mockRoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(null);
|
||||
const response = await queuedResponseHandler({
|
||||
queuedFormResponseId: "1",
|
||||
params: {},
|
||||
@@ -127,7 +138,7 @@ describe("queuedResponseHandler", () => {
|
||||
});
|
||||
|
||||
it("should throw if form has no fields", async () => {
|
||||
vi.mocked(RoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
|
||||
vi.mocked(mockRoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
|
||||
mockQueuedFormResponse
|
||||
);
|
||||
vi.mocked(getSerializableForm).mockResolvedValue({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSeri
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { defaultResponderForAppDir } from "../../defaultResponderForAppDir";
|
||||
|
||||
@@ -23,10 +24,10 @@ export const queuedResponseHandler = async ({
|
||||
queuedFormResponseId: string;
|
||||
params: Record<string, string | string[]>;
|
||||
}) => {
|
||||
const formResponseRepo = new RoutingFormResponseRepository(prisma);
|
||||
|
||||
// Get the queued response
|
||||
const queuedFormResponse = await RoutingFormResponseRepository.getQueuedFormResponseFromId(
|
||||
queuedFormResponseId
|
||||
);
|
||||
const queuedFormResponse = await formResponseRepo.getQueuedFormResponseFromId(queuedFormResponseId);
|
||||
|
||||
if (!queuedFormResponse) {
|
||||
return {
|
||||
@@ -48,7 +49,7 @@ export const queuedResponseHandler = async ({
|
||||
fieldsResponses: params,
|
||||
});
|
||||
|
||||
const formResponse = await RoutingFormResponseRepository.recordFormResponse({
|
||||
const formResponse = await formResponseRepo.recordFormResponse({
|
||||
formId: queuedFormResponse.formId,
|
||||
queuedFormResponseId: queuedFormResponse.id,
|
||||
// We record new response here as that might be different from the queued response depending on if the user changed something in b/w before clicking CTA and that something wasn't prerendered
|
||||
|
||||
@@ -17,12 +17,12 @@ vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({
|
||||
findTeamMembersMatchingAttributeLogic: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/server/repository/formResponse", () => ({
|
||||
RoutingFormResponseRepository: {
|
||||
recordQueuedFormResponse: vi.fn(),
|
||||
recordFormResponse: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock("@calcom/lib/server/repository/formResponse");
|
||||
|
||||
const mockRoutingFormResponseRepository = {
|
||||
recordQueuedFormResponse: vi.fn(),
|
||||
recordFormResponse: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock("./crmRouting/routerGetCrmContactOwnerEmail", () => ({
|
||||
default: vi.fn(),
|
||||
@@ -107,6 +107,9 @@ const mockResponse: z.infer<typeof ZResponseInputSchema>["response"] = {
|
||||
describe("handleResponse", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.mocked(RoutingFormResponseRepository).mockImplementation(
|
||||
() => mockRoutingFormResponseRepository as any
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw a TRPCError for missing required fields", async () => {
|
||||
@@ -150,7 +153,7 @@ describe("handleResponse", () => {
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
vi.mocked(RoutingFormResponseRepository.recordFormResponse).mockResolvedValue(dbFormResponse);
|
||||
vi.mocked(mockRoutingFormResponseRepository.recordFormResponse).mockResolvedValue(dbFormResponse);
|
||||
|
||||
const result = await handleResponse({
|
||||
response: mockResponse,
|
||||
@@ -161,7 +164,7 @@ describe("handleResponse", () => {
|
||||
isPreview: false,
|
||||
});
|
||||
|
||||
expect(RoutingFormResponseRepository.recordFormResponse).toHaveBeenCalledWith({
|
||||
expect(mockRoutingFormResponseRepository.recordFormResponse).toHaveBeenCalledWith({
|
||||
formId: mockForm.id,
|
||||
response: mockResponse,
|
||||
chosenRouteId: null,
|
||||
@@ -185,7 +188,7 @@ describe("handleResponse", () => {
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
vi.mocked(RoutingFormResponseRepository.recordQueuedFormResponse).mockResolvedValue(queuedResponse);
|
||||
vi.mocked(mockRoutingFormResponseRepository.recordQueuedFormResponse).mockResolvedValue(queuedResponse);
|
||||
|
||||
const result = await handleResponse({
|
||||
response: mockResponse,
|
||||
@@ -197,12 +200,12 @@ describe("handleResponse", () => {
|
||||
queueFormResponse: true,
|
||||
});
|
||||
|
||||
expect(RoutingFormResponseRepository.recordQueuedFormResponse).toHaveBeenCalledWith({
|
||||
expect(mockRoutingFormResponseRepository.recordQueuedFormResponse).toHaveBeenCalledWith({
|
||||
formId: mockForm.id,
|
||||
response: mockResponse,
|
||||
chosenRouteId: null,
|
||||
});
|
||||
expect(RoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
|
||||
expect(mockRoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
|
||||
expect(onSubmissionOfFormResponse).not.toHaveBeenCalled();
|
||||
expect(result.queuedFormResponse).toEqual(queuedResponse);
|
||||
expect(result.formResponse).toBeNull();
|
||||
@@ -219,7 +222,7 @@ describe("handleResponse", () => {
|
||||
isPreview: true,
|
||||
});
|
||||
|
||||
expect(RoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
|
||||
expect(mockRoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
|
||||
expect(onSubmissionOfFormResponse).not.toHaveBeenCalled();
|
||||
expect(result.isPreview).toBe(true);
|
||||
expect(result.formResponse).toBeDefined();
|
||||
@@ -237,7 +240,7 @@ describe("handleResponse", () => {
|
||||
queueFormResponse: true,
|
||||
});
|
||||
|
||||
expect(RoutingFormResponseRepository.recordQueuedFormResponse).not.toHaveBeenCalled();
|
||||
expect(mockRoutingFormResponseRepository.recordQueuedFormResponse).not.toHaveBeenCalled();
|
||||
expect(result.isPreview).toBe(true);
|
||||
expect(result.queuedFormResponse).toBeDefined();
|
||||
expect(result.queuedFormResponse?.id).toBe("00000000-0000-0000-0000-000000000000");
|
||||
|
||||
@@ -7,6 +7,7 @@ import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeam
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { withReporting } from "@calcom/lib/sentryWrapper";
|
||||
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { ZResponseInputSchema } from "@calcom/trpc/server/routers/viewer/routing-forms/response.schema";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -161,15 +162,16 @@ const _handleResponse = async ({
|
||||
}
|
||||
let dbFormResponse, queuedFormResponse;
|
||||
if (!isPreview) {
|
||||
const formResponseRepo = new RoutingFormResponseRepository(prisma);
|
||||
if (queueFormResponse) {
|
||||
queuedFormResponse = await RoutingFormResponseRepository.recordQueuedFormResponse({
|
||||
queuedFormResponse = await formResponseRepo.recordQueuedFormResponse({
|
||||
formId: form.id,
|
||||
response,
|
||||
chosenRouteId,
|
||||
});
|
||||
dbFormResponse = null;
|
||||
} else {
|
||||
dbFormResponse = await RoutingFormResponseRepository.recordFormResponse({
|
||||
dbFormResponse = await formResponseRepo.recordFormResponse({
|
||||
formId: form.id,
|
||||
response,
|
||||
chosenRouteId,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
interface RecordFormResponseInput {
|
||||
@@ -8,7 +8,9 @@ interface RecordFormResponseInput {
|
||||
}
|
||||
|
||||
export class RoutingFormResponseRepository {
|
||||
private static generateCreateFormResponseData(
|
||||
constructor(private prismaClient: PrismaClient) {}
|
||||
|
||||
private generateCreateFormResponseData(
|
||||
input: RecordFormResponseInput & { queuedFormResponseId?: string | null }
|
||||
) {
|
||||
return {
|
||||
@@ -27,24 +29,24 @@ export class RoutingFormResponseRepository {
|
||||
};
|
||||
}
|
||||
|
||||
static async recordFormResponse(
|
||||
async recordFormResponse(
|
||||
input: RecordFormResponseInput & {
|
||||
queuedFormResponseId?: string | null;
|
||||
}
|
||||
) {
|
||||
return await prisma.app_RoutingForms_FormResponse.create({
|
||||
return await this.prismaClient.app_RoutingForms_FormResponse.create({
|
||||
data: this.generateCreateFormResponseData(input),
|
||||
});
|
||||
}
|
||||
|
||||
static async recordQueuedFormResponse(input: RecordFormResponseInput) {
|
||||
return await prisma.app_RoutingForms_QueuedFormResponse.create({
|
||||
async recordQueuedFormResponse(input: RecordFormResponseInput) {
|
||||
return await this.prismaClient.app_RoutingForms_QueuedFormResponse.create({
|
||||
data: this.generateCreateFormResponseData(input),
|
||||
});
|
||||
}
|
||||
|
||||
static async findFormResponseIncludeForm({ routingFormResponseId }: { routingFormResponseId: number }) {
|
||||
return await prisma.app_RoutingForms_FormResponse.findUnique({
|
||||
async findFormResponseIncludeForm({ routingFormResponseId }: { routingFormResponseId: number }) {
|
||||
return await this.prismaClient.app_RoutingForms_FormResponse.findUnique({
|
||||
where: {
|
||||
id: routingFormResponseId,
|
||||
},
|
||||
@@ -61,8 +63,8 @@ export class RoutingFormResponseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async findQueuedFormResponseIncludeForm({ queuedFormResponseId }: { queuedFormResponseId: string }) {
|
||||
return await prisma.app_RoutingForms_QueuedFormResponse.findUnique({
|
||||
async findQueuedFormResponseIncludeForm({ queuedFormResponseId }: { queuedFormResponseId: string }) {
|
||||
return await this.prismaClient.app_RoutingForms_QueuedFormResponse.findUnique({
|
||||
where: {
|
||||
id: queuedFormResponseId,
|
||||
},
|
||||
@@ -79,8 +81,8 @@ export class RoutingFormResponseRepository {
|
||||
});
|
||||
}
|
||||
|
||||
static async getQueuedFormResponseFromId(id: string) {
|
||||
return await prisma.app_RoutingForms_QueuedFormResponse.findUnique({
|
||||
async getQueuedFormResponseFromId(id: string) {
|
||||
return await this.prismaClient.app_RoutingForms_QueuedFormResponse.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
|
||||
@@ -142,7 +142,8 @@ export class AvailableSlotsService {
|
||||
}
|
||||
const dynamicEventType = getDefaultEvent(input.eventTypeSlug);
|
||||
|
||||
const usersForDynamicEventType = await new UserRepository(prisma).findManyUsersForDynamicEventType({
|
||||
const userRepo = new UserRepository(prisma);
|
||||
const usersForDynamicEventType = await userRepo.findManyUsersForDynamicEventType({
|
||||
currentOrgDomain: isValidOrgDomain ? currentOrgDomain : null,
|
||||
usernameList: Array.isArray(input.usernameList)
|
||||
? input.usernameList
|
||||
@@ -249,7 +250,8 @@ export class AvailableSlotsService {
|
||||
) {
|
||||
const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
|
||||
log.info("getUserIdFromUsername", safeStringify({ organizationDetails, username }));
|
||||
const [user] = await new UserRepository(prisma).findUsersByUsername({
|
||||
const userRepo = new UserRepository(prisma);
|
||||
const [user] = await userRepo.findUsersByUsername({
|
||||
usernameList: [username],
|
||||
orgSlug: isValidOrgDomain ? currentOrgDomain : null,
|
||||
});
|
||||
@@ -938,11 +940,13 @@ export class AvailableSlotsService {
|
||||
|
||||
let routingFormResponse = null;
|
||||
if (routingFormResponseId) {
|
||||
routingFormResponse = await RoutingFormResponseRepository.findFormResponseIncludeForm({
|
||||
const formResponseRepo = new RoutingFormResponseRepository(prisma);
|
||||
routingFormResponse = await formResponseRepo.findFormResponseIncludeForm({
|
||||
routingFormResponseId,
|
||||
});
|
||||
} else if (queuedFormResponseId) {
|
||||
routingFormResponse = await RoutingFormResponseRepository.findQueuedFormResponseIncludeForm({
|
||||
const formResponseRepo = new RoutingFormResponseRepository(prisma);
|
||||
routingFormResponse = await formResponseRepo.findQueuedFormResponseIncludeForm({
|
||||
queuedFormResponseId,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user