github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/lib/client/ws/client_test.go (about)

     1  package ws
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	types "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types"
    13  	"github.com/gorilla/websocket"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  // createTestServer creates a test WS server
    19  func createTestServer(
    20  	t *testing.T,
    21  	handler http.Handler,
    22  ) *httptest.Server {
    23  	t.Helper()
    24  
    25  	s := httptest.NewServer(handler)
    26  	t.Cleanup(s.Close)
    27  
    28  	return s
    29  }
    30  
    31  func TestClient_SendRequest(t *testing.T) {
    32  	t.Parallel()
    33  
    34  	t.Run("request timed out", func(t *testing.T) {
    35  		t.Parallel()
    36  
    37  		var (
    38  			upgrader = websocket.Upgrader{}
    39  
    40  			request = types.RPCRequest{
    41  				JSONRPC: "2.0",
    42  				ID:      types.JSONRPCStringID("id"),
    43  			}
    44  		)
    45  
    46  		ctx, cancelFn := context.WithCancel(context.Background())
    47  		defer cancelFn()
    48  
    49  		// Create the server
    50  		handler := func(w http.ResponseWriter, r *http.Request) {
    51  			c, err := upgrader.Upgrade(w, r, nil)
    52  			require.NoError(t, err)
    53  
    54  			defer c.Close()
    55  
    56  			for {
    57  				_, message, err := c.ReadMessage()
    58  				if websocket.IsUnexpectedCloseError(err) {
    59  					return
    60  				}
    61  
    62  				require.NoError(t, err)
    63  
    64  				// Parse the message
    65  				var req types.RPCRequest
    66  				require.NoError(t, json.Unmarshal(message, &req))
    67  				require.Equal(t, request.ID.String(), req.ID.String())
    68  
    69  				// Simulate context cancellation mid-request parsing
    70  				cancelFn()
    71  			}
    72  		}
    73  
    74  		s := createTestServer(t, http.HandlerFunc(handler))
    75  		url := "ws" + strings.TrimPrefix(s.URL, "http")
    76  
    77  		// Create the client
    78  		c, err := NewClient(url)
    79  		require.NoError(t, err)
    80  
    81  		defer func() {
    82  			assert.NoError(t, c.Close())
    83  		}()
    84  
    85  		// Try to send the request, but wait for
    86  		// the context to be cancelled
    87  		response, err := c.SendRequest(ctx, request)
    88  		require.Nil(t, response)
    89  
    90  		assert.ErrorIs(t, err, ErrTimedOut)
    91  	})
    92  
    93  	t.Run("valid request sent", func(t *testing.T) {
    94  		t.Parallel()
    95  
    96  		var (
    97  			upgrader = websocket.Upgrader{}
    98  
    99  			request = types.RPCRequest{
   100  				JSONRPC: "2.0",
   101  				ID:      types.JSONRPCStringID("id"),
   102  			}
   103  
   104  			response = types.RPCResponse{
   105  				JSONRPC: "2.0",
   106  				ID:      request.ID,
   107  			}
   108  		)
   109  
   110  		// Create the server
   111  		handler := func(w http.ResponseWriter, r *http.Request) {
   112  			c, err := upgrader.Upgrade(w, r, nil)
   113  			require.NoError(t, err)
   114  
   115  			defer c.Close()
   116  
   117  			for {
   118  				mt, message, err := c.ReadMessage()
   119  				if websocket.IsUnexpectedCloseError(err) {
   120  					return
   121  				}
   122  
   123  				require.NoError(t, err)
   124  
   125  				// Parse the message
   126  				var req types.RPCRequest
   127  				require.NoError(t, json.Unmarshal(message, &req))
   128  				require.Equal(t, request.ID.String(), req.ID.String())
   129  
   130  				marshalledResponse, err := json.Marshal(response)
   131  				require.NoError(t, err)
   132  
   133  				require.NoError(t, c.WriteMessage(mt, marshalledResponse))
   134  			}
   135  		}
   136  
   137  		s := createTestServer(t, http.HandlerFunc(handler))
   138  		url := "ws" + strings.TrimPrefix(s.URL, "http")
   139  
   140  		// Create the client
   141  		c, err := NewClient(url)
   142  		require.NoError(t, err)
   143  
   144  		defer func() {
   145  			assert.NoError(t, c.Close())
   146  		}()
   147  
   148  		// Try to send the valid request
   149  		ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5)
   150  		defer cancelFn()
   151  
   152  		resp, err := c.SendRequest(ctx, request)
   153  		require.NoError(t, err)
   154  
   155  		assert.Equal(t, response.ID, resp.ID)
   156  		assert.Equal(t, response.JSONRPC, resp.JSONRPC)
   157  		assert.Equal(t, response.Result, resp.Result)
   158  		assert.Equal(t, response.Error, resp.Error)
   159  	})
   160  }
   161  
   162  func TestClient_SendBatch(t *testing.T) {
   163  	t.Parallel()
   164  
   165  	t.Run("batch timed out", func(t *testing.T) {
   166  		t.Parallel()
   167  
   168  		var (
   169  			upgrader = websocket.Upgrader{}
   170  
   171  			request = types.RPCRequest{
   172  				JSONRPC: "2.0",
   173  				ID:      types.JSONRPCStringID("id"),
   174  			}
   175  
   176  			batch = types.RPCRequests{request}
   177  		)
   178  
   179  		ctx, cancelFn := context.WithCancel(context.Background())
   180  		defer cancelFn()
   181  
   182  		// Create the server
   183  		handler := func(w http.ResponseWriter, r *http.Request) {
   184  			c, err := upgrader.Upgrade(w, r, nil)
   185  			require.NoError(t, err)
   186  
   187  			defer c.Close()
   188  
   189  			for {
   190  				_, message, err := c.ReadMessage()
   191  				if websocket.IsUnexpectedCloseError(err) {
   192  					return
   193  				}
   194  
   195  				require.NoError(t, err)
   196  
   197  				// Parse the message
   198  				var req types.RPCRequests
   199  				require.NoError(t, json.Unmarshal(message, &req))
   200  
   201  				require.Len(t, req, 1)
   202  				require.Equal(t, request.ID.String(), req[0].ID.String())
   203  
   204  				// Simulate context cancellation mid-request parsing
   205  				cancelFn()
   206  			}
   207  		}
   208  
   209  		s := createTestServer(t, http.HandlerFunc(handler))
   210  		url := "ws" + strings.TrimPrefix(s.URL, "http")
   211  
   212  		// Create the client
   213  		c, err := NewClient(url)
   214  		require.NoError(t, err)
   215  
   216  		defer func() {
   217  			assert.NoError(t, c.Close())
   218  		}()
   219  
   220  		// Try to send the request, but wait for
   221  		// the context to be cancelled
   222  		response, err := c.SendBatch(ctx, batch)
   223  		require.Nil(t, response)
   224  
   225  		assert.ErrorIs(t, err, ErrTimedOut)
   226  	})
   227  
   228  	t.Run("valid batch sent", func(t *testing.T) {
   229  		t.Parallel()
   230  
   231  		var (
   232  			upgrader = websocket.Upgrader{}
   233  
   234  			request = types.RPCRequest{
   235  				JSONRPC: "2.0",
   236  				ID:      types.JSONRPCStringID("id"),
   237  			}
   238  
   239  			response = types.RPCResponse{
   240  				JSONRPC: "2.0",
   241  				ID:      request.ID,
   242  			}
   243  
   244  			batch         = types.RPCRequests{request}
   245  			batchResponse = types.RPCResponses{response}
   246  		)
   247  
   248  		// Create the server
   249  		handler := func(w http.ResponseWriter, r *http.Request) {
   250  			c, err := upgrader.Upgrade(w, r, nil)
   251  			require.NoError(t, err)
   252  
   253  			defer c.Close()
   254  
   255  			for {
   256  				mt, message, err := c.ReadMessage()
   257  				if websocket.IsUnexpectedCloseError(err) {
   258  					return
   259  				}
   260  
   261  				require.NoError(t, err)
   262  
   263  				// Parse the message
   264  				var req types.RPCRequests
   265  				require.NoError(t, json.Unmarshal(message, &req))
   266  
   267  				require.Len(t, req, 1)
   268  				require.Equal(t, request.ID.String(), req[0].ID.String())
   269  
   270  				marshalledResponse, err := json.Marshal(batchResponse)
   271  				require.NoError(t, err)
   272  
   273  				require.NoError(t, c.WriteMessage(mt, marshalledResponse))
   274  			}
   275  		}
   276  
   277  		s := createTestServer(t, http.HandlerFunc(handler))
   278  		url := "ws" + strings.TrimPrefix(s.URL, "http")
   279  
   280  		// Create the client
   281  		c, err := NewClient(url)
   282  		require.NoError(t, err)
   283  
   284  		defer func() {
   285  			assert.NoError(t, c.Close())
   286  		}()
   287  
   288  		// Try to send the valid request
   289  		ctx, cancelFn := context.WithTimeout(context.Background(), time.Second*5)
   290  		defer cancelFn()
   291  
   292  		resp, err := c.SendBatch(ctx, batch)
   293  		require.NoError(t, err)
   294  
   295  		require.Len(t, resp, 1)
   296  
   297  		assert.Equal(t, response.ID, resp[0].ID)
   298  		assert.Equal(t, response.JSONRPC, resp[0].JSONRPC)
   299  		assert.Equal(t, response.Result, resp[0].Result)
   300  		assert.Equal(t, response.Error, resp[0].Error)
   301  	})
   302  }