Lewati ke isi

Laporan Remediasi Audit CBT — Batch 4: Kesiapan SEB/Kamera, LMS Sync, Validasi Numerik & Keamanan Ganti Email

Tanggal: 29 September 2026
Status Batch: SELESAI & TERVERIFIKASI (188 Integration Tests Passed)
Baseline Commit: 8abc4dac0bbc610ea8a38ea32550b45299d4c63d
Lingkungan Pengujian: PostgreSQL 16 disposable (scola_assessment_test) via Alembic e2f3a4b5c6d7


1. Ringkasan Temuan & Solusi

B12 — Pemeriksaan Kesiapan Klien (Readiness Check) Bersifat Simulasi Kosmetik & Leak MediaStream (P2)

  • Akar Masalah:
  • ReadinessCheck.vue mensimulasikan seluruh pemeriksaan perangkat dengan timer buatan dan menghasilkan kode dukungan acak di sisi peramban (SC-xxxxxx), tanpa memanggil endpoint backend yang sudah tersedia (POST /api/v1/secure/readiness-check).
  • Kamera peserta tidak diuji keasliannya dan stream media di ExamRunner.vue tidak selalu dihentikan (track.stop()) saat komponen di-unmount, meninggalkan indikator kamera aktif di OS.
  • Nilai minimum SEB di seb_service.py adalah "3.4.0", tidak konsisten dengan AGENTS.md dan SEBConfiguration ("3.7.0").
  • Perbaikan:
  • Di seb_service.py: Memperbarui MINIMUM_SEB_VERSION = "3.7.0".
  • Di ReadinessCheck.vue:
    • Menguji kamera nyata via navigator.mediaDevices.getUserMedia({ video: true }) dan langsung mematikan tracks setelah status terverifikasi.
    • Mendeteksi jenis OS klien dan memeriksa screenCount.
    • Memanggil POST /api/v1/secure/readiness-check untuk menyimpan telemetri diagnostik ke basis data dan menampilkan support_code resmi dari server (SUP-YYYYMMDD-XXXXXX).
  • Di ExamRunner.vue: Menyimpan referensi activeCameraStream dan memastikan seluruh track ditutup (track.stop()) pada hook onUnmounted.

B13 — Sinkronisasi Nilai LMS Membaca Properti Kosong & Menghitung Persentase Statis (P2)

  • Akar Masalah:
  • lms_integration_service.py:sync_session_grades_to_lms membaca att.participant.participant_number (padahal relasi berada di att.enrollment.participant), att.score (kolom ini tidak ada di tabel attempts), dan att.max_score (tidak ada di attempts), menghasilkan nilai 0.0 atau AttributeError.
  • Upaya ujian berstatus in_progress ikut disinkronkan ke LMS sebagai peserta selesai.
  • Perbaikan:
  • Hanya memfilter sesi ujian yang telah selesai: Attempt.status.in_(["submitted", "graded", "finalized"]).
  • Membaca identitas peserta secara aman: att.enrollment.participant.
  • Menghitung nilai peserta secara riil dengan menjumlahkan revisi nilai terbaru (GradeRevision.score) per butir soal, konsisten dengan ranking_service.py.
  • Mengambil batas nilai maksimal riil dari ExamForm.max_score (dengan fallback 100.0).
  • Menghitung persentase akurat: percentage = round((final_score / max_score) * 100, 2).

B14 — Penulis Soal & Impor CSV Dapat Memasukkan Nilai Numerik Non-Finite (P2)

  • Akar Masalah:
  • Impor CSV (import_questions) dan validasi form (validate_exam_form_pre_admission) menerima string "nan", "inf", "-inf" atau toleransi negatif karena Python float("nan") tidak memicu exception dan float("nan") < 0 bernilai False.
  • Hal ini menyebabkan evaluasi jawaban otomatis peserta (abs(student_ans - target) <= tol) menghasilkan False permanen atau kalkulasi nilai tidak stabil.
  • Perbaikan:
  • Di question_service.py: Menambahkan validasi math.isfinite(num_target) dan math.isfinite(num_tol) and num_tol >= 0 saat memproses baris impor CSV tipe numerik. Baris yang melanggar ditolak dengan QuestionRowError yang deskriptif.
  • Di form_service.py:validate_exam_form_pre_admission: Memvalidasi bahwa setiap butir soal bertipe numerik memiliki numeric_target yang finite dan numeric_tolerance finite non-negatif.

