Initial import of verae-middleware from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 14:23:39 -04:00
commit cc2ab54161
66 changed files with 6590 additions and 0 deletions

49
test/unit/app.test.js Normal file
View file

@ -0,0 +1,49 @@
/**
* GATE 2 (partial) HTTP shell health endpoint.
*/
import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { createApp } from '../../src/app.js';
describe('createApp', () => {
/** @type {import('http').Server} */
let server;
/** @type {number} */
let port;
before(async () => {
const app = createApp();
await new Promise((resolve) => {
server = app.listen(0, '127.0.0.1', resolve);
});
port = server.address().port;
});
after(async () => {
await new Promise((resolve) => server.close(resolve));
});
it('GET /health returns ok', async () => {
const res = await fetch(`http://127.0.0.1:${port}/health`);
assert.equal(res.status, 200);
const body = await res.json();
assert.equal(body.status, 'ok');
assert.equal(body.service, 'verae-zapier-middleware');
assert.equal(typeof body.mockVerae, 'boolean');
assert.equal(typeof body.natsEnabled, 'boolean');
assert.equal(typeof body.natsConnected, 'boolean');
assert.ok(res.headers.get('x-trace-id'));
});
it('protected /zapier path requires auth', async () => {
const res = await fetch(`http://127.0.0.1:${port}/zapier/v1/timestamp`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: '{}',
});
assert.equal(res.status, 401);
const body = await res.json();
assert.equal(body.code, 'UNAUTHORIZED');
});
});

34
test/unit/config.test.js Normal file
View file

@ -0,0 +1,34 @@
/**
* GATE 2 (partial) config exports.
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { config, PLAN_LIMITS } from '../../src/config.js';
describe('config', () => {
it('exposes required keys for HTTP and NATS', () => {
for (const key of [
'port',
'host',
'veraeApiBaseUrl',
'mockVerae',
'natsEnabled',
'natsUrl',
'tokenSecret',
'jobPollIntervalMs',
'jobPollMaxAttempts',
'waitTimeoutMs',
'storePath',
]) {
assert.notEqual(config[key], undefined, `missing config.${key}`);
}
});
it('defines plan limits for free through enterprise', () => {
for (const plan of ['free', 'starter', 'pro', 'enterprise']) {
assert.ok(PLAN_LIMITS[plan], plan);
assert.equal(typeof PLAN_LIMITS[plan].requestsPerMinute, 'number');
}
});
});

165
test/unit/debug.test.js Normal file
View file

@ -0,0 +1,165 @@
/**
* GATE 1 Debug facility unit tests.
* Must pass before Phase 2.
*/
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import {
parseDebugVeraeEnv,
loadDebugConfig,
shouldLog,
createDebugger,
setDebugTestSink,
redact,
withTrace,
getTraceId,
generateTraceId,
} from '../../src/debug/index.js';
describe('parseDebugVeraeEnv', () => {
it('disables when unset or empty', () => {
assert.equal(parseDebugVeraeEnv(undefined).enabled, false);
assert.equal(parseDebugVeraeEnv('').enabled, false);
assert.equal(parseDebugVeraeEnv('off').enabled, false);
});
it('enables all namespaces for 1 or *', () => {
const a = parseDebugVeraeEnv('1');
assert.equal(a.enabled, true);
assert.equal(a.namespaces, null);
const b = parseDebugVeraeEnv('*');
assert.equal(b.enabled, true);
assert.equal(b.namespaces, null);
});
it('parses comma-separated namespaces', () => {
const { enabled, namespaces } = parseDebugVeraeEnv('auth, NATS, jobs');
assert.equal(enabled, true);
assert.ok(namespaces.has('auth'));
assert.ok(namespaces.has('nats'));
assert.ok(namespaces.has('jobs'));
assert.equal(namespaces.has('webhooks'), false);
});
});
describe('shouldLog', () => {
it('respects level thresholds', () => {
const config = {
enabled: true,
namespaces: null,
level: 'warn',
filePath: null,
};
assert.equal(shouldLog(config, 'app', 'debug'), false);
assert.equal(shouldLog(config, 'app', 'warn'), true);
assert.equal(shouldLog(config, 'app', 'error'), true);
});
it('filters by namespace', () => {
const config = {
enabled: true,
namespaces: new Set(['auth']),
level: 'debug',
filePath: null,
};
assert.equal(shouldLog(config, 'auth', 'debug'), true);
assert.equal(shouldLog(config, 'nats', 'debug'), false);
});
});
describe('redact', () => {
it('redacts sensitive keys and token-like strings', () => {
const out = redact({
password: 'secret',
apiKey: 'zmw_abc123def456ghi789jkl',
jobId: 'keep-me',
authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.aaa.bbb',
nested: { veraeToken: 'zmt_payload.sig' },
});
assert.equal(out.password, '[REDACTED]');
assert.equal(out.apiKey, '[REDACTED]');
assert.equal(out.jobId, 'keep-me');
assert.equal(out.authorization, '[REDACTED]');
assert.equal(out.nested.veraeToken, '[REDACTED]');
});
});
describe('createDebugger', () => {
/** @type {string[]} */
let lines;
const prev = { ...process.env };
beforeEach(() => {
lines = [];
setDebugTestSink((line) => lines.push(line));
});
afterEach(() => {
setDebugTestSink(null);
for (const key of Object.keys(process.env)) {
if (!(key in prev)) delete process.env[key];
}
Object.assign(process.env, prev);
});
it('is silent when DEBUG_VERAE is unset', () => {
delete process.env.DEBUG_VERAE;
const log = createDebugger('auth');
log.debug('should not appear', { x: 1 });
assert.equal(lines.length, 0);
});
it('emits only selected namespaces', () => {
process.env.DEBUG_VERAE = 'auth';
createDebugger('auth').debug('auth-line');
createDebugger('nats').debug('nats-line');
assert.equal(lines.length, 1);
assert.match(lines[0], /auth-line/);
assert.doesNotMatch(lines[0], /nats-line/);
});
it('redacts secrets in meta', () => {
process.env.DEBUG_VERAE = 'auth';
createDebugger('auth').debug('login', {
password: 'hunter2',
token: 'zmw_supersecretvaluehere12',
});
assert.equal(lines.length, 1);
assert.doesNotMatch(lines[0], /hunter2/);
assert.doesNotMatch(lines[0], /zmw_supersecret/);
assert.match(lines[0], /REDACTED/);
});
});
describe('withTrace', () => {
it('propagates traceId to nested async work', async () => {
const outerId = generateTraceId();
let innerId = null;
await withTrace({ traceId: outerId, span: 'outer' }, async () => {
assert.equal(getTraceId(), outerId);
await withTrace({ span: 'inner' }, async () => {
innerId = getTraceId();
});
});
assert.equal(innerId, outerId);
assert.equal(getTraceId(), null);
});
});
describe('loadDebugConfig', () => {
it('reads level and file from env', () => {
process.env.DEBUG_VERAE = '1';
process.env.DEBUG_VERAE_LEVEL = 'error';
process.env.DEBUG_VERAE_FILE = '/tmp/verae-debug.log';
const cfg = loadDebugConfig(process.env);
assert.equal(cfg.enabled, true);
assert.equal(cfg.level, 'error');
assert.equal(cfg.filePath, '/tmp/verae-debug.log');
});
});

