diff --git a/api/__mocks__/exam-environment-exam.ts b/api/__mocks__/exam-environment-exam.ts index c871a92be0f..28eda433a2f 100644 --- a/api/__mocks__/exam-environment-exam.ts +++ b/api/__mocks__/exam-environment-exam.ts @@ -248,7 +248,8 @@ export const generatedExam: ExamEnvironmentGeneratedExam = { } ] } - ] + ], + version: 1 }; export const examAttempt: ExamEnvironmentExamAttempt = { @@ -293,7 +294,8 @@ export const examAttempt: ExamEnvironmentExamAttempt = { } ], startTimeInMS: Date.now(), - userId: defaultUserId + userId: defaultUserId, + version: 1 }; export const examAttemptSansSubmissionTimeInMS: Static< @@ -340,7 +342,8 @@ export const exam: ExamEnvironmentExam = { config, questionSets, prerequisites: ['67112fe1c994faa2c26d0b1d'], - deprecated: false + deprecated: false, + version: 1 }; export async function seedEnvExam() { diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index 86206e7365b..a4f17fe3f34 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -182,6 +182,9 @@ model ExamEnvironmentExam { prerequisites String[] @db.ObjectId /// If `deprecated`, the exam should no longer be considered for users deprecated Boolean + /// Version of the record + /// The default must be incremented by 1, if anything in the schema changes + version Int @default(1) // Relations generatedExams ExamEnvironmentGeneratedExam[] @@ -313,6 +316,9 @@ model ExamEnvironmentExamAttempt { questionSets ExamEnvironmentQuestionSetAttempt[] /// Time exam was started as milliseconds since epoch startTimeInMS Int + /// Version of the record + /// The default must be incremented by 1, if anything in the schema changes + version Int @default(1) // Relations user user @relation(fields: [userId], references: [id], onDelete: Cascade) @@ -347,6 +353,9 @@ model ExamEnvironmentGeneratedExam { questionSets ExamEnvironmentGeneratedQuestionSet[] /// If `deprecated`, the generation should not longer be considered for users deprecated Boolean + /// Version of the record + /// The default must be incremented by 1, if anything in the schema changes + version Int @default(1) // Relations exam ExamEnvironmentExam @relation(fields: [examId], references: [id], onDelete: Cascade) @@ -549,6 +558,9 @@ model ExamEnvironmentExamModeration { /// Date the exam attempt was added to the moderation queue submissionDate DateTime @default(now()) @db.Date + /// Version of the record + /// The default must be incremented by 1, if anything in the schema changes + version Int @default(1) // Relations examAttempt ExamEnvironmentExamAttempt @relation(fields: [examAttemptId], references: [id], onDelete: Cascade) diff --git a/api/src/exam-environment/routes/exam-environment.test.ts b/api/src/exam-environment/routes/exam-environment.test.ts index e4e04c73e88..3e6698cd6e6 100644 --- a/api/src/exam-environment/routes/exam-environment.test.ts +++ b/api/src/exam-environment/routes/exam-environment.test.ts @@ -512,7 +512,9 @@ describe('/exam-environment/', () => { generatedExamId: generatedExam!.id, questionSets: [], // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - startTimeInMS: expect.any(Number) + startTimeInMS: expect.any(Number), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + version: expect.any(Number) }); }); diff --git a/api/src/exam-environment/utils/exam-environment.test.ts b/api/src/exam-environment/utils/exam-environment.test.ts index c86793735b7..7fe9682a9d5 100644 --- a/api/src/exam-environment/utils/exam-environment.test.ts +++ b/api/src/exam-environment/utils/exam-environment.test.ts @@ -1,11 +1,16 @@ -import { ExamEnvironmentAnswer } from '@prisma/client'; +import { + ExamEnvironmentAnswer, + ExamEnvironmentQuestionType +} from '@prisma/client'; import { type Static } from '@fastify/type-provider-typebox'; import { exam, examAttempt, - generatedExam + generatedExam, + oid } from '../../../__mocks__/exam-environment-exam'; import * as schemas from '../schemas'; +import { setupServer } from '../../../jest.utils'; import { checkAttemptAgainstGeneratedExam, checkPrerequisites, @@ -408,3 +413,117 @@ describe('Exam Environment', () => { }); }); }); + +describe('Exam Environment Schema', () => { + setupServer(); + describe('ExamEnvironmentExam', () => { + afterAll(async () => { + await fastifyTestInstance.prisma.examEnvironmentExam.deleteMany({}); + }); + + it("If this test fails and you've deliberately altered the schema, then increment the `version` field by 1", async () => { + const configQuestionSets = [ + { + numberOfCorrectAnswers: 0, + numberOfIncorrectAnswers: 0, + numberOfQuestions: 0, + numberOfSet: 0, + type: ExamEnvironmentQuestionType.MultipleChoice + } + ]; + const tags = [ + { + group: [''], + numberOfQuestions: 0 + } + ]; + const config = { + name: '', + note: '', + passingPercent: 0.0, + questionSets: configQuestionSets, + retakeTimeInMS: 0, + tags, + totalTimeInMS: 0 + }; + + const questions = [ + { + answers: [ + { + id: oid(), + isCorrect: false, + text: '' + } + ], + audio: { captions: '', url: '' }, + deprecated: false, + id: oid(), + tags: [''], + text: '' + } + ]; + const questionSets = [ + { + context: '', + id: oid(), + questions, + type: ExamEnvironmentQuestionType.MultipleChoice + } + ]; + const data = { + config, + deprecated: false, + prerequisites: [oid()], + questionSets + }; + + await fastifyTestInstance.prisma.examEnvironmentExam.create({ + data + }); + }); + }); + describe('ExamEnvironmentGeneratedExam', () => { + afterAll(async () => { + await fastifyTestInstance.prisma.examEnvironmentGeneratedExam.deleteMany( + {} + ); + }); + it("If this test fails and you've deliberately altered the schema, then increment the `version` field by 1", async () => { + await fastifyTestInstance.prisma.examEnvironmentGeneratedExam.create({ + data: { + deprecated: false, + examId: oid(), + questionSets: [ + { id: oid(), questions: [{ answers: [oid()], id: oid() }] } + ] + } + }); + }); + }); + describe('ExamEnvironmentExamAttempt', () => { + afterAll(async () => { + await fastifyTestInstance.prisma.examEnvironmentExamAttempt.deleteMany( + {} + ); + }); + it("If this test fails and you've deliberately altered the schema, then increment the `version` field by 1", async () => { + await fastifyTestInstance.prisma.examEnvironmentExamAttempt.create({ + data: { + examId: oid(), + generatedExamId: oid(), + questionSets: [ + { + id: oid(), + questions: [ + { answers: [oid()], id: oid(), submissionTimeInMS: 0 } + ] + } + ], + startTimeInMS: 0, + userId: oid() + } + }); + }); + }); +}); diff --git a/api/src/exam-environment/utils/exam-environment.ts b/api/src/exam-environment/utils/exam-environment.ts index 8ea0a4bdd73..df02b3b9768 100644 --- a/api/src/exam-environment/utils/exam-environment.ts +++ b/api/src/exam-environment/utils/exam-environment.ts @@ -41,7 +41,7 @@ export function checkPrerequisites( export type UserExam = Omit< ExamEnvironmentExam, - 'questionSets' | 'config' | 'id' | 'prerequisites' | 'deprecated' + 'questionSets' | 'config' | 'id' | 'prerequisites' | 'deprecated' | 'version' > & { config: Omit; questionSets: (Omit & { @@ -280,7 +280,7 @@ export function userAttemptToDatabaseAttemptQuestionSets( */ export function generateExam( exam: ExamEnvironmentExam -): Omit { +): Omit { const examCopy = structuredClone(exam); const TIMEOUT_IN_MS = 5_000;