unrip/test/solver-relay-ws.test.mjs
philipp 8641c60ab7
All checks were successful
deploy / deploy (push) Successful in 33s
Suppress duplicate relay websocket close errors
Proof: npm test; npm run operator-dashboard:build; node --test test/near-intents-ws.test.mjs test/solver-relay-ws.test.mjs; PYTHONPATH=. python3 test/bootstrap_script_static_test.py; 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: nested error events emitted during WebSocket close are the same close attempt, not independent evidence that should be logged twice.

Still fake: Notification emissions are limited to credited deposits, completed withdrawals, and completed trades with durable inventory movement; generic alert notification policy remains disabled.
2026-04-16 14:36:18 +02:00

181 lines
4.8 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { startSolverRelayWs } from '../src/venues/near-intents/solver-relay-ws.mjs';
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function installMockWebSocket() {
const original = globalThis.WebSocket;
const instances = [];
class MockWebSocket {
static CONNECTING = 0;
static OPEN = 1;
static CLOSING = 2;
static CLOSED = 3;
constructor(url, options) {
this.url = url;
this.options = options;
this.readyState = MockWebSocket.CONNECTING;
this.sent = [];
this.listeners = new Map();
this.closeCalls = 0;
this.emitErrorDuringClose = false;
instances.push(this);
}
addEventListener(type, listener) {
const existing = this.listeners.get(type) || [];
existing.push(listener);
this.listeners.set(type, existing);
}
send(payload) {
this.sent.push(payload);
}
close() {
this.closeCalls += 1;
if (this.readyState === MockWebSocket.CLOSED) return;
if (this.emitErrorDuringClose) {
this.emit('error', new Error('socket failed while closing'));
}
this.readyState = MockWebSocket.CLOSED;
this.emit('close', {});
}
open() {
this.readyState = MockWebSocket.OPEN;
this.emit('open', {});
}
error(error = new Error('socket failed')) {
this.emit('error', error);
}
message(payload) {
this.emit('message', { data: JSON.stringify(payload) });
}
emit(type, event) {
for (const listener of this.listeners.get(type) || []) listener(event);
}
}
globalThis.WebSocket = MockWebSocket;
return {
instances,
restore() {
globalThis.WebSocket = original;
},
};
}
test('solver relay request timeout includes time spent waiting for websocket connection', async () => {
const mock = installMockWebSocket();
const client = await startSolverRelayWs({
apiKey: 'api-key',
wsUrl: 'wss://relay.example/ws',
reconnectDelayMs: 1000,
});
try {
await assert.rejects(
client.request('quote_response', [], { timeoutMs: 5 }),
/quote_response timed out/,
);
assert.equal(mock.instances[0].sent.length, 0);
} finally {
client.close();
mock.restore();
}
});
test('solver relay reconnects after websocket error even when no close event is emitted by the runtime', async () => {
const mock = installMockWebSocket();
const client = await startSolverRelayWs({
apiKey: 'api-key',
wsUrl: 'wss://relay.example/ws',
reconnectDelayMs: 1,
});
try {
assert.equal(mock.instances.length, 1);
mock.instances[0].error(new Error('network failed'));
await delay(10);
assert.equal(client.getState().connected, false);
assert.ok(client.getState().reconnect_count >= 2);
assert.ok(mock.instances.length >= 2);
} finally {
client.close();
mock.restore();
}
});
test('solver relay websocket close is reentrant-safe when close emits an error', async () => {
const mock = installMockWebSocket();
const errors = [];
const logger = {
error: (event) => errors.push(event),
info: () => {},
warn: () => {},
};
const client = await startSolverRelayWs({
apiKey: 'api-key',
wsUrl: 'wss://relay.example/ws',
logger,
reconnectDelayMs: 1,
});
try {
assert.equal(mock.instances.length, 1);
mock.instances[0].open();
mock.instances[0].emitErrorDuringClose = true;
assert.doesNotThrow(() => {
mock.instances[0].error(new Error('network failed'));
});
await delay(10);
assert.equal(mock.instances[0].closeCalls, 1);
assert.equal(errors.filter((event) => event === 'socket_error').length, 1);
assert.equal(client.getState().connected, false);
assert.ok(client.getState().reconnect_count >= 2);
assert.ok(mock.instances.length >= 2);
} finally {
client.close();
mock.restore();
}
});
test('solver relay sends queued request after connection and resolves rpc result', async () => {
const mock = installMockWebSocket();
const client = await startSolverRelayWs({
apiKey: 'api-key',
wsUrl: 'wss://relay.example/ws',
reconnectDelayMs: 1000,
subscriptions: ['quote_status'],
});
try {
const responsePromise = client.request('quote_response', [{ quote_id: 'quote-1' }], { timeoutMs: 50 });
mock.instances[0].open();
assert.equal(mock.instances[0].sent.length, 2);
const sent = mock.instances[0].sent.map((entry) => JSON.parse(entry));
const request = sent.find((entry) => entry.method === 'quote_response');
assert.ok(request);
mock.instances[0].message({ id: request.id, result: 'OK' });
await assert.doesNotReject(responsePromise);
assert.equal(await responsePromise, 'OK');
} finally {
client.close();
mock.restore();
}
});