From f254851e0191adfc170a9d1eeebf511d22e965f6 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 8 Aug 2022 12:30:01 +0800 Subject: [PATCH] itest: refactor `testRestAPI` --- lntest/itest/list_on_test.go | 4 + lntest/itest/lnd_rest_api_test.go | 229 +++++++++++++------------- lntest/itest/lnd_test_list_on_test.go | 4 - 3 files changed, 116 insertions(+), 121 deletions(-) diff --git a/lntest/itest/list_on_test.go b/lntest/itest/list_on_test.go index 1d8d48e54..23446bd12 100644 --- a/lntest/itest/list_on_test.go +++ b/lntest/itest/list_on_test.go @@ -287,4 +287,8 @@ var allTestCasesTemp = []*lntemp.TestCase{ Name: "resolution handoff", TestFunc: testResHandoff, }, + { + Name: "REST API", + TestFunc: testRestAPI, + }, } diff --git a/lntest/itest/lnd_rest_api_test.go b/lntest/itest/lnd_rest_api_test.go index 16f7c6c17..43aff6110 100644 --- a/lntest/itest/lnd_rest_api_test.go +++ b/lntest/itest/lnd_rest_api_test.go @@ -2,7 +2,6 @@ package itest import ( "bytes" - "context" "crypto/tls" "encoding/base64" "encoding/hex" @@ -24,8 +23,8 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/verrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc" - "github.com/lightningnetwork/lnd/lntest" - "github.com/stretchr/testify/assert" + "github.com/lightningnetwork/lnd/lntemp" + "github.com/lightningnetwork/lnd/lntemp/node" "github.com/stretchr/testify/require" ) @@ -57,19 +56,21 @@ var ( // testRestAPI tests that the most important features of the REST API work // correctly. -func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { +func testRestAPI(ht *lntemp.HarnessTest) { testCases := []struct { name string - run func(*testing.T, *lntest.HarnessNode, *lntest.HarnessNode) + run func(*testing.T, *node.HarnessNode, *node.HarnessNode) }{{ name: "simple GET", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Check that the parsing into the response proto // message works. resp := &lnrpc.GetInfoResponse{} err := invokeGET(a, "/v1/getinfo", resp) require.Nil(t, err, "getinfo") - assert.Equal(t, "#3399ff", resp.Color, "node color") + require.Equal(t, "#3399ff", resp.Color, "node color") // Make sure we get the correct field names (snake // case). @@ -77,20 +78,22 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { a, "/v1/getinfo", "GET", nil, nil, ) require.Nil(t, err, "getinfo") - assert.Contains( + require.Contains( t, string(resp2), "best_header_timestamp", "getinfo", ) }, }, { name: "simple POST and GET with query param", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Add an invoice, testing POST in the process. req := &lnrpc.Invoice{Value: 1234} resp := &lnrpc.AddInvoiceResponse{} err := invokePOST(a, "/v1/invoices", req, resp) require.Nil(t, err, "add invoice") - assert.Equal(t, 32, len(resp.RHash), "invoice rhash") + require.Equal(t, 32, len(resp.RHash), "invoice rhash") // Make sure we can call a GET endpoint with a hex // encoded URL part. @@ -98,11 +101,14 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { resp2 := &lnrpc.Invoice{} err = invokeGET(a, url, resp2) require.Nil(t, err, "query invoice") - assert.Equal(t, int64(1234), resp2.Value, "invoice amt") + require.Equal(t, int64(1234), resp2.Value, + "invoice amt") }, }, { name: "GET with base64 encoded byte slice in path", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + url := "/v2/router/mc/probability/%s/%s/%d" url = fmt.Sprintf( url, urlEnc.EncodeToString(a.PubKey[:]), @@ -115,38 +121,37 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { }, }, { name: "GET with map type query param", - run: func(t *testing.T, a, b *lntest.HarnessNode) { - // Get a new wallet address from Alice. - ctxb := context.Background() - newAddrReq := &lnrpc.NewAddressRequest{ - Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, - } - addrRes, err := a.NewAddress(ctxb, newAddrReq) - require.Nil(t, err, "get address") + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + + // Use a fake address. + addr := "bcrt1qlutnwklt4u2548cufrjmsjclewugr9lcpnkzag" // Create the full URL with the map query param. url := "/v1/transactions/fee?target_conf=%d&" + "AddrToAmount[%s]=%d" - url = fmt.Sprintf(url, 2, addrRes.Address, 50000) + url = fmt.Sprintf(url, 2, addr, 50000) resp := &lnrpc.EstimateFeeResponse{} - err = invokeGET(a, url, resp) + err := invokeGET(a, url, resp) require.Nil(t, err, "estimate fee") - assert.Greater(t, resp.FeeSat, int64(253), "fee") + require.Greater(t, resp.FeeSat, int64(253), "fee") }, }, { name: "sub RPC servers REST support", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Query autopilot status. res1 := &autopilotrpc.StatusResponse{} err := invokeGET(a, "/v2/autopilot/status", res1) require.Nil(t, err, "autopilot status") - assert.Equal(t, false, res1.Active, "autopilot status") + require.Equal(t, false, res1.Active, "autopilot status") // Query the version RPC. res2 := &verrpc.Version{} err = invokeGET(a, "/v2/versioner/version", res2) require.Nil(t, err, "version") - assert.Greater( + require.Greater( t, res2.AppMinor, uint32(0), "lnd minor version", ) @@ -157,11 +162,13 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { a, "/v2/wallet/address/next", req1, res3, ) require.Nil(t, err, "address") - assert.NotEmpty(t, res3.Addr, "address") + require.NotEmpty(t, res3.Addr, "address") }, }, { name: "CORS headers", - run: func(t *testing.T, a, b *lntest.HarnessNode) { + run: func(t *testing.T, a, b *node.HarnessNode) { + t.Helper() + // Alice allows all origins. Make sure we get the same // value back in the CORS header that we send in the // Origin header. @@ -171,12 +178,12 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { a, "/v1/getinfo", "OPTIONS", nil, reqHeaders, ) require.Nil(t, err, "getinfo") - assert.Equal( + require.Equal( t, "https://foo.bar:9999", resHeaders.Get("Access-Control-Allow-Origin"), "CORS header", ) - assert.Equal(t, 0, len(body)) + require.Equal(t, 0, len(body)) // Make sure that we don't get a value set for Bob which // doesn't allow any CORS origin. @@ -184,17 +191,17 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { b, "/v1/getinfo", "OPTIONS", nil, reqHeaders, ) require.Nil(t, err, "getinfo") - assert.Equal( + require.Equal( t, "", resHeaders.Get("Access-Control-Allow-Origin"), "CORS header", ) - assert.Equal(t, 0, len(body)) + require.Equal(t, 0, len(body)) }, }} wsTestCases := []struct { name string - run func(ht *harnessTest, net *lntest.NetworkHarness) + run func(ht *lntemp.HarnessTest) }{{ name: "websocket subscription", run: wsTestCaseSubscription, @@ -212,39 +219,33 @@ func testRestAPI(net *lntest.NetworkHarness, ht *harnessTest) { // Make sure Alice allows all CORS origins. Bob will keep the default. // We also make sure the ping/pong messages are sent very often, so we // can test them without waiting half a minute. - net.Alice.Cfg.ExtraArgs = append( - net.Alice.Cfg.ExtraArgs, "--restcors=\"*\"", + alice, bob := ht.Alice, ht.Bob + alice.Cfg.ExtraArgs = append( + alice.Cfg.ExtraArgs, "--restcors=\"*\"", fmt.Sprintf("--ws-ping-interval=%s", pingInterval), fmt.Sprintf("--ws-pong-wait=%s", pongWait), ) - err := net.RestartNode(net.Alice, nil) - if err != nil { - ht.t.Fatalf("Could not restart Alice to set CORS config: %v", - err) - } + ht.RestartNode(alice) for _, tc := range testCases { tc := tc - ht.t.Run(tc.name, func(t *testing.T) { - tc.run(t, net.Alice, net.Bob) + ht.Run(tc.name, func(t *testing.T) { + tc.run(t, alice, bob) }) } for _, tc := range wsTestCases { tc := tc - ht.t.Run(tc.name, func(t *testing.T) { - ht := &harnessTest{ - t: t, testCase: ht.testCase, lndHarness: net, - } - tc.run(ht, net) + ht.Run(tc.name, func(t *testing.T) { + st := ht.Subtest(t) + tc.run(st) }) } } -func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) { +func wsTestCaseSubscription(ht *lntemp.HarnessTest) { // Find out the current best block so we can subscribe to the next one. - hash, height, err := net.Miner.Client.GetBestBlock() - require.Nil(ht.t, err, "get best block") + hash, height := ht.Miner.GetBestBlock() // Create a new subscription to get block epoch events. req := &chainrpc.BlockEpoch{ @@ -252,11 +253,11 @@ func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) { Height: uint32(height), } url := "/v2/chainnotifier/register/blocks" - c, err := openWebSocket(net.Alice, url, "POST", req, nil) - require.Nil(ht.t, err, "websocket") + c, err := openWebSocket(ht.Alice, url, "POST", req, nil) + require.NoError(ht, err, "websocket") defer func() { err := c.WriteMessage(websocket.CloseMessage, closeMsg) - require.NoError(ht.t, err) + require.NoError(ht, err) _ = c.Close() }() @@ -300,30 +301,25 @@ func wsTestCaseSubscription(ht *harnessTest, net *lntest.NetworkHarness) { }() // Mine a block and make sure we get a message for it. - blockHashes, err := net.Miner.Client.Generate(1) - require.Nil(ht.t, err, "generate blocks") - assert.Equal(ht.t, 1, len(blockHashes), "num blocks") + blockHashes := ht.Miner.GenerateBlocks(1) select { case msg := <-msgChan: - assert.Equal( - ht.t, blockHashes[0].CloneBytes(), msg.Hash, + require.Equal( + ht, blockHashes[0].CloneBytes(), msg.Hash, "block hash", ) case err := <-errChan: - ht.t.Fatalf("Received error from WS: %v", err) + ht.Fatalf("Received error from WS: %v", err) case <-timeout: - ht.t.Fatalf("Timeout before message was received") + ht.Fatalf("Timeout before message was received") } } -func wsTestCaseSubscriptionMacaroon(ht *harnessTest, - net *lntest.NetworkHarness) { - +func wsTestCaseSubscriptionMacaroon(ht *lntemp.HarnessTest) { // Find out the current best block so we can subscribe to the next one. - hash, height, err := net.Miner.Client.GetBestBlock() - require.Nil(ht.t, err, "get best block") + hash, height := ht.Miner.GetBestBlock() // Create a new subscription to get block epoch events. req := &chainrpc.BlockEpoch{ @@ -335,22 +331,23 @@ func wsTestCaseSubscriptionMacaroon(ht *harnessTest, // This time we send the macaroon in the special header // Sec-Websocket-Protocol which is the only header field available to // browsers when opening a WebSocket. - mac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + alice := ht.Alice + mac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(ht.t, err, "read admin mac") + require.NoError(ht, err, "read admin mac") macBytes, err := mac.MarshalBinary() - require.NoError(ht.t, err, "marshal admin mac") + require.NoError(ht, err, "marshal admin mac") customHeader := make(http.Header) customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf( "Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes), )) - c, err := openWebSocket(net.Alice, url, "POST", req, customHeader) - require.Nil(ht.t, err, "websocket") + c, err := openWebSocket(alice, url, "POST", req, customHeader) + require.Nil(ht, err, "websocket") defer func() { err := c.WriteMessage(websocket.CloseMessage, closeMsg) - require.NoError(ht.t, err) + require.NoError(ht, err) _ = c.Close() }() @@ -394,52 +391,49 @@ func wsTestCaseSubscriptionMacaroon(ht *harnessTest, }() // Mine a block and make sure we get a message for it. - blockHashes, err := net.Miner.Client.Generate(1) - require.Nil(ht.t, err, "generate blocks") - assert.Equal(ht.t, 1, len(blockHashes), "num blocks") + blockHashes := ht.Miner.GenerateBlocks(1) select { case msg := <-msgChan: - assert.Equal( - ht.t, blockHashes[0].CloneBytes(), msg.Hash, + require.Equal( + ht, blockHashes[0].CloneBytes(), msg.Hash, "block hash", ) case err := <-errChan: - ht.t.Fatalf("Received error from WS: %v", err) + ht.Fatalf("Received error from WS: %v", err) case <-timeout: - ht.t.Fatalf("Timeout before message was received") + ht.Fatalf("Timeout before message was received") } } -func wsTestCaseBiDirectionalSubscription(ht *harnessTest, - net *lntest.NetworkHarness) { - +func wsTestCaseBiDirectionalSubscription(ht *lntemp.HarnessTest) { initialRequest := &lnrpc.ChannelAcceptResponse{} url := "/v1/channels/acceptor" // This time we send the macaroon in the special header // Sec-Websocket-Protocol which is the only header field available to // browsers when opening a WebSocket. - mac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + alice := ht.Alice + mac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(ht.t, err, "read admin mac") + require.NoError(ht, err, "read admin mac") macBytes, err := mac.MarshalBinary() - require.NoError(ht.t, err, "marshal admin mac") + require.NoError(ht, err, "marshal admin mac") customHeader := make(http.Header) customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf( "Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes), )) conn, err := openWebSocket( - net.Alice, url, "POST", initialRequest, customHeader, + alice, url, "POST", initialRequest, customHeader, ) - require.Nil(ht.t, err, "websocket") + require.Nil(ht, err, "websocket") defer func() { err := conn.WriteMessage(websocket.CloseMessage, closeMsg) _ = conn.Close() - require.NoError(ht.t, err) + require.NoError(ht, err) }() // Buffer the message channel to make sure we're always blocking on @@ -528,29 +522,30 @@ func wsTestCaseBiDirectionalSubscription(ht *harnessTest, // Before we start opening channels, make sure the two nodes are // connected. - net.EnsureConnected(ht.t, net.Alice, net.Bob) + bob := ht.Bob + ht.EnsureConnected(alice, bob) // Open 3 channels to make sure multiple requests and responses can be // sent over the web socket. const numChannels = 3 for i := 0; i < numChannels; i++ { - openChannelAndAssert( - ht, net, net.Bob, net.Alice, - lntest.OpenChannelParams{Amt: 500000}, + chanPoint := ht.OpenChannel( + bob, alice, lntemp.OpenChannelParams{Amt: 500000}, ) + defer ht.CloseChannel(bob, chanPoint) select { case <-msgChan: case err := <-errChan: - ht.t.Fatalf("Received error from WS: %v", err) + ht.Fatalf("Received error from WS: %v", err) case <-timeout: - ht.t.Fatalf("Timeout before message was received") + ht.Fatalf("Timeout before message was received") } } } -func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { +func wsTestPingPongTimeout(ht *lntemp.HarnessTest) { initialRequest := &lnrpc.InvoiceSubscription{ AddIndex: 1, SettleIndex: 1, } @@ -559,25 +554,26 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { // This time we send the macaroon in the special header // Sec-Websocket-Protocol which is the only header field available to // browsers when opening a WebSocket. - mac, err := net.Alice.ReadMacaroon( - net.Alice.AdminMacPath(), defaultTimeout, + alice := ht.Alice + mac, err := alice.ReadMacaroon( + alice.Cfg.AdminMacPath, defaultTimeout, ) - require.NoError(ht.t, err, "read admin mac") + require.NoError(ht, err, "read admin mac") macBytes, err := mac.MarshalBinary() - require.NoError(ht.t, err, "marshal admin mac") + require.NoError(ht, err, "marshal admin mac") customHeader := make(http.Header) customHeader.Set(lnrpc.HeaderWebSocketProtocol, fmt.Sprintf( "Grpc-Metadata-Macaroon+%s", hex.EncodeToString(macBytes), )) conn, err := openWebSocket( - net.Alice, url, "GET", initialRequest, customHeader, + alice, url, "GET", initialRequest, customHeader, ) - require.Nil(ht.t, err, "websocket") + require.Nil(ht, err, "websocket") defer func() { err := conn.WriteMessage(websocket.CloseMessage, closeMsg) _ = conn.Close() - require.NoError(ht.t, err) + require.NoError(ht, err) }() // We want to be able to read invoices for a long time, making sure we @@ -650,27 +646,26 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { // Let's create five invoices and wait for them to arrive. We'll wait // for at least one ping/pong cycle between each invoice. - ctxb := context.Background() const numInvoices = 5 const value = 123 const memo = "websocket" for i := 0; i < numInvoices; i++ { - _, err := net.Alice.AddInvoice(ctxb, &lnrpc.Invoice{ + invoice := &lnrpc.Invoice{ Value: value, Memo: memo, - }) - require.NoError(ht.t, err) + } + alice.RPC.AddInvoice(invoice) select { case streamMsg := <-invoices: - require.Equal(ht.t, int64(value), streamMsg.Value) - require.Equal(ht.t, memo, streamMsg.Memo) + require.Equal(ht, int64(value), streamMsg.Value) + require.Equal(ht, memo, streamMsg.Memo) case err := <-errChan: - require.Fail(ht.t, "Error reading invoice: %v", err) + require.Fail(ht, "Error reading invoice: %v", err) case <-timeout: - require.Fail(ht.t, "No invoice msg received in time") + require.Fail(ht, "No invoice msg received in time") } // Let's wait for at least a whole ping/pong cycle to happen, so @@ -683,7 +678,7 @@ func wsTestPingPongTimeout(ht *harnessTest, net *lntest.NetworkHarness) { // invokeGET calls the given URL with the GET method and appropriate macaroon // header fields then tries to unmarshal the response into the given response // proto message. -func invokeGET(node *lntest.HarnessNode, url string, resp proto.Message) error { +func invokeGET(node *node.HarnessNode, url string, resp proto.Message) error { _, rawResp, err := makeRequest(node, url, "GET", nil, nil) if err != nil { return err @@ -695,7 +690,7 @@ func invokeGET(node *lntest.HarnessNode, url string, resp proto.Message) error { // invokePOST calls the given URL with the POST method, request body and // appropriate macaroon header fields then tries to unmarshal the response into // the given response proto message. -func invokePOST(node *lntest.HarnessNode, url string, req, +func invokePOST(node *node.HarnessNode, url string, req, resp proto.Message) error { // Marshal the request to JSON using the jsonpb marshaler to get correct @@ -715,7 +710,7 @@ func invokePOST(node *lntest.HarnessNode, url string, req, // makeRequest calls the given URL with the given method, request body and // appropriate macaroon header fields and returns the raw response body. -func makeRequest(node *lntest.HarnessNode, url, method string, +func makeRequest(node *node.HarnessNode, url, method string, request io.Reader, additionalHeaders http.Header) (http.Header, []byte, error) { @@ -748,7 +743,7 @@ func makeRequest(node *lntest.HarnessNode, url, method string, // openWebSocket opens a new WebSocket connection to the given URL with the // appropriate macaroon headers and sends the request message over the socket. -func openWebSocket(node *lntest.HarnessNode, url, method string, +func openWebSocket(node *node.HarnessNode, url, method string, req proto.Message, customHeader http.Header) (*websocket.Conn, error) { // Prepare our macaroon headers and assemble the full URL from the @@ -785,8 +780,8 @@ func openWebSocket(node *lntest.HarnessNode, url, method string, // addAdminMacaroon reads the admin macaroon from the node and appends it to // the HTTP header fields. -func addAdminMacaroon(node *lntest.HarnessNode, header http.Header) error { - mac, err := node.ReadMacaroon(node.AdminMacPath(), defaultTimeout) +func addAdminMacaroon(node *node.HarnessNode, header http.Header) error { + mac, err := node.ReadMacaroon(node.Cfg.AdminMacPath, defaultTimeout) if err != nil { return err } diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index 2f0d0cf42..e5040037e 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -146,10 +146,6 @@ var allTestCases = []*testCase{ name: "send multi path payment", test: testSendMultiPathPayment, }, - { - name: "REST API", - test: testRestAPI, - }, { name: "forward interceptor", test: testForwardInterceptorBasic,