105
test/unit/store.test.js Normal file
View file

@ -0,0 +1,105 @@
/**
* GATE 3 Store unit tests
*/
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import {
useTempStore,
seedFreeTenant,
seedEnterpriseTenant,
} from '../helpers.js';
import { getTenantByApiKey, resolveLimits, getTenant } from '../../src/store/tenants.js';
import {
createWebhook,
getActiveWebhooks,
listWebhooksForTenant,
deleteWebhook,
} from '../../src/store/webhooks.js';
import { getUsage, incrementUsage } from '../../src/store/usage.js';
import { reloadStore, getStore } from '../../src/store/db.js';
describe('stores', () => {
/** @type {ReturnType<typeof useTempStore>} */
let ctx;
beforeEach(() => {
ctx = useTempStore();
});
afterEach(() => {
ctx.cleanup();
});
it('create tenant → API key resolves to same tenant', () => {
const { tenant, apiKey } = seedFreeTenant();
const found = getTenantByApiKey(apiKey);
assert.ok(found);
assert.equal(found.id, tenant.id);
assert.equal(found.plan, 'free');
});
it('free plan limits applied; enterprise contract overrides', () => {
const { tenant: free } = seedFreeTenant();
const freeLimits = resolveLimits(free);
assert.equal(freeLimits.timestamps, 50);
assert.equal(freeLimits.batch, false);
const { tenant: ent } = seedEnterpriseTenant();
const entLimits = resolveLimits(ent);
assert.equal(entLimits.timestamps, 10);
assert.equal(entLimits.batch, true);
assert.equal(entLimits.batchMaxItems, 5);
});
it('webhook isolation per tenant', () => {
seedFreeTenant({ id: 'a' });
seedFreeTenant({ id: 'b', veraeUsername: 'u2' });
createWebhook({
tenantId: 'a',
targetUrl: 'https://hooks.example/a',
event: 'timestamp.completed',
});
createWebhook({
tenantId: 'b',
targetUrl: 'https://hooks.example/b',
event: 'timestamp.completed',
});
const aHooks = listWebhooksForTenant('a');
const bHooks = getActiveWebhooks('b', 'timestamp.completed');
assert.equal(aHooks.length, 1);
assert.equal(aHooks[0].targetUrl, 'https://hooks.example/a');
assert.equal(bHooks.length, 1);
assert.equal(bHooks[0].tenantId, 'b');
});
it('persist + reload round-trip preserves data', () => {
const { tenant, apiKey } = seedFreeTenant({ id: 'persist-me' });
incrementUsage(tenant.id, 'timestamps', 3);
createWebhook({
tenantId: tenant.id,
targetUrl: 'https://hooks.example/x',
event: 'timestamp.completed',
});
reloadStore(ctx.storePath);
assert.equal(getTenant('persist-me')?.name, tenant.name);
assert.equal(getTenantByApiKey(apiKey)?.id, 'persist-me');
assert.equal(getUsage('persist-me').timestamps, 3);
assert.equal(getStore().webhooks.length, 1);
});
it('deleteWebhook removes by id', () => {
seedFreeTenant({ id: 'w' });
const hook = createWebhook({
tenantId: 'w',
targetUrl: 'https://hooks.example/w',
event: 'timestamp.completed',
});
assert.equal(deleteWebhook({ tenantId: 'w', hookId: hook.id }), true);
assert.equal(listWebhooksForTenant('w').length, 0);
});
});

