# Strategi Pengujian & Quality Gates (Testing Strategy) Dokumen ini mendefinisikan gerbang kualitas (*quality gates*) dan strategi pengujian menyeluruh di KeluargaMart Monorepo. --- ## 1. Tingkatan Pengujian (Test Hierarchy) ```mermaid graph TD subgraph L1["Level 1: Contracts & Linting"] Drift["Drift Guard (pnpm drift:check)"] Lint["Static Lint (pnpm lint)"] end subgraph L2["Level 2: Unit & Component Tests"] GoTest["Go Unit Tests (pnpm test:backend)"] VueTest["Vitest Vue/Nuxt Tests (pnpm turbo run test)"] PkgTest["Shared Packages Tests (packages/*)"] end subgraph L3["Level 3: Build & Bundling Verification"] GoBuild["Go Binary Compilation (pnpm build:backend)"] ViteBuild["Vite & Nuxt Production Bundling (pnpm build)"] end subgraph L4["Level 4: Container & Runtime Integration"] PodmanConfig["Compose Syntax & Network Check"] HealthCheck["Liveness & Readiness Probes (/health, /ready)"] end subgraph L5["Level 5: Live E2E Browser Sweep (Playwright)"] PlaywrightERP["ERP Admin Login & Session Sweep"] PlaywrightPOS["POS Cashier Register Sweep"] PlaywrightB2B["B2B Storefront SSR & Hydration Sweep"] PlaywrightAPI["Reverse Proxy & API Route Verification"] end L1 --> L2 --> L3 --> L4 --> L5 ``` --- ## 2. Command Matriks Pengujian | Lapisan | Lingkup | Command | Ambang Batas Lulus | |---|---|---|---| | **Contract** | OpenAPI vs Catalog | `pnpm drift:check` | 0 perbedaan (395 routes, 207 permissions) | | **Backend** | Go Modules | `pnpm test:backend` | 100% test modules pass, 0 race conditions | | **Packages** | Contracts | `pnpm --filter @keluargamart/contracts test` | 4 tests pass | | **Packages** | Permissions | `pnpm --filter @keluargamart/permissions test` | 5 tests pass | | **Packages** | Utils | `pnpm --filter @keluargamart/utils test` | 6 tests pass | | **Packages** | ApiClient | `pnpm --filter @keluargamart/api-client test` | 3 tests pass | | **ERP** | Vue Admin | `pnpm --filter @keluargamart/erp test` | 41 test files, 361 tests pass | | **POS** | Vue Kasir | `pnpm --filter @keluargamart/pos test` | 21 test files, 123 tests pass | | **B2B** | Nuxt SSR | `pnpm --filter @keluargamart/b2b test` | 22 test files, 182 tests pass | | **NextReport** | Node Service | `pnpm --filter @keluargamart/nextreport test` | Syntax validity check pass | | **Workspace** | Seluruh Monorepo | `pnpm test` | **850+ tests passing tanpa kegagalan** | | **E2E Sweep** | Live Frontend-to-Backend | `pnpm sweep` / `pnpm test:e2e` | **100% checks pass (74 checks di ERP, POS, B2B, Backend)** | | **UI Deep Sweep** | Form, Button, Modal Audit | `pnpm test:ui` / `pnpm sweep:deep` | **100% pass (2.698 tombol, 475 form fields, 19 modals, 0 crash)** | | **Data Flow E2E** | Siklus Operasional Nyata | `pnpm test:flow` | **100% pass (28 langkah: PO -> GR -> POS -> B2B -> Ledger)** | --- ## 3. Aturan Quality Gate pada CI/CD Pada pipeline GitHub Actions (`.github/workflows/ci.yml`), merge PR ke `main` otomatis diblokir jika: 1. Terdeteksi rute atau permission yang tidak terdaftar di shared package (`drift-guard` fail). 2. Terdapat setidaknya 1 kegagalan pada unit test Go atau frontend. 3. Terjadi kegagalan kompilasi pada Vite, Nuxt, atau Go compiler. --- ## 4. Live Full Sweep E2E Menggunakan Playwright Untuk memverifikasi ekosistem secara utuh dari browser frontend hingga API backend, jalankan skrip live sweep: ```bash pnpm sweep # Atau: bun scripts/playwright-sweep.mjs ``` ### Cakupan Pengujian Live E2E Sweep: 1. **Core Backend API (`:3004`)**: - Memastikan probe `GET /health` merespons `HTTP 200 OK` dengan metadata runtime yang valid. 2. **ERP Admin Portal (`:3001`)**: - Membuka halaman login `http://127.0.0.1:3001/login`. - Mengisi kredensial admin dan men-trigger autentikasi via internal Nginx reverse proxy ke backend `POST /api/v1/auth/login`. - Memverifikasi pengalihan sesi ke Dashboard dan menyimpan snapshot layar di `docs/sweep-artifacts/erp-dashboard.png`. 3. **POS Cashier Register (`:3003`)**: - Membuka terminal kasir `http://127.0.0.1:3003/login`. - Mengautentikasi akun kasir/admin, memastikan token tersimpan dan sesi transaksi kasir `/pos` aktif. - Menyimpan snapshot layar di `docs/sweep-artifacts/pos-terminal.png`. 4. **B2B Wholesale Storefront (`:3002`)**: - Membuka toko grosir `http://127.0.0.1:3002`. - Memverifikasi server-side rendering (SSR Nitro) dan hidrasi komponen Vue client-side berjalan mulus. - Menyimpan snapshot layar di `docs/sweep-artifacts/b2b-storefront.png`. --- ## 5. Exhaustive UI Interaction Sweep (`pnpm test:ui` / `pnpm sweep:deep`) Untuk menguji ketahanan form, tombol, dialog modal, dan formula matematika di seluruh 44 halaman ERP, POS, dan B2B: ```bash pnpm test:ui # Menjalankan: node scripts/exhaustive-interaction-sweep.mjs ``` ### Ruang Lingkup Audit Interaksi: - **Audit Elemen Halaman**: Memeriksa keberadaan dan status *enabled* pada 2.698+ tombol dan 475 field form. - **Pengujian Input**: Mengetikkan query pencarian, filter angka, rentang tanggal, dan barcode tanpa memicu mutasi destruktif. - **Siklus Modal**: Membuka dialog modal tambah data dan menutupnya kembali secara aman via tombol batal/escape. - **Validasi Formula Finansial & Persediaan**: Memverifikasi kalkulasi subtotal POS, margin laba, kartu mutasi stok, dan pembacaan neraca buku besar. --- ## 6. Real Operational Data Flow E2E Test (`pnpm test:flow`) Untuk menguji siklus hidup data bisnis secara nyata (*real transactional mutations*) dari pengadaan hingga pembukuan akuntansi: ```bash pnpm test:flow # Menjalankan: node scripts/e2e-data-flow-test.mjs ``` ### Tahapan Siklus Data Bisnis (28 Langkah): 1. **Master Data & Outlet**: Menyiapkan supplier `PT Sumber Makmur Berkah [TEST-FLOW]`, produk `Kopi Susu Mantap [TEST-FLOW]` (SKU: `MAK-20261031-001`, Barcode: `8997788990011`), dan outlet toko `STR-PST`. 2. **Purchasing & Inflow (PO -> GR)**: Membuat PO 20 pcs @ Rp 10.000, approval admin, dan penerimaan fisik (GR). Stok bertambah +20 pcs dan jurnal hutang dagang (JU) terbit otomatis. 3. **Retail POS Cashier Flow**: Buka shift kasir (modal Rp 150.000), transaksi kasir POS tunai 3 pcs (@ Rp 15.000 = Rp 45.000), pemotongan stok otomatis (-3 pcs), dan tutup shift dengan selisih kas Rp 0. 4. **B2B Wholesale & Distribution**: Pesanan grosir 5 pcs (@ Rp 15.000 = Rp 75.000), konfirmasi SO, pembuatan Delivery Order (DO), dispatch pengiriman armada (SHP), konfirmasi serah terima barang (-5 pcs), dan pelunasan invoice via transfer bank. 5. **Akuntansi Double-Entry & Buku Besar**: Memverifikasi total debet == total kredit (Rp 1.300.000,00) dengan selisih Rp 0,00. 6. **Verifikasi Visual Browser (Playwright)**: Membuka dashboard ERP dan POS untuk memastikan transaksi, stok fisik (91 pcs), kartu mutasi, dan struk kasir tercatat rapi di antarmuka web. > **Kebijakan Data Test (Option A):** Data pengujian disimpan di database dengan penanda `[TEST-FLOW]` untuk memudahkan inspeksi visual oleh klien dan tim QA. Laporan hasil uji otomatis dicatat di `docs/LAPORAN_PENGUJIAN_FLOW_DATA_END_TO_END.txt`. --- ## 7. Gerbang Khusus: Pemisahan Alur Pickup vs Delivery (Fulfillment-Mode Segregation) Keluarga order e-commerce mencakup DUA operasi berbeda dalam satu record: **Ambil Sendiri di Toko (pickup)** dan **Kirim ke Alamat (delivery)**. Semua defek keluarga ini bersumber dari satu permukaan yang memperlakukan keduanya sama. Setiap perubahan yang menyentuh order WAJIB menjawab matriks ini per permukaan (referensi lengkap: bagian 7-A di `AGENTS.md`): | Permukaan | Pickup | Delivery | |---|---|---| | Daftar POS "Ambil Toko" (`for-pickup`) | WAJIB tampil | TIDAK boleh | | ERP "Packing Order" | TIDAK boleh | WAJIB tampil | | Auto-DO saat confirm | TIDAK dibuat | WAJIB dibuat | | Tombol POS "Serahkan Barang" | Berfungsi dari `pending_approval` | — (rantai gudang) | | B2B "Lacak Pengiriman" | TIDAK boleh tampil | WAJIB tampil | | Stepper "Status Pesanan" + "Dokumen" di B2B | TIDAK dirender | WAJIB dirender | Cara memeriksanya (semua WAJIB sebelum menyatakan selesai): 1. **Filter backend sadar mode** — `for-pickup` dan packing list (`exclude_pickup`) memfilter via `isPickupOrder` / marker `shipping_address`, BUKAN `order_type` saja. 2. **Artifak otomatis menghormati mode** — apa pun yang dibuat otomatis saat confirm (invoice, DO) melewati order pickup atau benar untuk kedua mode. 3. **Gerbang transisi menyebut mode** — lompatan `pending_approval → delivered` legal HANYA untuk serah terima kasir pada pickup tunai. 4. **UI menyembunyikan afordansi mode lain** — order pickup tidak menampilkan tombol lacak, stepper kurir, dokumen DO, maupun aksi packing; order kirim tidak muncul di daftar pickup kasir. 5. **Satu marker, tiga konsumen, selalu sinkron** — `internal/sales.isPickupOrder` (backend), `isPickupOrder` di `apps/b2b/app/utils/paymentMethods.ts`, dan pola SQL `exclude_pickup` harus sepakat; diuji terhadap marker literal `[Ambil Sendiri di Toko]`. 6. **Verifikasi di UI berjalan dengan order NYATA per mode** — suite unit yang hijau tidak membuktikan daftar yang dilihat kasir. Buat satu order pickup dan satu order kirim, lalu periksa tiga daftar (POS till, ERP packing, ERP delivery orders) dan dua halaman detail (B2B, ERP packing detail). --- ## 8. Gerbang Khusus: UI-Orphan (Fitur Frontend tanpa Backend) Elemen frontend yang membaca field atau memanggil endpoint yang tidak diimplementasikan backend akan selamanya tampil kosong (atau gagal diam) dan TETAP lolos semua unit test — karena unit test men-stub API. Panel POD ("Foto POD ✓", "Belum Ada Foto POD") pernah ter-shipping persis seperti ini: lengkap di ERP, tapi field yang dibacanya tidak pernah ada. Sebelum me-shipping UI yang menampilkan data: 1. **Telusuri field ke sumbernya.** Grep field respons (mis. `proof_photo`) di `internal/**` dan `db/query/**`. Jika tidak ada yang memproduksinya, UI itu orphan — implementasikan backend-nya atau hapus UI-nya. 2. **Satu round-trip mengalahkan mock.** Terhadap stack lokal, jalankan alur nyata (upload → simpan → baca kembali) dan pastikan nilai yang dirender UI berasal dari database, bukan dari stub. 3. **UI baru yang menampilkan data tersimpan butuh kolom + route.** Jika jawaban atas "migration dan endpoint mana yang melayani ini?" adalah hening, pekerjaan belum selesai. Sebaliknya, kolom backend baru untuk data user-facing sebaiknya di-shipping bersama UI yang menampilkannya. 4. **Halaman tanpa route belum terkirim.** Layar POS "Ambil Toko" sudah lengkap (daftar, dialog serah terima, struk) tetapi tidak punya entri router, tidak ada menu, dan memanggil dua method service yang tidak ada — tidak bisa dijangkau di build mana pun, dan tidak ada test yang bisa menangkapnya karena komponennya tidak pernah di-mount. Sebelum UI disebut selesai: route resolve, menu muncul untuk permission yang berhak, dan Playwright mencapai halaman itu dengan mengklik — bukan mengetik URL. 5. **Setiap layar yang menulis wajib punya jalur negatif yang terlihat.** Aksi yang pasti ditolak backend (serah terima pickup transfer yang belum lunas) harus dimatikan dan dijelaskan di UI; kalau tidak, pengguna pertama menemukan aturannya lewat error toast. --- ## 9. Gerbang Khusus: Status Terminal & Hook Efek Samping Transisi status belum selesai saat `UPDATE` jalan — baru selesai ketika semua hook setelahnya berjalan dan baris tetap memegang status yang diminta. Defect 1 Nov: `AdvanceFulfillment` menulis `delivered`, lalu memicu `onConfirm` untuk SETIAP transisi; `onConfirm` membuat invoice yang `SetSOStatus('invoiced')`-nya tanpa syarat, sehingga setiap serah terima di kasir berakhir `invoiced` dengan invoice terbuka hantu. Unit test lolos karena fake store tidak punya hook. 1. **Hook milik transisi yang namanya.** `onConfirm` hanya saat order mencapai `confirmed`. Delivery, picking, shipping bukan konfirmasi. 2. **Jangan pernah menulis status mundur.** Stamp efek samping (`invoiced`, `paid`, `completed`) wajib bersyarat status pra-fulfillment; hook telat atau ganda harus no-op, bukan membatalkan kerja yang sudah terjadi. 3. **Terminal itu terminal.** `delivered`/`cancelled`/`completed` tidak boleh ditimpa invoice, pembayaran, atau retry. Koreksi lewat reversal/retur. 4. **Uji keadaan akhir, setelah hook.** Test transisi meng-assert status yang dikembalikan endpoint DAN bahwa tidak ada dokumen ekstra (serah terima cash pickup menyisakan nol invoice terbuka). Fake store tanpa hook tidak bisa membuktikan ini — sambungkan hook-nya atau verifikasi live. 5. **Uji tiap pintu masuk terpisah.** Order yang sama bisa dimajukan dari ERP (rantai gudang), POS (serah terima kasir), dan B2B (konfirmasi pembeli). Jalankan ketiganya di stack lokal dan baca status dari database, bukan dari toast. --- ## 10. Gerbang Khusus: Aritmatika Uang Harus Reconcile di Layar ``` harga_satuan × qty − diskon_baris − diskon_member/promo = subtotal ``` 1. **Tampilkan semua komponen yang dikurangkan.** Detail sales order ERP menampilkan `Harga` 15.000 dan `Subtotal` 14.700 dengan `Diskon` 0, karena diskon member disimpan di kolom terpisah (`membership_discount_amount`) dan tidak pernah dirender. Pembeli membacanya sebagai bug harga — dan benar: aritmatika di layar tidak menutup. 2. **Kolom diskon terpisah tetap diskon.** Gabungkan ke diskon yang tampil, atau beri label eksplisit di baris dan ringkasan. 3. **Jangan mengubah uang final demi layar terlihat benar.** Perbaiki tampilan (dan penulis data untuk baris berikutnya), bukan ledger historis. 4. **Reconcile manual setelah perubahan harga.** Buka order nyata untuk tiap sumber harga — harga list, tier member, voucher, diskon baris manual, multi-satuan (dus/pcs) — dan cek Harga × Qty − Diskon = Subtotal serta Σ baris + pajak + ongkir − diskon order = Total. --- ## 11. Ringkasan Parity Dashboard (KPI ↔ Drilldown) Tambahan operasional untuk §8 AGENTS.md: - **Satu predikat, satu jendela.** Agregat kartu dan daftar modal wajib memakai predikat, set status, jendela tanggal, dan pengecualian yang sama — didefinisikan sekali (`activeOrderStatuses` di `internal/reports/repository.go`) lalu dipakai di `COUNT(*)` dan `SELECT … WHERE`. - **Kartu yang bisa diklik dengan array drilldown kosong adalah defect.** Uji payload dengan dataset non-kosong; laporan 1 Nov ("dashboard berangka, detail 0") persis ini: handler tidak pernah meng-assign `pendingInvoices`/`activePOs`. - **Verifikasi artefak yang berjalan.** Setelah rebuild, buktikan dengan query atau round-trip HTTP yang hasilnya mustahil ada di build lama. Komentar hilang saat kompilasi — mencarinya di `strings` binary tidak membuktikan apa pun. Bila hasil live bertentangan dengan source, curigai artefaknya dulu (layer cache, `--force-recreate`, tag lama). ### Peta Sumber Data Dashboard ERP (rujukan wajib) Setiap kartu di `/dashboard` WAJIB punya baris di tabel ini. Menambah kartu tanpa query nyata (proxy, konstanta, atau `len()` dari slice lain) dilarang. | Kartu | Endpoint | Sumber SQL (repository) | Drilldown (key `charts`) | |---|---|---|---| | Pendapatan Bulan Ini | `/dashboard/kpis` | `SumMonthSales` (`sales_orders`, status ≠ cancelled, bulan berjalan) | `revenueTrend` (titik terakhir) | | Penjualan Hari Ini | `/dashboard/kpis` | `TodaySales` (`sales_orders`, `order_date = hari ini`, status ≠ cancelled) | `todaySales` | | Order Aktif | `/dashboard/kpis` | `PendingOrdersCount` (`status IN activeOrderStatuses`) | `activeOrders` (predikat sama) | | PO Aktif | `/dashboard/kpis` | `ActivePOsCount` (`purchase_orders`, status aktif, `remaining_amount > 0`) | `activePOs` | | Piutang | `/dashboard/kpis` | `SumReceivablesBalance` (`invoices` unpaid/partial) | `pendingInvoices` | | Net Cashflow | `/dashboard/kpis` | `MonthNetCashflow` (payments + PO paid + cashflows, bulan berjalan) | `cashflow` (slot terakhir) | | Nilai Stok (HPP) | `/dashboard/kpis` | `StockSummary` / `BalanceSheetSnapshot` | `stockValueProducts` | | Order Terbaru (widget) | `/dashboard/charts` | 8 order terakhir (semua status) | `recentOrders` | Aturan: `activeOrderStatuses` didefinisikan SEKALI di `internal/reports/repository.go` dan dipakai oleh `PendingOrdersCount` maupun query `activeOrders`. Menambah status baru pada satu tempat tanpa yang lain membuat kartu dan modal berbeda — defect 1 Nov.