import { ConfigTierCatalog, DEFAULT_PRICING, DEFAULT_TIERS, InMemoryPricingStore, quoteCall, } from '../src/pricing'; const noUsage = { metadataBytes: 0, attachmentBytes: 0 }; describe('quoteCall', () => { it('prices free endpoints at 0 on every tier, ignoring the multiplier', () => { for (const tier of DEFAULT_TIERS) { expect(quoteCall(DEFAULT_PRICING, tier.id, 'status', noUsage).totalCents).toBe(0); expect(quoteCall(DEFAULT_PRICING, tier.id, 'storage-list', noUsage).totalCents).toBe(0); } }); it('applies the tier multiplier to fixed list prices', () => { // transform list price: 4 cents expect(quoteCall(DEFAULT_PRICING, 'free', 'transform', noUsage).totalCents).toBe(4); expect(quoteCall(DEFAULT_PRICING, 'pro', 'transform', noUsage).totalCents).toBe(2); expect(quoteCall(DEFAULT_PRICING, 'business', 'transform', noUsage).totalCents).toBe(1); }); it('prices storage as base + per-KB metadata + per-MB attachments at list rates', () => { const usage = { metadataBytes: 2048, attachmentBytes: 2 * 1024 * 1024 }; // list: 10 + 2 * 1 + 2 * 50 = 112 expect(quoteCall(DEFAULT_PRICING, 'free', 'storage', usage).totalCents).toBe(112); expect(quoteCall(DEFAULT_PRICING, 'pro', 'storage', usage).totalCents).toBe(56); expect(quoteCall(DEFAULT_PRICING, 'business', 'storage', usage).totalCents).toBe(28); }); it('rounds partial KB and MB up before applying the multiplier', () => { const q = quoteCall(DEFAULT_PRICING, 'free', 'storage', { metadataBytes: 1, attachmentBytes: 1, }); // list: 10 + 1 KB * 1 + 1 MB * 50 = 61 expect(q.listCents).toBe(61); expect(q.totalCents).toBe(61); }); it('exposes the list-price breakdown and the multiplied total', () => { const q = quoteCall(DEFAULT_PRICING, 'pro', 'storage', { metadataBytes: 1024, attachmentBytes: 0, }); expect(q.breakdown).toEqual({ baseCents: 10, metadataCents: 1, attachmentCents: 0 }); expect(q.listCents).toBe(11); expect(q.totalCents).toBe(6); // Math.round(11 * 0.5) }); it('lets a per-customer multiplier override beat the tier multiplier', () => { expect(quoteCall(DEFAULT_PRICING, 'free', 'transform', noUsage, 0.5).totalCents).toBe(2); expect(quoteCall(DEFAULT_PRICING, 'free', 'status', noUsage, 0.5).totalCents).toBe(0); }); it('falls back to the tier default rule for endpoints not on the rate card', () => { // pro default rule: fixed 8 list -> round(8 * 0.5) = 4 expect(quoteCall(DEFAULT_PRICING, 'pro', 'experimental', noUsage).totalCents).toBe(4); }); it('throws for an endpoint with no rate-card entry and no tier default', () => { expect(() => quoteCall(DEFAULT_PRICING, 'free', 'experimental', noUsage)).toThrow( 'No price rule', ); }); it('throws for an unknown tier', () => { expect(() => quoteCall(DEFAULT_PRICING, 'platinum', 'status', noUsage)).toThrow( 'Unknown tier', ); }); }); describe('ConfigTierCatalog', () => { it('finds tiers by id and lists them', () => { const catalog = new ConfigTierCatalog(DEFAULT_TIERS); expect(catalog.find('pro')?.multiplier).toBe(0.5); expect(catalog.find('nope')).toBeUndefined(); expect(catalog.list().map((t) => t.id)).toEqual(['free', 'pro', 'business']); }); it('supports adding a customer type as pure config', () => { const catalog = new ConfigTierCatalog([ ...DEFAULT_TIERS, { id: 'edu', name: 'Education', multiplier: 0.4, monthlyCreditCents: 500 }, ]); expect(catalog.find('edu')?.name).toBe('Education'); }); }); describe('InMemoryPricingStore', () => { it('seeds from the default rate card and tiers', () => { const store = new InMemoryPricingStore(); expect(store.getRateCard().endpoints.status).toEqual({ kind: 'free' }); expect(store.getTiers().map((t) => t.id)).toEqual(['free', 'pro', 'business']); }); it('upserts and deletes endpoints', () => { const store = new InMemoryPricingStore(); store.upsertEndpoint('experimental', { kind: 'fixed', fixedCents: 9 }); expect(store.getRateCard().endpoints.experimental).toEqual({ kind: 'fixed', fixedCents: 9, }); store.deleteEndpoint('experimental'); expect(store.getRateCard().endpoints.experimental).toBeUndefined(); }); it('upserts and deletes tiers', () => { const store = new InMemoryPricingStore(); store.upsertTier({ id: 'edu', name: 'Education', multiplier: 0.4, monthlyCreditCents: 500 }); expect(store.getTiers().find((t) => t.id === 'edu')?.multiplier).toBe(0.4); store.upsertTier({ id: 'edu', name: 'Education', multiplier: 0.3, monthlyCreditCents: 500 }); expect(store.getTiers().filter((t) => t.id === 'edu')).toHaveLength(1); store.deleteTier('edu'); expect(store.getTiers().find((t) => t.id === 'edu')).toBeUndefined(); }); });