From 1d860051c62cb13b08be36d8c24027ee7d57718a Mon Sep 17 00:00:00 2001 From: Pavan Kumar VH Date: Thu, 10 Sep 2026 13:41:33 +0530 Subject: [PATCH] Throw error for invalid currency conversion inputs with tests The previous version silently returned 0 when centsPerCredit was invalid or credits/amountInCents was NaN/Infinity. This is dangerous in money-conversion paths because misconfigurations would silently produce wrong values rather than failing loudly. Changed to throw explicit errors with descriptive messages so invalid inputs are caught immediately during development and testing. Added comprehensive test coverage for: - Normal conversion cases - Non-positive centsPerCredit (zero, negative) - NaN inputs (credits, amountInCents) - Infinity inputs (positive and negative) All 10 tests pass. --- common/src/util/__tests__/currency.test.ts | 56 ++++++++++++++++++++++ common/src/util/currency.ts | 22 +++++++++ 2 files changed, 78 insertions(+) create mode 100644 common/src/util/__tests__/currency.test.ts diff --git a/common/src/util/__tests__/currency.test.ts b/common/src/util/__tests__/currency.test.ts new file mode 100644 index 0000000000..757d6b4003 --- /dev/null +++ b/common/src/util/__tests__/currency.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'bun:test' + +import { + convertCreditsToUsdCents, + convertStripeGrantAmountToCredits, +} from '../currency' + +describe('convertCreditsToUsdCents', () => { + it('converts credits to cents correctly', () => { + expect(convertCreditsToUsdCents(1, 100)).toBe(100) + expect(convertCreditsToUsdCents(1.5, 100)).toBe(150) + expect(convertCreditsToUsdCents(0.1, 100)).toBe(10) + }) + + it('throws for non-positive centsPerCredit', () => { + expect(() => convertCreditsToUsdCents(100, 0)).toThrow('centsPerCredit must be positive') + expect(() => convertCreditsToUsdCents(100, -10)).toThrow('centsPerCredit must be positive') + }) + + it('throws for NaN credits', () => { + expect(() => convertCreditsToUsdCents(NaN, 100)).toThrow('credits must be finite') + }) + + it('throws for Infinity credits', () => { + expect(() => convertCreditsToUsdCents(Infinity, 100)).toThrow('credits must be finite') + }) + + it('throws for negative Infinity credits', () => { + expect(() => convertCreditsToUsdCents(-Infinity, 100)).toThrow('credits must be finite') + }) +}) + +describe('convertStripeGrantAmountToCredits', () => { + it('converts cents to credits correctly', () => { + expect(convertStripeGrantAmountToCredits(10000, 100)).toBe(100) + expect(convertStripeGrantAmountToCredits(15000, 100)).toBe(150) + expect(convertStripeGrantAmountToCredits(1000, 100)).toBe(10) + }) + + it('throws for non-positive centsPerCredit', () => { + expect(() => convertStripeGrantAmountToCredits(10000, 0)).toThrow('centsPerCredit must be positive') + expect(() => convertStripeGrantAmountToCredits(10000, -10)).toThrow('centsPerCredit must be positive') + }) + + it('throws for NaN amountInCents', () => { + expect(() => convertStripeGrantAmountToCredits(NaN, 100)).toThrow('amountInCents must be finite') + }) + + it('throws for Infinity amountInCents', () => { + expect(() => convertStripeGrantAmountToCredits(Infinity, 100)).toThrow('amountInCents must be finite') + }) + + it('throws for negative Infinity amountInCents', () => { + expect(() => convertStripeGrantAmountToCredits(-Infinity, 100)).toThrow('amountInCents must be finite') + }) +}) \ No newline at end of file diff --git a/common/src/util/currency.ts b/common/src/util/currency.ts index b3499c01e5..ccdbc5cca6 100644 --- a/common/src/util/currency.ts +++ b/common/src/util/currency.ts @@ -3,11 +3,22 @@ * @param credits The number of credits to convert * @param centsPerCredit The cost per credit in cents * @returns The amount in USD cents + * @throws Error if centsPerCredit is not positive or credits is not finite */ export function convertCreditsToUsdCents( credits: number, centsPerCredit: number, ): number { + if (!(centsPerCredit > 0)) { + throw new Error( + `convertCreditsToUsdCents: centsPerCredit must be positive, got ${centsPerCredit}`, + ) + } + if (!Number.isFinite(credits)) { + throw new Error( + `convertCreditsToUsdCents: credits must be finite, got ${credits}`, + ) + } return Math.ceil(credits * centsPerCredit) } @@ -16,10 +27,21 @@ export function convertCreditsToUsdCents( * @param amountInCents The amount in USD cents * @param centsPerCredit The cost per credit in cents * @returns The number of credits + * @throws Error if centsPerCredit is not positive or amountInCents is not finite */ export function convertStripeGrantAmountToCredits( amountInCents: number, centsPerCredit: number, ): number { + if (!(centsPerCredit > 0)) { + throw new Error( + `convertStripeGrantAmountToCredits: centsPerCredit must be positive, got ${centsPerCredit}`, + ) + } + if (!Number.isFinite(amountInCents)) { + throw new Error( + `convertStripeGrantAmountToCredits: amountInCents must be finite, got ${amountInCents}`, + ) + } return Math.floor(amountInCents / centsPerCredit) }