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 }