mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-20 12:03:11 -04:00
refactor(api): more, smaller tests (#54671)
This commit is contained in:
committed by
GitHub
parent
f897769f0b
commit
d06bbab6f8
@@ -67,12 +67,25 @@ export const completedTrophyChallenges = [
|
||||
{
|
||||
id: '647f85d407d29547b3bee1bb',
|
||||
solution: 'challenge-solution',
|
||||
completedDate: 1695064765244
|
||||
completedDate: 1695064765244,
|
||||
files: []
|
||||
}
|
||||
];
|
||||
|
||||
export type ExamSubmission = {
|
||||
userExamQuestions: {
|
||||
id: string;
|
||||
question: string;
|
||||
answer: {
|
||||
id: string;
|
||||
answer: string;
|
||||
};
|
||||
}[];
|
||||
examTimeInSeconds: number;
|
||||
};
|
||||
|
||||
// failed: 0 correct
|
||||
export const userExam1 = {
|
||||
export const examWithZeroCorrect: ExamSubmission = {
|
||||
userExamQuestions: [
|
||||
{
|
||||
id: '3bbl2mx2mq',
|
||||
@@ -94,7 +107,7 @@ export const userExam1 = {
|
||||
};
|
||||
|
||||
// passed: 1 correct
|
||||
export const userExam2 = {
|
||||
export const examWithOneCorrect: ExamSubmission = {
|
||||
userExamQuestions: [
|
||||
{
|
||||
id: '3bbl2mx2mq',
|
||||
@@ -116,7 +129,7 @@ export const userExam2 = {
|
||||
};
|
||||
|
||||
// passed: 2 correct
|
||||
export const userExam3 = {
|
||||
export const examWithTwoCorrect: ExamSubmission = {
|
||||
userExamQuestions: [
|
||||
{
|
||||
id: '3bbl2mx2mq',
|
||||
@@ -138,7 +151,7 @@ export const userExam3 = {
|
||||
};
|
||||
|
||||
// passed: 3 correct
|
||||
export const userExam4 = {
|
||||
export const examWithAllCorrect: ExamSubmission = {
|
||||
userExamQuestions: [
|
||||
{
|
||||
id: '3bbl2mx2mq',
|
||||
@@ -159,7 +172,7 @@ export const userExam4 = {
|
||||
examTimeInSeconds: 20
|
||||
};
|
||||
|
||||
export const mockResults1 = {
|
||||
export const mockResultsZeroCorrect = {
|
||||
numberOfCorrectAnswers: 0,
|
||||
numberOfQuestionsInExam: 3,
|
||||
percentCorrect: 0,
|
||||
@@ -168,7 +181,7 @@ export const mockResults1 = {
|
||||
examTimeInSeconds: 20
|
||||
};
|
||||
|
||||
export const mockResults2 = {
|
||||
export const mockResultsOneCorrect = {
|
||||
numberOfCorrectAnswers: 1,
|
||||
numberOfQuestionsInExam: 3,
|
||||
percentCorrect: 33.3,
|
||||
@@ -177,7 +190,7 @@ export const mockResults2 = {
|
||||
examTimeInSeconds: 20
|
||||
};
|
||||
|
||||
export const mockResults3 = {
|
||||
export const mockResultsTwoCorrect = {
|
||||
numberOfCorrectAnswers: 2,
|
||||
numberOfQuestionsInExam: 3,
|
||||
percentCorrect: 66.7,
|
||||
@@ -186,7 +199,7 @@ export const mockResults3 = {
|
||||
examTimeInSeconds: 20
|
||||
};
|
||||
|
||||
export const mockResults4 = {
|
||||
export const mockResultsAllCorrect = {
|
||||
numberOfCorrectAnswers: 3,
|
||||
numberOfQuestionsInExam: 3,
|
||||
percentCorrect: 100,
|
||||
@@ -197,25 +210,27 @@ export const mockResults4 = {
|
||||
|
||||
const completedExamChallenge = {
|
||||
id: examChallengeId,
|
||||
challengeType: 17
|
||||
challengeType: 17,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
completedDate: expect.any(Number)
|
||||
};
|
||||
|
||||
export const completedExamChallenge1 = {
|
||||
export const completedExamChallengeZeroCorrect = {
|
||||
...completedExamChallenge,
|
||||
examResults: mockResults1
|
||||
examResults: mockResultsZeroCorrect
|
||||
};
|
||||
|
||||
export const completedExamChallenge2 = {
|
||||
export const completedExamChallengeOneCorrect = {
|
||||
...completedExamChallenge,
|
||||
examResults: mockResults2
|
||||
examResults: mockResultsOneCorrect
|
||||
};
|
||||
|
||||
export const completedExamChallenge3 = {
|
||||
export const completedExamChallengeTwoCorrect = {
|
||||
...completedExamChallenge,
|
||||
examResults: mockResults3
|
||||
examResults: mockResultsTwoCorrect
|
||||
};
|
||||
|
||||
export const completedExamChallenge4 = {
|
||||
export const completedExamChallengeAllCorrect = {
|
||||
...completedExamChallenge,
|
||||
examResults: mockResults4
|
||||
examResults: mockResultsAllCorrect
|
||||
};
|
||||
|
||||
@@ -175,6 +175,7 @@ If you are seeing this error, the root cause is likely an error thrown in the be
|
||||
|
||||
export const defaultUserId = '64c7810107dd4782d32baee7';
|
||||
export const defaultUserEmail = 'foo@bar.com';
|
||||
export const defaultUsername = 'fcc-test-user';
|
||||
|
||||
export async function devLogin(): Promise<string[]> {
|
||||
await fastifyTestInstance.prisma.user.deleteMany({
|
||||
@@ -184,7 +185,8 @@ export async function devLogin(): Promise<string[]> {
|
||||
await fastifyTestInstance.prisma.user.create({
|
||||
data: {
|
||||
...createUserInput(defaultUserEmail),
|
||||
id: defaultUserId
|
||||
id: defaultUserId,
|
||||
username: defaultUsername
|
||||
}
|
||||
});
|
||||
const res = await superRequest('/signin', { method: 'GET' });
|
||||
|
||||
@@ -4,6 +4,7 @@ const mockVerifyTrophyWithMicrosoft = jest.fn();
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { omit } from 'lodash';
|
||||
import { Static } from '@fastify/type-provider-typebox';
|
||||
|
||||
import { challengeTypes } from '../../../shared/config/challenge-types';
|
||||
import {
|
||||
@@ -13,24 +14,26 @@ import {
|
||||
superRequest,
|
||||
seedExam,
|
||||
defaultUserEmail,
|
||||
createSuperRequest
|
||||
createSuperRequest,
|
||||
defaultUsername
|
||||
} from '../../jest.utils';
|
||||
import {
|
||||
completedExamChallenge2,
|
||||
completedExamChallenge3,
|
||||
completedExamChallenge4,
|
||||
completedExamChallengeOneCorrect,
|
||||
completedExamChallengeTwoCorrect,
|
||||
completedExamChallengeAllCorrect,
|
||||
completedTrophyChallenges,
|
||||
examChallengeId,
|
||||
mockResults1,
|
||||
mockResults2,
|
||||
mockResults3,
|
||||
mockResults4,
|
||||
userExam1,
|
||||
userExam2,
|
||||
userExam3,
|
||||
userExam4
|
||||
mockResultsZeroCorrect,
|
||||
mockResultsTwoCorrect,
|
||||
mockResultsAllCorrect,
|
||||
examWithZeroCorrect,
|
||||
examWithOneCorrect,
|
||||
examWithTwoCorrect,
|
||||
examWithAllCorrect,
|
||||
type ExamSubmission
|
||||
} from '../../__mocks__/exam';
|
||||
import { Answer } from '../utils/exam-types';
|
||||
import type { getSessionUser } from '../schemas/user/get-session-user';
|
||||
|
||||
jest.mock('./helpers/challenge-helpers', () => {
|
||||
const originalModule = jest.requireActual<
|
||||
@@ -1493,173 +1496,184 @@ describe('challengeRoutes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('POST handles submitting a failing exam', async () => {
|
||||
const now = Date.now();
|
||||
|
||||
// Submit exam with 0 correct answers
|
||||
const response = await superRequest('/exam-challenge-completed', {
|
||||
const submitExam = async (exam: ExamSubmission) => {
|
||||
return superRequest('/exam-challenge-completed', {
|
||||
method: 'POST',
|
||||
setCookies
|
||||
}).send({
|
||||
id: examChallengeId,
|
||||
challengeType: 17,
|
||||
userCompletedExam: userExam1
|
||||
userCompletedExam: exam
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
completedChallenges = [],
|
||||
completedExams = [],
|
||||
progressTimestamps = []
|
||||
} = (await fastifyTestInstance.prisma.user.findFirst({
|
||||
where: { email: 'foo@bar.com' }
|
||||
})) || {};
|
||||
test('POST handles submitting a failing exam', async () => {
|
||||
const now = Date.now();
|
||||
|
||||
// Submit exam with 0 correct answers
|
||||
const response = await submitExam(examWithZeroCorrect);
|
||||
|
||||
type GetSessionUserResponseBody = Static<
|
||||
(typeof getSessionUser)['response']['200']
|
||||
>['user'];
|
||||
|
||||
const res = (await superGet('/user/get-session-user')).body as {
|
||||
user: GetSessionUserResponseBody;
|
||||
};
|
||||
|
||||
const { completedChallenges, completedExams, calendar } =
|
||||
res.user[defaultUsername]!;
|
||||
|
||||
// should have the 1 prerequisite challenge
|
||||
expect(completedChallenges).toHaveLength(1);
|
||||
expect(completedExams).toHaveLength(1);
|
||||
expect(progressTimestamps).toHaveLength(0);
|
||||
expect(completedChallenges).toMatchObject(completedTrophyChallenges);
|
||||
expect(completedExams[0]).toMatchObject({
|
||||
expect(calendar).toStrictEqual({});
|
||||
expect(completedChallenges).toEqual(completedTrophyChallenges);
|
||||
expect(completedExams[0]).toEqual({
|
||||
id: '647e22d18acb466c97ccbef8',
|
||||
challengeType: 17,
|
||||
examResults: mockResults1
|
||||
completedDate: expect.any(Number),
|
||||
examResults: mockResultsZeroCorrect
|
||||
});
|
||||
|
||||
expect(completedExams[0]?.completedDate).toBeGreaterThan(now);
|
||||
expect(response.body).toMatchObject({
|
||||
points: 0,
|
||||
alreadyCompleted: false,
|
||||
examResults: mockResults1
|
||||
examResults: mockResultsZeroCorrect
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
test('POST handles submitting multiple passing exams', async () => {
|
||||
// Submit exam with 2/3 correct answers
|
||||
const nowA = Date.now();
|
||||
const responseA = await superRequest('/exam-challenge-completed', {
|
||||
method: 'POST',
|
||||
setCookies
|
||||
}).send({
|
||||
id: examChallengeId,
|
||||
challengeType: 17,
|
||||
userCompletedExam: userExam3
|
||||
});
|
||||
test("POST always adds to the user's completedExams", async () => {
|
||||
let now = Date.now();
|
||||
// The first exam should be stored in the user's completedExams
|
||||
await submitExam(examWithAllCorrect);
|
||||
|
||||
const userA = await fastifyTestInstance.prisma.user.findFirst({
|
||||
let { completedExams } =
|
||||
await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { id: defaultUserId }
|
||||
});
|
||||
|
||||
expect(completedExams).toHaveLength(1);
|
||||
expect(completedExams[0]).toEqual(completedExamChallengeAllCorrect);
|
||||
expect(completedExams[0]?.completedDate).toBeGreaterThan(now);
|
||||
expect(completedExams[0]?.completedDate).toBeLessThan(Date.now());
|
||||
|
||||
now = Date.now();
|
||||
// the second exam should be added to the exams, not replace the first
|
||||
await submitExam(examWithOneCorrect);
|
||||
|
||||
completedExams = (
|
||||
await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { id: defaultUserId }
|
||||
})
|
||||
).completedExams;
|
||||
|
||||
expect(completedExams).toHaveLength(2);
|
||||
expect(completedExams).toEqual(
|
||||
expect.arrayContaining([
|
||||
completedExamChallengeAllCorrect,
|
||||
completedExamChallengeOneCorrect
|
||||
])
|
||||
);
|
||||
expect(completedExams[1]?.completedDate).toBeGreaterThan(now);
|
||||
expect(completedExams[1]?.completedDate).toBeLessThan(Date.now());
|
||||
});
|
||||
|
||||
test('POST updates user progress if they have not completed the exam before', async () => {
|
||||
// Submit exam with 2/3 correct answers
|
||||
const now = Date.now();
|
||||
const res = await submitExam(examWithTwoCorrect);
|
||||
|
||||
const user = await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { id: defaultUserId }
|
||||
});
|
||||
|
||||
const completedChallengesA = userA?.completedChallenges || [];
|
||||
const completedExamsA = userA?.completedExams || [];
|
||||
const progressTimestampsA = userA?.progressTimestamps || [];
|
||||
|
||||
// should add to completedChallenges
|
||||
expect(completedChallengesA).toHaveLength(2);
|
||||
expect(completedChallengesA).toMatchObject([
|
||||
expect(user.completedChallenges).toHaveLength(2);
|
||||
expect(user.completedChallenges).toMatchObject([
|
||||
...completedTrophyChallenges,
|
||||
completedExamChallenge3
|
||||
completedExamChallengeTwoCorrect
|
||||
]);
|
||||
expect(completedChallengesA[1]?.completedDate).toBeGreaterThan(nowA);
|
||||
|
||||
// should add to completedExams
|
||||
expect(completedExamsA).toHaveLength(1);
|
||||
expect(completedExamsA[0]).toMatchObject(completedExamChallenge3);
|
||||
expect(completedExamsA[0]?.completedDate).toBeGreaterThan(nowA);
|
||||
expect(user.completedChallenges[1]?.completedDate).toBeGreaterThan(
|
||||
now
|
||||
);
|
||||
|
||||
// should add to progressTimestamps
|
||||
expect(progressTimestampsA).toHaveLength(1);
|
||||
expect(user.progressTimestamps).toHaveLength(1);
|
||||
|
||||
expect(responseA.body).toMatchObject({
|
||||
expect(res.body).toMatchObject({
|
||||
points: 1,
|
||||
alreadyCompleted: false,
|
||||
examResults: mockResults3
|
||||
examResults: mockResultsTwoCorrect
|
||||
});
|
||||
expect(responseA.statusCode).toBe(200);
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
// Submit exam with 1/3 correct answers (worse exam than already submitted)
|
||||
const now2 = Date.now();
|
||||
const response2 = await superRequest('/exam-challenge-completed', {
|
||||
method: 'POST',
|
||||
setCookies
|
||||
}).send({
|
||||
id: examChallengeId,
|
||||
challengeType: 17,
|
||||
userCompletedExam: userExam2
|
||||
});
|
||||
test('POST does not update user progress if new exam is not an improvement', async () => {
|
||||
// Submit exam with 2/3 correct answers
|
||||
await submitExam(examWithTwoCorrect);
|
||||
|
||||
const user2 = await fastifyTestInstance.prisma.user.findFirst({
|
||||
const user1 = await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { id: defaultUserId }
|
||||
});
|
||||
|
||||
const completedChallenges2 = user2?.completedChallenges || [];
|
||||
const completedExams2 = user2?.completedExams || [];
|
||||
const progressTimestamps2 = user2?.progressTimestamps || [];
|
||||
// Submit exam with 2/3 correct answers (no improvement)
|
||||
const res2 = await submitExam(examWithTwoCorrect);
|
||||
|
||||
// should not add to or update completedChallenges
|
||||
expect(completedChallenges2).toHaveLength(2);
|
||||
expect(completedChallenges2).toMatchObject([
|
||||
...completedTrophyChallenges,
|
||||
// should still have old completed challenge (should not update)
|
||||
completedExamChallenge3
|
||||
]);
|
||||
expect(completedChallenges2[1]?.completedDate).toBeLessThan(now2);
|
||||
const user2 = await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { id: defaultUserId }
|
||||
});
|
||||
|
||||
// should add to completedExams
|
||||
expect(completedExams2).toHaveLength(2);
|
||||
expect(completedExams2[1]).toMatchObject(completedExamChallenge2);
|
||||
expect(completedExams2[1]?.completedDate).toBeGreaterThan(nowA);
|
||||
// should not update user progress
|
||||
expect(user2.completedChallenges).toEqual(user1.completedChallenges);
|
||||
expect(user2.progressTimestamps).toEqual(user1.progressTimestamps);
|
||||
|
||||
// should not add to progressTimestamps
|
||||
expect(progressTimestamps2).toHaveLength(1);
|
||||
|
||||
expect(response2.body).toMatchObject({
|
||||
expect(res2.body).toMatchObject({
|
||||
points: 1,
|
||||
alreadyCompleted: true,
|
||||
examResults: mockResults2
|
||||
examResults: mockResultsTwoCorrect
|
||||
});
|
||||
expect(response2.statusCode).toBe(200);
|
||||
expect(res2.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
// Submit exam with 3/3 correct answers (better exam than already submitted)
|
||||
const now3 = Date.now();
|
||||
const response3 = await superRequest('/exam-challenge-completed', {
|
||||
method: 'POST',
|
||||
setCookies
|
||||
}).send({
|
||||
id: examChallengeId,
|
||||
challengeType: 17,
|
||||
userCompletedExam: userExam4
|
||||
});
|
||||
test('POST updates user progress if exam is an improvement', async () => {
|
||||
// Submit exam with 2/3 correct answers
|
||||
await submitExam(examWithTwoCorrect);
|
||||
const user1 = await fastifyTestInstance.prisma.user.findUniqueOrThrow(
|
||||
{
|
||||
where: { id: defaultUserId }
|
||||
}
|
||||
);
|
||||
|
||||
const user3 = await fastifyTestInstance.prisma.user.findFirst({
|
||||
// Submit improved exam
|
||||
const res = await submitExam(examWithAllCorrect);
|
||||
|
||||
const user2 = await fastifyTestInstance.prisma.user.findFirstOrThrow({
|
||||
where: { email: 'foo@bar.com' }
|
||||
});
|
||||
|
||||
const completedChallenges3 = user3?.completedChallenges || [];
|
||||
const completedExams3 = user3?.completedExams || [];
|
||||
const progressTimestamps3 = user3?.progressTimestamps || [];
|
||||
|
||||
// should update existing completedChallenge
|
||||
expect(completedChallenges3).toHaveLength(2);
|
||||
expect(completedChallenges3).toMatchObject([
|
||||
expect(user2.completedChallenges).toHaveLength(2);
|
||||
expect(user2.completedChallenges).toMatchObject([
|
||||
...completedTrophyChallenges,
|
||||
completedExamChallenge4
|
||||
completedExamChallengeAllCorrect
|
||||
]);
|
||||
expect(completedChallenges3[1]?.completedDate).toBeLessThan(now3);
|
||||
expect(user2.completedChallenges[1]?.completedDate).toEqual(
|
||||
user1.completedChallenges[1]?.completedDate
|
||||
);
|
||||
|
||||
// should add to completedExams
|
||||
expect(completedExams3).toHaveLength(3);
|
||||
expect(completedExams3[2]).toMatchObject(completedExamChallenge4);
|
||||
expect(completedExams3[2]?.completedDate).toBeGreaterThan(now3);
|
||||
// they have not completed anything new, so progressTimestamps should
|
||||
// remain the same
|
||||
expect(user2.progressTimestamps).toEqual(user1.progressTimestamps);
|
||||
|
||||
expect(progressTimestamps3).toHaveLength(1);
|
||||
|
||||
expect(response3.body).toMatchObject({
|
||||
expect(res.body).toMatchObject({
|
||||
points: 1,
|
||||
alreadyCompleted: true,
|
||||
examResults: mockResults4
|
||||
examResults: mockResultsAllCorrect
|
||||
});
|
||||
expect(response3.statusCode).toBe(200);
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Exam, Question } from '@prisma/client';
|
||||
import {
|
||||
examJson,
|
||||
userExam1,
|
||||
userExam2,
|
||||
userExam3,
|
||||
userExam4,
|
||||
mockResults1,
|
||||
mockResults2,
|
||||
mockResults3,
|
||||
mockResults4
|
||||
examWithZeroCorrect,
|
||||
examWithOneCorrect,
|
||||
examWithTwoCorrect,
|
||||
examWithAllCorrect,
|
||||
mockResultsZeroCorrect,
|
||||
mockResultsOneCorrect,
|
||||
mockResultsTwoCorrect,
|
||||
mockResultsAllCorrect
|
||||
} from '../../__mocks__/exam';
|
||||
import { generateRandomExam, createExamResults } from './exam';
|
||||
import { GeneratedExam } from './exam-types';
|
||||
@@ -45,19 +45,31 @@ describe('Exam helpers', () => {
|
||||
});
|
||||
|
||||
describe('createExamResults()', () => {
|
||||
const examResults1 = createExamResults(userExam1, examJson as Exam);
|
||||
const examResults2 = createExamResults(userExam2, examJson as Exam);
|
||||
const examResults3 = createExamResults(userExam3, examJson as Exam);
|
||||
const examResults4 = createExamResults(userExam4, examJson as Exam);
|
||||
const examResults1 = createExamResults(
|
||||
examWithZeroCorrect,
|
||||
examJson as Exam
|
||||
);
|
||||
const examResults2 = createExamResults(
|
||||
examWithOneCorrect,
|
||||
examJson as Exam
|
||||
);
|
||||
const examResults3 = createExamResults(
|
||||
examWithTwoCorrect,
|
||||
examJson as Exam
|
||||
);
|
||||
const examResults4 = createExamResults(
|
||||
examWithAllCorrect,
|
||||
examJson as Exam
|
||||
);
|
||||
|
||||
it('failing exam should return correct results', () => {
|
||||
expect(examResults1).toEqual(mockResults1);
|
||||
expect(examResults1).toEqual(mockResultsZeroCorrect);
|
||||
});
|
||||
|
||||
it('passing exam should return correct results', () => {
|
||||
expect(examResults2).toEqual(mockResults2);
|
||||
expect(examResults3).toEqual(mockResults3);
|
||||
expect(examResults4).toEqual(mockResults4);
|
||||
expect(examResults2).toEqual(mockResultsOneCorrect);
|
||||
expect(examResults3).toEqual(mockResultsTwoCorrect);
|
||||
expect(examResults4).toEqual(mockResultsAllCorrect);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user