Skip to content

Commit b2fefdc

Browse files
authored
fixing production bugs and adding tests to make sure these bugs dont … (#176)
* fixing production bugs and adding tests to make sure these bugs dont regress * update fix * update
1 parent d8d3540 commit b2fefdc

16 files changed

+2332
-54
lines changed

.secrets.baseline

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": null,
44
"lines": null
55
},
6-
"generated_at": "2025-10-20T16:35:31Z",
6+
"generated_at": "2025-10-23T17:29:45Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -149,7 +149,7 @@
149149
{
150150
"hashed_secret": "2c0580ffd7d80319531cf629f5e90f747b1386f1",
151151
"is_verified": false,
152-
"line_number": 2500,
152+
"line_number": 2561,
153153
"type": "Secret Keyword",
154154
"verified_result": null
155155
}
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
/* eslint-disable */
2+
import { Test, TestingModule } from "@nestjs/testing";
3+
import { QuestionType } from "@prisma/client";
4+
import { CreateQuestionResponseAttemptRequestDto } from "src/api/assignment/attempt/dto/question-response/create.question.response.attempt.request.dto";
5+
import { QuestionDto } from "src/api/assignment/dto/update.questions.request.dto";
6+
import { Logger } from "winston";
7+
import { GRADING_AUDIT_SERVICE } from "../../../attempt.constants";
8+
import { GradingContext } from "../../interfaces/grading-context.interface";
9+
import { LocalizationService } from "../../utils/localization.service";
10+
import { ChoiceGradingStrategy } from "../choice-grading.strategy";
11+
12+
describe("ChoiceGradingStrategy - Type Safety Tests", () => {
13+
let strategy: ChoiceGradingStrategy;
14+
15+
beforeEach(async () => {
16+
const mockLogger = {
17+
error: jest.fn(),
18+
warn: jest.fn(),
19+
log: jest.fn(),
20+
debug: jest.fn(),
21+
info: jest.fn(),
22+
child: jest.fn().mockReturnThis(),
23+
} as unknown as Logger;
24+
25+
const module: TestingModule = await Test.createTestingModule({
26+
providers: [
27+
ChoiceGradingStrategy,
28+
{
29+
provide: LocalizationService,
30+
useValue: {
31+
getLocalizedString: jest.fn((key: string) => key),
32+
},
33+
},
34+
{
35+
provide: GRADING_AUDIT_SERVICE,
36+
useValue: {
37+
recordGrading: jest.fn(),
38+
},
39+
},
40+
{
41+
provide: "winston",
42+
useValue: mockLogger,
43+
},
44+
],
45+
}).compile();
46+
47+
strategy = module.get<ChoiceGradingStrategy>(ChoiceGradingStrategy);
48+
});
49+
50+
describe("extractLearnerResponse - Type Safety", () => {
51+
it("should handle null learnerChoices", async () => {
52+
const requestDto = {
53+
learnerChoices: null,
54+
} as any as CreateQuestionResponseAttemptRequestDto;
55+
56+
const result = await strategy.extractLearnerResponse(requestDto);
57+
expect(result).toEqual([]);
58+
});
59+
60+
it("should handle undefined learnerChoices", async () => {
61+
const requestDto = {
62+
learnerChoices: undefined,
63+
} as any as CreateQuestionResponseAttemptRequestDto;
64+
65+
const result = await strategy.extractLearnerResponse(requestDto);
66+
expect(result).toEqual([]);
67+
});
68+
69+
it("should accept valid string array", async () => {
70+
const requestDto = {
71+
learnerChoices: ["choice1", "choice2"],
72+
} as any as CreateQuestionResponseAttemptRequestDto;
73+
74+
const result = await strategy.extractLearnerResponse(requestDto);
75+
expect(result).toEqual(["choice1", "choice2"]);
76+
});
77+
78+
it("should accept empty array", async () => {
79+
const requestDto = {
80+
learnerChoices: [],
81+
} as any as CreateQuestionResponseAttemptRequestDto;
82+
83+
const result = await strategy.extractLearnerResponse(requestDto);
84+
expect(result).toEqual([]);
85+
});
86+
87+
it("should accept single choice as array", async () => {
88+
const requestDto = {
89+
learnerChoices: ["single-choice"],
90+
} as any as CreateQuestionResponseAttemptRequestDto;
91+
92+
const result = await strategy.extractLearnerResponse(requestDto);
93+
expect(result).toEqual(["single-choice"]);
94+
});
95+
96+
it("should convert numeric learner choices to strings", async () => {
97+
const requestDto = {
98+
learnerChoices: [1, 2, 3],
99+
} as any as CreateQuestionResponseAttemptRequestDto;
100+
101+
const result = await strategy.extractLearnerResponse(requestDto);
102+
expect(result).toEqual(["1", "2", "3"]);
103+
});
104+
105+
it("should extract text from object based learner choices", async () => {
106+
const requestDto = {
107+
learnerChoices: [
108+
{ value: "Option A" },
109+
{ label: "Option B" },
110+
{ choice: { text: "Option C" } },
111+
],
112+
} as any as CreateQuestionResponseAttemptRequestDto;
113+
114+
const result = await strategy.extractLearnerResponse(requestDto);
115+
expect(result).toEqual(["Option A", "Option B", "Option C"]);
116+
});
117+
});
118+
119+
describe("validateResponse - Single Choice", () => {
120+
const mockSingleChoiceQuestion: QuestionDto = {
121+
id: 1,
122+
question: "Choose one option",
123+
type: QuestionType.SINGLE_CORRECT,
124+
totalPoints: 10,
125+
assignmentId: 1,
126+
gradingContextQuestionIds: [],
127+
choices: [
128+
{ id: 1, choice: "Option A", isCorrect: true, points: 10 },
129+
{ id: 2, choice: "Option B", isCorrect: false, points: 0 },
130+
],
131+
} as any;
132+
133+
it("should reject multiple choices for single-choice question", async () => {
134+
const requestDto = {
135+
learnerChoices: ["choice1", "choice2"],
136+
language: "en",
137+
} as any as CreateQuestionResponseAttemptRequestDto;
138+
139+
await expect(
140+
strategy.validateResponse(mockSingleChoiceQuestion, requestDto),
141+
).rejects.toThrow();
142+
});
143+
144+
it("should accept single choice for single-choice question", async () => {
145+
const requestDto = {
146+
learnerChoices: ["choice1"],
147+
language: "en",
148+
} as any as CreateQuestionResponseAttemptRequestDto;
149+
150+
const result = await strategy.validateResponse(
151+
mockSingleChoiceQuestion,
152+
requestDto,
153+
);
154+
expect(result).toBe(true);
155+
});
156+
157+
it("should accept empty array for single-choice question", async () => {
158+
const requestDto = {
159+
learnerChoices: [],
160+
language: "en",
161+
} as any as CreateQuestionResponseAttemptRequestDto;
162+
163+
const result = await strategy.validateResponse(
164+
mockSingleChoiceQuestion,
165+
requestDto,
166+
);
167+
expect(result).toBe(true);
168+
});
169+
170+
it("should accept null choices for single-choice question", async () => {
171+
const requestDto = {
172+
learnerChoices: null,
173+
language: "en",
174+
} as any as CreateQuestionResponseAttemptRequestDto;
175+
176+
const result = await strategy.validateResponse(
177+
mockSingleChoiceQuestion,
178+
requestDto,
179+
);
180+
expect(result).toBe(true);
181+
});
182+
});
183+
184+
describe("validateResponse - Multiple Choice", () => {
185+
const mockMultipleChoiceQuestion: QuestionDto = {
186+
id: 1,
187+
question: "Choose multiple options",
188+
type: QuestionType.MULTIPLE_CORRECT,
189+
totalPoints: 10,
190+
assignmentId: 1,
191+
gradingContextQuestionIds: [],
192+
choices: [
193+
{ id: 1, choice: "Option A", isCorrect: true, points: 5 },
194+
{ id: 2, choice: "Option B", isCorrect: true, points: 5 },
195+
{ id: 3, choice: "Option C", isCorrect: false, points: 0 },
196+
],
197+
} as any;
198+
199+
it("should accept multiple choices for multiple-choice question", async () => {
200+
const requestDto = {
201+
learnerChoices: ["choice1", "choice2"],
202+
language: "en",
203+
} as any as CreateQuestionResponseAttemptRequestDto;
204+
205+
const result = await strategy.validateResponse(
206+
mockMultipleChoiceQuestion,
207+
requestDto,
208+
);
209+
expect(result).toBe(true);
210+
});
211+
212+
it("should accept single choice for multiple-choice question", async () => {
213+
const requestDto = {
214+
learnerChoices: ["choice1"],
215+
language: "en",
216+
} as any as CreateQuestionResponseAttemptRequestDto;
217+
218+
const result = await strategy.validateResponse(
219+
mockMultipleChoiceQuestion,
220+
requestDto,
221+
);
222+
expect(result).toBe(true);
223+
});
224+
225+
it("should accept empty array for multiple-choice question", async () => {
226+
const requestDto = {
227+
learnerChoices: [],
228+
language: "en",
229+
} as any as CreateQuestionResponseAttemptRequestDto;
230+
231+
const result = await strategy.validateResponse(
232+
mockMultipleChoiceQuestion,
233+
requestDto,
234+
);
235+
expect(result).toBe(true);
236+
});
237+
});
238+
239+
describe("gradeResponse - Type Safety", () => {
240+
const mockContext: GradingContext = {
241+
assignmentInstructions: "",
242+
questionAnswerContext: [],
243+
assignmentId: 1,
244+
language: "en",
245+
userRole: "learner" as any,
246+
metadata: {},
247+
};
248+
249+
const mockSingleChoiceQuestion: QuestionDto = {
250+
id: 1,
251+
question: "Choose one",
252+
type: QuestionType.SINGLE_CORRECT,
253+
totalPoints: 10,
254+
assignmentId: 1,
255+
gradingContextQuestionIds: [],
256+
choices: [
257+
{ id: 1, choice: "Correct", isCorrect: true, points: 10 },
258+
{ id: 2, choice: "Wrong", isCorrect: false, points: 0 },
259+
],
260+
} as any;
261+
262+
it("should handle empty learner response", async () => {
263+
const result = await strategy.gradeResponse(
264+
mockSingleChoiceQuestion,
265+
[],
266+
mockContext,
267+
);
268+
269+
expect(result).toBeDefined();
270+
expect(result.totalPoints).toBeDefined();
271+
});
272+
273+
it("should handle null-like learner response", async () => {
274+
const result = await strategy.gradeResponse(
275+
mockSingleChoiceQuestion,
276+
null as any,
277+
mockContext,
278+
);
279+
280+
expect(result).toBeDefined();
281+
expect(result.totalPoints).toBeDefined();
282+
});
283+
284+
it("should handle single valid choice", async () => {
285+
const result = await strategy.gradeResponse(
286+
mockSingleChoiceQuestion,
287+
["1"],
288+
mockContext,
289+
);
290+
291+
expect(result).toBeDefined();
292+
expect(result.totalPoints).toBeDefined();
293+
});
294+
295+
it("should gracefully handle numeric learner choices during grading", async () => {
296+
const { responseDto, learnerResponse } = await strategy.handleResponse(
297+
mockSingleChoiceQuestion,
298+
{
299+
learnerChoices: [123 as any],
300+
language: "en",
301+
} as CreateQuestionResponseAttemptRequestDto,
302+
mockContext,
303+
);
304+
305+
expect(learnerResponse).toEqual(["123"]);
306+
expect(responseDto).toBeDefined();
307+
expect(responseDto.totalPoints).toBe(0);
308+
});
309+
310+
it("should handle multiple choice question with multiple responses", async () => {
311+
const mockMultipleChoiceQuestion: QuestionDto = {
312+
id: 1,
313+
question: "Choose multiple",
314+
type: QuestionType.MULTIPLE_CORRECT,
315+
totalPoints: 10,
316+
assignmentId: 1,
317+
gradingContextQuestionIds: [],
318+
choices: [
319+
{ id: 1, choice: "Correct 1", isCorrect: true, points: 5 },
320+
{ id: 2, choice: "Correct 2", isCorrect: true, points: 5 },
321+
],
322+
} as any;
323+
324+
const result = await strategy.gradeResponse(
325+
mockMultipleChoiceQuestion,
326+
["1", "2"],
327+
mockContext,
328+
);
329+
330+
expect(result).toBeDefined();
331+
expect(result.totalPoints).toBeDefined();
332+
});
333+
});
334+
});

0 commit comments

Comments
 (0)