Some checks failed
deploy / deploy (push) Failing after 3m30s
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.
205 lines
6.6 KiB
JavaScript
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);
|
|
});
|