55
test/unit/tokens.test.js Normal file
View file

@ -0,0 +1,55 @@
/**
* GATE 4 Token tests
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
issueSessionToken,
parseSessionToken,
generateApiKey,
isApiKey,
extractBearerToken,
} from '../../src/lib/tokens.js';
import { config } from '../../src/config.js';
describe('tokens', () => {
it('issues and parses session tokens', () => {
const token = issueSessionToken({
tenantId: 't1',
veraeToken: 'mock-jwt-user',
expiresAt: '2099-01-01T00:00:00Z',
});
assert.ok(token.startsWith('zmt_'));
const parsed = parseSessionToken(token);
assert.equal(parsed.tenantId, 't1');
assert.equal(parsed.veraeToken, 'mock-jwt-user');
});
it('rejects forged session tokens', () => {
const token = issueSessionToken({
tenantId: 't1',
veraeToken: 'secret',
});
const forged = token.slice(0, -4) + 'xxxx';
assert.equal(parseSessionToken(forged), null);
});
it('rejects tokens signed with wrong secret', () => {
const token = issueSessionToken({
tenantId: 't1',
veraeToken: 'secret',
});
const original = config.tokenSecret;
config.tokenSecret = 'other-secret';
assert.equal(parseSessionToken(token), null);
config.tokenSecret = original;
});
it('generates api keys and extracts bearer', () => {
const key = generateApiKey();
assert.ok(isApiKey(key));
assert.equal(extractBearerToken(`Bearer ${key}`), key);
assert.equal(extractBearerToken(undefined), null);
});
});

View file

@ -0,0 +1,64 @@
/**
* GATE 4 Mock Verae client lifecycle
*/
import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { veraeClient, clearMockJobs } from '../../src/clients/veraeClient.js';
import { config } from '../../src/config.js';
import { setDebugTestSink, createDebugger } from '../../src/debug/index.js';
describe('veraeClient mock', () => {
beforeEach(() => {
clearMockJobs();
// ensure mock mode (set at process start via env in gate script)
assert.equal(config.mockVerae, true, 'MOCK_VERAE must be true for unit tests');
});
it('create → wait → completed', async () => {
const login = await veraeClient.login({ username: 'u', password: 'p' });
assert.ok(login.token.startsWith('mock-jwt-'));
const { jobId } = await veraeClient.createTimestamp(login.token, {
data: 'hello',
hashAlg: 'SHA256',
});
assert.ok(jobId);
const status = await veraeClient.waitForJob(login.token, jobId, {
maxAttempts: 40,
intervalMs: 20,
});
assert.equal(status.status, 'completed');
assert.ok(status.result.startsWith('mock-cert-'));
});
it('verify accepts mock certificates', async () => {
const login = await veraeClient.login({ username: 'u', password: 'p' });
const { jobId } = await veraeClient.createTimestamp(login.token, { data: 'x' });
const status = await veraeClient.waitForJob(login.token, jobId, {
maxAttempts: 40,
intervalMs: 20,
});
const result = await veraeClient.verify(login.token, { certificate: status.result });
assert.equal(result.valid, true);
});
it('debug http logs do not include Authorization values', () => {
const lines = [];
setDebugTestSink((line) => lines.push(line));
process.env.DEBUG_VERAE = 'http';
const log = createDebugger('http');
log.debug('verae request', {
method: 'POST',
path: '/api/timestamp',
authorization: 'Bearer super-secret-token-value',
hasToken: true,
});
setDebugTestSink(null);
delete process.env.DEBUG_VERAE;
assert.equal(lines.length, 1);
assert.doesNotMatch(lines[0], /super-secret-token-value/);
assert.match(lines[0], /REDACTED|hasToken/);
});
});