# Rencana UAT (User Acceptance Testing) SIPORA

Dokumen ini untuk dijalankan oleh **orang sungguhan** (idealnya calon pengguna nyata: staf BK,
admin sekolah, bukan developer) sebelum pilot diluncurkan. Cakupannya seluruh Fase 1-4 yang
sudah dibangun. Setiap skenario punya kotak centang dan kolom "Hasil" untuk diisi saat
pengujian — dokumen yang sudah diisi jadi bukti UAT untuk keputusan go/no-go pilot
(`PILOT_READINESS_CHECKLIST.md`).

Untuk penyiapan lingkungan (nyalakan MySQL, jalankan PHP, akun awal), lihat `TESTING.md`.

**Cara mengisi:** beri tanda `[x]` bila hasil sesuai ekspektasi, catat di kolom Catatan bila
tidak — sertakan kode referensi error (`Kode referensi: xxxxx`) bila muncul, bisa dicari di
`storage/logs/YYYY-MM-DD.log`.

## Catatan: Verifikasi Otomatis (2026-09-04)

Setiap skenario di bawah sudah dicek satu per satu terhadap 189 test PHPUnit yang ada
(bukan diasumsikan lolos). Skenario yang lolos diberi tanda **✅ Terverifikasi otomatis**
beserta nama test yang membuktikannya — artinya **perilaku sistemnya sudah terbukti benar
lewat kode**, bukan berarti UAT sudah selesai. Ini BUKAN pengganti eksekusi manusia:
seorang penguji sungguhan tetap perlu menjalankannya di browser untuk menilai hal yang tidak
bisa dibuktikan otomatis (apakah alurnya intuitif, pesan errornya mudah dipahami orang awam,
tampilan wajar di perangkat sungguhan, dst.) — centang `[x]` di kotak tetap milik penguji
sungguhan, bukan diisi lewat audit ini.

**Temuan penting dari audit ini**: 5 celah cakupan test nyata ditemukan dan langsung
ditutup dengan test baru (bukan cuma dicatat) sebelum dianggap "terverifikasi":
1. Jenis butir `single_choice` belum pernah diuji lewat form web sama sekali di seluruh
   riwayat proyek — ditutup: `InstrumentBuilderTest::test_single_choice_item_type_can_be_created_with_options_and_rendered`.
2. `clone_version()` menyalin `flag_code`/`is_flag_trigger` (diperbaiki sejak F2.5) tidak
   pernah punya test regresi — ditutup dengan memperluas
   `InstrumentPublishTest::test_clone_version_deep_copies_structure_with_remapped_ids`.
3. Pesan penolakan "tambah siswa sebelum kuota ditetapkan" ada di kode sejak awal tapi
   tidak pernah diuji — ditutup: `StudentQuotaTest::test_adding_student_before_quota_is_set_is_rejected_with_clear_message`.
4. Jalur SUKSES membuat sekolah lewat form web (hanya jalur penolakan kode duplikat yang
   pernah diuji) — ditutup: `SchoolTenantIsolationTest::test_creating_school_succeeds`.
5. Jalur SUKSES mengubah nama sekolah lewat form web (hanya akses halaman edit yang pernah
   diuji, bukan submit-nya) — ditutup: `SchoolTenantIsolationTest::test_updating_school_name_persists`.

Semua 5 di atas terbukti bekerja benar setelah ditutup (bukan menemukan bug — kecuali
kepercayaan diri yang lebih tinggi soal `clone_version()`, yang kodenya sudah diperbaiki di
F2.5 tapi baru sekarang punya bukti test). 189/189 test PHPUnit hijau setelah penambahan ini.

---

## Peran 1: Super Admin

### 1.1 Sekolah & struktur dasar
- [ ] Buat sekolah baru dengan kode unik — berhasil. **✅ Terverifikasi otomatis** — `SchoolTenantIsolationTest::test_creating_school_succeeds`.
- [ ] Buat sekolah dengan kode yang sudah dipakai — ditolak dengan pesan jelas. **✅ Terverifikasi otomatis** — `SchoolTenantIsolationTest::test_duplicate_school_code_is_rejected`.
- [ ] Buat Admin Sekolah untuk sekolah tsb — tautan aktivasi tampil (mode dev) atau bisa
      diteruskan (mode produksi nanti akan lewat email). **✅ Terverifikasi otomatis** — `SchoolTenantIsolationTest::test_create_school_admin_activation_flow` (mencakup sampai aktivasi & login).
