master-zapier-plan-draft/packages/zappier/tests/pricing.test.ts
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

121 lines
4.8 KiB
TypeScript

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