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/timestamp is idempotent by sha256', async () => { const { app } = buildApp(); const a = await request(app) .post('/v1/timestamp') .set('x-api-key', KEY) .send({ data: 'abc' }); expect(a.status).toBe(202); const b = await request(app) .post('/v1/timestamp') .set('x-api-key', KEY) .send({ data: 'abc' }); expect(b.body.jobId).toBe(a.body.jobId); expect(b.body.existing).toBe(true); const look = await request(app) .get(`/v1/hashes/${a.body.sha256}`) .set('x-api-key', KEY); expect(look.status).toBe(200); expect(look.body.jobId).toBe(a.body.jobId); const receipt = await request(app) .get(`/v1/receipts/${a.body.jobId}`) .set('x-api-key', KEY); expect(receipt.status).toBe(200); expect(receipt.body.type).toBe('verae.retrieval-receipt'); }); it('POST /v1/add returns the sum and is free', async () => { const { app } = buildApp(); const res = await request(app) .post('/v1/add') .set('x-api-key', KEY) .send({ number1: 2, number2: 3 }); expect(res.status).toBe(200); expect(res.body.sum).toBe(5); expect(res.body.number1).toBe(2); expect(res.body.number2).toBe(3); expect(res.body.quote.totalCents).toBe(0); }); it('POST /v1/add rejects missing fields', async () => { const { app } = buildApp(); const res = await request(app).post('/v1/add').set('x-api-key', KEY).send({ number1: 1 }); expect(res.status).toBe(400); }); 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 }); });