Lewati ke isi

Laporan Remediasi Audit CBT — Batch 2: Alur Peserta & Integritas Runner FE

Tanggal: 29 September 2026
Status Batch: SELESAI & TERVERIFIKASI
Baseline Commit: 8abc4dac0bbc610ea8a38ea32550b45299d4c63d
Lingkungan Pengujian: PostgreSQL 16 disposable (scola_assessment_test) & Vitest / vue-tsc


1. Ringkasan Temuan & Solusi

B01 — Admission & Runner Frontend Gagal Menyelesaikan Kontrak API (P1)

  • Akar Masalah:
  • FE memanggil GET /api/v1/runner/sessions/{id}/manifest yang tidak ada (404), dan PUT /answers yang tidak ada (404).
  • Nama field tidak cocok: expected_revision vs revision_number, selected_option vs selected_option_id, essay_text vs response_text.
  • Modal login memanggil /runner/auth alih-alih /api/v1/runner/auth/lookup.
  • Perbaikan:
  • Di useExamRunner.ts: Alur diubah menjadi POST /sessions/{id}/start $\rightarrow$ GET /attempts/{id}/items $\rightarrow$ POST /attempts/{id}/autosave.
  • Di ExamRunner.vue: Bindings disesuaikan dengan skema backend (selected_option_id, numeric_value, response_text).
  • Di ParticipantAuthModal.vue & ReadinessCheck.vue: Endpoint disesuaikan ke /api/v1/runner/auth/lookup, menyimpan runner_token, dan mendukung parameter query SEB handoff (session dan session_id).

B07 — Debounce Save Lintas Soal & Submit Tanpa Flush Membuang Jawaban (P1)

  • Akar Masalah:
  • Timer debounce tunggal global (autosaveTimeout) membatalkan penyimpanan soal sebelumnya saat peserta berpindah soal cepat (<500ms).
  • Submit langsung memanggil /submit tanpa menunggu autosave in-flight atau pending debounce selesai.
  • Perbaikan:
  • Mengganti timer global dengan antrean per-soal (pendingTimers = new Map<string, any>(), pendingDrafts = new Map<string, any>()). Timer Soal 1 tidak membatalkan timer Soal 2.
  • Menambahkan flush barrier pada submitExam: seluruh timer pending dieksekusi seketika dan di-await via Promise.allSettled sebelum memanggil POST /submit.

B08 — ACK Usang Menimpa State Lokal & Ketiadaan Rekonsiliasi Draft Gagal (P1)

  • Akar Masalah:
  • ACK lama yang tiba belakangan karena latensi menimpa jawaban yang lebih baru di state frontend.
  • Saat fetch error, draft tidak disimpan di memori dan tidak bisa direkonsiliasi.
  • Perbaikan:
  • Memasang monotonic ACK guard: respon ACK hanya memperbarui state lokal jika ack.accepted_revision >= currentRevision.
  • Jawaban selalu dicatat di answers.value[qId] dan pendingDrafts saat input, sehingga tidak hilang meskipun jaringan terputus.

B09 — Timer Mengikuti Jam Lokal Peserta & Rentan Submit Prematur (P1)

  • Akar Masalah:
  • updateCountdown membandingkan serverDeadline langsung dengan Date.now() lokal peserta. Jika jam laptop 5 menit lebih cepat, ujian tersubmit otomatis secara prematur.
  • Perbaikan:
  • Menghitung delta waktu server: serverTimeDelta = serverNowMs - Date.now().
  • Countdown menghitung sisa waktu berdasarkan waktu server aktual: nowServer = Date.now() + serverTimeDelta. Jam lokal yang tidak akurat tidak menyebabkan submit prematur.

B11 — Concurrent Start Attempt Memunculkan IntegrityError / HTTP 500 (P2)

  • Akar Masalah:
  • Dua request POST /sessions/{id}/start yang tiba bersamaan sama-sama membaca absence sebelum insert; yang kedua memicu unhandled IntegrityError (500).
  • Perbaikan:
  • Di runner_session_service.py:start_or_resume_attempt: menangkap IntegrityError, melakukan rollback subtransaksi secara aman, lalu me-resolve dan mengembalikan attempt yang telah dibuat secara idempoten (HTTP 200 OK).

2. File yang Diubah

File Keterangan Perubahan
frontend/src/composables/useExamRunner.ts Kontrak start/items/autosave, debounce per-soal, flush barrier submit, monotonic ACK guard, clock skew resistance.
frontend/src/views/runner/ExamRunner.vue Bindings field selected_option_id & response_text.
frontend/src/components/readiness/ParticipantAuthModal.vue Perbaikan endpoint auth lookup & penyimpanan runner_token.
frontend/src/views/runner/ReadinessCheck.vue Dukungan handoff query session_id/session & available_sessions.
backend/app/services/runner_session_service.py Penanganan IntegrityError pada concurrent start race condition.
frontend/tests/unit/useExamRunner.test.ts Unit test suite Vitest untuk B01, B07, B08, B09 (7 passed).
tests/integration/test_b11_concurrent_start_regression.py Regression test suite PostgreSQL disposable untuk B11 (1 passed).

3. Bukti Verifikasi Pengujian

A. Pengujian Backend PostgreSQL (B11 & Full Regression Suite)

  • Sebelum patch B11: tests/integration/test_b11_concurrent_start_regression.py FAILED (IntegrityError).
  • Sesudah patch B11:
    tests/integration/test_b11_concurrent_start_regression.py::test_b11_concurrent_start_requests_both_succeed_idempotently PASSED [100%]
    
  • Full Backend Integration Suite:
    TEST_DATABASE_URL="postgresql://cbt@/scola_assessment_test?host=/var/run/postgresql" \
    SCOLA_ASSESSMENT_ALLOW_TEST_DB_RESET=yes \
    PYTHONPATH=backend \
    .venv/bin/pytest tests/integration/ -q --tb=short
    
    Hasil: 169 passed, 5 warnings in 82.75s (0:01:22) — 100% lulus tanpa regresi!

B. Pengujian Frontend Vitest & Build

  • Vitest Unit Tests:
    npx vitest run tests/unit/useExamRunner.test.ts
    
    Hasil:
     ✓ tests/unit/useExamRunner.test.ts (7 tests) 40ms
       ✓ B01: loadManifest follows valid backend protocol (POST /start then GET /items)
       ✓ B01: loadManifest sets clear runnerError on failure
       ✓ B07: per-question debounce does not discard prior question save
       ✓ B07: submitExam flushes pending saves before calling submit endpoint
       ✓ B08: monotonic ACK guard ignores older out-of-order ACK
       ✓ B08: offline save failure retains draft locally without data loss
       ✓ B09: clock skew compensation prevents premature automatic submit
    Test Files  1 passed (1)
    Tests       7 passed (7)
    
  • Frontend Typecheck & Production Build:
    npm run build (vue-tsc && vite build)
    
    Hasil: 389 modules transformed. Built in 2.23s — Zero errors.