decred.org/dcrdex@v1.0.5/client/websocket/websocket_test.go (about) 1 //go:build !live 2 3 package websocket 4 5 import ( 6 "context" 7 "encoding/json" 8 "fmt" 9 "os" 10 "sync" 11 "testing" 12 "time" 13 14 "decred.org/dcrdex/client/core" 15 "decred.org/dcrdex/client/orderbook" 16 "decred.org/dcrdex/dex" 17 "decred.org/dcrdex/dex/msgjson" 18 ) 19 20 var ( 21 tCtx context.Context 22 unbip = dex.BipIDSymbol 23 ) 24 25 type TCore struct { 26 syncFeed core.BookFeed 27 syncErr error 28 notHas bool 29 notRunning bool 30 notOpen bool 31 } 32 33 func (c *TCore) SyncBook(dex string, base, quote uint32) (*orderbook.OrderBook, core.BookFeed, error) { 34 return nil, c.syncFeed, c.syncErr 35 } 36 func (c *TCore) WalletState(assetID uint32) *core.WalletState { 37 if c.notHas { 38 return nil 39 } 40 return &core.WalletState{ 41 Symbol: unbip(assetID), 42 AssetID: assetID, 43 Open: !c.notOpen, 44 Running: !c.notRunning, 45 } 46 } 47 func (c *TCore) AckNotes(ids []dex.Bytes) {} 48 49 type TConn struct { 50 msg []byte 51 reads [][]byte // data for ReadMessage 52 respReady chan []byte // signal from WriteMessage 53 close chan struct{} // Close tells ReadMessage to return with error 54 } 55 56 var readTimeout = 10 * time.Second // ReadMessage must not return constantly with nothing 57 58 func (c *TConn) ReadMessage() (int, []byte, error) { 59 if len(c.reads) > 0 { 60 var read []byte 61 // pop front 62 read, c.reads = c.reads[0], c.reads[1:] 63 return len(read), read, nil 64 } 65 66 select { 67 case <-c.close: // receive from nil channel blocks 68 return 0, nil, fmt.Errorf("closed") 69 case <-time.After(readTimeout): 70 return 0, nil, fmt.Errorf("read timeout") 71 } 72 } 73 74 func (c *TConn) addRead(read []byte) { 75 // push back 76 c.reads = append(c.reads, read) 77 } 78 79 func (c *TConn) SetWriteDeadline(t time.Time) error { 80 return nil 81 } 82 83 func (c *TConn) WriteControl(messageType int, data []byte, deadline time.Time) error { 84 return nil 85 } 86 87 func (c *TConn) SetReadDeadline(t time.Time) error { 88 return nil 89 } 90 91 func (c *TConn) SetReadLimit(int64) {} 92 93 func (c *TConn) WriteMessage(_ int, msg []byte) error { 94 c.msg = msg 95 select { 96 case c.respReady <- msg: 97 default: 98 } 99 return nil 100 } 101 102 func (c *TConn) Close() error { 103 // If the test has a non-nil close channel, signal close. 104 select { 105 case c.close <- struct{}{}: 106 default: 107 } 108 return nil 109 } 110 111 type tLink struct { 112 cl *wsClient 113 conn *TConn 114 } 115 116 func newLink() *tLink { 117 conn := &TConn{ 118 respReady: make(chan []byte, 1), 119 close: make(chan struct{}, 1), 120 } 121 ipk := dex.IPKey{16, 16, 120, 120 /* ipv6 1010:7878:: */} 122 cl := newWSClient(ipk.String(), conn, func(*msgjson.Message) *msgjson.Error { return nil }, dex.StdOutLogger("ws_TEST", dex.LevelTrace)) 123 return &tLink{ 124 cl: cl, 125 conn: conn, 126 } 127 } 128 129 func newTServer() (*Server, *TCore) { 130 c := &TCore{} 131 return New(c, dex.StdOutLogger("TEST", dex.LevelTrace)), c 132 } 133 134 type tBookFeed struct{} 135 136 func (*tBookFeed) Next() <-chan *core.BookUpdate { 137 return make(chan *core.BookUpdate, 1) 138 } 139 func (*tBookFeed) Close() {} 140 func (*tBookFeed) Candles(dur string) error { 141 return nil 142 } 143 144 func TestMain(m *testing.M) { 145 var shutdown func() 146 tCtx, shutdown = context.WithCancel(context.Background()) 147 doIt := func() int { 148 // Not counted as coverage, must test Archiver constructor explicitly. 149 defer shutdown() 150 return m.Run() 151 } 152 os.Exit(doIt()) 153 } 154 155 func TestLoadMarket(t *testing.T) { 156 srv, tCore := newTServer() 157 // NOTE that Server is not running. We are just using the handleMessage 158 // method to route to the proper handler with a configured logger and Core. 159 160 link := newLink() 161 linkWg, err := link.cl.Connect(tCtx) 162 if err != nil { 163 t.Fatalf("WSLink Start: %v", err) 164 } 165 166 // This test is not running WebServer or calling handleWS/websocketHandler, 167 // so manually stop the marketSyncer started by wsLoadMarket and the WSLink 168 // before returning from this test. 169 defer func() { 170 link.cl.shutDownFeed() 171 link.cl.Disconnect() 172 linkWg.Wait() 173 }() 174 175 params := &marketLoad{ 176 Host: "abc", 177 Base: uint32(1), 178 Quote: uint32(2), 179 } 180 181 subscription, _ := msgjson.NewRequest(1, "loadmarket", params) 182 183 ensureGood := func() { 184 t.Helper() 185 // Create a new feed for every request because a Close()d feed cannot be 186 // reused. 187 tCore.syncFeed = &tBookFeed{} 188 msgErr := srv.handleMessage(link.cl, subscription) 189 if msgErr != nil { 190 t.Fatalf("'loadmarket' error: %d: %s", msgErr.Code, msgErr.Message) 191 } 192 if link.cl.feed.loop == nil { 193 t.Fatalf("nil book feed waiter after 'loadmarket'") 194 } 195 } 196 197 // Initial success. 198 ensureGood() 199 200 // Unsubscribe. 201 unsub, _ := msgjson.NewRequest(2, "unmarket", nil) 202 msgErr := srv.handleMessage(link.cl, unsub) 203 if msgErr != nil { 204 t.Fatalf("'unmarket' error: %d: %s", msgErr.Code, msgErr.Message) 205 } 206 207 if link.cl.feed != nil { 208 t.Fatalf("non-nil book feed waiter after 'unmarket'") 209 } 210 211 // Make sure a sync error propagates. 212 tCore.syncErr = fmt.Errorf("expected dummy error") 213 msgErr = srv.handleMessage(link.cl, subscription) 214 if msgErr == nil { 215 t.Fatalf("no handleMessage error from Sync error") 216 } 217 tCore.syncErr = nil 218 219 // Success again. 220 ensureGood() 221 } 222 223 func TestHandleMessage(t *testing.T) { 224 link := newLink() 225 srv, _ := newTServer() 226 227 // NOTE: link is not started because the handlers in this test do not 228 // actually use it. 229 230 var msg *msgjson.Message 231 232 ensureErr := func(name string, wantCode int) { 233 got := srv.handleMessage(link.cl, msg) 234 if got == nil { 235 t.Fatalf("%s: no error", name) 236 } 237 if wantCode != got.Code { 238 t.Fatalf("%s, wanted %d, got %d", name, wantCode, got.Code) 239 } 240 } 241 242 // Send a response, which is unsupported on the web server. 243 msg, _ = msgjson.NewResponse(1, nil, nil) 244 ensureErr("bad route", msgjson.UnknownMessageType) 245 246 // Unknown route. 247 msg, _ = msgjson.NewRequest(1, "123", nil) 248 ensureErr("bad route", msgjson.UnknownMessageType) 249 250 // Set the route correctly. 251 wsHandlers["123"] = func(*Server, *wsClient, *msgjson.Message) *msgjson.Error { 252 return nil 253 } 254 255 rpcErr := srv.handleMessage(link.cl, msg) 256 if rpcErr != nil { 257 t.Fatalf("error for good message: %d: %s", rpcErr.Code, rpcErr.Message) 258 } 259 } 260 261 func TestClientMap(t *testing.T) { 262 srv, _ := newTServer() 263 resp := make(chan []byte, 1) 264 conn := &TConn{ 265 respReady: resp, 266 close: make(chan struct{}, 1), 267 } 268 // msg.ID == 0 gets an error response, which can be discarded. 269 read, _ := json.Marshal(msgjson.Message{ID: 0}) 270 conn.addRead(read) 271 272 // Create the context that the http request handler would receive. 273 ctx, shutdown := context.WithCancel(context.Background()) 274 var wg sync.WaitGroup 275 wg.Add(1) 276 go func() { 277 ipk := dex.IPKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255 /* ipv4 */, 127, 0, 0, 1} 278 srv.connect(ctx, conn, ipk.String()) 279 wg.Done() 280 }() 281 282 // When a response to our dummy message is received, the client should be in 283 // RPCServer's client map. 284 <-resp 285 286 var cl *wsClient 287 srv.clientsMtx.Lock() 288 i := len(srv.clients) 289 if i != 1 { 290 t.Fatalf("expected 1 client in server map, found %d", i) 291 } 292 for _, c := range srv.clients { 293 cl = c 294 break 295 } 296 srv.clientsMtx.Unlock() 297 298 // Close the server and make sure the connection is closed. 299 shutdown() 300 wg.Wait() // websocketHandler since it's using log 301 if !cl.Off() { 302 t.Fatal("connection not closed on server shutdown") 303 } 304 }