test/shared/booking-fee.test.ts
Rama main · réplica de lectura del repositorio original.
import { expect } from "@std/expect";
import { describe, it as test } from "@std/testing/bdd";
import {
calculateBookingFee,
feeSubtotalFor,
getBookingFeeAmount,
itemsSubtotal,
} from "#shared/booking-fee.ts";
import { priceCheckout } from "#shared/checkout-pricing.ts";
import { testWithSetting } from "#test-utils/settings.ts";
describe("feeSubtotalFor", () => {
const items = [{ quantity: 2, unitPrice: 500 }];
test("uses the item subtotal when no override is given", () => {
expect(feeSubtotalFor({ items })).toBe(1000);
});
test("uses an explicit feeSubtotal override when present", () => {
// 0 (fee-free balance payment) and a larger value (deposit fee on full).
expect(feeSubtotalFor({ feeSubtotal: 0, items })).toBe(0);
expect(feeSubtotalFor({ feeSubtotal: 4000, items })).toBe(4000);
});
});
describe("calculateBookingFee", () => {
test("applies the fee percentage to the subtotal", () => {
expect(calculateBookingFee(1000, 2.9)).toBe(29);
});
test("rounds fractional results to the nearest minor unit", () => {
// 999 × 1.5% = 14.985 → 15; 100 × 2.5% = 2.5 → 3 (half rounds up).
// Fees must land on whole minor units so Stripe/Square accept the charge.
expect(calculateBookingFee(999, 1.5)).toBe(15);
expect(calculateBookingFee(100, 2.5)).toBe(3);
});
test("charges no fee when the percent is zero or negative", () => {
expect(calculateBookingFee(1000, 0)).toBe(0);
expect(calculateBookingFee(1000, -1)).toBe(0);
});
});
describe("getBookingFeeAmount", () => {
testWithSetting(
"returns 0 when no booking fee is configured",
{ booking_fee: "0" },
() => {
expect(getBookingFeeAmount(10000)).toBe(0);
},
);
testWithSetting(
"applies the configured fee to the supplied subtotal",
{ booking_fee: "2.5" },
() => {
expect(getBookingFeeAmount(10000)).toBe(250);
},
);
testWithSetting(
"treats an empty fee setting as no fee",
{ booking_fee: "" },
() => {
// Admins can blank the field to disable the booking fee; the guard
// must stop the empty string turning into NaN and poisoning cart totals.
expect(getBookingFeeAmount(10000)).toBe(0);
},
);
testWithSetting(
"treats a non-numeric fee setting as no fee",
{ booking_fee: "not a number" },
() => {
// parseFloat returns NaN for garbage input — the `|| 0` guard in
// getBookingFee() must prevent NaN ever reaching the payment provider.
expect(getBookingFeeAmount(10000)).toBe(0);
},
);
});
describe("reservation booking fee", () => {
testWithSetting(
"is charged on the full order total, not the deposit total",
{ booking_fee: "5", currency: "GBP" },
() => {
const item = {
listingId: 1,
name: "General",
quantity: 3,
slug: "general",
unitPrice: 1000,
};
const order = priceCheckout({
address: "",
date: null,
email: "buyer@example.com",
items: [item],
name: "Buyer",
phone: "",
reservationAmount: "10",
special_instructions: "",
});
expect(order.extras).toEqual([
{ amount: 150, key: "fee", name: "Booking fee", quantity: 1 },
]);
},
);
});
describe("itemsSubtotal", () => {
test("returns 0 for an empty cart", () => {
expect(itemsSubtotal([])).toBe(0);
});
test("sums unitPrice × quantity across mixed items", () => {
expect(
itemsSubtotal([
{ quantity: 3, unitPrice: 500 },
{ quantity: 1, unitPrice: 250 },
{ quantity: 2, unitPrice: 1000 },
]),
).toBe(3750);
});
test("counts free (zero-priced) items as contributing nothing", () => {
// Free tickets alongside paid ones must not inflate the subtotal, which
// would otherwise push up the percentage-based booking fee.
expect(
itemsSubtotal([
{ quantity: 5, unitPrice: 0 },
{ quantity: 1, unitPrice: 750 },
]),
).toBe(750);
});
});