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  }