@@ -10,6 +10,7 @@ import {
1010 WebSocketClassProvider ,
1111 WebsocketReverseMappingTransport ,
1212 WebSocketTransport ,
13+ type WebSocketTransportConfig ,
1314} from '../../src/transports'
1415import { SingleNumberResultResponse , sleep } from '../../src/util'
1516import { mockWebSocketProvider , runAllUntilTime , TestAdapter } from '../../src/util/testing-utils'
@@ -65,6 +66,7 @@ const createAdapter = (
6566 context : EndpointContext < WebSocketTypes > ,
6667 ) => Promise < void > | void ,
6768 pongHandler ?: ( connection : WebSocket , data : Buffer ) => void ,
69+ buildersOverride ?: WebSocketTransportConfig < WebSocketTypes > [ 'builders' ] ,
6870) : Adapter => {
6971 const websocketTransport = new WebSocketTransport < WebSocketTypes > ( {
7072 url : ( ) => ENDPOINT_URL ,
@@ -99,7 +101,7 @@ const createAdapter = (
99101 heartbeat : heartbeatHandler ,
100102 pong : pongHandler ,
101103 } ,
102- builders : {
104+ builders : buildersOverride ?? {
103105 subscribeMessage : ( params ) => `S:${ params . base } /${ params . quote } ` ,
104106 unsubscribeMessage : ( params ) => ( {
105107 request : 'unsubscribe' ,
@@ -200,6 +202,81 @@ test.serial('connects to websocket, subscribes, gets message, unsubscribes', asy
200202 await t . context . clock . runToLastAsync ( )
201203} )
202204
205+ test . serial ( 'filters out undefined subscribe and unsubscribe messages from builders' , async ( t ) => {
206+ mockWebSocketProvider ( WebSocketClassProvider )
207+ const mockWsServer = new Server ( ENDPOINT_URL , { mock : false } )
208+ const messagesReceived : string [ ] = [ ]
209+
210+ mockWsServer . on ( 'connection' , ( socket ) => {
211+ socket . on ( 'message' , ( data ) => {
212+ messagesReceived . push ( typeof data === 'string' ? data : data . toString ( ) )
213+ socket . send (
214+ JSON . stringify ( {
215+ pair : `ETH/DOGE` ,
216+ value : price ,
217+ } ) ,
218+ )
219+ } )
220+ } )
221+
222+ const adapter = createAdapter (
223+ {
224+ WS_SUBSCRIPTION_UNRESPONSIVE_TTL : 180_000 ,
225+ WS_SUBSCRIPTION_TTL : 60_000 ,
226+ } ,
227+ undefined ,
228+ undefined ,
229+ {
230+ subscribeMessage : ( params ) => {
231+ return params . base === 'SKIP' ? undefined : `S:${ params . base } /${ params . quote } `
232+ } ,
233+ unsubscribeMessage : ( params ) => {
234+ return params . quote === 'SKIPUNSUB' ? undefined : `U:${ params . base } /${ params . quote } `
235+ } ,
236+ } ,
237+ )
238+
239+ const testAdapter = await TestAdapter . startWithMockedCache ( adapter , t . context )
240+
241+ // ETH first so the socket gets a subscribe + provider message before SKIP (no subscribe payload).
242+ await testAdapter . request ( { base : 'ETH' , quote : 'DOGE' } )
243+ await runAllUntilTime ( t . context . clock , BACKGROUND_EXECUTE_MS_WS + 200 )
244+ await testAdapter . request ( { base : 'SKIP' , quote : 'DOGE' } )
245+ await runAllUntilTime ( t . context . clock , BACKGROUND_EXECUTE_MS_WS + 200 )
246+ await testAdapter . request ( { base : 'ETH' , quote : 'SKIPUNSUB' } )
247+ await runAllUntilTime ( t . context . clock , BACKGROUND_EXECUTE_MS_WS + 200 )
248+
249+ t . false (
250+ messagesReceived . includes ( 'S:SKIP/DOGE' ) ,
251+ 'no subscribe payload for feeds where subscribeMessage returns undefined' ,
252+ )
253+ t . false (
254+ messagesReceived . some ( ( m ) => m === 'undefined' ) ,
255+ 'undefined must not be serialized and sent' ,
256+ )
257+ t . true ( messagesReceived . includes ( 'S:ETH/DOGE' ) )
258+ t . true ( messagesReceived . includes ( 'S:ETH/SKIPUNSUB' ) )
259+
260+ await runAllUntilTime (
261+ t . context . clock ,
262+ adapter . config . settings . WS_SUBSCRIPTION_TTL + BACKGROUND_EXECUTE_MS_WS * 3 + 100 ,
263+ )
264+
265+ const unsubscribePayloads = messagesReceived . filter ( ( m ) => m . startsWith ( 'U:' ) )
266+ t . false (
267+ unsubscribePayloads . some ( ( m ) => m === 'U:ETH/SKIPUNSUB' ) ,
268+ 'unsubscribe builder returned undefined for SKIPUNSUB quote — must not send that payload' ,
269+ )
270+ t . true (
271+ unsubscribePayloads . includes ( 'U:ETH/DOGE' ) ,
272+ 'defined unsubscribe payloads are still sent for stale subs that map to a message' ,
273+ )
274+
275+ testAdapter . api . close ( )
276+ mockWsServer . close ( )
277+ await t . context . clock . runToLastAsync ( )
278+ } )
279+
203280test . serial ( 'reconnects when url changed' , async ( t ) => {
204281 // Mock WS
205282 mockWebSocketProvider ( WebSocketClassProvider )
0 commit comments