verae-zapier-simulator/test/pipeline.test.js

134 lines
5.9 KiB
JavaScript

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { Simulator, sha256Hex } from '../src/pipeline.js';
describe('Zapier simulator pipeline', () => {
it('signup then connect then register+wait with metadata', async () => {
const s = new Simulator();
const sign = await s.run({ action: 'signup', input: { email: 'ada@example.com', plan: 'pro' } });
assert.ok(sign.response.apiKey.startsWith('zpk_'));
const conn = await s.run({ action: 'connect_zapier', input: { apiKey: sign.response.apiKey } });
assert.equal(conn.response.connected, true);
const wait = await s.run({
action: 'timestamp_and_wait',
includeAttached: true,
input: { data: 'hello from zapier', publicMetadata: { source: 'zap' } },
});
assert.equal(wait.response.status, 'completed');
assert.equal(wait.response.receipts[0].kind, 'seal');
assert.ok(wait.response.receipts.some((r) => r.kind === 'metadata-attach'));
assert.ok(wait.trace.some((e) => e.address === 'verae.zapier.jobs.watch'));
assert.ok(wait.trace.some((e) => e.address === 'verae.zapier.jobs.events'));
assert.ok(wait.trace.some((e) => e.address === 'verae.archive.put'));
assert.ok(wait.trace.some((e) => e.address === 'verae.archive.query'));
const zapNats = wait.trace.filter(
(e) => e.module === 'zapier-platform-app' && String(e.address).startsWith('verae.'),
);
assert.equal(zapNats.length, 0);
assert.equal(wait.monitor.findings.find((f) => f.id === 'zapier-no-nats').ok, true);
assert.equal(wait.monitor.findings.find((f) => f.id === 'chain-no-bytes').ok, true);
});
it('async create returns jobId; second register returns original seal', async () => {
const s = new Simulator();
const a = await s.run({ action: 'create_timestamp', input: { data: 'once' } });
assert.equal(a.response.status, 'accepted');
const b = await s.run({ action: 'create_timestamp', input: { data: 'once' } });
assert.equal(b.response.existing, true);
assert.equal(b.response.jobId, a.response.jobId);
});
it('central lookup finds itemized hash and misses a bulk leaf', async () => {
const s = new Simulator();
const reg = await s.run({ action: 'timestamp_and_wait', input: { data: 'solo' } });
const hit = await s.run({ action: 'hash_lookup', input: { sha256: reg.response.sha256 } });
assert.equal(hit.response.exists, true);
assert.equal(hit.response.itemizedOnMainChain, true);
const batch = await s.run({
action: 'batch_timestamp',
input: { items: 'invoice-1001\ninvoice-1002\ninvoice-1003' },
});
assert.equal(batch.response.leafCount, 3);
const leaf = batch.response.leaves[0];
assert.equal(leaf, sha256Hex('invoice-1001'));
const miss = await s.run({ action: 'hash_lookup', input: { sha256: leaf } });
assert.equal(miss.response.exists, false);
assert.ok(miss.monitor.suggestions.some((x) => x.id === 'tree-lookup-action'));
});
it('tree lookup proves a leaf that is only in the bulk summary', async () => {
const s = new Simulator();
const batch = await s.run({
action: 'batch_timestamp',
input: { items: ['alpha-doc', 'beta-doc', 'gamma-doc'] },
});
const leaf = batch.response.leaves[1];
const found = await s.run({ action: 'tree_lookup', input: { sha256: leaf } });
assert.equal(found.response.exists, true);
assert.equal(found.response.itemizedOnMainChain, false);
assert.equal(found.response.proofOk, true);
assert.equal(found.response.merkleRoot, batch.response.merkleRoot);
assert.ok(found.response.receipts.some((r) => r.kind === 'tree-leaf'));
assert.ok(found.response.receipts.some((r) => r.kind === 'seal' && r.of === 'merkleRoot'));
assert.ok(found.trace.some((e) => e.status === 'silent'));
assert.ok(found.trace.some((e) => String(e.address).startsWith('verae.archive.reply.')));
const bloomLeak = found.trace.filter((e) => e.status === 'silent' && String(e.address).includes('archive.reply'));
assert.equal(bloomLeak.length, 0);
});
it('402 quota is traced and suggested', async () => {
const s = new Simulator();
const r = await s.run({ action: 'create_timestamp', input: { data: 'x' }, faults: { failEdge402: true } });
assert.equal(r.response.status, 402);
assert.ok(r.monitor.suggestions.some((x) => x.id === '402-upgrade-url'));
});
it('NATS events drop then recovery retry', async () => {
const s = new Simulator();
const fail = await s.run({
action: 'timestamp_and_wait',
input: { data: 'drop' },
faults: { failNatsDropEvents: true },
});
assert.equal(fail.response.status, 'pending');
assert.ok(fail.monitor.suggestions.some((x) => x.id === 'wait-timeout' || x.id === 'add-retry'));
const ok = await s.run({
action: 'timestamp_and_wait',
input: { data: 'drop-recover' },
faults: { failNatsDropEvents: true, recover: true },
});
assert.equal(ok.response.status, 'completed');
assert.ok(ok.trace.some((e) => e.status === 'retry'));
});
it('chain delay is monitored', async () => {
const s = new Simulator();
const r = await s.run({
action: 'create_timestamp',
input: { data: 'slow' },
faults: { chainMs: 220 },
});
assert.equal(r.response.status, 'accepted');
assert.ok(r.monitor.findings.some((f) => f.kind === 'delay' && f.ok === false));
});
it('files never appear on the chain hop payload', async () => {
const s = new Simulator();
const r = await s.run({
action: 'timestamp_and_wait',
includeAttached: true,
input: {
data: 'with-file',
files: [{ path: 'a.txt', data: 'bytes' }],
},
});
const chain = r.trace.filter((e) => e.module === 'verae-chain-client');
for (const e of chain) {
assert.equal(e.payload?.files, undefined);
assert.equal(e.payload?.contentBase64, undefined);
}
assert.ok(r.response.files.length >= 1);
});
});