Compare commits

...

1 Commits

Author SHA1 Message Date
Cursor Agent
13c7a4a0e9 fix: ensure quotaFull property is always present in response object to prevent destructuring TypeError
Fixes FORMBRICKS-KP

The issue occurred when evaluateResponseQuotas returned an error result
without the quotaFull property. The conditional spread operator would then
omit quotaFull from the response object, causing a TypeError when the route
handler attempted to destructure it.

Changes:
- Always include quotaFull property in response objects from createResponseWithQuotaEvaluation
- Explicitly include quotaFull property in evaluateResponseQuotas error returns
- Update tests to reflect new behavior where quotaFull is always present
2026-02-18 11:26:48 +00:00
7 changed files with 41 additions and 14 deletions

View File

@@ -98,10 +98,11 @@ describe("updateResponseWithQuotaEvaluation", () => {
});
});
test("should return response without quotaFull when quota evaluation returns no quotaFull", async () => {
test("should return response with quotaFull as undefined when quota evaluation returns no quotaFull", async () => {
mockUpdateResponse.mockResolvedValue(mockResponse);
mockEvaluateResponseQuotas.mockResolvedValue({
shouldEndSurvey: false,
quotaFull: undefined,
});
const result = await updateResponseWithQuotaEvaluation(mockResponseId, mockResponseInput);
@@ -117,8 +118,11 @@ describe("updateResponseWithQuotaEvaluation", () => {
tx: mockTx,
});
expect(result).toEqual(mockResponse);
expect(result).not.toHaveProperty("quotaFull");
expect(result).toEqual({
...mockResponse,
quotaFull: undefined,
});
expect(result).toHaveProperty("quotaFull");
});
test("should use default language when response language is null", async () => {
@@ -126,6 +130,7 @@ describe("updateResponseWithQuotaEvaluation", () => {
mockUpdateResponse.mockResolvedValue(responseWithNullLanguage);
mockEvaluateResponseQuotas.mockResolvedValue({
shouldEndSurvey: false,
quotaFull: undefined,
});
const result = await updateResponseWithQuotaEvaluation(mockResponseId, mockResponseInput);
@@ -140,6 +145,9 @@ describe("updateResponseWithQuotaEvaluation", () => {
tx: mockTx,
});
expect(result).toEqual(responseWithNullLanguage);
expect(result).toEqual({
...responseWithNullLanguage,
quotaFull: undefined,
});
});
});

View File

@@ -23,7 +23,7 @@ export const updateResponseWithQuotaEvaluation = async (
return {
...response,
...(quotaResult.quotaFull && { quotaFull: quotaResult.quotaFull }),
quotaFull: quotaResult.quotaFull,
};
});

View File

@@ -163,7 +163,7 @@ describe("createResponseWithQuotaEvaluation", () => {
mockIsFormbricksCloud = false;
});
test("should return response without quotaFull when no quota violations", async () => {
test("should return response with quotaFull as undefined when no quota violations", async () => {
// Mock quota evaluation to return no violations
vi.mocked(evaluateResponseQuotas).mockResolvedValue({
shouldEndSurvey: false,
@@ -198,8 +198,9 @@ describe("createResponseWithQuotaEvaluation", () => {
displayId: null,
contact: null,
tags: [],
quotaFull: undefined,
});
expect(result).not.toHaveProperty("quotaFull");
expect(result).toHaveProperty("quotaFull");
});
test("should return response with quotaFull when quota is exceeded with endSurvey action", async () => {

View File

@@ -69,7 +69,7 @@ export const createResponseWithQuotaEvaluation = async (
return {
...response,
...(quotaResult.quotaFull && { quotaFull: quotaResult.quotaFull }),
quotaFull: quotaResult.quotaFull,
};
});

View File

@@ -225,10 +225,13 @@ describe("createResponseWithQuotaEvaluation V2", () => {
});
});
test("should create response and return it without quotaFull when no quota is full", async () => {
test("should create response and return it with quotaFull as null when no quota is full", async () => {
const result = await createResponseWithQuotaEvaluation(mockResponseInput);
expect(result).toEqual(expectedResponse);
expect(result).toEqual({
...expectedResponse,
quotaFull: null,
});
expect(evaluateResponseQuotas).toHaveBeenCalledWith({
surveyId: mockResponseInput.surveyId,
responseId: expectedResponse.id,
@@ -262,4 +265,19 @@ describe("createResponseWithQuotaEvaluation V2", () => {
tx: mockTx,
});
});
test("should handle quota evaluation returning undefined quotaFull without throwing", async () => {
vi.mocked(evaluateResponseQuotas).mockResolvedValue({
shouldEndSurvey: false,
quotaFull: undefined,
});
const result: TResponseWithQuotaFull = await createResponseWithQuotaEvaluation(mockResponseInput);
expect(result).toEqual({
...expectedResponse,
quotaFull: undefined,
});
expect(result).toHaveProperty("quotaFull");
});
});

View File

@@ -32,7 +32,7 @@ export const createResponseWithQuotaEvaluation = async (
return {
...response,
...(quotaResult.quotaFull && { quotaFull: quotaResult.quotaFull }),
quotaFull: quotaResult.quotaFull,
};
});

View File

@@ -44,12 +44,12 @@ export const evaluateResponseQuotas = async (input: QuotaEvaluationInput): Promi
const quotas = await getQuotas(surveyId);
if (!quotas || quotas.length === 0) {
return { shouldEndSurvey: false };
return { shouldEndSurvey: false, quotaFull: undefined };
}
const survey = await getSurvey(surveyId);
if (!survey) {
return { shouldEndSurvey: false };
return { shouldEndSurvey: false, quotaFull: undefined };
}
const isDefaultLanguage = survey.languages.find((lang) => lang.default)?.language.code === language;
const result = evaluateQuotas(survey, data, variables, quotas, isDefaultLanguage ? "default" : language);
@@ -74,6 +74,6 @@ export const evaluateResponseQuotas = async (input: QuotaEvaluationInput): Promi
};
} catch (error) {
logger.error({ error, responseId }, "Error evaluating quotas for response");
return { shouldEndSurvey: false };
return { shouldEndSurvey: false, quotaFull: undefined };
}
};