Skip to content

Commit c9a47dd

Browse files
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>
1 parent 1c9778e commit c9a47dd

6 files changed

Lines changed: 64 additions & 41 deletions

File tree

apps/web/app/api/routing-forms/queued-response/__tests__/queued-response.test.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect, vi } from "vitest";
1+
import { beforeEach, describe, it, expect, vi } from "vitest";
22

33
import { onSubmissionOfFormResponse } from "@calcom/app-store/routing-forms/lib/formSubmissionUtils";
44
import { getResponseToStore } from "@calcom/app-store/routing-forms/lib/getResponseToStore";
@@ -8,6 +8,11 @@ import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/for
88
import { queuedResponseHandler } from "../route";
99

1010
vi.mock("@calcom/lib/server/repository/formResponse");
11+
12+
const mockRoutingFormResponseRepository = {
13+
getQueuedFormResponseFromId: vi.fn(),
14+
recordFormResponse: vi.fn(),
15+
};
1116
vi.mock("@calcom/app-store/routing-forms/lib/getSerializableForm");
1217
vi.mock("@calcom/app-store/routing-forms/lib/getResponseToStore");
1318
vi.mock("@calcom/app-store/routing-forms/lib/formSubmissionUtils");
@@ -43,8 +48,14 @@ const mockQueuedFormResponse = {
4348
};
4449