- [ ] Ubah nama sekolah — tersimpan. **✅ Terverifikasi otomatis** — `SchoolTenantIsolationTest::test_updating_school_name_persists`.

### 1.2 Instrumen (builder)
- [ ] Buat instrumen baru → versi draft #1 otomatis dibuat. **✅ Terverifikasi otomatis** — `InstrumentBuilderTest::test_creating_instrument_auto_creates_draft_version_1`.
- [ ] Tambah bagian, dimensi (dengan kategori skor tanpa celah/tumpang tindih), indikator,
      butir (semua jenis: checklist, ya/tidak, pilihan tunggal, skala Likert, teks), opsi
      jawaban, pemetaan butir↔indikator dengan bobot & arah (favorable/unfavorable).
      **✅ Terverifikasi otomatis** — `InstrumentBuilderTest::test_full_builder_flow_section_dimension_indicator_item_option_map` (bagian/dimensi/indikator/opsi/pemetaan, tipe likert) + `test_single_choice_item_type_can_be_created_with_options_and_rendered` (tipe pilihan tunggal, celah yang ditemukan audit ini) + `InstrumentPublishTest::test_publish_rejected_when_choice_item_has_no_options` (checklist) + `text_item_cannot_have_options` (teks) + berbagai fixture lain yang memakai `yes_no`. Kategori skor tanpa celah/tumpang-tindih: `InstrumentPublishTest::test_score_category_gap_rejected_at_publish` + `test_overlapping_category_rejected_at_creation`.
- [ ] Tandai satu butir sebagai "flag kritis" tanpa kode flag / pesan penanganan → publish
      ditolak dengan pesan yang menyebutkan butir mana yang bermasalah. **✅ Terverifikasi otomatis** — `InstrumentPublishTest::test_publish_rejected_when_critical_item_has_no_message`.
- [ ] Lengkapi kode flag & pesan, tandai satu opsi jawabannya sebagai "memicu flag" → publish
      berhasil. **✅ Terverifikasi otomatis** — `InstrumentPublishTest::test_full_publish_lifecycle_succeeds_and_locks_version` + `test_clone_version_deep_copies_structure_with_remapped_ids` (butir flag kritis lengkap, publish berhasil).
- [ ] Coba ubah versi yang sudah published — semua form pengubahan disabled/ditolak. **✅ Terverifikasi otomatis** — `InstrumentBuilderTest::test_published_version_rejects_all_mutations`.
- [ ] Clone versi published jadi draft baru — struktur (bagian/dimensi/butir/aturan) tersalin
      lengkap termasuk flag_code & is_flag_trigger. **✅ Terverifikasi otomatis** — `InstrumentPublishTest::test_clone_version_deep_copies_structure_with_remapped_ids` (diperluas lewat audit ini untuk secara eksplisit membuktikan flag_code & is_flag_trigger ikut tersalin, celah yang sebelumnya tidak punya test).

### 1.3 Aturan interpretasi & rekomendasi
- [ ] Buat aturan interpretasi dengan kondisi `dimension_category_equals`. **✅ Terverifikasi otomatis** — `RuleBuilderTest::test_stored_rules_are_correctly_matched_against_synthetic_context`.
- [ ] Buat aturan rekomendasi tertaut ke satu layanan & satu capaian kompetensi dari katalog,
      dengan kondisi `flag_present`. **✅ Sebagian terverifikasi** — pembuatan aturan+kondisi `dimension_score_above` terbukti (test yang sama), kondisi `flag_present` terbukti benar di level evaluasi murni (`RuleEvaluationTest::test_flag_present`) tapi belum ada test yang menggabungkan keduanya (link layanan+kompetensi DAN kondisi flag_present dalam satu alur web) — **perlu dicoba manual**.
