unrip/test/notification-layer.test.mjs
philipp c5a214ce06
Some checks failed
deploy / deploy (push) Failing after 3m30s
Notify on durable fund and trade outcomes
Proof: npm test; npm run operator-dashboard:build; PYTHONPATH=. python3 test/render_release_manifest_test.py; PYTHONPATH=. python3 test/repo_deployments_test.py; PYTHONPATH=. python3 test/ntfy_manifest_test.py; kubectl kustomize deploy/k8s/base.

Assumptions: notifications should be emitted by history-writer after durable writes and outcome refreshes, and only for credited deposits, completed withdrawals, and completed trades with linked inventory movement evidence.

Still fake: Generic alert notification policy is not re-enabled; withdrawal submitted notifications are not emitted; old historical outcomes are not backfilled as notifications; fee-complete realized PnL is still unavailable.
2026-04-16 14:23:29 +02:00

205 lines
6.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildIntentRequestOutcomeNotification,
buildLiquidityActionNotification,
buildQuoteOutcomeNotification,
createNotificationDispatcher,
hasProvenSettlement,
observedAtOrAfter,
} from '../src/core/notification-layer.mjs';
const BTC = { assetId: 'nep141:btc.omft.near', symbol: 'BTC', decimals: 8 };
const EURE = { assetId: 'nep141:eure.omft.near', symbol: 'EURe', decimals: 18 };
const config = {
tradingBtc: BTC,
tradingEure: EURE,
assetRegistry: new Map([
[BTC.assetId, BTC],
[EURE.assetId, EURE],
]),
};
test('liquidity notifications only emit credited deposits and completed withdrawals', () => {
assert.equal(buildLiquidityActionNotification({
config,
event: {
payload: {
action_type: 'deposit_status_observed',
status: 'PENDING',
chain: 'eth:100',
asset_id: EURE.assetId,
details: { amount: '5000000000000000000', tx_hash: 'deposit-tx-1' },
},
},
}), null);
const deposit = buildLiquidityActionNotification({
config,
event: {
payload: {
action_type: 'deposit_status_observed',
status: 'CREDITED',
chain: 'eth:100',
asset_id: EURE.assetId,
details: { amount: '5000000000000000000', tx_hash: 'deposit-tx-1' },
},
},
});
assert.equal(deposit.notification_type, 'deposit_credited');
assert.match(deposit.message, /Deposit credited: 5 EURe/);
assert.match(deposit.message, /tx=deposit-tx-1/);
assert.equal(buildLiquidityActionNotification({
config,
event: {
payload: {
action_type: 'withdrawal_status_changed',
status: 'SUBMITTED',
chain: 'btc:mainnet',
asset_id: BTC.assetId,
details: { amount: '100000', withdrawal_hash: 'withdrawal-1' },
},
},
}), null);
const withdrawal = buildLiquidityActionNotification({
config,
event: {
payload: {
action_type: 'withdrawal_status_changed',
status: 'COMPLETED',
chain: 'btc:mainnet',
asset_id: BTC.assetId,
details: { amount: '100000', withdrawal_hash: 'withdrawal-1', transfer_tx_hash: 'btc-tx-1' },
},
},
});
assert.equal(withdrawal.notification_type, 'withdrawal_completed');
assert.match(withdrawal.message, /Withdrawal completed: 0.001 BTC/);
assert.match(withdrawal.message, /transfer=btc-tx-1/);
});
test('trade notifications require completed outcome with durable settlement evidence', () => {
const submitted = {
quote_id: 'quote-submitted',
outcome_status: 'submitted',
attribution_status: 'unattributed',
attributed_inventory_delta: null,
};
assert.equal(hasProvenSettlement(submitted), false);
assert.equal(buildQuoteOutcomeNotification({ record: submitted, config }), null);
const completedWithoutDelta = {
quote_id: 'quote-fake-completed',
outcome_status: 'completed',
attribution_status: 'unattributed',
attributed_inventory_delta: null,
};
assert.equal(hasProvenSettlement(completedWithoutDelta), false);
assert.equal(buildQuoteOutcomeNotification({ record: completedWithoutDelta, config }), null);
const completed = {
quote_id: 'quote-real',
decision_id: 'decision-real',
command_id: 'command-real',
gross_edge_pct: '0.49',
outcome_status: 'completed',
attribution_status: 'heuristic_match',
attributed_inventory_delta: {
inventory_id: 'inventory-after',
delta_units: {
[BTC.assetId]: '-12345',
[EURE.assetId]: '76500000000000000000',
},
},
};
const notification = buildQuoteOutcomeNotification({ record: completed, config });
assert.equal(notification.notification_type, 'trade_completed');
assert.match(notification.message, /Quote trade completed: quote-real/);
assert.match(notification.message, /-0.00012345 BTC/);
assert.match(notification.message, /\+76.5 EURe/);
});
test('own request notifications do not emit from relay acceptance alone', () => {
const accepted = {
request_id: 'request-accepted',
outcome_status: 'awaiting_settlement',
submission_status: 'accepted_by_relay',
relay_status: 'SETTLED',
attribution_status: 'unattributed',
attributed_inventory_delta: null,
};
assert.equal(buildIntentRequestOutcomeNotification({ record: accepted, config }), null);
const completed = {
request_id: 'request-completed',
idempotency_key: 'idem-1',
intent_hash: 'intent-hash-1',
outcome_status: 'completed',
attribution_status: 'linked_settlement',
attributed_inventory_delta: {
inventory_id: 'inventory-after-request',
delta_units: {
[EURE.assetId]: '-5000000000000000000',
[BTC.assetId]: '8000',
},
},
};
const notification = buildIntentRequestOutcomeNotification({ record: completed, config });
assert.equal(notification.notification_type, 'trade_completed');
assert.match(notification.message, /Own request completed: request-completed/);
assert.match(notification.message, /-5 EURe/);
assert.match(notification.message, /\+0.00008 BTC/);
});
test('notification dispatcher publishes each durable notification key once', async () => {
const calls = [];
const claimed = new Set();
const finished = [];
const dispatcher = createNotificationDispatcher({
client: {
isConfigured: () => true,
async publish(notification) {
calls.push(notification);
return { ok: true, status: 200, response: '{"id":"msg-1"}' };
},
},
store: {
async claim(notification) {
if (claimed.has(notification.notification_key)) return false;
claimed.add(notification.notification_key);
return true;
},
async finish(notification, result) {
finished.push({ notification, result });
},
},
});
const notification = {
notification_key: 'deposit_credited:eth:100:eure:tx-1:5000',
notification_type: 'deposit_credited',
source_kind: 'liquidity_action',
source_id: 'tx-1',
title: 'Deposit credited',
message: 'Deposit credited: 5 EURe',
priority: 'default',
tags: ['unrip', 'deposit'],
};
assert.equal((await dispatcher.publishOnce(notification)).delivered, true);
assert.deepEqual(await dispatcher.publishOnce(notification), {
ok: true,
skipped: true,
reason: 'notification_already_delivered',
});
assert.equal(calls.length, 1);
assert.equal(finished.length, 1);
});
test('outcome recency filter keeps startup refresh from sending historical notifications', () => {
assert.equal(observedAtOrAfter('2026-04-16T10:00:00.000Z', '2026-04-16T09:59:59.000Z'), true);
assert.equal(observedAtOrAfter('2026-04-16T09:00:00.000Z', '2026-04-16T09:59:59.000Z'), false);
});