github.com/bitfinexcom/bitfinex-api-go@v0.0.0-20210608095005-9e0b26f200fb/tests/integration/v2/reconnect_test.go (about) 1 package tests 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "testing" 8 "time" 9 10 "github.com/bitfinexcom/bitfinex-api-go/pkg/models/common" 11 "github.com/bitfinexcom/bitfinex-api-go/pkg/models/ticker" 12 "github.com/bitfinexcom/bitfinex-api-go/v2/websocket" 13 ) 14 15 var ( 16 wsPort = 4001 17 wsService *TestWsService 18 apiRecv *listener 19 apiClient *websocket.Client 20 ) 21 22 func assertDisconnect(maxWait time.Duration, client *websocket.Client) error { 23 loops := 5 24 delay := maxWait / time.Duration(loops) 25 for i := 0; i < loops; i++ { 26 if !client.IsConnected() { 27 return nil 28 } 29 time.Sleep(delay) 30 } 31 return fmt.Errorf("peer did not disconnect in %s", maxWait.String()) 32 } 33 34 // convienence 35 func newTestParams(wsPort int) *websocket.Parameters { 36 p := websocket.NewDefaultParameters() 37 p.ShutdownTimeout = time.Second * 4 38 p.URL = fmt.Sprintf("ws://localhost:%d", wsPort) 39 p.AutoReconnect = true 40 p.CapacityPerConnection = 2000 41 p.HeartbeatTimeout = time.Millisecond * 10 42 p.ReconnectInterval = time.Millisecond * 500 // first reconnect is instant, won't need to wait on this 43 return p 44 } 45 46 func setup(t *testing.T, hbTimeout time.Duration, autoReconnect, auth bool) { 47 if wsService != nil { 48 wsService.Stop() 49 } 50 if apiClient != nil { 51 apiClient.Close() 52 } 53 54 time.Sleep(time.Millisecond * 250) 55 56 wsService = NewTestWsService(wsPort) 57 wsService.PublishOnConnect(`{"event":"info","version":2}`) 58 err := wsService.Start() 59 if err != nil { 60 t.Fatal(err) 61 } 62 63 // create client 64 params := newTestParams(wsPort) 65 params.HeartbeatTimeout = hbTimeout 66 params.AutoReconnect = autoReconnect 67 factory := websocket.NewWebsocketAsynchronousFactory(params) 68 nonce := &IncrementingNonceGenerator{} 69 apiClient = websocket.NewWithParamsAsyncFactoryNonce(params, factory, nonce) 70 if auth { 71 apiClient.Credentials("apiKey1", "apiSecret1") 72 } 73 74 // setup listener 75 // listener closes when apiClient is closed 76 apiRecv = newListener() 77 apiRecv.run(apiClient.Listen()) 78 79 // set ws options 80 err_con := apiClient.Connect() 81 if err_con != nil { 82 t.Fatal(err_con) 83 } 84 85 if err := wsService.WaitForClientCount(1); err != nil { 86 t.Fatal(err) 87 } 88 } 89 90 func TestReconnectResubscribeWithAuthBlah(t *testing.T) { 91 // create transport & nonce mocks 92 setup(t, time.Second*10, true, true) 93 94 // begin test 95 infoEv, err := apiRecv.nextInfoEvent() 96 if err != nil { 97 t.Fatal(err) 98 } 99 expInfoEv := websocket.InfoEvent{ 100 Version: 2, 101 } 102 assert(t, &expInfoEv, infoEv) 103 104 msg, err := wsService.WaitForMessage(0, 0) 105 if err != nil { 106 t.Fatal(err) 107 } 108 if `{"subId":"nonce1","event":"auth","apiKey":"apiKey1","authSig":"6e7e3ab737bac9d36b6c3170356c9483edb0079cb65a2afa81efa9a6b906e0c3aeb16b574a44073dff4c0f604adbdd7d","authPayload":"AUTHnonce1","authNonce":"nonce1"}` != msg { 109 t.Fatalf("[1] did not expect to receive msg: %s", msg) 110 } 111 wsService.Broadcast(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce1","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) 112 authEv, err := apiRecv.nextAuthEvent() 113 if err != nil { 114 t.Fatal(err) 115 } 116 expAuthEv := websocket.AuthEvent{ 117 Event: "auth", 118 Status: "OK", 119 ChanID: 0, 120 UserID: 1, 121 SubID: "nonce1", 122 AuthID: "valid-auth-guid", 123 } 124 assert(t, &expAuthEv, authEv) 125 126 // subscriptions 127 // trade sub 128 _, err = apiClient.SubscribeTrades(context.Background(), "tBTCUSD") 129 if err != nil { 130 t.Fatal(err) 131 } 132 msg, err = wsService.WaitForMessage(0, 1) 133 if err != nil { 134 t.Fatal(err) 135 } 136 if `{"subId":"nonce2","event":"subscribe","channel":"trades","symbol":"tBTCUSD"}` != msg { 137 t.Fatalf("[2] did not expect to receive: %s", msg) 138 } 139 wsService.Broadcast(`{"event":"subscribed","channel":"trades","chanId":5,"symbol":"tBTCUSD","subId":"nonce2","pair":"BTCUSD"}`) 140 tradeSub, err := apiRecv.nextSubscriptionEvent() 141 if err != nil { 142 t.Fatal(err) 143 } 144 expTradeSub := websocket.SubscribeEvent{ 145 Symbol: "tBTCUSD", 146 SubID: "nonce2", 147 Channel: "trades", 148 } 149 assert(t, &expTradeSub, tradeSub) 150 151 // book sub 152 _, err = apiClient.SubscribeBook(context.Background(), "tBTCUSD", common.Precision0, common.FrequencyRealtime, 25) 153 if err != nil { 154 t.Fatal(err) 155 } 156 msg, err = wsService.WaitForMessage(0, 2) 157 if err != nil { 158 t.Fatal(err) 159 } 160 if `{"subId":"nonce3","event":"subscribe","channel":"book","symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25"}` != msg { 161 t.Fatalf("[3] did not expect to receive: %s", msg) 162 } 163 wsService.Broadcast(`{"event":"subscribed","channel":"book","chanId":8,"symbol":"tBTCUSD","subId":"nonce3","pair":"BTCUSD","prec":"P0","freq":"F0","len":"25"}`) 164 bookSub, err := apiRecv.nextSubscriptionEvent() 165 if err != nil { 166 t.Fatal(err) 167 } 168 expBookSub := websocket.SubscribeEvent{ 169 Symbol: "tBTCUSD", 170 SubID: "nonce3", 171 Channel: "book", 172 Frequency: string(common.FrequencyRealtime), 173 Precision: string(common.Precision0), 174 } 175 assert(t, &expBookSub, bookSub) 176 // abrupt disconnect 177 wsService.Stop() 178 179 now := time.Now() 180 // wait for client disconnect to start reconnect looping 181 err = assertDisconnect(time.Second*20, apiClient) 182 if err != nil { 183 t.Fatal(err) 184 } 185 // nolint:megacheck 186 diff := time.Now().Sub(now) 187 t.Logf("client disconnect detected in %s", diff.String()) 188 // recreate service 189 wsService = NewTestWsService(wsPort) 190 // fresh service, no clients 191 if wsService.TotalClientCount() != 0 { 192 t.Fatalf("total client count %d, expected non-zero", wsService.TotalClientCount()) 193 } 194 err_ws := wsService.Start() 195 if err_ws != nil { 196 t.Fatal(err_ws) 197 } 198 if err := wsService.WaitForClientCount(1); err != nil { 199 t.Fatal(err) 200 } 201 wsService.Broadcast(`{"event":"info","version":2}`) 202 infoEv, err = apiRecv.nextInfoEvent() 203 if err != nil { 204 t.Fatal(err) 205 } 206 assert(t, &expInfoEv, infoEv) 207 208 // assert authentication again 209 msg, err = wsService.WaitForMessage(0, 0) 210 if err != nil { 211 t.Fatal(err) 212 } 213 if `{"subId":"nonce4","event":"auth","apiKey":"apiKey1","authSig":"3e424670c0fa4dcb293eea38b9fe62cca49cacc595da01a493d6b9328517a5c940b22141fecf16f653c2662b298238f4","authPayload":"AUTHnonce4","authNonce":"nonce4"}` != msg { 214 t.Fatalf("[4] did not expect to receive msg: %s", msg) 215 } 216 wsService.Broadcast(`{"event":"auth","status":"OK","chanId":0,"userId":1,"subId":"nonce4","auth_id":"valid-auth-guid","caps":{"orders":{"read":1,"write":0},"account":{"read":1,"write":0},"funding":{"read":1,"write":0},"history":{"read":1,"write":0},"wallets":{"read":1,"write":0},"withdraw":{"read":0,"write":0},"positions":{"read":1,"write":0}}}`) 217 authEv, err = apiRecv.nextAuthEvent() 218 if err != nil { 219 t.Fatal(err) 220 } 221 expAuthEv = websocket.AuthEvent{ 222 Event: "auth", 223 Status: "OK", 224 ChanID: 0, 225 UserID: 1, 226 SubID: "nonce4", 227 AuthID: "valid-auth-guid", 228 } 229 assert(t, &expAuthEv, authEv) 230 231 // ensure client automatically resubscribes 232 msg, err = wsService.WaitForMessage(0, 1) 233 if err != nil { 234 t.Fatal(err) 235 } 236 if `{"subId":"nonce5","event":"subscribe","channel":"trades","symbol":"tBTCUSD"}` != msg { 237 t.Fatalf("[6] did not expect to receive: %s", msg) 238 } 239 wsService.Broadcast(`{"event":"subscribed","channel":"trades","chanId":5,"symbol":"tBTCUSD","subId":"nonce5","pair":"BTCUSD"}`) 240 tradeSub, err = apiRecv.nextSubscriptionEvent() 241 if err != nil { 242 t.Fatal(err) 243 } 244 expTradeSub = websocket.SubscribeEvent{ 245 Symbol: "tBTCUSD", 246 SubID: "nonce5", 247 Channel: "trades", 248 } 249 assert(t, &expTradeSub, tradeSub) 250 msg, err = wsService.WaitForMessage(0, 2) 251 if err != nil { 252 t.Fatal(err) 253 } 254 if `{"subId":"nonce6","event":"subscribe","channel":"book","symbol":"tBTCUSD","prec":"P0","freq":"F0","len":"25"}` != msg { 255 t.Fatalf("[5] did not expect to receive: %s", msg) 256 } 257 wsService.Broadcast(`{"event":"subscribed","channel":"book","chanId":8,"symbol":"tBTCUSD","subId":"nonce6","pair":"BTCUSD","prec":"P0","freq":"F0","len":"25"}`) 258 bookSub, err = apiRecv.nextSubscriptionEvent() 259 if err != nil { 260 t.Fatal(err) 261 } 262 expBookSub = websocket.SubscribeEvent{ 263 Symbol: "tBTCUSD", 264 SubID: "nonce6", 265 Channel: "book", 266 Frequency: string(common.FrequencyRealtime), 267 Precision: string(common.Precision0), 268 Len: "25", 269 } 270 assert(t, &expBookSub, bookSub) 271 // API client thinks it's connected 272 if !apiClient.IsConnected() { 273 t.Fatal("not reconnected to websocket") 274 } 275 } 276 277 func TestHeartbeatTimeoutNoReconnectBlah(t *testing.T) { 278 // create transport & nonce mocks 279 setup(t, time.Second, false, false) 280 281 // begin test 282 msg, err := apiRecv.nextInfoEvent() 283 if err != nil { 284 t.Fatal(err) 285 } 286 infoEv := websocket.InfoEvent{ 287 Version: 2, 288 } 289 assert(t, &infoEv, msg) 290 291 _, err = apiClient.SubscribeTicker(context.Background(), "tBTCUSD") 292 if err != nil { 293 t.Fatal(err) 294 } 295 wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) 296 297 // expect timeout channel heartbeat 298 time.Sleep(time.Second * 2) 299 300 if apiClient.IsConnected() { 301 t.Fatal("API client still connected, expected heartbeat disconnect") 302 } 303 } 304 305 // also tests resubscribes 306 func TestHeartbeatTimeoutReconnectBlah(t *testing.T) { 307 // create transport & nonce mocks 308 setup(t, time.Second, true, false) 309 310 // begin test 311 // info msg automatically sends 312 msg, err := apiRecv.nextInfoEvent() 313 if err != nil { 314 t.Fatal(err) 315 } 316 infoEv := websocket.InfoEvent{ 317 Version: 2, 318 } 319 assert(t, &infoEv, msg) 320 321 // use ticker sub to check for reconnect 322 _, err = apiClient.SubscribeTicker(context.Background(), "tBTCUSD") 323 if err != nil { 324 t.Fatal(err) 325 } 326 m, err := wsService.WaitForMessage(0, 0) 327 if err != nil { 328 t.Fatal(err) 329 } 330 if `{"subId":"nonce1","event":"subscribe","channel":"ticker","symbol":"tBTCUSD"}` != m { 331 t.Fatalf("[1] did not expect to receive: %s", m) 332 } 333 wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) 334 tickerSub, err := apiRecv.nextSubscriptionEvent() 335 if err != nil { 336 t.Fatal(err) 337 } 338 expTickerSub := websocket.SubscribeEvent{ 339 Symbol: "tBTCUSD", 340 SubID: "nonce1", 341 Channel: "ticker", 342 } 343 assert(t, &expTickerSub, tickerSub) 344 // expect timeout channel heartbeat 345 time.Sleep(time.Second * 2) 346 wsService.Broadcast(`{"event":"info","version":2}`) 347 348 // begin test 349 // info msg automatically sends 350 _, err = apiRecv.nextInfoEvent() 351 if err != nil { 352 t.Fatal(err) 353 } 354 355 // check reconnect subscriptions 356 m, err = wsService.WaitForMessage(0, 0) 357 if err != nil { 358 t.Fatal(err) 359 } 360 if `{"subId":"nonce2","event":"subscribe","channel":"ticker","symbol":"tBTCUSD"}` != m { 361 t.Fatalf("[2] did not expect to receive: %s", m) 362 } 363 wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce2","pair":"BTCUSD"}`) 364 tickerSub, err = apiRecv.nextSubscriptionEvent() 365 if err != nil { 366 t.Fatal(err) 367 } 368 expTickerSub = websocket.SubscribeEvent{ 369 Symbol: "tBTCUSD", 370 SubID: "nonce2", 371 Channel: "ticker", 372 } 373 assert(t, &expTickerSub, tickerSub) 374 } 375 376 func TestHeartbeatNoTimeoutDataBlah(t *testing.T) { 377 // create transport & nonce mocks 378 setup(t, time.Second, true, false) 379 380 // begin test 381 // info msg automatically sends 382 msg, err := apiRecv.nextInfoEvent() 383 if err != nil { 384 t.Fatal(err) 385 } 386 infoEv := websocket.InfoEvent{ 387 Version: 2, 388 } 389 assert(t, &infoEv, msg) 390 391 // use ticker sub to check for reconnect 392 _, err = apiClient.SubscribeTicker(context.Background(), "tBTCUSD") 393 if err != nil { 394 t.Fatal(err) 395 } 396 m, err := wsService.WaitForMessage(0, 0) 397 if err != nil { 398 t.Fatal(err) 399 } 400 if `{"subId":"nonce1","event":"subscribe","channel":"ticker","symbol":"tBTCUSD"}` != m { 401 t.Fatalf("[1] did not expect to receive: %s", m) 402 } 403 wsService.Broadcast(`{"event":"subscribed","channel":"ticker","chanId":5,"symbol":"tBTCUSD","subId":"nonce1","pair":"BTCUSD"}`) 404 tickerSub, err := apiRecv.nextSubscriptionEvent() 405 if err != nil { 406 t.Fatal(err) 407 } 408 expTickerSub := websocket.SubscribeEvent{ 409 Symbol: "tBTCUSD", 410 SubID: "nonce1", 411 Channel: "ticker", 412 } 413 assert(t, &expTickerSub, tickerSub) 414 415 // would normally timeout here, but we can publish data to prevent 416 // the 1 second timeout 417 for i := 0; i < 8; i++ { 418 wsService.Broadcast(`[5,[14957,68.17328796,14958,55.29588132,-659,-0.0422,14971,53723.08813995,16494,14454]]`) 419 time.Sleep(time.Millisecond * 250) 420 } 421 422 tick, err := apiRecv.nextTick() 423 if err != nil { 424 log.Fatal(err) 425 } 426 expTicker := ticker.Ticker{ 427 Symbol: "tBTCUSD", 428 Bid: 14957, 429 BidSize: 68.17328796, 430 Ask: 14958, 431 AskSize: 55.29588132, 432 DailyChange: -659, 433 DailyChangePerc: -0.0422, 434 LastPrice: 14971, 435 Volume: 53723.08813995, 436 High: 16494, 437 Low: 14454, 438 } 439 assert(t, &expTicker, tick) 440 441 if !apiClient.IsConnected() { 442 t.Fatal("expected client connected, client has disconnected") 443 } 444 }