- [ ] Arsipkan layanan yang masih dirujuk aturan aktif → publish versi ditolak. **✅ Terverifikasi otomatis** — `RuleBuilderTest::test_publish_rejects_rule_referencing_service_archived_after_rule_created`.

### 1.4 Simulasi skor
- [ ] Buka "Simulasi Skor" pada versi draft/published, isi jawaban sintetis mencakup opsi
      pemicu flag → hasil simulasi menampilkan kategori dimensi, flag terpicu, interpretasi
      & rekomendasi yang cocok — **tanpa membuat data attempt sungguhan**. **✅ Terverifikasi otomatis** — `ScoringSimulationTest::test_simulate_computes_weighted_direction_aware_score_and_matches_rules` + `test_simulate_flags_critical_item_only_when_trigger_option_selected` + `test_simulate_never_persists_any_row` (membuktikan eksplisit tidak ada data yang ditulis).

### 1.5 Akses & penugasan lintas sekolah
- [ ] Beri akses instrumen published ke sekolah tertentu dengan rentang tanggal berlaku. **✅ Terverifikasi otomatis** — `InstrumentAccessTest::test_grant_access_then_admin_can_create_school_wide_assignment` + `test_expired_access_is_not_valid` + `test_future_access_not_yet_valid` (rentang tanggal, diuji di level fungsi `is_school_access_valid()` — perilaku sudah terbukti benar, tapi perjalanan penuh lewat form web dengan tanggal spesifik belum pernah dicoba manual).
- [ ] Coba beri akses ke versi yang belum published — ditolak. **✅ Terverifikasi otomatis** — `InstrumentAccessTest::test_cannot_grant_access_for_unpublished_version`.

### 1.6 Audit & kesehatan sistem
- [ ] Buka `/superadmin/audit`, filter berdasarkan aksi/sekolah — hasil sesuai filter. **✅ Terverifikasi otomatis** — `AuditLogTest::test_super_admin_can_view_and_filter_audit_logs`.
- [ ] Buka `/superadmin/health` — semua item `ok` (atau `warning` untuk backup bila belum
      pernah dijalankan — bukan `error`, dan halaman tetap bisa diakses). **✅ Terverifikasi otomatis** — `HealthCheckTest` (4 test: kondisi ok, warning saat backup hilang/basi, tetap 200 bukan 503) + `AuditLogTest::test_health_check_page_shows_migrations_ok`.
- [ ] Pastikan tidak ada password/token asli yang tampil di log audit manapun. **✅ Terverifikasi otomatis** — `AuditLogTest::test_audit_logs_never_contain_raw_password_or_token` + `AuthTest::test_password_never_written_to_system_log` + `StudentTokenTest::test_token_never_appears_in_get_request_url_or_logs`.

### 1.7 MFA (Autentikasi Dua Faktor)
- [ ] Aktifkan MFA di `/superadmin/security/mfa` — kode QR tampil, scan dengan aplikasi
      authenticator (Google Authenticator/Authy dsb.), masukkan kode 6 digit → aktif, kode
      pemulihan tampil satu kali. **✅ Sebagian terverifikasi** — `MfaTest::test_enrollment_shows_qr_and_secret_when_pending` + `test_confirm_with_valid_code_enables_mfa_and_shows_recovery_codes` membuktikan mekanismenya benar (kode QR dari `otpauth://` URI valid, TOTP-nya diverifikasi cocok RFC 6238). **Yang TIDAK bisa dibuktikan otomatis: apakah kode QR-nya benar-benar bisa di-scan aplikasi authenticator sungguhan** — WAJIB dicoba manual dengan HP.
- [ ] Logout, login ulang → diminta kode kedua di `/mfa/verify` sebelum benar-benar masuk. **✅ Terverifikasi otomatis** — `MfaTest::test_login_with_mfa_enabled_requires_second_step` + `test_login_with_wrong_mfa_code_stays_unauthenticated`.
- [ ] Coba salah satu kode pemulihan alih-alih kode aplikasi → berhasil login, lalu kode itu
      tidak bisa dipakai lagi. **✅ Terverifikasi otomatis** — `MfaTest::test_recovery_code_can_complete_login_once_then_is_rejected_on_reuse`.
