fix(api): require academic honesty policy before allowing exams (#64760)

This commit is contained in:
Shaun Hamilton
2026-01-27 06:56:13 +02:00
committed by GitHub
parent 5ff971687c
commit a4d11d6f94
3 changed files with 53 additions and 10 deletions
@@ -55,6 +55,11 @@ describe('/exam-environment/', () => {
examEnvironmentAuthorizationToken =
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
res.body.examEnvironmentAuthorizationToken;
await fastifyTestInstance.prisma.user.update({
where: { id: defaultUserId },
data: { isHonest: true }
});
});
afterAll(async () => {
@@ -695,6 +700,11 @@ describe('/exam-environment/', () => {
where: { id: mock.examId },
data: { deprecated: false }
});
await fastifyTestInstance.prisma.user.update({
where: { id: defaultUserId },
data: { isHonest: true }
});
});
it('should return 200', async () => {
@@ -721,6 +731,34 @@ describe('/exam-environment/', () => {
expect(res.status).toBe(200);
});
it('should return all exams as unable to take, if user has not accepted academic honesty policy', async () => {
await fastifyTestInstance.prisma.user.update({
where: { id: defaultUserId },
data: { isHonest: false }
});
const res = await superGet('/exam-environment/exams').set(
'exam-environment-authorization-token',
examEnvironmentAuthorizationToken
);
expect(res.body).toStrictEqual([
{
canTake: false,
config: {
name: mock.exam.config.name,
note: mock.exam.config.note,
passingPercent: mock.exam.config.passingPercent,
totalTimeInS: mock.exam.config.totalTimeInS,
retakeTimeInS: mock.exam.config.retakeTimeInS
},
id: mock.examId,
prerequisites: mock.exam.prerequisites
}
]);
expect(res.status).toBe(200);
});
it('should not return any deprecated exams', async () => {
await fastifyTestInstance.prisma.examEnvironmentExam.update({
where: { id: mock.examId },
@@ -78,7 +78,8 @@ describe('Exam Environment mocked Math.random', () => {
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' }]
completedChallenges: [{ id: '1' }, { id: '2' }],
isHonest: true
};
const prerequisites = ['1', '2'];
@@ -87,7 +88,8 @@ describe('Exam Environment mocked Math.random', () => {
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' }]
completedChallenges: [{ id: '2' }],
isHonest: false
};
const prerequisites = ['1', '2'];
@@ -1,3 +1,4 @@
/* eslint-disable jsdoc/require-description-complete-sentence */
// TODO: enable this, since strings don't make good errors.
/* eslint-disable @typescript-eslint/only-throw-error */
import {
@@ -21,21 +22,23 @@ import { mapErr } from '../../utils/index.js';
import { ExamAttemptStatus } from '../schemas/exam-environment-exam-attempt.js';
import { ERRORS } from './errors.js';
interface CompletedChallengeId {
completedChallenges: {
id: string;
}[];
interface PartialUser {
completedChallenges: { id: string }[];
isHonest: boolean | null;
}
/**
* Checks if all exam prerequisites have been met by the user.
* Checks if all exam prerequisites have been met by the user:
* - completed challenges linked to exam
* - user is required to have accepted the academic honesty policy
*/
export function checkPrerequisites(
user: CompletedChallengeId,
user: PartialUser,
prerequisites: ExamEnvironmentExam['prerequisites']
) {
return prerequisites.every(p =>
user.completedChallenges.some(c => c.id === p)
return (
user.isHonest &&
prerequisites.every(p => user.completedChallenges.some(c => c.id === p))
);
}