bugproof-demo-shoplite · case_20260926_b86b
Cart total is ₹0.01 lower than it should be for some products
The cart subtotal was being calculated incorrectly for products with non-whole-rupee prices, causing GST to be slightly off by ₹0.01. This happened because the new code multiplied the line totals by the GST rate using decimal floating-point numbers before rounding, which introduced a tiny error. The fix restores the previous correct method of converting to integer paise first and uses a dedicated helper function to calculate GST accurately.
Written by IBM watsonx.ai ibm/granite-4-h-small from the case evidence.
Reproduced — a new test failed before the fix
1 of 1 repro test failed on the unfixed code
AssertionError: expected 188.86 to be 188.87 // Object.is equality
Culprit commit identified
Pinned by git bisect in 6 steps · matches the seeded answer key
98e2fce feat(cart): sum multiple line items in cart totals
Fixed — the repro test and the full suite pass
120 of 120 tests pass after the fix
Reviewed — the Critic approved
Fix is correct: calculateGst() in src/tax/gst.ts multiplies integer paise by the rate percent then divides by 100 and rounds, eliminating the floating-point precision loss that caused the one-paisa shortfall.
APPROVED
Tests untouched — the fix changed no existing test
The fix commit only adds the repro test; no existing test was modified or deleted (git diff of the fix commit)
+ tests/bugproof/case_20260926_b86b.test.ts
Written by the Fixer, whose edit permission covers src/ only; the check above confirms no existing test changed.
--- a/src/cart/totals.ts
+++ b/src/cart/totals.ts
@@ -1,6 +1,6 @@
import { fromPaise, roundMoney, toPaise } from "../currency/money";
-import { GST_RATE } from "../tax/gst";
+import { calculateGst } from "../tax/gst";
import type { Cart } from "./cart";
import { couponDiscount } from "./coupons";
import { lineItems, sumBy } from "./lineItems";
@@ -23,9 +23,9 @@
export function computeTotals(cart: Cart): CartTotals {
const lines = lineItems(cart);
const subtotal = roundMoney(sumBy(lines, (line) => line.lineTotal));
- const tax = roundMoney(sumBy(lines, (line) => line.lineTotal * GST_RATE));
+ const tax = calculateGst(subtotal);
// Coupons come off the pre-tax subtotal; GST is charged on the list price.
// Cap combined discount at subtotal so total can never go negative.The repro test fails on the code before the fix and passes on the fix.
git clone https://github.com/GegarinSagolsem/bugproof-demo-shoplite && cd bugproof-demo-shoplite && npm ci git checkout decb79c~1 # the code before the fix git checkout decb79c -- tests/bugproof/case_20260926_b86b.test.ts # add the repro test npx vitest run tests/bugproof/case_20260926_b86b.test.ts # fails: the bug is reproduced git checkout decb79c # the fix npx vitest run tests/bugproof/case_20260926_b86b.test.ts # passes
commit 98e2fcea53add7e7bc6d748086d820aa2f7d485f
Author: GegarinSagolsem <176033453+GegarinSagolsem@users.noreply.github.com>
Date: Fri Sep 25 18:55:03 2026 +0000
feat(cart): sum multiple line items in cart totals
---
src/cart/totals.ts | 10 ++++++----
tests/cart/totals.test.ts | 8 ++++++++
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/src/cart/totals.ts b/src/cart/totals.ts
index 9fea2ff..60efd0b 100644
--- a/src/cart/totals.ts
+++ b/src/cart/totals.ts
@@ -1,7 +1,8 @@
-import { fromPaise, toPaise } from "../currency/money";
-import { calculateGst } from "../tax/gst";
+import { fromPaise, roundMoney, toPaise } from "../currency/money";
+import { GST_RATE } from "../tax/gst";
import type { Cart } from "./cart";
import { couponDiscount } from "./coupons";
+import { lineItems, sumBy } from "./lineItems";
export const FREE_SHIPPING_THRESHOLD = 999;
export const SHIPPING_FEE = 49;
@@ -21,8 +22,9 @@ export function shippingFor(subtotal: number, lineCount: number): number {
}
export function computeTotals(cart: Cart): CartTotals {
- const subtotal = fromPaise(cart.items.reduce((sum, item) => sum + toPaise(item.unitPrice) * item.quantity, 0));
- const tax = calculateGst(subtotal);
+ const lines = lineItems(cart);
+ const subtotal = roundMoney(sumBy(lines, (line) => line.lineTotal));
+ const tax = roundMoney(sumBy(lines, (line) => line.lineTotal * GST_RATE));
// Coupons come off the pre-tax subtotal; GST is charged on the list price.
const discount = Math.min(couponDiscount(subtotal, cart.couponCode), subtotal);
diff --git a/tests/cart/totals.test.ts b/tests/cart/totals.test.ts
index 8104b44..783acdd 100644
--- a/tests/cart/totals.test.ts
+++ b/tests/cart/totals.test.ts
@@ -42,3 +42,11 @@ describe("computeTotals", () => {
});
});
});
+
+describe("computeTotals with multiple line items", () => {
+ it("sums the line totals into the subtotal", () => {
+ const totals = computeTotals(cartWith(["p01", 1], ["p16", 2], ["p06", 1]));
+ expect(totals.subtotal).toBe(349 + 398 + 399);
+ expect(totals.itemCount).toBe(4);
+ });
+});
The cart was under-charging GST by ₹0.01 for products with non-whole-rupee prices (e.g. Cast Iron Tawa at ₹1,049.25). The culprit commit 98e2fcea changed computeTotals() to compute tax via roundMoney(sumBy(lines, l => l.lineTotal * GST_RATE)) — floating-point multiplication per line before rounding — instead of calling the existing calculateGst() helper that converts to integer paise first. The fix restores the correct call: tax = calculateGst(subtotal), with a matching import update. The repro test (tests/bugproof/case_20260926_b86b.test.ts) went from red (₹188.86) to green (₹188.87), and all 120 tests pass.