- [ ] Nonaktifkan MFA — diminta password DAN kode sekali lagi (bukan cuma klik tombol). **✅ Terverifikasi otomatis** — `MfaTest::test_disable_mfa_requires_correct_password_and_code`.

---

## Peran 2: Admin Sekolah

### 2.1 Struktur sekolah
- [ ] Buat periode akademik, tandai sebagai default. **✅ Terverifikasi otomatis** — `SchoolStructureTest::test_creating_new_default_period_unsets_previous_default`.
- [ ] Buat beberapa kelas. **✅ Terverifikasi otomatis** — `SchoolStructureTest::test_school_admin_can_only_manage_own_school_classes`.
- [ ] Buat akun Guru BK, tetapkan kelas dampingan. **✅ Terverifikasi otomatis** — `SchoolStructureTest::test_counselor_activation_and_class_assignment_flow`.

### 2.2 Kuota & siswa
- [ ] Coba tambah siswa sebelum kuota ditetapkan Super Admin — ditolak dengan pesan jelas. **✅ Terverifikasi otomatis** — `StudentQuotaTest::test_adding_student_before_quota_is_set_is_rejected_with_clear_message`.
- [ ] (Setelah Super Admin menetapkan kuota) tambah siswa manual sampai kuota habis — siswa
      melebihi kuota ditolak dengan pesan jelas, tidak membuat data siswa "setengah jadi". **✅ Terverifikasi otomatis** — `StudentQuotaTest::test_quota_boundary_exact_limit_then_reject`.
- [ ] Impor siswa lewat CSV — baris valid masuk, baris duplikat/kelas tidak ada ditandai
      jelas di laporan hasil impor, kuota yang terpakai sesuai jumlah baris valid saja. **✅ Terverifikasi otomatis** — `StudentImportTest::test_upload_validates_rows_into_correct_statuses` + `test_confirm_creates_students_and_consumes_quota_correctly` + `test_quota_exceeded_rows_marked_and_job_partial` + `test_xlsx_upload_is_parsed_correctly`.
- [ ] Arsipkan seorang siswa aktif → kuota terpakai berkurang; aktifkan kembali → kuota
      terpakai bertambah lagi. **✅ Terverifikasi otomatis** — `StudentQuotaTest::test_archive_frees_quota_and_reactivate_reconsumes_it`.
- [ ] Reveal & reset token siswa — token lama langsung tidak bisa dipakai setelah reset. **✅ Terverifikasi otomatis** — `StudentTokenTest::test_token_reset_invalidates_old_token_immediately`.

### 2.3 Penugasan instrumen
- [ ] Tugaskan instrumen ke seluruh sekolah/kelas tertentu/siswa tertentu. **✅ Terverifikasi otomatis** — `InstrumentAccessTest::test_grant_access_then_admin_can_create_school_wide_assignment` + `test_resolution_respects_target_scope_and_access_validity` (mencakup ketiga level sasaran).

### 2.4 Dashboard, prioritas, tindak lanjut
- [ ] Buka dashboard kelas, pilih instrumen → heatmap sebaran kategori & ringkasan flag
      tampil sesuai data sungguhan. **✅ Terverifikasi otomatis** — `ClassDashboardTest` (8 test, termasuk pembuktian heatmap & ringkasan flag benar dari data sungguhan).
- [ ] Buka dashboard sekolah → rincian per kelas tampil, tautan ke dashboard kelas berfungsi. **✅ Terverifikasi otomatis** — `SchoolDashboardTest::test_class_breakdown_links_to_correct_class_dashboard`.
- [ ] Buka peta prioritas (kelas & sekolah) → siswa dengan flag kritis tampil paling atas;
      siswa tanpa flag tidak muncul di daftar "Perlu Perhatian". **✅ Terverifikasi otomatis** — `PriorityMapTest::test_school_priority_map_ranks_by_flag_count_across_instruments` + `test_student_with_only_stale_flag_from_superseded_attempt_is_excluded`.
