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.
This commit is contained in:
commit
b4150c8250
1364 changed files with 6814366 additions and 0 deletions
108
packages/zappier/tests/app.test.ts
Normal file
108
packages/zappier/tests/app.test.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import request from 'supertest';
|
||||
import { buildApp } from '../src/app';
|
||||
|
||||
const KEY = 'key-ada'; // seeded free-tier customer
|
||||
|
||||
describe('Zappier API', () => {
|
||||
it('rejects calls without an API key', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app).get('/v1/status');
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('GET /v1/status is free', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app).get('/v1/status').set('x-api-key', KEY);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.status).toBe('ok');
|
||||
expect(res.body.quote.totalCents).toBe(0);
|
||||
});
|
||||
|
||||
it('POST /v1/transform uppercases text at the multiplied fixed price', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app)
|
||||
.post('/v1/transform')
|
||||
.set('x-api-key', KEY)
|
||||
.send({ text: 'hello' });
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.output).toBe('HELLO');
|
||||
expect(res.body.quote.totalCents).toBe(4); // list 4 x free-tier multiplier 1
|
||||
});
|
||||
|
||||
it('rejects a request that violates the OpenAPI schema with 400', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app)
|
||||
.post('/v1/transform')
|
||||
.set('x-api-key', KEY)
|
||||
.send({ wrong: 1 });
|
||||
expect(res.status).toBe(400);
|
||||
});
|
||||
|
||||
it('POST /v1/storage stores metadata plus attachments and quotes by size', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app)
|
||||
.post('/v1/storage')
|
||||
.set('x-api-key', KEY)
|
||||
.field('metadata', JSON.stringify({ title: 'report' }))
|
||||
.attach('attachments', Buffer.alloc(1024 * 1024), 'one.bin')
|
||||
.attach('attachments', Buffer.alloc(1024 * 1024), 'two.bin');
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.id).toBeTruthy();
|
||||
// metadata string is 20 bytes -> 1 KB; 2 MB attachments
|
||||
// list 10 + 1 * 1 + 2 * 50 = 111, free-tier multiplier 1
|
||||
expect(res.body.quote.totalCents).toBe(111);
|
||||
});
|
||||
|
||||
it('POST /v1/storage with malformed metadata JSON returns 400 and charges nothing', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app)
|
||||
.post('/v1/storage')
|
||||
.set('x-api-key', KEY)
|
||||
.field('metadata', '{not json');
|
||||
expect(res.status).toBe(400);
|
||||
expect(res.body.error).toMatch(/invalid metadata JSON/);
|
||||
const usageRes = await request(app).get('/v1/usage').set('x-api-key', KEY);
|
||||
expect(usageRes.status).toBe(200);
|
||||
expect(usageRes.body.totalCents).toBe(0);
|
||||
});
|
||||
|
||||
it('GET /v1/storage lists the caller items newest first', async () => {
|
||||
const { app } = buildApp();
|
||||
await request(app)
|
||||
.post('/v1/storage')
|
||||
.set('x-api-key', KEY)
|
||||
.field('metadata', JSON.stringify({ title: 'a' }));
|
||||
await request(app)
|
||||
.post('/v1/storage')
|
||||
.set('x-api-key', KEY)
|
||||
.field('metadata', JSON.stringify({ title: 'b' }));
|
||||
const res = await request(app).get('/v1/storage').set('x-api-key', KEY);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.items).toHaveLength(2);
|
||||
expect(res.body.items[0].metadata.title).toBe('b');
|
||||
});
|
||||
|
||||
it('GET /v1/usage returns the caller summary with monthly credit applied', async () => {
|
||||
const { app } = buildApp();
|
||||
await request(app)
|
||||
.post('/v1/transform')
|
||||
.set('x-api-key', KEY)
|
||||
.send({ text: 'x' });
|
||||
const res = await request(app).get('/v1/usage').set('x-api-key', KEY);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.customerId).toBe('cust_1');
|
||||
expect(res.body.calls).toBe(1);
|
||||
expect(res.body.totalCents).toBe(4);
|
||||
expect(res.body.includedCents).toBe(4); // free-tier credit (100) covers it
|
||||
expect(res.body.billableCents).toBe(0);
|
||||
});
|
||||
|
||||
it('charges less for a business-tier customer', async () => {
|
||||
const { app } = buildApp();
|
||||
const res = await request(app)
|
||||
.post('/v1/transform')
|
||||
.set('x-api-key', 'key-linus')
|
||||
.send({ text: 'hello' });
|
||||
expect(res.body.quote.totalCents).toBe(1); // list 4 x business multiplier 0.25
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue