/** * GATE 6 — Full HTTP middleware (NATS off, MOCK_VERAE on) */ import { describe, it, before, after } from 'node:test'; import assert from 'node:assert/strict'; import http from 'node:http'; import { createApp } from '../../src/app.js'; import { useTempStore, seedProTenant } from '../helpers.js'; import { config } from '../../src/config.js'; import { startInProcessJobPoller, stopInProcessJobPoller } from '../../src/workers/inProcessJobPoller.js'; import { getActiveWebhooks } from '../../src/store/webhooks.js'; describe('HTTP API (mock verae, nats off)', () => { /** @type {ReturnType} */ let ctx; /** @type {import('http').Server} */ let server; /** @type {number} */ let port; /** @type {string} */ let apiKey; /** @type {object[]} */ let webhookDeliveries; /** @type {import('http').Server} */ let hookServer; /** @type {number} */ let hookPort; before(async () => { assert.equal(config.mockVerae, true); assert.equal(config.natsEnabled, false); // Fast poll for wait path config.jobPollIntervalMs = 20; config.jobPollMaxAttempts = 50; ctx = useTempStore(); const seeded = seedProTenant(); apiKey = seeded.apiKey; webhookDeliveries = []; await new Promise((resolve) => { hookServer = http.createServer((req, res) => { let body = ''; req.on('data', (c) => { body += c; }); req.on('end', () => { webhookDeliveries.push(JSON.parse(body || '{}')); res.writeHead(200); res.end('ok'); }); }); hookServer.listen(0, '127.0.0.1', () => { hookPort = hookServer.address().port; resolve(); }); }); const app = createApp({ load: false }); await new Promise((resolve) => { server = app.listen(0, '127.0.0.1', () => { port = server.address().port; resolve(); }); }); startInProcessJobPoller(); }); after(async () => { stopInProcessJobPoller(); await new Promise((resolve) => server.close(resolve)); await new Promise((resolve) => hookServer.close(resolve)); ctx.cleanup(); }); function authHeaders() { return { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }; } it('POST /zapier/v1/timestamp → 202 + jobId', async () => { const res = await fetch(`http://127.0.0.1:${port}/zapier/v1/timestamp`, { method: 'POST', headers: authHeaders(), body: JSON.stringify({ data: 'payload-async' }), }); assert.equal(res.status, 202); const body = await res.json(); assert.ok(body.jobId); }); it('POST /zapier/v1/timestamp/wait → completed status', async () => { const res = await fetch(`http://127.0.0.1:${port}/zapier/v1/timestamp/wait`, { method: 'POST', headers: authHeaders(), body: JSON.stringify({ data: 'payload-wait', hashAlg: 'SHA256' }), }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.status, 'completed'); assert.ok(body.result); assert.ok(body.metadata?.certificate || body.result); }); it('POST /zapier/v1/verify → valid true', async () => { const waitRes = await fetch(`http://127.0.0.1:${port}/zapier/v1/timestamp/wait`, { method: 'POST', headers: authHeaders(), body: JSON.stringify({ data: 'to-verify' }), }); const done = await waitRes.json(); const res = await fetch(`http://127.0.0.1:${port}/zapier/v1/verify`, { method: 'POST', headers: authHeaders(), body: JSON.stringify({ certificate: done.result }), }); assert.equal(res.status, 200); const body = await res.json(); assert.equal(body.valid, true); }); it('webhook subscribe stores targetUrl; complete delivers', async () => { const sub = await fetch(`http://127.0.0.1:${port}/zapier/v1/webhooks/subscribe`, { method: 'POST', headers: authHeaders(), body: JSON.stringify({ targetUrl: `http://127.0.0.1:${hookPort}/hook`, event: 'timestamp.completed', }), }); assert.equal(sub.status, 201); const hook = await sub.json(); assert.ok(hook.id); const hooks = getActiveWebhooks('tenant-test-pro', 'timestamp.completed'); assert.ok(hooks.some((h) => h.id === hook.id)); webhookDeliveries.length = 0; const create = await fetch(`http://127.0.0.1:${port}/zapier/v1/timestamp`, { method: 'POST', headers: authHeaders(), body: JSON.stringify({ data: 'webhook-trigger-me' }), }); const { jobId } = await create.json(); assert.ok(jobId); // Wait for in-process poller to deliver const deadline = Date.now() + 5000; while (webhookDeliveries.length === 0 && Date.now() < deadline) { await new Promise((r) => setTimeout(r, 50)); } assert.ok(webhookDeliveries.length >= 1, 'expected webhook delivery'); assert.equal(webhookDeliveries[0].event, 'timestamp.completed'); assert.equal(webhookDeliveries[0].jobId, jobId); }); });