Initial import of zappier-edge from zapier monorepo
This commit is contained in:
commit
48cd80ce5f
120 changed files with 19867 additions and 0 deletions
121
tests/pricing.test.ts
Normal file
121
tests/pricing.test.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue