decred.org/dcrdex@v1.0.5/client/cmd/testbinance/harness_test.go (about) 1 //go:build harness 2 3 package main 4 5 import ( 6 "bytes" 7 "context" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 "net/url" 13 "strings" 14 "testing" 15 "time" 16 17 "decred.org/dcrdex/client/comms" 18 "decred.org/dcrdex/client/mm/libxc/bntypes" 19 "decred.org/dcrdex/dex" 20 ) 21 22 const testAPIKey = "Test Client 3000" 23 24 func TestMain(m *testing.M) { 25 log = dex.StdOutLogger("T", dex.LevelTrace) 26 m.Run() 27 } 28 29 func TestUtxoWallet(t *testing.T) { 30 ctx, cancel := context.WithCancel(context.Background()) 31 defer cancel() 32 w, err := newUtxoWallet(ctx, "btc") 33 if err != nil { 34 t.Fatalf("newUtxoWallet error: %v", err) 35 } 36 testWallet(t, ctx, w) 37 } 38 39 func TestEvmWallet(t *testing.T) { 40 ctx, cancel := context.WithCancel(context.Background()) 41 defer cancel() 42 w, err := newEvmWallet(ctx, "eth") 43 if err != nil { 44 t.Fatalf("newUtxoWallet error: %v", err) 45 } 46 testWallet(t, ctx, w) 47 } 48 49 func testWallet(t *testing.T, ctx context.Context, w Wallet) { 50 addr := w.DepositAddress() 51 fmt.Println("##### Deposit address:", addr) 52 txID, err := w.Send(ctx, addr, 0.1) 53 if err != nil { 54 t.Fatalf("Send error: %v", err) 55 } 56 fmt.Println("##### Self-send tx ID:", txID) 57 for i := 0; i < 3; i++ { 58 confs, err := w.Confirmations(ctx, txID) 59 if err != nil { 60 fmt.Println("##### Confirmations error:", err) 61 if i < 2 { 62 fmt.Println("##### Trying again in 15 seconds") 63 time.Sleep(time.Second * 15) 64 } 65 } else { 66 fmt.Println("##### Confirmations:", confs) 67 return 68 } 69 } 70 t.Fatal("Failed to get confirmations") 71 } 72 73 func getInto(method, endpoint string, thing interface{}) error { 74 req, err := http.NewRequest(method, "http://localhost:37346"+endpoint, nil) 75 if err != nil { 76 return err 77 } 78 return requestInto(req, thing) 79 } 80 81 func requestInto(req *http.Request, thing interface{}) error { 82 req.Header.Add("X-MBX-APIKEY", testAPIKey) 83 84 resp, err := http.DefaultClient.Do(req) 85 if err != nil || thing == nil { 86 return err 87 } 88 defer resp.Body.Close() 89 b, _ := io.ReadAll(resp.Body) 90 return json.Unmarshal(b, thing) 91 } 92 93 func printThing(name string, thing interface{}) { 94 b, _ := json.MarshalIndent(thing, "", " ") 95 fmt.Println("#####", name, ":", string(b)) 96 } 97 98 func newWebsocketClient(ctx context.Context, uri string, handler func([]byte)) (comms.WsConn, *dex.ConnectionMaster, error) { 99 wsClient, err := comms.NewWsConn(&comms.WsCfg{ 100 URL: uri, 101 PingWait: pongWait, 102 Logger: dex.StdOutLogger("W", log.Level()), 103 RawHandler: handler, 104 ConnectHeaders: http.Header{"X-MBX-APIKEY": []string{testAPIKey}}, 105 }) 106 if err != nil { 107 return nil, nil, fmt.Errorf("Error creating websocket client: %v", err) 108 } 109 wsCM := dex.NewConnectionMaster(wsClient) 110 if err = wsCM.ConnectOnce(ctx); err != nil { 111 return nil, nil, fmt.Errorf("Error connecting websocket client: %v", err) 112 } 113 return wsClient, wsCM, nil 114 } 115 116 func TestMarketFeed(t *testing.T) { 117 // httpURL := "http://localhost:37346" 118 wsURL := "ws://localhost:37346" 119 120 ctx, cancel := context.WithCancel(context.Background()) 121 defer cancel() 122 123 var xcInfo bntypes.ExchangeInfo 124 if err := getInto("GET", "/api/v3/exchangeInfo", &xcInfo); err != nil { 125 t.Fatalf("Error getting exchange info: %v", err) 126 } 127 printThing("ExchangeInfo", xcInfo) 128 129 depthStreamID := func(mkt *bntypes.Market) string { 130 return fmt.Sprintf("%s@depth", strings.ToLower(mkt.Symbol)) 131 } 132 133 mkt0, mkt1 := xcInfo.Symbols[0], xcInfo.Symbols[1] 134 135 streamHandler := func(b []byte) { 136 fmt.Printf("\n##### Market stream -> %s \n\n", string(b)) 137 } 138 139 uri := fmt.Sprintf(wsURL+"/stream?streams=%s", depthStreamID(mkt0)) 140 wsClient, wsCM, err := newWebsocketClient(ctx, uri, streamHandler) 141 if err != nil { 142 t.Fatal(err) 143 } 144 defer wsCM.Disconnect() 145 146 var book bntypes.OrderbookSnapshot 147 if err := getInto("GET", fmt.Sprintf("/api/v3/depth?symbol=%s", mkt0.Symbol), &book); err != nil { 148 t.Fatalf("Error getting depth for symbol %s: %v", mkt0.Symbol, err) 149 } 150 151 printThing("First order book", &book) 152 153 addSubB, _ := json.Marshal(&bntypes.StreamSubscription{ 154 Method: "SUBSCRIBE", 155 Params: []string{depthStreamID(mkt1)}, 156 ID: 1, 157 }) 158 159 fmt.Println("##### Sending second market subscription in 30 seconds") 160 161 select { 162 case <-time.After(time.Second * 30): 163 if err := wsClient.SendRaw(addSubB); err != nil { 164 t.Fatalf("error sending subscription stream request: %v", err) 165 } 166 fmt.Println("##### Sent second market subscription") 167 var book bntypes.OrderbookSnapshot 168 if err := getInto("GET", fmt.Sprintf("/api/v3/depth?symbol=%s", mkt1.Symbol), &book); err != nil { 169 t.Fatalf("Error getting depth for symbol %s: %v", mkt1.Symbol, err) 170 } 171 printThing("Second order book", &book) 172 case <-ctx.Done(): 173 return 174 } 175 176 unubB, _ := json.Marshal(&bntypes.StreamSubscription{ 177 Method: "UNSUBSCRIBE", 178 Params: []string{depthStreamID(mkt0), depthStreamID(mkt1)}, 179 ID: 2, 180 }) 181 182 fmt.Println("##### Monitoring stream for 5 minutes") 183 select { 184 case <-time.After(time.Minute * 5): 185 if err := wsClient.SendRaw(unubB); err != nil { 186 t.Fatalf("error sending unsubscribe request: %v", err) 187 } 188 fmt.Println("##### All done") 189 case <-ctx.Done(): 190 return 191 } 192 } 193 194 func TestAccountFeed(t *testing.T) { 195 // httpURL := "http://localhost:37346" 196 wsURL := "ws://localhost:37346" 197 198 ctx, cancel := context.WithCancel(context.Background()) 199 defer cancel() 200 201 var addrResp struct { 202 Address string `json:"address"` 203 } 204 if err := getInto("GET", "/sapi/v1/capital/deposit/address?coin=BTC", &addrResp); err != nil { 205 t.Fatalf("Error getting deposit address: %v", err) 206 } 207 208 printThing("Deposit address", addrResp) 209 210 w, err := newUtxoWallet(ctx, "btc") 211 if err != nil { 212 t.Fatalf("Error constructing btc wallet: %v", err) 213 } 214 txID, err := w.Send(ctx, addrResp.Address, 0.1) 215 if err != nil { 216 t.Fatalf("Send error: %v", err) 217 } 218 219 streamHandler := func(b []byte) { 220 fmt.Printf("\n##### Account stream -> %s \n\n", string(b)) 221 } 222 223 _, wsCM, err := newWebsocketClient(ctx, wsURL+"/ws/testListenKey", streamHandler) 224 if err != nil { 225 t.Fatal(err) 226 } 227 defer wsCM.Disconnect() 228 229 endpoint := "/sapi/v1/capital/deposit/hisrec?amt=0.1&coin=BTC&network=BTC&txid=" + txID 230 for { 231 var resp []*bntypes.PendingDeposit 232 if err := getInto("GET", endpoint, &resp); err != nil { 233 t.Fatalf("Error getting deposit confirmations: %v", err) 234 } 235 printThing("Deposit Status", resp) 236 if len(resp) > 0 && resp[0].Status == bntypes.DepositStatusCredited { 237 fmt.Println("##### Deposit confirmed") 238 break 239 } 240 fmt.Println("##### Checking unconfirmed deposit again in 15 seconds") 241 select { 242 case <-time.After(time.Second * 15): 243 case <-ctx.Done(): 244 return 245 } 246 } 247 248 withdrawAddr := w.DepositAddress() 249 form := make(url.Values) 250 form.Add("coin", "BTC") 251 form.Add("network", "BTC") 252 form.Add("address", withdrawAddr) 253 form.Add("amount", "0.1") 254 bodyString := form.Encode() 255 req, err := http.NewRequest(http.MethodPost, "http://localhost:37346/sapi/v1/capital/withdraw/apply", bytes.NewBufferString(bodyString)) 256 if err != nil { 257 t.Fatalf("Error constructing withdraw request: %v", err) 258 } 259 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 260 var withdrawResp struct { 261 ID string `json:"id"` 262 } 263 if err := requestInto(req, &withdrawResp); err != nil { 264 t.Fatalf("Error requesting withdraw: %v", err) 265 } 266 267 printThing("Withdraw response", &withdrawResp) 268 269 var withdraws []*withdrawalHistoryStatus 270 if err := getInto("GET", "/sapi/v1/capital/withdraw/history?coin=BTC", &withdraws); err != nil { 271 t.Fatalf("Error fetching withdraw history: %v", err) 272 } 273 printThing("Withdraw history", withdraws) 274 275 }