10 KiB
10 KiB
Epic 2 Retrospektif — Ujian & Sertifikat (LMS-BGN)
Tanggal: 2025-11-12 Cakupan: Siklus sesi ujian, penilaian, ringkasan, daftar ujian, pengiriman ulang sertifikat
Ringkasan Hasil
- Endpoint backend untuk Epic 2 telah dibuat dan diverifikasi dengan pengujian.
- Contoh REST Client dan cURL tersedia untuk QA manual.
- Kontrak mengikuti
docs/api/openapi.yamldan diselaraskan dengan kebutuhan UI inti. - Integrasi frontend sengaja ditunda sesuai instruksi; fokus pada pelurusan dokumentasi.
Cakupan yang Diimplementasikan
POST /api/exam-session— membuat sesi ujian (validasi dasar).POST /api/exam-session/{sessionId}/score— penilaian batch denganidempotencyKey.GET /api/exam-session/{sessionId}/summary— ringkasan hasil teragregasi.GET /api/exams— daftar ujian terpaginasikan dengan filter teksq.POST /api/certificates/{certificateId}/resend— pengiriman ulang email (diterima + throttling).
Cakupan OpenAPI & Kuis Interaktif
ExamSessionCreateRequest: saat ini memuatexamIddanuserId. Mode interaktif ditentukan dari metadata ujian di sisi server (lihatExamListItem.isInteractive). Tidak ada skema bernamaInteractiveExamterpisah pada spesifikasi sekarang.ExamScoreRequest: memuatmode(batch|stream),idempotencyKey, dananswers[]dengan bentuk sederhana{ questionId, choiceIndex }. Bentuk ini belum mencakup tipe interaktif (hotspot, puzzle, scenario) darisampleInteractiveExamData; perlu perluasan skema di fase selanjutnya.ExamSummaryResponse: bentuk respons diselaraskan dengan kebutuhan halaman ringkasan (judul, skor, total, jawaban per soal) dan sesuai contoh di spesifikasi.ExamListItem: memuatisInteractive: booleansehingga UI dapat membedakan kuis interaktif di listing.- Konsekuensi: Integrasi penuh fitur interaktif memerlukan penambahan skema dan payload yang lebih kaya (mis. struktur hotspot, puzzle, scenario) ke OpenAPI sebelum wiring frontend.
Kualitas & Metrik
- Pengujian: 17 lulus, 0 gagal (Jest + Supertest).
- Durasi suite: ~1.8s lokal.
- Artefak QA:
- REST Client:
docs/qa/assessments/backend-endpoints.http - cURL:
docs/qa/assessments/curl-examples.md
- REST Client:
Hal yang Berjalan Baik
- Bootstrap backend cepat dengan Express; pemisahan concern jelas.
- Idempotensi & throttling dasar untuk route sensitif.
- Pengujian menyentuh happy-path dan kasus error inti.
- Dokumentasi dan contoh memudahkan verifikasi manual.
Tantangan
- Integrasi frontend belum dihubungkan:
src/app/exam-session/page.tsx,exam-summary, danexamsmasih butuh wiring API (ditunda sesuai instruksi). - Data masih in-memory; belum ada persistence atau auth sehingga alur belum realistis.
- Kesenjangan pemetaan
sampleInteractiveExamDatake payload OpenAPI saat ini (belum ada skema interaktif rinci).
Risiko & Mitigasi
- Drift kontrak vs ekspektasi frontend — Mitigasi: patuhi
docs/api/openapi.yaml, perluas skema untuk interaktif, dan sesuaikan pengujian. - Deliverability email & audit — Mitigasi: rencanakan konfigurasi SMTP, audit log, dan retry/backoff.
- Tidak ada persistence — Mitigasi: definisikan skema minimal, tambah layer DB ringan, pindahkan state idempotensi dari memori ke storage.
- Keamanan & rate limiting — Mitigasi: tambahkan auth checks dan rate-limit untuk route ujian & sertifikat.
Tindak Lanjut (Tanpa Implementasi Langsung)
- Perluas OpenAPI untuk mendukung tipe interaktif (hotspot, puzzle, scenario) dan bentuk jawaban yang relevan.
- Definisikan pemetaan dari
sampleInteractiveExamDatake payload backend (create/score/summary) dan tambahkan contoh di dokumentasi. - Tambah persistence minimal untuk sesi & jawaban; pertimbangkan auth dasar.
- Siapkan rencana wiring frontend ke backend untuk sprint berikutnya, sesuai skema yang diperluas.
- CI: jalankan
npm testpada push/PR. - Observabilitas: logging terstruktur & audit trail untuk alur ujian/sertifikat.
Referensi
- Konteks:
docs/epic-2-context.md - OpenAPI:
docs/api/openapi.yaml - QA:
docs/qa/assessments/backend-endpoints.http,docs/qa/assessments/curl-examples.md - Frontend (belum terhubung):
src/app/exam-session/page.tsx,src/app/exam-summary/page.tsx,src/app/exams/page.tsx
Contoh Payload — Interaktif (Draft)
Catatan: Contoh berikut bertujuan memudahkan diskusi skema. Beberapa field bertanda "proposal" tidak ada dalam spesifikasi saat ini dan akan diajukan di perluasan OpenAPI berikutnya.
Create (proposal ekstensi)
Request:
{
"examId": "interactive-dapur-mbg-001",
"userId": "user-123",
"isInteractive": true,
"idempotencyKey": "create-5a1d9e2c-9f12-4d3a-b3a1-7c8e0b4f6a11",
"clientContext": {
"device": "web",
"appVersion": "0.1.0",
"locale": "id-ID"
}
}
Response (sesuai spesifikasi saat ini):
{
"sessionId": "sess-001",
"status": "created",
"startTime": "2025-11-12T08:00:00Z"
}
Score (proposal dukungan tipe interaktif)
Request:
{
"mode": "batch",
"idempotencyKey": "score-8d3bc2f0-26a3-4fd0-9b4c-5e2b7b1f9c77",
"answers": [
{ "questionId": "1", "type": "enhanced_mcq", "choiceIndex": 0, "confidence": 4 },
{ "questionId": "video-scenario-1", "type": "video_scenario", "scenarioAnswers": [
{ "stepId": "step1", "selectedOptionId": "opt4" },
{ "stepId": "step2", "selectedOptionId": "opt4" }
]},
{ "questionId": "hotspot-scenario-1", "type": "image_hotspot", "selectedHotspots": ["hotspot1", "hotspot2", "hotspot4"] },
{ "questionId": "media-gallery-1", "type": "media_gallery", "viewedItems": ["video1", "image1", "video2", "image2", "document1", "image3"] },
{ "questionId": "3", "type": "puzzle", "matches": [
{ "pieceId": "ccp", "targetId": "def1" },
{ "pieceId": "haccp", "targetId": "def2" },
{ "pieceId": "sanitasi", "targetId": "def3" },
{ "pieceId": "kontaminasi", "targetId": "def4" }
]},
{ "questionId": "6", "type": "scenario", "selectedOptionId": "option2" }
]
}
Response (contoh ringkas):
{
"totalScore": 85,
"totalQuestions": 10,
"correctAnswers": 8,
"scorePercent": 85,
"perQuestion": [
{ "questionId": "1", "isCorrect": true, "earnedPoints": 10, "maxPoints": 10 },
{ "questionId": "video-scenario-1", "isCorrect": true, "earnedPoints": 20, "maxPoints": 20 }
]
}
Summary (kompatibel + proposal detail)
Response (kompatibel):
{
"examId": "interactive-dapur-mbg-001",
"examTitle": "Kuis Interaktif Dapur MBG",
"totalQuestions": 10,
"correctAnswers": 8,
"score": 85,
"timeSpent": "25 menit 30 detik",
"completedAt": "2025-11-12T08:30:00Z",
"answers": [
{
"questionId": "1",
"question": "Apa langkah pertama dalam memastikan keamanan pangan di Dapur MBG?",
"userAnswer": "Melakukan analisis bahaya",
"correctAnswer": "Melakukan analisis bahaya",
"isCorrect": true,
"type": "enhanced_mcq"
},
{
"questionId": "video-scenario-1",
"question": "Analisis Situasi Dapur: Identifikasi Masalah Keamanan Pangan",
"userAnswer": "step1: opt4; step2: opt4",
"correctAnswer": "step1: opt4; step2: opt4",
"isCorrect": true,
"type": "video_scenario"
},
{
"questionId": "hotspot-scenario-1",
"question": "Identifikasi Area Bermasalah dalam Tata Letak Dapur",
"userAnswer": "hotspot1,hotspot2,hotspot4",
"correctAnswer": "hotspot1,hotspot2,hotspot3,hotspot4,hotspot5",
"isCorrect": false,
"type": "image_hotspot"
},
{
"questionId": "3",
"question": "Cocokkan istilah keamanan pangan dengan definisinya",
"userAnswer": "ccp->def1; haccp->def2; sanitasi->def3; kontaminasi->def4",
"correctAnswer": "ccp->def1; haccp->def2; sanitasi->def3; kontaminasi->def4",
"isCorrect": true,
"type": "puzzle"
}
]
}
Catatan kompatibilitas:
- Agar tetap kompatibel,
userAnsweruntuk tipe interaktif diringkas sebagai string; perluasan dapat menambahkandetailsopsional berisi struktur lengkap per tipe (proposal).
Catatan Implementasi (Update)
- Artefak QA telah disinkronkan: payload union untuk
ExamScoreRequesttersedia didocs/qa/assessments/backend-endpoints.http, termasuk blok uji MCQ-only untuk hasil skor positif. - Backend kini memparse dan menilai tipe interaktif (video_scenario, image_hotspot, media_gallery, puzzle, scenario) dengan skema sederhana, termasuk partial scoring per tipe; bentuk
{ questionId, choiceIndex }tetap didukung untuk MCQ. - Respons
GET /api/exam-session/{sessionId}/summarysekarang menyertakananswers[].detailsper tipe (langkah video, hotspot terpilih vs kunci, progres gallery, langkah puzzle, dan opsi scenario) sehingga kompatibel + kaya detail sesuai contoh draft. - Pemeriksaan Authorization (Bearer) diterapkan server-side untuk endpoint ujian: create session, score, dan summary. QA contoh sudah menyertakan header
Authorization: Bearer test-token.
NFR & Observabilitas (Ringkas)
- Logging terstruktur dengan korelasi
requestId/sessionIduntuk create/score/summary/resend - Metrics:
score_latency_ms,summary_latency_ms,error_rate,resend_throttle_hits - Alerting sederhana untuk lonjakan error rata-rata p95
- Audit trail untuk
resend(recipient, status, attempt)
Dependencies (Versi Target)
- Node.js
18.x, Express4.x, Jest29.x, Supertest6.x, Nodemailer6.x
Acceptance Criteria Atomik + Pemetaan Test
- POST
/api/exam-sessionmembuat sesi valid → tes integrasi create (Bearer wajib) - POST
/api/exam-session/{sessionId}/scoremenghitung skor (MCQ+interaktif) & idempotent → tes integrasi score + idempotency - GET
/api/exam-session/{sessionId}/summarymenampilkan agregat &answers[].details→ tes integrasi summary - POST
/api/certificates/{certificateId}/resendthrottling + audit → tes integrasi resend
Traceability Matrix (Ringkas)
| AC ID | Bagian | Komponen | Story | Tes |
|---|---|---|---|---|
| R-AC1 | Create Session | server.js route create |
docs/stories/2-1-exam-session-create-api.md |
backend/tests/server.test.js (create session) |
| R-AC2 | Scoring & Idempotency | scorer util + route score | docs/stories/2-2-exam-scoring-api.md |
backend/tests/server.test.js (score + idempotency) |
| R-AC3 | Summary kompatibel | summary builder + route summary | docs/stories/2-3-exam-summary-read-api.md |
backend/tests/server.test.js (summary) |
| R-AC4 | Resend throttled + audit | resend handler | TBD: docs/stories/2-4-certificate-resend-api.md |
backend/tests/server.test.js (resend) |