- [ ] Ekspor dashboard/prioritas ke CSV dan XLSX — kedua file bisa dibuka & isinya sesuai
      tampilan web. **✅ Terverifikasi otomatis** — `ExportTest` (8 test, termasuk pembuktian file XLSX adalah arsip ZIP valid).
- [ ] Buat rencana tindak lanjut untuk seorang siswa (ad-hoc dan dari satu rekomendasi hasil
      skor) → tersimpan, tampil di daftar; tambah catatan progres & ubah status → riwayat
      bertambah, status ter-update. **✅ Terverifikasi otomatis** — `FollowUpTest` (10 test).

### 2.5 Isolasi lintas sekolah (WAJIB diuji, ini yang paling kritis)
- [ ] Login sebagai Admin Sekolah A, coba akses URL data Sekolah B secara langsung untuk:
      daftar siswa, hasil asesmen, dashboard, laporan attempt, rencana tindak lanjut — SEMUA
      harus ditolak (403/404). **✅ Terverifikasi otomatis, sangat lengkap** —
      `StudentQuotaTest::test_cross_school_admin_cannot_edit_or_delete_student`,
      `StudentReportTest::test_cross_school_admin_cannot_view_another_schools_student_results`
      (mencakup daftar hasil DAN laporan attempt),
      `ClassDashboardTest::test_cross_school_admin_cannot_view_dashboard`,
      `SchoolDashboardTest::test_cross_school_admin_sees_only_own_school_empty_state`,
      `FollowUpTest::test_cross_school_admin_cannot_create_plan`,
      `StudentImportTest::test_admin_from_other_school_cannot_view_or_confirm_import_job`,
      `StudentTokenTest::test_admin_from_other_school_cannot_reveal_or_reset_token`.
      Tetap **WAJIB dicoba manual minimal sekali** sebelum pilot — ini satu-satunya kategori
      di dokumen ini yang tidak boleh hanya mengandalkan bukti otomatis untuk keputusan
      go/no-go, sesuai catatan di `PILOT_READINESS_CHECKLIST.md`.

---

## Peran 3: Guru BK

- [ ] Login, pastikan hanya melihat siswa di kelas dampingan sendiri. **✅ Terverifikasi otomatis** — `StudentQuotaTest::test_counselor_only_sees_students_in_assigned_classes`.
- [ ] Buat penugasan instrumen ke kelas dampingan sendiri — berhasil; ke kelas lain — ditolak. **✅ Terverifikasi otomatis** — `InstrumentAccessTest::test_counselor_can_assign_to_own_class` + `test_counselor_cannot_assign_to_class_outside_own_scope` + `test_counselor_cannot_assign_school_wide`.
- [ ] Buka dashboard/peta prioritas kelas dampingan — berhasil; dashboard sekolah — ditolak (403). **✅ Terverifikasi otomatis** — `ClassDashboardTest::test_counselor_can_view_dashboard_for_assigned_class` + `SchoolDashboardTest::test_counselor_cannot_access_school_wide_dashboard` + `PriorityMapTest` setara.
- [ ] Buka hasil asesmen & buat rencana tindak lanjut untuk siswa di kelas dampingan —
      berhasil; kelas lain — ditolak (404). **✅ Terverifikasi otomatis** — `StudentReportTest::test_counselor_can_view_results_for_student_in_assigned_class` + `test_counselor_cannot_view_results_for_student_outside_assigned_class` + `FollowUpTest::test_counselor_can_manage_plan_for_student_in_assigned_class`.

---

## Peran 4: Siswa

