feat(api): add prerequisites to env exam (#56731)

Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
Shaun Hamilton
2024-10-24 02:49:58 +02:00
committed by GitHub
parent 692c1dfc14
commit 08bc090636
7 changed files with 88 additions and 25 deletions

View File

@@ -338,7 +338,8 @@ export const examAttemptSansSubmissionTimeInMS: Static<
export const exam: EnvExam = {
id: examId,
config,
questionSets
questionSets,
prerequisites: ['67112fe1c994faa2c26d0b1d']
};
export async function seedEnvExam() {

View File

@@ -151,11 +151,13 @@ model user {
/// An exam for the Exam Environment App as designed by the examiners
model EnvExam {
/// Globally unique exam id
id String @id @default(auto()) @map("_id") @db.ObjectId
id String @id @default(auto()) @map("_id") @db.ObjectId
/// All questions for a given exam
questionSets EnvQuestionSet[]
questionSets EnvQuestionSet[]
/// Configuration for exam metadata
config EnvConfig
config EnvConfig
/// ObjectIds for required challenges/blocks to take the exam
prerequisites String[] @db.ObjectId
// Relations
generatedExams EnvGeneratedExam[]

View File

@@ -240,6 +240,15 @@ describe('/exam-environment/', () => {
describe('POST /exam-environment/generated-exam', () => {
afterEach(async () => {
await fastifyTestInstance.prisma.envExamAttempt.deleteMany();
// Add prerequisite id to user completed challenge
await fastifyTestInstance.prisma.user.update({
where: { id: defaultUserId },
data: {
completedChallenges: [
{ id: mock.exam.prerequisites.at(0)!, completedDate: Date.now() }
]
}
});
await mock.seedEnvExam();
});
@@ -262,8 +271,30 @@ describe('/exam-environment/', () => {
expect(res.status).toBe(404);
});
xit('should return an error if the exam prerequisites are not met', async () => {
// TODO: Waiting on prerequisites
it('should return an error if the exam prerequisites are not met', async () => {
await fastifyTestInstance.prisma.user.update({
where: { id: defaultUserId },
data: {
completedChallenges: []
}
});
const body: Static<typeof examEnvironmentPostExamGeneratedExam.body> = {
examId: mock.exam.id
};
const res = await superPost('/exam-environment/exam/generated-exam')
.send(body)
.set(
'exam-environment-authorization-token',
examEnvironmentAuthorizationToken
);
expect(res.body).toStrictEqual({
code: 'FCC_EINVAL_EXAM_ENVIRONMENT_PREREQUISITES',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
message: expect.any(String)
});
expect(res.status).toBe(403);
});
it('should return an error if the exam has been attempted in the last 24 hours', async () => {

View File

@@ -170,7 +170,7 @@ async function postExamGeneratedExamHandler(
// Check user has completed prerequisites
const user = req.user!;
const isExamPrerequisitesMet = checkPrerequisites(user, true);
const isExamPrerequisitesMet = checkPrerequisites(user, exam.prerequisites);
if (!isExamPrerequisitesMet) {
void reply.code(403);
@@ -582,12 +582,13 @@ async function getExams(
const exams = await this.prisma.envExam.findMany({
select: {
id: true,
config: true
config: true,
prerequisites: true
}
});
const availableExams = exams.map(exam => {
const isExamPrerequisitesMet = checkPrerequisites(user, true);
const isExamPrerequisitesMet = checkPrerequisites(user, exam.prerequisites);
return {
id: exam.id,

View File

@@ -3,6 +3,7 @@ import { exam, examAttempt, generatedExam } from '../../../__mocks__/env-exam';
import * as schemas from '../schemas';
import {
checkAttemptAgainstGeneratedExam,
checkPrerequisites,
constructUserExam,
generateExam,
userAttemptToDatabaseAttemptQuestionSets,
@@ -56,9 +57,27 @@ describe('Exam Environment', () => {
).toBe(false);
});
});
xdescribe('checkPrequisites()', () => {
// TODO: Awaiting implementation
describe('checkPrequisites()', () => {
it("should return true if all items in the second argument exist in the first argument's `.completedChallenges[].id`", () => {
const user = {
completedChallenges: [{ id: '1' }, { id: '2' }]
};
const prerequisites = ['1', '2'];
expect(checkPrerequisites(user, prerequisites)).toBe(true);
});
it("should return false if any items in the second argument do not exist in the first argument's `.completedChallenges[].id`", () => {
const user = {
completedChallenges: [{ id: '2' }]
};
const prerequisites = ['1', '2'];
expect(checkPrerequisites(user, prerequisites)).toBe(false);
});
});
describe('constructUserExam()', () => {
it('should not provide the answers', () => {
const userExam = constructUserExam(generatedExam, exam);

View File

@@ -7,22 +7,33 @@ import {
EnvGeneratedExam,
EnvMultipleChoiceQuestion,
EnvQuestionSet,
EnvQuestionSetAttempt,
user
EnvQuestionSetAttempt
} from '@prisma/client';
import { type Static } from '@fastify/type-provider-typebox';
import * as schemas from '../schemas';
/**
* Checks if all exam prerequisites have been met by the user.
*
* TODO: This will be done by getting the challenges required from the curriculum.
*/
export function checkPrerequisites(_user: user, _prerequisites: unknown) {
return true;
interface CompletedChallengeId {
completedChallenges: {
id: string;
}[];
}
export type UserExam = Omit<EnvExam, 'questionSets' | 'config' | 'id'> & {
/**
* Checks if all exam prerequisites have been met by the user.
*/
export function checkPrerequisites(
user: CompletedChallengeId,
prerequisites: EnvExam['prerequisites']
) {
return prerequisites.every(p =>
user.completedChallenges.some(c => c.id === p)
);
}
export type UserExam = Omit<
EnvExam,
'questionSets' | 'config' | 'id' | 'prerequisites'
> & {
config: Omit<EnvExam['config'], 'tags' | 'questionSets'>;
questionSets: (Omit<EnvQuestionSet, 'questions'> & {
questions: (Omit<

View File

@@ -1,15 +1,13 @@
---
id: 645147516c245de4d11eb7ba
title: Front End Development Certification Exam
challengeType: 17
challengeType: 24
dashedName: front-end-development-certification-exam
---
# --description--
Here are some rules:
- click start
Start your exam in the exam environment app.
# --instructions--