4550
describe("queuedResponseHandler", () => {
51+
beforeEach(() => {
52+
vi.mocked(RoutingFormResponseRepository).mockImplementation(
53+
() => mockRoutingFormResponseRepository as any
54+
);
55+
});
56+
4657
it("should process a queued form response", async () => {
47-
vi.mocked(RoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
58+
vi.mocked(mockRoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
4859
mockQueuedFormResponse
4960
);
5061

@@ -90,7 +101,7 @@ describe("queuedResponseHandler", () => {
90101
team: null,
91102
} as unknown as Awaited<ReturnType<typeof onSubmissionOfFormResponse>>);
92103

93-
vi.mocked(RoutingFormResponseRepository.recordFormResponse).mockResolvedValue({
104+
vi.mocked(mockRoutingFormResponseRepository.recordFormResponse).mockResolvedValue({
94105
id: "mock-form-id",
95106
name: "Test Form",
96107
description: "Test Form Description",
@@ -102,7 +113,7 @@ describe("queuedResponseHandler", () => {
102113
},
103114
team: null,
104115
chosenRouteId: "mock-chosen-route-id",
105-
} as unknown as Awaited<ReturnType<typeof RoutingFormResponseRepository.recordFormResponse>>);
116+
} as unknown as Awaited<ReturnType<typeof mockRoutingFormResponseRepository.recordFormResponse>>);
106117

107118
const response = await queuedResponseHandler({
108119
queuedFormResponseId: "1",
@@ -115,7 +126,7 @@ describe("queuedResponseHandler", () => {
115126
});
116127

117128
it("if no queued form response is found, should return early", async () => {
118-
vi.mocked(RoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(null);
129+
vi.mocked(mockRoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(null);
119130
const response = await queuedResponseHandler({
120131
queuedFormResponseId: "1",
121132
params: {},
@@ -127,7 +138,7 @@ describe("queuedResponseHandler", () => {
127138
});
128139

129140
it("should throw if form has no fields", async () => {
130-
vi.mocked(RoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
141+
vi.mocked(mockRoutingFormResponseRepository.getQueuedFormResponseFromId).mockResolvedValue(
131142
mockQueuedFormResponse
132143
);
133144
vi.mocked(getSerializableForm).mockResolvedValue({

apps/web/app/api/routing-forms/queued-response/route.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getSerializableForm } from "@calcom/app-store/routing-forms/lib/getSeri
88
import logger from "@calcom/lib/logger";
99
import { safeStringify } from "@calcom/lib/safeStringify";
1010
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
11+
import prisma from "@calcom/prisma";
1112

1213
import { defaultResponderForAppDir } from "../../defaultResponderForAppDir";
1314

@@ -23,10 +24,10 @@ export const queuedResponseHandler = async ({
2324
queuedFormResponseId: string;
2425
params: Record<string, string | string[]>;
2526
}) => {
27+
const formResponseRepo = new RoutingFormResponseRepository(prisma);
28+
2629
// Get the queued response
27-
const queuedFormResponse = await RoutingFormResponseRepository.getQueuedFormResponseFromId(
28-
queuedFormResponseId
29-
);
30+
const queuedFormResponse = await formResponseRepo.getQueuedFormResponseFromId(queuedFormResponseId);
3031

3132
if (!queuedFormResponse) {
3233
return {
@@ -48,7 +49,7 @@ export const queuedResponseHandler = async ({
4849
fieldsResponses: params,
4950
});
5051

51-
const formResponse = await RoutingFormResponseRepository.recordFormResponse({
52+
const formResponse = await formResponseRepo.recordFormResponse({
5253
formId: queuedFormResponse.formId,
5354
queuedFormResponseId: queuedFormResponse.id,
5455
// 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

packages/app-store/routing-forms/lib/handleResponse.test.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({
1717
findTeamMembersMatchingAttributeLogic: vi.fn(),
1818
}));
1919

20-
vi.mock("@calcom/lib/server/repository/formResponse", () => ({
21-
RoutingFormResponseRepository: {
22-
recordQueuedFormResponse: vi.fn(),
23-
recordFormResponse: vi.fn(),
24-
},
25-
}));
20+
vi.mock("@calcom/lib/server/repository/formResponse");
21+
22+
const mockRoutingFormResponseRepository = {
23+
recordQueuedFormResponse: vi.fn(),
24+
recordFormResponse: vi.fn(),
25+
};
2626

2727
vi.mock("./crmRouting/routerGetCrmContactOwnerEmail", () => ({
2828
default: vi.fn(),
@@ -107,6 +107,9 @@ const mockResponse: z.infer<typeof ZResponseInputSchema>["response"] = {
107107
describe("handleResponse", () => {
108108
beforeEach(() => {
109109
vi.resetAllMocks();
110+
vi.mocked(RoutingFormResponseRepository).mockImplementation(
111+
() => mockRoutingFormResponseRepository as any
112+
);
110113
});
111114

112115
it("should throw a TRPCError for missing required fields", async () => {
@@ -150,7 +153,7 @@ describe("handleResponse", () => {
150153
createdAt: new Date(),
151154
updatedAt: new Date(),
152155
};
153-
vi.mocked(RoutingFormResponseRepository.recordFormResponse).mockResolvedValue(dbFormResponse);
156+
vi.mocked(mockRoutingFormResponseRepository.recordFormResponse).mockResolvedValue(dbFormResponse);
154157

155158
const result = await handleResponse({
156159
response: mockResponse,
@@ -161,7 +164,7 @@ describe("handleResponse", () => {
161164
isPreview: false,
162165
});
163166

164-
expect(RoutingFormResponseRepository.recordFormResponse).toHaveBeenCalledWith({
167+
expect(mockRoutingFormResponseRepository.recordFormResponse).toHaveBeenCalledWith({
165168
formId: mockForm.id,
166169
response: mockResponse,
167170
chosenRouteId: null,
@@ -185,7 +188,7 @@ describe("handleResponse", () => {
185188
createdAt: new Date(),
186189
updatedAt: new Date(),
187190
};
188-
vi.mocked(RoutingFormResponseRepository.recordQueuedFormResponse).mockResolvedValue(queuedResponse);
191+
vi.mocked(mockRoutingFormResponseRepository.recordQueuedFormResponse).mockResolvedValue(queuedResponse);
189192

190193
const result = await handleResponse({
191194
response: mockResponse,
@@ -197,12 +200,12 @@ describe("handleResponse", () => {
197200
queueFormResponse: true,
198201
});
199202

200-
expect(RoutingFormResponseRepository.recordQueuedFormResponse).toHaveBeenCalledWith({
203+
expect(mockRoutingFormResponseRepository.recordQueuedFormResponse).toHaveBeenCalledWith({
201204
formId: mockForm.id,
202205
response: mockResponse,
203206
chosenRouteId: null,
204207
});
205-
expect(RoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
208+
expect(mockRoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
206209
expect(onSubmissionOfFormResponse).not.toHaveBeenCalled();
207210
expect(result.queuedFormResponse).toEqual(queuedResponse);
208211
expect(result.formResponse).toBeNull();
@@ -219,7 +222,7 @@ describe("handleResponse", () => {
219222
isPreview: true,
220223
});
221224

222-
expect(RoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
225+
expect(mockRoutingFormResponseRepository.recordFormResponse).not.toHaveBeenCalled();
223226
expect(onSubmissionOfFormResponse).not.toHaveBeenCalled();
224227
expect(result.isPreview).toBe(true);
225228
expect(result.formResponse).toBeDefined();
@@ -237,7 +240,7 @@ describe("handleResponse", () => {
237240
queueFormResponse: true,
238241
});
239242

240-
expect(RoutingFormResponseRepository.recordQueuedFormResponse).not.toHaveBeenCalled();
243+
expect(mockRoutingFormResponseRepository.recordQueuedFormResponse).not.toHaveBeenCalled();
241244
expect(result.isPreview).toBe(true);
242245
expect(result.queuedFormResponse).toBeDefined();
243246
expect(result.queuedFormResponse?.id).toBe("00000000-0000-0000-0000-000000000000");

packages/app-store/routing-forms/lib/handleResponse.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { findTeamMembersMatchingAttributeLogic } from "@calcom/lib/raqb/findTeam
77
import { safeStringify } from "@calcom/lib/safeStringify";
88
import { withReporting } from "@calcom/lib/sentryWrapper";
99
import { RoutingFormResponseRepository } from "@calcom/lib/server/repository/formResponse";
10+
import prisma from "@calcom/prisma";
1011
import type { ZResponseInputSchema } from "@calcom/trpc/server/routers/viewer/routing-forms/response.schema";
1112

1213
import { TRPCError } from "@trpc/server";
@@ -161,15 +162,16 @@ const _handleResponse = async ({
161162
}
162163
let dbFormResponse, queuedFormResponse;
163164
if (!isPreview) {
165+
const formResponseRepo = new RoutingFormResponseRepository(prisma);
164166
if (queueFormResponse) {
165-
queuedFormResponse = await RoutingFormResponseRepository.recordQueuedFormResponse({
167+
queuedFormResponse = await formResponseRepo.recordQueuedFormResponse({
166168
formId: form.id,
167169
response,
168170
chosenRouteId,
169171
});
170172
dbFormResponse = null;
171173
} else {
172-
dbFormResponse = await RoutingFormResponseRepository.recordFormResponse({
174+
dbFormResponse = await formResponseRepo.recordFormResponse({
173175
formId: form.id,
174176
response,
175177
chosenRouteId,

packages/lib/server/repository/formResponse.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import prisma from "@calcom/prisma";
1+
import type { PrismaClient } from "@calcom/prisma";
22
import type { Prisma } from "@calcom/prisma/client";
33

44
interface RecordFormResponseInput {
@@ -8,7 +8,9 @@ interface RecordFormResponseInput {
88
}
99

1010
export class RoutingFormResponseRepository {
11-
private static generateCreateFormResponseData(
11+
constructor(private prismaClient: PrismaClient) {}
12+
13+
private generateCreateFormResponseData(
1214
input: RecordFormResponseInput & { queuedFormResponseId?: string | null }
1315
) {
1416
return {
@@ -27,24 +29,24 @@ export class RoutingFormResponseRepository {
2729
};
2830
}
2931

30-
static async recordFormResponse(
32+
async recordFormResponse(
3133
input: RecordFormResponseInput & {
3234
queuedFormResponseId?: string | null;
3335
}
3436
) {
35-
return await prisma.app_RoutingForms_FormResponse.create({
37+
return await this.prismaClient.app_RoutingForms_FormResponse.create({
3638
data: this.generateCreateFormResponseData(input),
3739
});
3840
}
3941

40-
static async recordQueuedFormResponse(input: RecordFormResponseInput) {
41-
return await prisma.app_RoutingForms_QueuedFormResponse.create({
42+
async recordQueuedFormResponse(input: RecordFormResponseInput) {
43+
return await this.prismaClient.app_RoutingForms_QueuedFormResponse.create({
4244
data: this.generateCreateFormResponseData(input),
4345
});
4446
}
4547

46-
static async findFormResponseIncludeForm({ routingFormResponseId }: { routingFormResponseId: number }) {
47-
return await prisma.app_RoutingForms_FormResponse.findUnique({
48+
async findFormResponseIncludeForm({ routingFormResponseId }: { routingFormResponseId: number }) {
49+
return await this.prismaClient.app_RoutingForms_FormResponse.findUnique({
4850
where: {
4951
id: routingFormResponseId,
5052
},
@@ -61,8 +63,8 @@ export class RoutingFormResponseRepository {
6163
});
6264
}
6365

64-
static async findQueuedFormResponseIncludeForm({ queuedFormResponseId }: { queuedFormResponseId: string }) {
65-
return await prisma.app_RoutingForms_QueuedFormResponse.findUnique({
66+
async findQueuedFormResponseIncludeForm({ queuedFormResponseId }: { queuedFormResponseId: string }) {
67+
return await this.prismaClient.app_RoutingForms_QueuedFormResponse.findUnique({
6668
where: {
6769
id: queuedFormResponseId,
6870
},
@@ -79,8 +81,8 @@ export class RoutingFormResponseRepository {
7981
});
8082
}
8183

82-
static async getQueuedFormResponseFromId(id: string) {
83-
return await prisma.app_RoutingForms_QueuedFormResponse.findUnique({
84+
async getQueuedFormResponseFromId(id: string) {
85+
return await this.prismaClient.app_RoutingForms_QueuedFormResponse.findUnique({
8486
where: {
8587
id,
8688
},

packages/trpc/server/routers/viewer/slots/util.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ export class AvailableSlotsService {
142142
}
143143
const dynamicEventType = getDefaultEvent(input.eventTypeSlug);
144144

145-
const usersForDynamicEventType = await new UserRepository(prisma).findManyUsersForDynamicEventType({
145+
const userRepo = new UserRepository(prisma);
146+
const usersForDynamicEventType = await userRepo.findManyUsersForDynamicEventType({
146147
currentOrgDomain: isValidOrgDomain ? currentOrgDomain : null,
147148
usernameList: Array.isArray(input.usernameList)
148149
? input.usernameList
@@ -249,7 +250,8 @@ export class AvailableSlotsService {
249250
) {
250251
const { currentOrgDomain, isValidOrgDomain } = organizationDetails;
251252
log.info("getUserIdFromUsername", safeStringify({ organizationDetails, username }));
252-
const [user] = await new UserRepository(prisma).findUsersByUsername({
253+
const userRepo = new UserRepository(prisma);
254+
const [user] = await userRepo.findUsersByUsername({
253255
usernameList: [username],
254256
orgSlug: isValidOrgDomain ? currentOrgDomain : null,
255257
});
@@ -938,11 +940,13 @@ export class AvailableSlotsService {
938940

939941
let routingFormResponse = null;
940942
if (routingFormResponseId) {
941-
routingFormResponse = await RoutingFormResponseRepository.findFormResponseIncludeForm({
943+
const formResponseRepo = new RoutingFormResponseRepository(prisma);
944+
routingFormResponse = await formResponseRepo.findFormResponseIncludeForm({
942945
routingFormResponseId,
943946
});
944947
} else if (queuedFormResponseId) {
945-
routingFormResponse = await RoutingFormResponseRepository.findQueuedFormResponseIncludeForm({
948+
const formResponseRepo = new RoutingFormResponseRepository(prisma);
949+
routingFormResponse = await formResponseRepo.findQueuedFormResponseIncludeForm({
946950
queuedFormResponseId,
947951
});
948952
}

0 commit comments

Comments
 (0)