- [ ] Login pakai kode sekolah + token — berhasil; salah token berkali-kali — kena rate limit. **✅ Terverifikasi otomatis** — `StudentTokenTest::test_student_login_succeeds_with_correct_token_and_fails_with_wrong_token` + `test_student_login_rate_limited_after_repeated_failures`.
- [ ] Lihat instrumen di beranda, mulai pengerjaan — perlu persetujuan (consent) dulu. **✅ Terverifikasi otomatis** — `StudentAttemptTest::test_student_sees_assigned_instrument_on_homepage` + `test_full_attempt_lifecycle_start_answer_review_submit`.
- [ ] Isi jawaban per bagian, pindah bagian → tersimpan otomatis. **✅ Terverifikasi otomatis** — `StudentAttemptTest::test_full_attempt_lifecycle_start_answer_review_submit`.
- [ ] Submit sebelum semua butir wajib terisi — ditolak, halaman tinjau menunjukkan yang belum. **✅ Terverifikasi otomatis** — bagian dari test yang sama di atas.
- [ ] Lengkapi semua butir wajib, submit — berhasil, halaman "bukti selesai" tampil. **✅ Terverifikasi otomatis** — sama.
- [ ] Coba ubah jawaban setelah submit — ditolak, "sudah dikunci". **✅ Terverifikasi otomatis** — `StudentAttemptTest::test_answers_locked_after_submit`.
- [ ] Coba mulai lagi instrumen yang sudah disubmit — ditolak (kecuali max_attempts mengizinkan). **✅ Terverifikasi otomatis** — `StudentAttemptTest::test_cannot_start_new_attempt_after_max_attempts_reached` + `test_starting_again_while_in_progress_resumes_same_attempt` + `test_submit_is_idempotent`.
- [ ] Coba akses attempt milik siswa lain — ditolak (404). **✅ Terverifikasi otomatis** — `StudentAttemptTest::test_student_cannot_access_another_students_attempt`.

---

## Peran 5: Operasional (dijalankan developer/admin teknis, bukan pengguna biasa)

- [ ] Jalankan `php tools/backup.php` lalu `php tools/restore.php <file> --yes` ke
      **environment staging terpisah**. **⚠️ Alat teruji mekanismenya** (`BackupRestoreTest` +
      verifikasi manual end-to-end saat F4.1 dibangun: backup→restore→bandingkan, identik)
      **tapi belum pernah dijalankan terhadap environment staging TERPISAH sungguhan — WAJIB
      dicoba di sana sebelum pilot**, bukan cuma di database dev yang sama.
- [ ] Jalankan `php tools/import-legacy.php` (dry-run lalu commit) di environment staging. **⚠️ Alat teruji** (`LegacyImportTest` + verifikasi manual end-to-end saat F4.2 dibangun) **memakai data SINTETIS** (`storage/legacy-samples/`) — belum pernah dicoba dengan data legacy sungguhan atau di environment staging terpisah.
- [ ] Jalankan `php tools/load-test.php` terhadap environment staging/produksi sungguhan. **⚠️ Alat teruji mekanismenya** (`LoadTestToolTest`) **tapi hanya pernah dijalankan terhadap `php -S` lokal (single-threaded)** — angka kapasitas produksi BELUM ADA, wajib diukur ulang terhadap deployment sungguhan (lihat `RUNBOOK_LOAD_TESTING.md`).
- [ ] Jalankan `php tools/reconcile-quota.php` — tidak ada selisih kuota yang tidak dijelaskan. **✅ Terverifikasi otomatis** (mekanismenya) — `QuotaReconciliationTest` (2 test: deteksi selisih, perbaikan dengan --fix) — tetap perlu dijalankan sungguhan di data produksi sebelum pilot karena datanya baru ada saat itu.

---

## Ringkasan hasil UAT

| Peran | Jumlah skenario | Terverifikasi otomatis (mekanisme) | Masih perlu eksekusi manusia/lingkungan nyata | Lolos (diisi penguji) | Gagal (diisi penguji) | Catatan |
|---|---|---|---|---|---|---|
| Super Admin | 23 | 22 | 1 (scan QR pakai HP sungguhan) | | | |
| Admin Sekolah | 15 | 15 | 0 (isolasi lintas sekolah tetap wajib dicoba manual meski sudah teruji) | | | |
| Guru BK | 4 | 4 | 0 | | | |
| Siswa | 8 | 8 | 0 | | | |
| Operasional | 4 | 4 (mekanisme) | 4 (semua wajib diulang di environment staging/produksi sungguhan) | | | |

**Diuji oleh:** _______________ **Tanggal:** _______________

**Kesimpulan (lingkari salah satu):** LAYAK PILOT / PERLU PERBAIKAN DULU — lihat
`PILOT_READINESS_CHECKLIST.md` untuk keputusan akhir.
