Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m12s
CI/CD Pipeline / Security Scanning (push) Successful in 2m21s
CI/CD Pipeline / Lint and Format (push) Failing after 36s
CI/CD Pipeline / Terraform Validation (push) Failing after 22s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 25s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 23s
Validation / validate-genesis (push) Successful in 26s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 9s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m15s
Validation / validate-documentation (push) Failing after 15s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 26s
Verify Deployment / Verify Deployment (push) Failing after 56s
Add operator settlement terminal UI/API, swift-listener service, compliance idempotency gates, GRU reserve dashboards, and @dbis/integration-foundation for typed HYBX/ISO adapter contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
262 lines
7.7 KiB
TypeScript
262 lines
7.7 KiB
TypeScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { MockHybxClient } from './hybx/MockHybxClient';
|
|
import { loadHybxConfig } from './hybx/config';
|
|
import { MockComplianceDecisionEngine } from './compliance/ComplianceDecisionEngine';
|
|
import { createAuditEvent, redactMetadata } from './audit/AuditEvent';
|
|
import { deriveIdempotencyKey } from './resilience/idempotency';
|
|
import {
|
|
validateCanonicalFinancialEvent,
|
|
type CanonicalFinancialEvent,
|
|
} from './iso20022/CanonicalFinancialEvent';
|
|
import { assertTenantIsolation } from './entities/RegulatedEntity';
|
|
import { verifyWebhookSignature, computeWebhookSignature } from './webhooks/verifySignature';
|
|
import { HttpHybxClient } from './hybx/HttpHybxClient';
|
|
import { MockReconciliationEngine } from './reconciliation/ReconciliationStatus';
|
|
import { CircuitBreaker } from './resilience/circuitBreaker';
|
|
|
|
describe('MockHybxClient', () => {
|
|
it('returns typed mock payment response', async () => {
|
|
const client = new MockHybxClient('sandbox');
|
|
const res = await client.initiatePayment({
|
|
idempotencyKey: 'key-001',
|
|
entityId: 'ent-1',
|
|
tenantId: 'tenant-a',
|
|
amount: '100.00',
|
|
currency: 'USD',
|
|
debtorAccountRef: 'debtor-1',
|
|
creditorAccountRef: 'creditor-1',
|
|
});
|
|
assert.equal(res.status, 'accepted');
|
|
assert.ok(res.paymentId.startsWith('mock-pay-'));
|
|
assert.ok(res.uetr);
|
|
});
|
|
});
|
|
|
|
describe('loadHybxConfig', () => {
|
|
it('loads sandbox placeholders', () => {
|
|
const cfg = loadHybxConfig({
|
|
testMode: true,
|
|
env: {
|
|
HYBX_ENVIRONMENT: 'sandbox',
|
|
HYBX_BASE_URL: 'https://hybx-sandbox.example.invalid',
|
|
HYBX_CLIENT_ID: 'placeholder',
|
|
HYBX_CLIENT_SECRET: 'placeholder',
|
|
HYBX_API_KEY: 'placeholder',
|
|
HYBX_WEBHOOK_SECRET: 'placeholder',
|
|
},
|
|
});
|
|
assert.equal(cfg.environment, 'sandbox');
|
|
});
|
|
|
|
it('rejects production in test mode', () => {
|
|
assert.throws(() =>
|
|
loadHybxConfig({
|
|
testMode: true,
|
|
env: { HYBX_ENVIRONMENT: 'production', HYBX_BASE_URL: 'https://x' },
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('MockComplianceDecisionEngine', () => {
|
|
it('blocks sanctions hits', async () => {
|
|
const engine = new MockComplianceDecisionEngine();
|
|
const res = await engine.evaluateTransaction({
|
|
entityId: 'e1',
|
|
tenantId: 't1',
|
|
amount: '10',
|
|
currency: 'USD',
|
|
transactionType: 'payment',
|
|
riskHints: ['sanctions_hit'],
|
|
});
|
|
assert.equal(res.decision, 'block');
|
|
});
|
|
|
|
it('reviews PEP matches', async () => {
|
|
const engine = new MockComplianceDecisionEngine();
|
|
const res = await engine.evaluateTransaction({
|
|
entityId: 'e1',
|
|
tenantId: 't1',
|
|
amount: '10',
|
|
currency: 'USD',
|
|
transactionType: 'payment',
|
|
riskHints: ['pep_match'],
|
|
});
|
|
assert.equal(res.decision, 'review');
|
|
});
|
|
});
|
|
|
|
describe('audit redaction', () => {
|
|
it('removes secret-like and PII-like fields', () => {
|
|
const redacted = redactMetadata({
|
|
api_key: 'sk-test-dummy',
|
|
email: 'user@example.com',
|
|
amount: '100',
|
|
});
|
|
assert.equal(redacted.api_key, '[REDACTED]');
|
|
assert.equal(redacted.email, '[REDACTED]');
|
|
assert.equal(redacted.amount, '100');
|
|
});
|
|
|
|
it('creates audit event with redacted metadata', () => {
|
|
const evt = createAuditEvent({
|
|
audit_event_id: 'a1',
|
|
actor_type: 'service',
|
|
actor_id: 'svc-1',
|
|
tenant_id: 't1',
|
|
entity_id: 'e1',
|
|
action: 'payment.initiated',
|
|
resource_type: 'payment',
|
|
resource_id: 'p1',
|
|
metadata: { client_secret: 'dummy-secret' },
|
|
});
|
|
assert.equal(evt.metadata_redacted.client_secret, '[REDACTED]');
|
|
});
|
|
});
|
|
|
|
describe('idempotency', () => {
|
|
it('returns stable keys for identical inputs', () => {
|
|
const input = {
|
|
tenantId: 't1',
|
|
entityId: 'e1',
|
|
operation: 'payment',
|
|
payload: { amount: '10' },
|
|
};
|
|
assert.equal(deriveIdempotencyKey(input), deriveIdempotencyKey(input));
|
|
});
|
|
|
|
it('returns distinct keys for distinct inputs', () => {
|
|
const a = deriveIdempotencyKey({
|
|
tenantId: 't1',
|
|
entityId: 'e1',
|
|
operation: 'payment',
|
|
});
|
|
const b = deriveIdempotencyKey({
|
|
tenantId: 't2',
|
|
entityId: 'e1',
|
|
operation: 'payment',
|
|
});
|
|
assert.notEqual(a, b);
|
|
});
|
|
});
|
|
|
|
describe('CanonicalFinancialEvent', () => {
|
|
const valid: CanonicalFinancialEvent = {
|
|
schema_version: '1.0',
|
|
message_id: 'msg-1',
|
|
end_to_end_id: 'e2e-1',
|
|
debtor: { id: 'd1' },
|
|
creditor: { id: 'c1' },
|
|
amount: '100.00',
|
|
currency: 'USD',
|
|
source_system: 'hybx',
|
|
target_system: 'omnl',
|
|
entity_id: 'e1',
|
|
tenant_id: 't1',
|
|
};
|
|
|
|
it('validates required fields', () => {
|
|
assert.equal(validateCanonicalFinancialEvent(valid).valid, true);
|
|
assert.equal(validateCanonicalFinancialEvent({}).valid, false);
|
|
});
|
|
});
|
|
|
|
describe('tenant isolation', () => {
|
|
it('throws on tenant mismatch', () => {
|
|
assert.throws(() => assertTenantIsolation('t1', 't2'));
|
|
});
|
|
});
|
|
|
|
describe('HttpHybxClient', () => {
|
|
it('refuses production environment', () => {
|
|
assert.throws(
|
|
() =>
|
|
new HttpHybxClient({
|
|
testMode: false,
|
|
config: {
|
|
environment: 'production',
|
|
baseUrl: 'https://hybx.example.invalid',
|
|
clientId: 'p',
|
|
clientSecret: 'p',
|
|
apiKey: 'p',
|
|
webhookSecret: 'p',
|
|
requestTimeoutMs: 1000,
|
|
maxRetries: 1,
|
|
},
|
|
}),
|
|
/refuses production/
|
|
);
|
|
});
|
|
|
|
it('parses webhook payload shape', () => {
|
|
const client = new HttpHybxClient({
|
|
testMode: true,
|
|
config: {
|
|
environment: 'sandbox',
|
|
baseUrl: 'https://hybx-sandbox.example.invalid',
|
|
clientId: 'placeholder',
|
|
clientSecret: 'placeholder',
|
|
apiKey: 'placeholder',
|
|
webhookSecret: 'placeholder',
|
|
requestTimeoutMs: 1000,
|
|
maxRetries: 1,
|
|
},
|
|
});
|
|
const parsed = client.parseWebhookPayload(
|
|
JSON.stringify({ eventType: 'settlement.completed', eventId: 'evt-1', data: { amount: '100' } })
|
|
);
|
|
assert.equal(parsed.eventType, 'settlement.completed');
|
|
assert.equal(parsed.eventId, 'evt-1');
|
|
assert.equal((parsed.data as { amount: string }).amount, '100');
|
|
});
|
|
});
|
|
|
|
describe('MockReconciliationEngine', () => {
|
|
it('detects missing fineract refs', async () => {
|
|
const engine = new MockReconciliationEngine();
|
|
const snap = await engine.reconcileTripleState({
|
|
tenantId: 't1',
|
|
entityId: 'e1',
|
|
hybxRefs: ['ref-1'],
|
|
fineractRefs: [],
|
|
chainRefs: ['ref-1'],
|
|
});
|
|
assert.equal(snap.status, 'breaks_found');
|
|
assert.ok(snap.breaks.length >= 1);
|
|
});
|
|
});
|
|
|
|
describe('CircuitBreaker', () => {
|
|
it('opens after threshold failures', async () => {
|
|
const cb = new CircuitBreaker({ failureThreshold: 2, resetTimeoutMs: 60_000 });
|
|
const fail = () => cb.execute(async () => { throw new Error('fail'); });
|
|
await assert.rejects(fail);
|
|
await assert.rejects(fail);
|
|
assert.equal(cb.getState(), 'open');
|
|
});
|
|
});
|
|
|
|
describe('webhook verification', () => {
|
|
it('verifies HMAC signature', () => {
|
|
const secret = 'test-webhook-secret-dummy';
|
|
const payload = '{"event":"payment.settled"}';
|
|
const sig = computeWebhookSignature(secret, payload);
|
|
assert.equal(
|
|
verifyWebhookSignature({ secret, payload, signature: sig }),
|
|
true
|
|
);
|
|
assert.equal(
|
|
verifyWebhookSignature({ secret, payload, signature: 'bad' }),
|
|
false
|
|
);
|
|
});
|
|
|
|
it('verifies sha256= prefixed OMNL-style signatures', () => {
|
|
const secret = 'omnl-webhook-secret';
|
|
const payload = '{"event":"ReserveCommitted"}';
|
|
const sig = `sha256=${computeWebhookSignature(secret, payload)}`;
|
|
assert.equal(verifyWebhookSignature({ secret, payload, signature: sig }), true);
|
|
});
|
|
});
|