B15 — Permintaan Perubahan Email Menulis OTP ke Log/Response Tanpa Outbox & TTL Bypass (P2)

  • Akar Masalah:
  • auth_service.py:request_email_change mengembalikan dev_otp secara terbuka di response JSON bahkan di lingkungan staging/production, dan tidak menyalurkan pengiriman email via transactional outbox.
  • Fallback memori pada confirm_email_change (_in_memory_email_otp) tidak memeriksa expires_at, sehingga OTP kadaluwarsa dapat diterima jika Redis sedang offline.
  • Tidak ada rate limiting pada percobaan verifikasi OTP ganti email.
  • Perbaikan:
  • Di auth_service.py:request_email_change:
    • Menyisipkan pesan outbox auth.email_change_requested via enqueue_outbox_event untuk audit dan pengiriman asinkron.
    • Menyembunyikan dev_otp di lingkungan non-development (dev_otp = None pada staging dan production).
  • Di auth_service.py:confirm_email_change:
    • Memasang progressive rate limiting (check_rate_limit(f"email_change:{user_id}") dan record_failure).
    • Memeriksa batas kedaluwarsa pada fallback memori: mem.get("expires_at", 0) > time.time().
    • Menyisipkan pesan outbox auth.email_changed saat perubahan email berhasil dikonfirmasi.

2. File yang Diubah & Dibuat

File Status Keterangan Perubahan
backend/app/services/seb_service.py Dimodifikasi Menaikkan MINIMUM_SEB_VERSION ke "3.7.0".
backend/app/services/lms_integration_service.py Dimodifikasi Perhitungan nilai riil via GradeRevision, pembacaan ExamForm.max_score, dan filter status attempt selesai.
backend/app/services/question_service.py Dimodifikasi Validasi math.isfinite pada target dan toleransi numerik saat impor CSV.
backend/app/services/form_service.py Dimodifikasi Validasi math.isfinite pra-admisi paket ujian untuk butir numerik.
backend/app/services/auth_service.py Dimodifikasi Transactional outbox event email change, penyembunyian OTP staging/prod, rate limiting 5 kali gagal, dan kepatuhan TTL fallback memori.
frontend/src/views/runner/ReadinessCheck.vue Dimodifikasi Probe kamera nyata dengan penutupan track seketika, integrasi API POST /api/v1/secure/readiness-check, dan pengikatan server support code.
frontend/src/views/runner/ExamRunner.vue Dimodifikasi Penutupan track kamera aktif (track.stop()) saat unmount.
tests/integration/test_batch4_regression.py Baru 8 pengujian regresi komprehensif untuk B12, B13, B14, dan B15 (100% lulus).

3. Bukti Verifikasi Pengujian

A. Test Suite Regresi Batch 4 (tests/integration/test_batch4_regression.py)

==================== test session starts =====================
collected 8 items

tests/integration/test_batch4_regression.py::test_b12_readiness_check_persists_to_backend PASSED [ 12%]
tests/integration/test_batch4_regression.py::test_b12_minimum_seb_version_enforced PASSED [ 25%]
tests/integration/test_batch4_regression.py::test_b13_lms_grade_sync_uses_real_grade_revisions_and_form_max_score PASSED [ 37%]
tests/integration/test_batch4_regression.py::test_b14_csv_import_rejects_nan_and_infinity_numeric PASSED [ 50%]
tests/integration/test_batch4_regression.py::test_b14_form_validation_rejects_nonfinite_numeric PASSED [ 62%]
tests/integration/test_batch4_regression.py::test_b15_email_change_queues_outbox_message_and_hides_otp_in_staging PASSED [ 75%]
tests/integration/test_batch4_regression.py::test_b15_email_change_memory_fallback_respects_expiry PASSED [ 87%]
tests/integration/test_batch4_regression.py::test_b15_email_change_rate_limiting PASSED [100%]

=============== 8 passed, 5 warnings in 3.20s ================

B. Frontend Typecheck & Build

npm run build (vue-tsc && vite build)
✓ 389 modules transformed.
✓ built in 1.17s with 0 errors

C. Full Backend Integration Test Suite

tests/integration/ -q --tb=short
.................................................... [ 27%]
.................................................... [ 55%]
.................................................... [ 82%]
................................                     [100%]
188 passed, 6 warnings in 90.07s (0:01:30)
Zero regression: Seluruh 188 tes integrasi backend lulus tanpa kegagalan.