Skip to content

feat: Certification exams — server-graded timed assessments that issue a verified credential #1081

Description

@Anuoluwapo25

Summary

Build certification exams — timed, server-graded assessments drawn from a question bank that a learner takes at the end of a track, with a pass threshold, a retake cooldown, and a verifiable credential issued on passing. Quizzes today are practice-only: src/components/QuizEngine.tsx receives correctIndex in its props and grades entirely in the browser, which is fine for practice and unusable as the basis for a credential.

Background

LearnVault's whole premise is that learning is the proof of work — the credential has to mean something to the sponsor funding the scholarship and to the employer reading it. That requires an assessment whose answers the candidate cannot read out of the page they are sitting on.

This is the missing link between the existing course content and the existing credential machinery (server/src/controllers/certificates.controller.ts, the scholar_nft contract). Right now a learner can complete a track without ever demonstrating retained knowledge under any constraint.

Scope

Backend and frontend. Credential issuance reuses the existing certificate and ScholarNFT paths — no new contract work. Proctoring by webcam, identity verification, and browser lockdown are explicitly out of scope and should not be attempted here.

What to build

1. Question bank (database + admin)

  • Migration for exam_definitions, exam_questions, exam_attempts, and exam_answers. Questions carry difficulty, topic tags, and a correct-answer column that never leaves the server.
  • Per-exam config: question count, time limit, pass percentage, retake cooldown, and how many questions to draw per topic.
  • Admin CRUD for authoring exams and questions, following the existing server/src/controllers/admin-courses.controller.ts patterns and reusing the admin middleware.

2. Exam engine (backend)

  • POST /api/exams/:id/start creates an attempt, draws a randomised question set with shuffled options, and returns questions stripped of answers. Server records the deadline.
  • POST /api/exams/attempts/:id/answer saves one answer at a time so a dropped connection loses one question, not the exam.
  • POST /api/exams/attempts/:id/submit grades server-side, enforces the deadline server-side, and returns a per-topic breakdown.
  • Enforce the retake cooldown and reject attempts on a track the learner has not completed.
  • An attempt that passes triggers the existing certificate issuance path.

3. Exam runner (frontend)

  • A focused /exams/:id page: one question at a time, a countdown clock, a question navigator with answered/unanswered state, and a review screen before submission.
  • Answers persist as they are given, so a refresh resumes the same attempt with the correct remaining time rather than restarting or forfeiting it.
  • A results screen with the per-topic breakdown, a pass/fail outcome, the link to the earned credential, and — on a fail — which topics to revisit and when the retake unlocks.
  • Full keyboard operability, and a live-region announcement at the five-minute mark. A timed test that only signals visually excludes part of the audience.

4. i18n

  • All new strings across en, es, fr, sw, plus the regenerated pseudo-locale.

5. Tests

  • The start endpoint response contains no correct answers — assert on the serialised payload, this is the one test that matters most.
  • Grading is correct for single-answer, multi-answer, and unanswered questions.
  • A submission after the deadline is rejected even if the client clock says otherwise.
  • Two attempts draw different question sets from the same bank.
  • Cooldown blocks an early retake; passing issues exactly one credential, and resubmitting the same attempt does not issue a second.
  • Frontend: refresh mid-exam resumes the attempt with the correct time remaining.

Acceptance criteria

  • Admins can author exams and questions with topic tags and difficulty
  • Correct answers are never sent to the client, proven by test
  • Questions and options are randomised per attempt
  • Timing is enforced server-side; the client clock is not trusted
  • Answers save incrementally and an attempt survives a refresh
  • Grading returns a per-topic breakdown
  • Passing issues a credential through the existing certificate flow, exactly once
  • Retake cooldown enforced
  • Exam runner is fully keyboard and screen-reader accessible
  • All new strings translated across the four locales

Notes for contributors

  • Leave QuizEngine.tsx alone. In-lesson practice quizzes are a different product with different requirements, and merging the two would either weaken the exam or make practice quizzes needlessly heavy.
  • Never send the answer key to the client "and hide it in the UI". If it is in the network response, it is public.
  • Treat a submitted attempt as immutable. Re-grading a stored attempt should be possible; mutating one should not be.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

GrantFox OSSIssue tracked in GrantFox OSSMaybe RewardedIssue may be eligible for a GrantFox rewardOfficial Campaign | FWC26Campaign: Official Campaign | FWC26Third CampaignCampaign: Third CampaignbackendAPI/database/infrastructuredatabasePostgreSQL schema and migrationsenhancementNew feature or requestfeatureNew feature implementationfrontendReact/TypeScript UI work

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions