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) }