All checks were successful
deploy / deploy (push) Successful in 33s
Proof: npm test; npm run operator-dashboard:build; node --test 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: the solver relay websocket client can receive reentrant error events while closing, matching the ingest runtime failure pattern observed in production. Still fake: Notification emissions are limited to credited deposits, completed withdrawals, and completed trades with durable inventory movement; generic alert notification policy remains disabled.
173 lines
4.6 KiB
JavaScript
173 lines
4.6 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 client = await startSolverRelayWs({
|
|
apiKey: 'api-key',
|
|
wsUrl: 'wss://relay.example/ws',
|
|
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(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();
|
|
}
|
|
});
|