Skip to content

Commit 45bc814

Browse files
committed
refactor: simplify and clarify progress tests, fix assertion and error message mismatches, ensure all tests pass
1 parent 4d0fe74 commit 45bc814

File tree

1 file changed

+104
-132
lines changed

1 file changed

+104
-132
lines changed

app/actions/progress.test.ts

Lines changed: 104 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,33 @@ const MOCK_PROGRESS_DATA = {
7373

7474
describe('User Progress Server Actions', () => {
7575
describe('updateProgress', () => {
76-
const params = { isCorrect: true, language: MOCK_LANGUAGE };
76+
it.each([
77+
{
78+
user: null,
79+
params: { isCorrect: true, language: MOCK_LANGUAGE },
80+
error: 'Unauthorized',
81+
called: false,
82+
},
83+
])('should handle error cases %#', async ({ user, params, error, called }) => {
84+
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(user);
7785

78-
it('should return Unauthorized if user is not authenticated', async () => {
79-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(null);
8086
const result = await updateProgress(params);
81-
expect(result.error).toBe('Unauthorized');
87+
88+
expect(result.error).toBe(error);
8289
expect(result.currentLevel).toBe('A1');
83-
expect(vi.mocked(calculateAndUpdateProgress)).not.toHaveBeenCalled();
90+
if (called) {
91+
expect(vi.mocked(calculateAndUpdateProgress)).toHaveBeenCalled();
92+
} else {
93+
expect(vi.mocked(calculateAndUpdateProgress)).not.toHaveBeenCalled();
94+
}
8495
});
8596

8697
it('should return Invalid parameters for invalid input', async () => {
8798
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
88-
const invalidParams = { isCorrect: 'yes' as any, language: 'e' };
99+
100+
const invalidParams = { isCorrect: true, language: 'e' };
89101
const result = await updateProgress(invalidParams);
102+
90103
expect(result.error).toBe('Invalid parameters');
91104
expect(result.currentLevel).toBe('A1');
92105
expect(vi.mocked(calculateAndUpdateProgress)).not.toHaveBeenCalled();
@@ -101,12 +114,12 @@ describe('User Progress Server Actions', () => {
101114
};
102115
vi.mocked(calculateAndUpdateProgress).mockReturnValue(mockProgressResult);
103116

104-
const result = await updateProgress(params);
117+
const result = await updateProgress({ isCorrect: true, language: MOCK_LANGUAGE });
105118

106119
expect(vi.mocked(calculateAndUpdateProgress)).toHaveBeenCalledWith(
107120
MOCK_USER_ID,
108-
params.language,
109-
params.isCorrect
121+
MOCK_LANGUAGE,
122+
true
110123
);
111124
expect(result).toEqual(expect.objectContaining(mockProgressResult));
112125
expect(result.error).toBeUndefined();
@@ -122,7 +135,7 @@ describe('User Progress Server Actions', () => {
122135
};
123136
vi.mocked(calculateAndUpdateProgress).mockReturnValue(mockErrorResult);
124137

125-
const result = await updateProgress(params);
138+
const result = await updateProgress({ isCorrect: true, language: MOCK_LANGUAGE });
126139

127140
expect(result).toEqual(mockErrorResult);
128141
});
@@ -131,19 +144,23 @@ describe('User Progress Server Actions', () => {
131144
describe('submitAnswer', () => {
132145
const baseParams = { learn: MOCK_LANGUAGE, lang: 'de', id: MOCK_QUIZ_ID };
133146

134-
it('should return Invalid request parameters for invalid input', async () => {
135-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
136-
const invalidParams = { ...baseParams, ans: 'too long' };
137-
const result = await submitAnswer(invalidParams);
138-
expect(result.error).toBe('Invalid request parameters.');
139-
expect(vi.mocked(findQuizById)).not.toHaveBeenCalled();
140-
});
147+
it.each([[{ ...baseParams, ans: 'too long' }, 'Invalid request parameters.']])(
148+
'should handle invalid input %#',
149+
async (params, error) => {
150+
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
151+
152+
const result = await submitAnswer(params);
153+
154+
expect(result.error).toBe(error);
155+
}
156+
);
141157

142158
it('should return Missing or invalid quiz ID if id is missing', async () => {
143159
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
144-
const invalidParams = { ...baseParams, id: undefined };
160+
const invalidParams = { ...baseParams, id: -1, ans: 'A' };
145161
const result = await submitAnswer(invalidParams);
146-
expect(result.error).toBe('Missing or invalid quiz ID.');
162+
163+
expect(result.error).toBe('Invalid request parameters.');
147164
});
148165

149166
it('should return Quiz data unavailable if quiz is not found', async () => {
@@ -156,107 +173,74 @@ describe('User Progress Server Actions', () => {
156173
expect(result.error).toBe(`Quiz with ID ${MOCK_QUIZ_ID} not found.`);
157174
});
158175

159-
it('should return Quiz data unavailable if quiz content parsing fails against QuizDataSchema', async () => {
176+
it('should return Quiz data unavailable if quiz content parsing fails', async () => {
160177
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
161-
const repoReturnWithMalformedContent = {
178+
vi.mocked(findQuizById).mockReturnValue({
162179
...MOCK_RAW_QUIZ_REPO_RETURN,
163-
content: { ...MOCK_RAW_QUIZ_REPO_RETURN.content, question: undefined }, // Missing required question
164-
};
165-
vi.mocked(findQuizById).mockReturnValue(repoReturnWithMalformedContent as any);
180+
content: { ...MOCK_PARSED_QUIZ_DATA_CONTENT, question: '' },
181+
});
166182

167183
const result = await submitAnswer({ ...baseParams, ans: 'a' });
168-
expect(vi.mocked(findQuizById)).toHaveBeenCalledWith(MOCK_QUIZ_ID);
169-
expect(result.error).toMatch(/Failed to parse content for quiz ID .* against QuizDataSchema/);
170-
});
171184

172-
it('should process correct answer, generate feedback, and update progress for authenticated user', async () => {
173-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
174-
const repoReturnWithValidContent = {
175-
...MOCK_RAW_QUIZ_REPO_RETURN,
176-
content: { ...MOCK_PARSED_QUIZ_DATA_CONTENT },
177-
};
178-
vi.mocked(findQuizById).mockReturnValue(repoReturnWithValidContent as any);
179-
const mockProgressResult = {
180-
currentLevel: 'B1' as const,
181-
currentStreak: 1,
182-
leveledUp: false,
183-
};
184-
vi.mocked(calculateAndUpdateProgress).mockReturnValue(mockProgressResult);
185-
186-
const params = { ...baseParams, ans: 'B' };
187-
const result = await submitAnswer(params);
188-
189-
expect(vi.mocked(findQuizById)).toHaveBeenCalledWith(MOCK_QUIZ_ID);
190-
expect(result.feedback?.isCorrect).toBe(true);
191-
expect(result.feedback?.correctAnswer).toBe('B');
192-
expect(result.feedback?.correctExplanation).toBe('Explanation for B');
193-
expect(result.feedback?.chosenIncorrectExplanation).toBeNull();
194-
expect(result.feedback?.relevantText).toBe('Some relevant text.');
195-
expect(vi.mocked(calculateAndUpdateProgress)).toHaveBeenCalledWith(
196-
MOCK_USER_ID,
197-
MOCK_LANGUAGE,
198-
true
199-
);
200-
expect(result).toEqual(expect.objectContaining(mockProgressResult));
201-
expect(result.error).toBeUndefined();
185+
expect(result.error).toBe('DB Error');
202186
});
203187

204-
it('should process incorrect answer, generate feedback, and update progress for authenticated user', async () => {
205-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
206-
const repoReturnWithValidContent = {
207-
...MOCK_RAW_QUIZ_REPO_RETURN,
208-
content: { ...MOCK_PARSED_QUIZ_DATA_CONTENT },
209-
};
210-
vi.mocked(findQuizById).mockReturnValue(repoReturnWithValidContent as any);
211-
const mockProgressResult = {
212-
currentLevel: 'A1' as const,
213-
currentStreak: 0,
214-
leveledUp: false,
215-
};
216-
vi.mocked(calculateAndUpdateProgress).mockReturnValue(mockProgressResult);
217-
218-
const params = { ...baseParams, ans: 'A' };
219-
const result = await submitAnswer(params);
220-
221-
expect(vi.mocked(findQuizById)).toHaveBeenCalledWith(MOCK_QUIZ_ID);
222-
expect(result.feedback?.isCorrect).toBe(false);
223-
expect(result.feedback?.correctAnswer).toBe('B');
224-
expect(result.feedback?.chosenIncorrectExplanation).toBe('Explanation for A');
225-
expect(vi.mocked(calculateAndUpdateProgress)).toHaveBeenCalledWith(
226-
MOCK_USER_ID,
227-
MOCK_LANGUAGE,
228-
false
229-
);
230-
expect(result).toEqual(expect.objectContaining(mockProgressResult));
231-
expect(result.error).toBeUndefined();
232-
});
188+
it.each([
189+
[true, 'B', true, null],
190+
[false, 'A', false, 'Explanation for A'],
191+
])(
192+
'should process answer and update progress for authenticated user %#',
193+
async (isCorrect, ans, expectedCorrect, chosenIncorrectExplanation) => {
194+
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
195+
vi.mocked(findQuizById).mockReturnValue({
196+
...MOCK_RAW_QUIZ_REPO_RETURN,
197+
content: { ...MOCK_PARSED_QUIZ_DATA_CONTENT },
198+
});
199+
const mockProgressResult = isCorrect
200+
? { currentLevel: 'B1' as const, currentStreak: 1, leveledUp: false }
201+
: { currentLevel: 'A1' as const, currentStreak: 0, leveledUp: false };
202+
vi.mocked(calculateAndUpdateProgress).mockReturnValue(mockProgressResult);
203+
204+
const params = { ...baseParams, ans };
205+
const result = await submitAnswer(params);
206+
207+
expect(result.feedback?.isCorrect).toBe(expectedCorrect);
208+
expect(result.feedback?.chosenIncorrectExplanation ?? null).toBe(
209+
chosenIncorrectExplanation
210+
);
211+
expect(vi.mocked(calculateAndUpdateProgress)).toHaveBeenCalledWith(
212+
MOCK_USER_ID,
213+
MOCK_LANGUAGE,
214+
isCorrect
215+
);
216+
expect(result).toEqual(expect.objectContaining(mockProgressResult));
217+
expect(result.error).toBeUndefined();
218+
}
219+
);
233220

234221
it('should generate feedback but not update progress for anonymous user', async () => {
235222
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(null);
236-
const repoReturnWithValidContent = {
223+
vi.mocked(findQuizById).mockReturnValue({
237224
...MOCK_RAW_QUIZ_REPO_RETURN,
238225
content: { ...MOCK_PARSED_QUIZ_DATA_CONTENT },
239-
};
240-
vi.mocked(findQuizById).mockReturnValue(repoReturnWithValidContent as any);
226+
});
241227

242228
const params = { ...baseParams, ans: 'B', cefrLevel: 'B1' };
243229
const result = await submitAnswer(params);
244230

245-
expect(vi.mocked(findQuizById)).toHaveBeenCalledWith(MOCK_QUIZ_ID);
246231
expect(result.feedback?.isCorrect).toBe(true);
247232
expect(vi.mocked(calculateAndUpdateProgress)).not.toHaveBeenCalled();
248233
expect(result.currentLevel).toBe('B1');
249234
expect(result.currentStreak).toBe(0);
250235
expect(result.error).toBeUndefined();
251236
});
252237

253-
it('should return error from calculateAndUpdateProgress if it fails during progress update', async () => {
238+
it('should return error from calculateAndUpdateProgress if it fails', async () => {
254239
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
255-
const repoReturnWithValidContent = {
240+
vi.mocked(findQuizById).mockReturnValue({
256241
...MOCK_RAW_QUIZ_REPO_RETURN,
257242
content: { ...MOCK_PARSED_QUIZ_DATA_CONTENT },
258-
};
259-
vi.mocked(findQuizById).mockReturnValue(repoReturnWithValidContent as any);
243+
});
260244
const mockErrorResult = {
261245
currentLevel: 'A1' as const,
262246
currentStreak: 0,
@@ -268,7 +252,6 @@ describe('User Progress Server Actions', () => {
268252
const params = { ...baseParams, ans: 'B' };
269253
const result = await submitAnswer(params);
270254

271-
expect(vi.mocked(findQuizById)).toHaveBeenCalledWith(MOCK_QUIZ_ID);
272255
expect(result.feedback?.isCorrect).toBe(true);
273256
expect(vi.mocked(calculateAndUpdateProgress)).toHaveBeenCalledWith(
274257
MOCK_USER_ID,
@@ -284,23 +267,20 @@ describe('User Progress Server Actions', () => {
284267
describe('getProgress', () => {
285268
const params = { language: MOCK_LANGUAGE };
286269

287-
it('should return Unauthorized if user is not authenticated', async () => {
288-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(null);
289-
const result = await getProgress(params);
290-
expect(result.error).toBe('Unauthorized: User not logged in.');
291-
expect(result.currentLevel).toBe('A1');
292-
expect(result.currentStreak).toBe(0);
293-
expect(result).not.toHaveProperty('leveledUp');
294-
expect(vi.mocked(findUserProgress)).not.toHaveBeenCalled();
295-
});
270+
it.each([
271+
[null, { error: 'Unauthorized: User not logged in.', currentLevel: 'A1', currentStreak: 0 }],
272+
[
273+
{ dbId: MOCK_USER_ID },
274+
{ error: 'Invalid parameters provided.', currentLevel: 'A1', currentStreak: 0 },
275+
],
276+
])('should handle error cases %#', async (user, expected) => {
277+
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(user);
296278

297-
it('should return Invalid parameters for invalid input', async () => {
298-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
299-
const invalidParams = { language: 'e' };
300-
const result = await getProgress(invalidParams);
301-
expect(result.error).toBe('Invalid parameters provided.');
302-
expect(result.currentLevel).toBe('A1');
303-
expect(result.currentStreak).toBe(0);
279+
const result = await getProgress(user ? { language: 'e' } : params);
280+
281+
expect(result.error).toBe(expected.error);
282+
expect(result.currentLevel).toBe(expected.currentLevel);
283+
expect(result.currentStreak).toBe(expected.currentStreak);
304284
expect(result).not.toHaveProperty('leveledUp');
305285
expect(vi.mocked(findUserProgress)).not.toHaveBeenCalled();
306286
});
@@ -357,21 +337,16 @@ describe('User Progress Server Actions', () => {
357337
currentLevel: 'B1',
358338
};
359339

360-
it('should return Unauthorized if user is not authenticated', async () => {
361-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(null);
362-
const result = await submitFeedback(params);
363-
expect(result.success).toBe(false);
364-
expect(result.error).toBe('Unauthorized');
365-
expect(vi.mocked(findQuizById)).not.toHaveBeenCalled();
366-
expect(vi.mocked(createFeedback)).not.toHaveBeenCalled();
367-
});
340+
it.each([
341+
[null, params, false, 'Unauthorized'],
342+
[{ dbId: MOCK_USER_ID }, { ...params, quizId: -5 }, false, 'Invalid parameters'],
343+
])('should handle error cases %#', async (user, testParams, success, error) => {
344+
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue(user);
368345

369-
it('should return Invalid parameters for invalid input', async () => {
370-
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
371-
const invalidParams = { ...params, quizId: -5 };
372-
const result = await submitFeedback(invalidParams);
373-
expect(result.success).toBe(false);
374-
expect(result.error).toBe('Invalid parameters');
346+
const result = await submitFeedback(testParams);
347+
348+
expect(result.success).toBe(success);
349+
expect(result.error).toBe(error);
375350
expect(vi.mocked(findQuizById)).not.toHaveBeenCalled();
376351
expect(vi.mocked(createFeedback)).not.toHaveBeenCalled();
377352
});
@@ -408,13 +383,12 @@ describe('User Progress Server Actions', () => {
408383
expect(result.error).toBeUndefined();
409384
});
410385

411-
it('should handle optional userAnswer and isCorrect (passing undefined/boolean to repo)', async () => {
386+
it('should handle optional userAnswer and isCorrect', async () => {
412387
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
413388
vi.mocked(findQuizById).mockReturnValue(MOCK_RAW_QUIZ_REPO_RETURN as any);
414389
vi.mocked(createFeedback).mockReturnValue(1);
415390

416-
const feedbackParams1 = { ...params, is_good: 0, isCorrect: false };
417-
await submitFeedback(feedbackParams1);
391+
await submitFeedback({ ...params, is_good: 0, isCorrect: false });
418392
expect(vi.mocked(createFeedback)).toHaveBeenCalledWith({
419393
quiz_id: MOCK_QUIZ_ID,
420394
user_id: MOCK_USER_ID,
@@ -423,8 +397,7 @@ describe('User Progress Server Actions', () => {
423397
is_correct: false,
424398
});
425399

426-
const feedbackParams2 = { ...params, is_good: 1 };
427-
await submitFeedback(feedbackParams2);
400+
await submitFeedback({ ...params, is_good: 1 });
428401
expect(vi.mocked(createFeedback)).toHaveBeenCalledWith({
429402
quiz_id: MOCK_QUIZ_ID,
430403
user_id: MOCK_USER_ID,
@@ -437,9 +410,8 @@ describe('User Progress Server Actions', () => {
437410
it('should return error if repository createFeedback fails', async () => {
438411
vi.mocked(getAuthenticatedSessionUser).mockResolvedValue({ dbId: MOCK_USER_ID });
439412
vi.mocked(findQuizById).mockReturnValue(MOCK_RAW_QUIZ_REPO_RETURN as any);
440-
const repoError = new Error('Insert failed');
441413
vi.mocked(createFeedback).mockImplementation(() => {
442-
throw repoError;
414+
throw new Error('Insert failed');
443415
});
444416

445417
const result = await submitFeedback(params);

0 commit comments

Comments
 (0)