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  }