github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/wsclient/wsclient_test.go (about)

     1  // Copyright © 2021 Kaleido, Inc.
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  
    17  package wsclient
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"testing"
    25  
    26  	"github.com/gorilla/websocket"
    27  	"github.com/kaleido-io/firefly/internal/config"
    28  	"github.com/kaleido-io/firefly/internal/restclient"
    29  	"github.com/stretchr/testify/assert"
    30  )
    31  
    32  var utConfPrefix = config.NewPluginConfig("ws_unit_tests")
    33  
    34  func resetConf() {
    35  	config.Reset()
    36  	InitPrefix(utConfPrefix)
    37  }
    38  
    39  func TestWSClientE2E(t *testing.T) {
    40  
    41  	toServer, fromServer, url, close := NewTestWSServer(func(req *http.Request) {
    42  		assert.Equal(t, "/test/updated", req.URL.Path)
    43  	})
    44  	defer close()
    45  
    46  	afterConnect := func(ctx context.Context, w WSClient) error {
    47  		return w.Send(ctx, []byte(`after connect message`))
    48  	}
    49  
    50  	// Init from config
    51  	resetConf()
    52  	utConfPrefix.Set(restclient.HTTPConfigURL, url)
    53  	utConfPrefix.Set(WSConfigKeyPath, "/test")
    54  	wsClient, err := New(context.Background(), utConfPrefix, afterConnect)
    55  	assert.NoError(t, err)
    56  
    57  	//  Change the settings and connect
    58  	wsClient.SetURL(wsClient.URL() + "/updated")
    59  	err = wsClient.Connect()
    60  	assert.NoError(t, err)
    61  
    62  	// Receive the message automatically sent in afterConnect
    63  	message1 := <-toServer
    64  	assert.Equal(t, `after connect message`, message1)
    65  
    66  	// Tell the unit test server to send us a reply, and confirm it
    67  	fromServer <- `some data from server`
    68  	reply := <-wsClient.Receive()
    69  	assert.Equal(t, `some data from server`, string(reply))
    70  
    71  	// Send some data back
    72  	err = wsClient.Send(context.Background(), []byte(`some data to server`))
    73  	assert.NoError(t, err)
    74  
    75  	// Check the sevrer got it
    76  	message2 := <-toServer
    77  	assert.Equal(t, `some data to server`, message2)
    78  
    79  	// Close the client
    80  	wsClient.Close()
    81  
    82  }
    83  
    84  func TestWSClientBadURL(t *testing.T) {
    85  	resetConf()
    86  	utConfPrefix.Set(restclient.HTTPConfigURL, ":::")
    87  
    88  	_, err := New(context.Background(), utConfPrefix, nil)
    89  	assert.Regexp(t, "FF10162", err)
    90  }
    91  
    92  func TestHTTPToWSURLRemap(t *testing.T) {
    93  	resetConf()
    94  	utConfPrefix.Set(restclient.HTTPConfigURL, "http://test:12345")
    95  	utConfPrefix.Set(WSConfigKeyPath, "/websocket")
    96  
    97  	url, err := buildWSUrl(context.Background(), utConfPrefix)
    98  	assert.NoError(t, err)
    99  	assert.Equal(t, "ws://test:12345/websocket", url)
   100  }
   101  
   102  func TestHTTPSToWSSURLRemap(t *testing.T) {
   103  	resetConf()
   104  	utConfPrefix.Set(restclient.HTTPConfigURL, "https://test:12345")
   105  
   106  	url, err := buildWSUrl(context.Background(), utConfPrefix)
   107  	assert.NoError(t, err)
   108  	assert.Equal(t, "wss://test:12345", url)
   109  }
   110  
   111  func TestWSFailStartupHttp500(t *testing.T) {
   112  	svr := httptest.NewServer(http.HandlerFunc(
   113  		func(rw http.ResponseWriter, r *http.Request) {
   114  			assert.Equal(t, "custom value", r.Header.Get("Custom-Header"))
   115  			assert.Equal(t, "Basic dXNlcjpwYXNz", r.Header.Get("Authorization"))
   116  			rw.WriteHeader(500)
   117  			rw.Write([]byte(`{"error": "pop"}`))
   118  		},
   119  	))
   120  	defer svr.Close()
   121  
   122  	resetConf()
   123  	utConfPrefix.Set(restclient.HTTPConfigURL, fmt.Sprintf("ws://%s", svr.Listener.Addr()))
   124  	utConfPrefix.Set(restclient.HTTPConfigHeaders, map[string]interface{}{
   125  		"custom-header": "custom value",
   126  	})
   127  	utConfPrefix.Set(restclient.HTTPConfigAuthUsername, "user")
   128  	utConfPrefix.Set(restclient.HTTPConfigAuthPassword, "pass")
   129  	utConfPrefix.Set(restclient.HTTPConfigRetryInitDelay, 1)
   130  	utConfPrefix.Set(WSConfigKeyInitialConnectAttempts, 1)
   131  
   132  	w, _ := New(context.Background(), utConfPrefix, nil)
   133  	err := w.Connect()
   134  	assert.Regexp(t, "FF10161", err)
   135  }
   136  
   137  func TestWSFailStartupConnect(t *testing.T) {
   138  
   139  	svr := httptest.NewServer(http.HandlerFunc(
   140  		func(rw http.ResponseWriter, r *http.Request) {
   141  			rw.WriteHeader(500)
   142  		},
   143  	))
   144  	svr.Close()
   145  
   146  	resetConf()
   147  	utConfPrefix.Set(restclient.HTTPConfigURL, fmt.Sprintf("ws://%s", svr.Listener.Addr()))
   148  	utConfPrefix.Set(restclient.HTTPConfigRetryInitDelay, 1)
   149  	utConfPrefix.Set(WSConfigKeyInitialConnectAttempts, 1)
   150  
   151  	w, _ := New(context.Background(), utConfPrefix, nil)
   152  	err := w.Connect()
   153  	assert.Regexp(t, "FF10161", err)
   154  }
   155  
   156  func TestWSSendClosed(t *testing.T) {
   157  
   158  	resetConf()
   159  	utConfPrefix.Set(restclient.HTTPConfigURL, "ws://localhost:12345")
   160  
   161  	w, err := New(context.Background(), utConfPrefix, nil)
   162  	assert.NoError(t, err)
   163  	w.Close()
   164  
   165  	err = w.Send(context.Background(), []byte(`sent after close`))
   166  	assert.Regexp(t, "FF10160", err)
   167  }
   168  
   169  func TestWSSendCancelledContext(t *testing.T) {
   170  
   171  	w := &wsClient{
   172  		send:    make(chan []byte),
   173  		closing: make(chan struct{}),
   174  	}
   175  
   176  	ctx, cancel := context.WithCancel(context.Background())
   177  	cancel()
   178  	err := w.Send(ctx, []byte(`sent after close`))
   179  	assert.Regexp(t, "FF10159", err)
   180  }
   181  
   182  func TestWSConnectClosed(t *testing.T) {
   183  
   184  	w := &wsClient{
   185  		ctx:    context.Background(),
   186  		closed: true,
   187  	}
   188  
   189  	err := w.connect(false)
   190  	assert.Regexp(t, "FF10160", err)
   191  }
   192  
   193  func TestWSReadLoopSendFailure(t *testing.T) {
   194  
   195  	toServer, _, url, done := NewTestWSServer(nil)
   196  	defer done()
   197  
   198  	wsconn, _, err := websocket.DefaultDialer.Dial(url, nil)
   199  	wsconn.WriteJSON(map[string]string{"type": "listen", "topic": "topic1"})
   200  	assert.NoError(t, err)
   201  	<-toServer
   202  	wsconn.Close()
   203  	w := &wsClient{
   204  		ctx:      context.Background(),
   205  		closed:   true,
   206  		sendDone: make(chan []byte, 1),
   207  		wsconn:   wsconn,
   208  	}
   209  
   210  	// Close the sender channel
   211  	close(w.sendDone)
   212  
   213  	// Ensure the readLoop exits immediately
   214  	w.readLoop()
   215  
   216  }
   217  
   218  func TestWSReconnectFail(t *testing.T) {
   219  
   220  	_, _, url, done := NewTestWSServer(nil)
   221  	defer done()
   222  
   223  	wsconn, _, err := websocket.DefaultDialer.Dial(url, nil)
   224  	assert.NoError(t, err)
   225  	wsconn.Close()
   226  	ctxCancelled, cancel := context.WithCancel(context.Background())
   227  	cancel()
   228  	w := &wsClient{
   229  		ctx:     ctxCancelled,
   230  		receive: make(chan []byte),
   231  		send:    make(chan []byte),
   232  		closing: make(chan struct{}),
   233  		wsconn:  wsconn,
   234  	}
   235  	close(w.send) // will mean sender exits immediately
   236  
   237  	w.receiveReconnectLoop()
   238  }
   239  
   240  func TestWSSendFail(t *testing.T) {
   241  
   242  	_, _, url, done := NewTestWSServer(nil)
   243  	defer done()
   244  
   245  	wsconn, _, err := websocket.DefaultDialer.Dial(url, nil)
   246  	assert.NoError(t, err)
   247  	wsconn.Close()
   248  	w := &wsClient{
   249  		ctx:      context.Background(),
   250  		receive:  make(chan []byte),
   251  		send:     make(chan []byte, 1),
   252  		closing:  make(chan struct{}),
   253  		sendDone: make(chan []byte, 1),
   254  		wsconn:   wsconn,
   255  	}
   256  	w.send <- []byte(`wakes sender`)
   257  	w.sendLoop(make(chan struct{}))
   258  	<-w.sendDone
   259  }
   260  
   261  func TestWSSendInstructClose(t *testing.T) {
   262  
   263  	_, _, url, done := NewTestWSServer(nil)
   264  	defer done()
   265  
   266  	wsconn, _, err := websocket.DefaultDialer.Dial(url, nil)
   267  	assert.NoError(t, err)
   268  	wsconn.Close()
   269  	w := &wsClient{
   270  		ctx:      context.Background(),
   271  		receive:  make(chan []byte),
   272  		send:     make(chan []byte, 1),
   273  		closing:  make(chan struct{}),
   274  		sendDone: make(chan []byte, 1),
   275  		wsconn:   wsconn,
   276  	}
   277  	receiverClosed := make(chan struct{})
   278  	close(receiverClosed)
   279  	w.sendLoop(receiverClosed)
   280  	<-w.sendDone
   281  }