import express from 'express'; import request from 'supertest'; import { apiKeyAuth, Customer, InMemoryCustomerRepo } from '../src/auth'; import { meter } from '../src/meter'; import { DEFAULT_PRICING } from '../src/pricing'; import { InMemoryUsageRepo } from '../src/usage'; const FREE_ADA: Customer = { id: 'cust_1', name: 'Ada', tierId: 'free', apiKey: 'key-ada' }; function buildApp(seed: Customer[] = [FREE_ADA]) { const customers = new InMemoryCustomerRepo(seed); const usage = new InMemoryUsageRepo(); const app = express(); app.use(express.json()); app.use(apiKeyAuth(customers)); app.post('/transform', meter('transform', usage, DEFAULT_PRICING), (req, res) => res.json({ quote: res.locals.quote }), ); app.post('/storage', meter('storage', usage, DEFAULT_PRICING), (req, res) => res.json({ quote: res.locals.quote }), ); app.post('/experimental', meter('experimental', usage, DEFAULT_PRICING), (req, res) => res.json({ quote: res.locals.quote }), ); return { app, usage }; } describe('meter middleware', () => { it('quotes a fixed endpoint at the multiplied tier price and records usage', async () => { const { app, usage } = buildApp(); const res = await request(app) .post('/transform') .set('x-api-key', 'key-ada') .send({ text: 'hi' }); expect(res.status).toBe(200); expect(res.body.quote.totalCents).toBe(4); // free tier, multiplier 1 const entries = usage.listFor('cust_1'); expect(entries).toHaveLength(1); expect(entries[0].endpointId).toBe('transform'); expect(entries[0].cents).toBe(4); }); it('charges per KB of metadata on variable endpoints', async () => { const { app } = buildApp(); const res = await request(app) .post('/storage') .set('x-api-key', 'key-ada') .send({ metadata: { note: 'x'.repeat(2048) } }); // JSON of metadata is 2059 bytes -> 3 KB -> list 10 + 3 * 1 = 13 expect(res.body.quote.totalCents).toBe(13); }); it('applies a per-customer multiplier override', async () => { const { app } = buildApp([ { id: 'cust_vip', name: 'Vip', tierId: 'free', apiKey: 'key-vip', multiplierOverride: 0.5 }, ]); const res = await request(app) .post('/transform') .set('x-api-key', 'key-vip') .send({ text: 'hi' }); expect(res.body.quote.totalCents).toBe(2); // list 4 x override 0.5 }); it('returns 401 when no customer is attached', async () => { const usage = new InMemoryUsageRepo(); const app = express(); app.use(express.json()); app.post('/transform', meter('transform', usage, DEFAULT_PRICING), (req, res) => res.json({}), ); const res = await request(app).post('/transform').send({ text: 'hi' }); expect(res.status).toBe(401); expect(usage.listFor('cust_1')).toHaveLength(0); }); it('returns 403 for an endpoint with no price rule on the caller tier', async () => { const { app, usage } = buildApp(); const res = await request(app) .post('/experimental') .set('x-api-key', 'key-ada') .send({}); expect(res.status).toBe(403); expect(res.body.error).toMatch(/No price rule/); expect(usage.listFor('cust_1')).toHaveLength(0); }); });