🔬 LegacyLens ViewerAman

Modernization Impact Report

Dihasilkan oleh Ripple Tracer (IBM Bob 2.0 — LegacyLens PoC) Tanggal analisis: sesuai timestamp run


Ringkasan Eksekutif

  • Jumlah file dianalisis: 5 (utils/pricing.js, checkout.js, invoice.js, report.js, app.js)
  • Jumlah file diubah (sesi ini): 1 (utils/pricing.js — signature calculateDiscount() diubah dari callback ke async)
  • Jumlah file terdampak (ripple): 3 (checkout.js, invoice.js, report.js)
  • Status akhir: ✅ Aman — bug invoice.js diperbaiki, 17/17 test lolos

1. Perubahan yang Dilakukan

FilePerubahanAlasan
utils/pricing.jsSignature calculateDiscount(price, rate, callback) → async calculateDiscount(price, rate)Modernisasi dari callback ke async/await; error kini di-throw, bukan di-forward ke callback
checkout.jsprocessCheckout(order, callback) → async processCheckout(order); konsumsi via await calculateDiscount(...)Mengikuti signature baru pricing.js
report.jsRekursi callback processNext() → Promise.all(transactions.map(tx => calculateDiscount(...)))Lebih ringkas + paralel
app.jsSemua route handler function(req,res) → async (req,res) + try/catchMenyesuaikan modul yang kini semua berbasis Promise
package.jsonmoment 2.24.0 → 2.31.0Patch keamanan; tidak ada breaking change di API yang dipakai

2. Impact Map

File TerdampakTerhubung viaStatus MigrasiLevel Risiko
checkout.jsrequire('./utils/pricing') → memanggil calculateDiscount() langsung✅ Benar — menggunakan awaitTinggi
invoice.jsrequire('./utils/pricing') → memanggil calculateDiscount() langsung❌ BUG — await hilang di baris 41Tinggi
report.jsrequire('./utils/pricing') → memanggil calculateDiscount() via Promise.all✅ Benar — await Promise.all(...)Tinggi
app.jsrequire('./invoice') & require('./checkout') & require('./report')✅ Benar — route handler sudah async/awaitSedang

3. Hasil Validasi Test

TestFile yang DitestStatusCatatan
menghitung grand total dengan diskon 10%checkout.js✅ LolosgrandTotal = 180000
menghitung grand total tanpa diskon (rate 0)checkout.js✅ LolosgrandTotal = 50000
menghitung grand total dengan diskon 100%checkout.js✅ LolosgrandTotal = 0
melempar error jika harga negatifcheckout.js✅ Lolosthrows 'Harga tidak valid'
melempar error jika rate melebihi 100checkout.js✅ Lolosthrows 'Rate diskon tidak valid'
createInvoice — totalDue dengan diskon 15%invoice.js✅ LolostotalDue = 425000 (setelah fix await)
createInvoice — tanpa diskon (0%)invoice.js✅ LolostotalDue = amount asli
createInvoice — diskon 100%invoice.js✅ LolostotalDue = 0
createInvoice — properti wajib tidak undefinedinvoice.js✅ LolosinvoiceId, issuedAt, totalDue terisi
createInvoice — error amount negatifinvoice.js✅ Lolosthrows 'Harga tidak valid'
createInvoice — error rate > 100invoice.js✅ Lolosthrows 'Rate diskon tidak valid'
generateDiscountReport — akumulasi multiple transaksireport.js✅ LolostotalOriginal/Discounted/Saved benar
generateDiscountReport — transaksi tunggal tanpa diskonreport.js✅ LolostotalDiscounted = amount asli
generateDiscountReport — diskon 100%report.js✅ LolostotalDiscounted = 0
generateDiscountReport — array kosongreport.js✅ Lolossemua nilai = 0
generateDiscountReport — nullreport.js✅ Lolossemua nilai = 0
generateDiscountReport — error amount negatifreport.js✅ Lolosthrows 'Harga tidak valid'

Blind spot ditutup: Sebelum sesi ini, invoice.js dan report.js sama sekali tidak memiliki unit test. Bug di invoice.js tidak akan pernah tertangkap CI. Setelah fix await dan penambahan test suite, seluruh 17 test (3 suite) lolos 100% — blind spot tertutup.


4. Root Cause Trace

Rantai sebab-akibat:

utils/pricing.js (calculateDiscount)
  ↓ signature diubah: callback → async/await (return Promise)
    ↓ invoice.js baris 41: const result = calculateDiscount(...)
        ← HILANG 'await' — result = Promise object, bukan { original, discounted, ... }
          ↓ invoice.lineItems[0].amount = result.original  → undefined
          ↓ invoice.lineItems[1].amount = -result.savedAmount → NaN
          ↓ invoice.totalDue            = result.discounted  → undefined
          ↓ invoice.issuedAt            = result.calculatedAt → undefined
            ↓ app.js /invoice endpoint mengembalikan invoice dengan semua nilai rupiah undefined
              ← Tidak ada exception dilempar — bug SILENT

Penjelasan naratif:

calculateDiscount() di utils/pricing.js diubah dari callback pattern ke async function. Perubahan ini membuatnya mengembalikan Promise<result> — artinya setiap pemanggil wajib menggunakan await untuk mendapatkan nilai aktualnya.

checkout.js dan report.js berhasil dimigrasi dengan benar. Namun invoice.js baris 41 melewatkan await:

// SEBELUM FIX (buggy)
const result = calculateDiscount(transaction.amount, transaction.discountRate);
//             ^^^^^^^^^^^^^^^^^ Promise dikembalikan tapi tidak di-await
//             result = Promise { <pending> }, bukan { original, discounted, ... }

// SETELAH FIX (benar)
const result = await calculateDiscount(transaction.amount, transaction.discountRate);

Karena result berisi objek Promise (bukan data), akses ke result.original, result.discounted, result.savedAmount, dan result.calculatedAt semuanya menghasilkan undefined. JavaScript tidak melempar exception untuk akses properti yang tidak ada — ini yang membuat bug ini silent dan berbahaya.


5. Rekomendasi Lanjutan

  • [WAJIB — sudah dikerjakan] Tambahkan await di invoice.js baris 41.
  • [WAJIB — sudah dikerjakan] Tambahkan unit test untuk createInvoice() di tests/invoice.test.js.
  • [WAJIB — sudah dikerjakan] Tambahkan unit test untuk generateDiscountReport() di tests/report.test.js.
  • [DISARANKAN] Aktifkan TypeScript strict mode atau gunakan JSDoc @returns {Promise<DiscountResult>} + ESLint @typescript-eslint/no-floating-promises untuk menangkap missing-await secara statis di CI.
  • [DISARANKAN] Verifikasi endpoint /invoice via curl atau Postman setelah fix — pastikan totalDue berisi angka valid, bukan undefined.
  • [DISARANKAN] Pertimbangkan integration test untuk app.js endpoints menggunakan supertest agar perubahan di lapisan routing juga tercakup.

Dokumen ini di-generate oleh Ripple Tracer — IBM Bob 2.0 | LegacyLens PoC