Milestone 3: NATS wait (gate 9), hash lookup, zappier timestamp, NS1 tunnel
Wait-via-NATS returns completed or pending+jobId (GATE 9). Mock SHA256
idempotent register + GET /hashes/{sha256}. Zappier commercial edge has
POST /v1/timestamp and hash-lookup. GATE 12 smoke (signup + wait) passes.
NS1 NATS is 127.0.0.1:4222 on 70.88.205.138; SSH tunnel :14222. Local
nats-server -js used for isolated tests. Activate app adds Echo Text.
Learned: JetStream on NS1 is loopback-only; do not bind 4222 public.
This commit is contained in:
parent
10c663cc0c
commit
51ae79b75f
75 changed files with 1114 additions and 123 deletions
|
|
@ -3,7 +3,7 @@
|
|||
* @module clients/veraeClient
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { config } from '../config.js';
|
||||
import { AppError } from '../errors.js';
|
||||
import { createDebugger } from '../debug/logger.js';
|
||||
|
|
@ -11,6 +11,16 @@ import { createDebugger } from '../debug/logger.js';
|
|||
const log = createDebugger('http');
|
||||
|
||||
const mockJobs = new Map();
|
||||
/** @type {Map<string, { jobId: string, sha256: string, timestamp?: string, publicMetadata?: object, privateMetadata?: object }>} */
|
||||
const mockHashes = new Map();
|
||||
|
||||
/**
|
||||
* @param {string} data
|
||||
* @returns {string}
|
||||
*/
|
||||
export function sha256Hex(data) {
|
||||
return createHash('sha256').update(String(data), 'utf8').digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} ms
|
||||
|
|
@ -50,19 +60,36 @@ async function mockValidate(token) {
|
|||
};
|
||||
}
|
||||
|
||||
async function mockCreateTimestamp({ data, hashAlg }) {
|
||||
if (!data) {
|
||||
async function mockCreateTimestamp({ data, hashAlg, sha256, publicMetadata, privateMetadata }) {
|
||||
if (!data && !sha256) {
|
||||
throw new AppError('Invalid input data', { status: 400, code: 'VALIDATION_ERROR' });
|
||||
}
|
||||
const key = (sha256 || sha256Hex(data)).toLowerCase();
|
||||
const existing = mockHashes.get(key);
|
||||
if (existing) {
|
||||
const job = mockJobs.get(existing.jobId);
|
||||
return {
|
||||
jobId: existing.jobId,
|
||||
sha256: key,
|
||||
existing: true,
|
||||
timestamp: job?.completedAt ?? existing.timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
const jobId = randomUUID();
|
||||
mockJobs.set(jobId, {
|
||||
id: jobId,
|
||||
status: 'pending',
|
||||
createdAt: Date.now(),
|
||||
data,
|
||||
data: data ?? key,
|
||||
hashAlg: hashAlg ?? 'SHA256',
|
||||
sha256: key,
|
||||
publicMetadata: publicMetadata ?? {},
|
||||
privateMetadata: privateMetadata ?? {},
|
||||
});
|
||||
mockHashes.set(key, { jobId, sha256: key, publicMetadata, privateMetadata });
|
||||
|
||||
const delayMs = config.mockJobCompleteMs ?? 150;
|
||||
setTimeout(() => {
|
||||
const job = mockJobs.get(jobId);
|
||||
if (!job) return;
|
||||
|
|
@ -73,10 +100,14 @@ async function mockCreateTimestamp({ data, hashAlg }) {
|
|||
blockIndex: 42,
|
||||
timestamp: job.completedAt,
|
||||
certificate: job.result,
|
||||
sha256: key,
|
||||
publicMetadata: job.publicMetadata,
|
||||
};
|
||||
}, 150);
|
||||
const rec = mockHashes.get(key);
|
||||
if (rec) rec.timestamp = job.completedAt;
|
||||
}, delayMs);
|
||||
|
||||
return { jobId };
|
||||
return { jobId, sha256: key, existing: false };
|
||||
}
|
||||
|
||||
async function mockGetStatus(jobId) {
|
||||
|
|
@ -91,6 +122,24 @@ async function mockGetStatus(jobId) {
|
|||
completedAt: job.completedAt,
|
||||
metadata: job.metadata,
|
||||
error: job.error,
|
||||
sha256: job.sha256,
|
||||
};
|
||||
}
|
||||
|
||||
async function mockLookupHash(sha256) {
|
||||
const key = String(sha256 || '').toLowerCase();
|
||||
const rec = mockHashes.get(key);
|
||||
if (!rec) {
|
||||
throw new AppError('Hash not found', { status: 404, code: 'NOT_FOUND' });
|
||||
}
|
||||
const job = mockJobs.get(rec.jobId);
|
||||
return {
|
||||
sha256: key,
|
||||
exists: true,
|
||||
jobId: rec.jobId,
|
||||
timestamp: rec.timestamp ?? job?.completedAt,
|
||||
status: job?.status,
|
||||
publicMetadata: job?.publicMetadata ?? rec.publicMetadata ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +233,20 @@ export const veraeClient = {
|
|||
*/
|
||||
async createTimestamp(token, body) {
|
||||
if (config.mockVerae) return mockCreateTimestamp(body);
|
||||
return request('/api/timestamp', { method: 'POST', token, body });
|
||||
const liveBody = { data: body.data, hashAlg: body.hashAlg };
|
||||
return request('/api/timestamp', { method: 'POST', token, body: liveBody });
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @param {string} sha256
|
||||
*/
|
||||
async lookupHash(token, sha256) {
|
||||
if (config.mockVerae) return mockLookupHash(sha256);
|
||||
throw new AppError('Hash lookup is not on the live Verae OpenAPI', {
|
||||
status: 501,
|
||||
code: 'NOT_IMPLEMENTED',
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -284,4 +346,5 @@ export const veraeClient = {
|
|||
*/
|
||||
export function clearMockJobs() {
|
||||
mockJobs.clear();
|
||||
mockHashes.clear();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue