From 22548af6c5f3f05e1fafda789cf5ee26949bd606 Mon Sep 17 00:00:00 2001 From: Duy Luong <33130695+lmduy2612@users.noreply.github.com> Date: Fri, 2 Oct 2026 16:01:58 +0700 Subject: [PATCH] fix(quizzes): align selectedIndex max in submitQuizSchema with MAX_QUIZ_OPTIONS (#538) --- src/modules/quizzes/quiz.types.ts | 21 ++++++++++++--------- tests/unit/schemas/input-validation.test.ts | 14 +++++++++++--- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/modules/quizzes/quiz.types.ts b/src/modules/quizzes/quiz.types.ts index 9621a11..f8002e8 100644 --- a/src/modules/quizzes/quiz.types.ts +++ b/src/modules/quizzes/quiz.types.ts @@ -24,6 +24,16 @@ export const MAX_RETRIES_PER_MODULE_PER_DAY = 3; // calls). export const MAX_QUIZ_GENERATIONS_PER_MODULE_PER_HOUR = 5; +/** Smallest workable number of choices for a multiple-choice question. */ +export const MIN_QUIZ_OPTIONS = 2; + +/** + * Upper bound on choices per question (#388, #538). + * Aligns both authoredQuestionSchema and submitQuizSchema so a user cannot + * submit answer indices beyond the allowable options range. + */ +export const MAX_QUIZ_OPTIONS = 10; + // ─── Request Schemas ──────────────────────────────────────────────────────── export const generateQuizSchema = z.object({ @@ -53,8 +63,8 @@ export const submitQuizSchema = z.object({ .array( z.object({ questionId: z.string().min(1).max(100), - // Bound the index so out-of-range values can't be submitted. - selectedIndex: z.number().int().min(0).max(20), + // Bound the index so out-of-range values can't be submitted (#538). + selectedIndex: z.number().int().min(0).max(MAX_QUIZ_OPTIONS - 1), }) ) .min(1, "At least one answer is required") @@ -83,13 +93,6 @@ export const quizFeedbackSummaryQuerySchema = z.object({ // ─── Admin: Manual Quiz Authoring (#388) ───────────────────────────────────── -/** Smallest workable number of choices for a multiple-choice question. */ -export const MIN_QUIZ_OPTIONS = 2; -/** Upper bound on choices per question. Capped well below - * submitQuizSchema's static max(20) so a hand-authored question can never - * exceed what a submitted answer index can address. */ -export const MAX_QUIZ_OPTIONS = 10; - /** * One hand-authored question (#388). Matches the shape QuizService stores * after AI generation, minus the shuffle bookkeeping (see the note on diff --git a/tests/unit/schemas/input-validation.test.ts b/tests/unit/schemas/input-validation.test.ts index b5ba2f1..f0f1dc8 100644 --- a/tests/unit/schemas/input-validation.test.ts +++ b/tests/unit/schemas/input-validation.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; import { updateProfileSchema } from "../../../src/modules/users/user.types.js"; -import { submitQuizSchema } from "../../../src/modules/quizzes/quiz.types.js"; +import { submitQuizSchema, MAX_QUIZ_OPTIONS } from "../../../src/modules/quizzes/quiz.types.js"; import { createCourseSchema, listCoursesSchema, @@ -54,9 +54,17 @@ describe("submitQuizSchema", () => { expect(submitQuizSchema.safeParse({ answers: [] }).success).toBe(false); }); - it("rejects selectedIndex above the max bound", () => { + it("accepts selectedIndex within bounds (up to MAX_QUIZ_OPTIONS - 1)", () => { + expect( + submitQuizSchema.safeParse({ + answers: [{ questionId: "q1", selectedIndex: MAX_QUIZ_OPTIONS - 1 }], + }).success + ).toBe(true); + }); + + it("rejects selectedIndex at or above the MAX_QUIZ_OPTIONS bound", () => { const result = submitQuizSchema.safeParse({ - answers: [{ questionId: "q1", selectedIndex: 21 }], + answers: [{ questionId: "q1", selectedIndex: MAX_QUIZ_OPTIONS }], }); expect(result.success).toBe(false); });