github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/rpc/jsonrpc/jsonrpc_test.go (about) 1 package jsonrpc 2 3 import ( 4 "bytes" 5 "context" 6 crand "crypto/rand" 7 "encoding/json" 8 "errors" 9 mrand "math/rand" 10 "net/http" 11 "os/exec" 12 "testing" 13 "time" 14 15 "github.com/fortytw2/leaktest" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 19 tmbytes "github.com/ari-anchor/sei-tendermint/libs/bytes" 20 "github.com/ari-anchor/sei-tendermint/libs/log" 21 "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/client" 22 "github.com/ari-anchor/sei-tendermint/rpc/jsonrpc/server" 23 ) 24 25 // Client and Server should work over tcp or unix sockets 26 const ( 27 tcpAddr = "tcp://127.0.0.1:47768" 28 29 unixSocket = "/tmp/rpc_test.sock" 30 unixAddr = "unix://" + unixSocket 31 32 websocketEndpoint = "/websocket/endpoint" 33 34 testVal = "acbd" 35 ) 36 37 type RequestEcho struct { 38 Value string `json:"arg"` 39 } 40 41 type ResultEcho struct { 42 Value string `json:"value"` 43 } 44 45 type RequestEchoInt struct { 46 Value int `json:"arg"` 47 } 48 49 type ResultEchoInt struct { 50 Value int `json:"value"` 51 } 52 53 type RequestEchoBytes struct { 54 Value []byte `json:"arg"` 55 } 56 57 type ResultEchoBytes struct { 58 Value []byte `json:"value"` 59 } 60 61 type RequestEchoDataBytes struct { 62 Value tmbytes.HexBytes `json:"arg"` 63 } 64 65 type ResultEchoDataBytes struct { 66 Value tmbytes.HexBytes `json:"value"` 67 } 68 69 // Define some routes 70 var Routes = map[string]*server.RPCFunc{ 71 "echo": server.NewRPCFunc(EchoResult), 72 "echo_ws": server.NewWSRPCFunc(EchoWSResult), 73 "echo_bytes": server.NewRPCFunc(EchoBytesResult), 74 "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult), 75 "echo_int": server.NewRPCFunc(EchoIntResult), 76 } 77 78 func EchoResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) { 79 return &ResultEcho{v.Value}, nil 80 } 81 82 func EchoWSResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) { 83 return &ResultEcho{v.Value}, nil 84 } 85 86 func EchoIntResult(ctx context.Context, v *RequestEchoInt) (*ResultEchoInt, error) { 87 return &ResultEchoInt{v.Value}, nil 88 } 89 90 func EchoBytesResult(ctx context.Context, v *RequestEchoBytes) (*ResultEchoBytes, error) { 91 return &ResultEchoBytes{v.Value}, nil 92 } 93 94 func EchoDataBytesResult(ctx context.Context, v *RequestEchoDataBytes) (*ResultEchoDataBytes, error) { 95 return &ResultEchoDataBytes{v.Value}, nil 96 } 97 98 // launch unix and tcp servers 99 func setup(ctx context.Context, t *testing.T, logger log.Logger) error { 100 cmd := exec.Command("rm", "-f", unixSocket) 101 err := cmd.Start() 102 if err != nil { 103 return err 104 } 105 if err = cmd.Wait(); err != nil { 106 return err 107 } 108 109 tcpLogger := logger.With("socket", "tcp") 110 mux := http.NewServeMux() 111 server.RegisterRPCFuncs(mux, Routes, tcpLogger) 112 wm := server.NewWebsocketManager(tcpLogger, Routes, server.ReadWait(5*time.Second), server.PingPeriod(1*time.Second)) 113 mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler) 114 config := server.DefaultConfig() 115 listener1, err := server.Listen(tcpAddr, config.MaxOpenConnections) 116 if err != nil { 117 return err 118 } 119 go func() { 120 if err := server.Serve(ctx, listener1, mux, tcpLogger, config); err != nil { 121 if !errors.Is(err, http.ErrServerClosed) { 122 require.NoError(t, err) 123 } 124 } 125 }() 126 127 unixLogger := logger.With("socket", "unix") 128 mux2 := http.NewServeMux() 129 server.RegisterRPCFuncs(mux2, Routes, unixLogger) 130 wm = server.NewWebsocketManager(unixLogger, Routes) 131 mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler) 132 listener2, err := server.Listen(unixAddr, config.MaxOpenConnections) 133 if err != nil { 134 return err 135 } 136 go func() { 137 if err := server.Serve(ctx, listener2, mux2, unixLogger, config); err != nil { 138 if !errors.Is(err, http.ErrServerClosed) { 139 require.NoError(t, err) 140 } 141 } 142 }() 143 144 // wait for servers to start 145 time.Sleep(time.Second * 2) 146 return nil 147 } 148 149 func echoViaHTTP(ctx context.Context, cl client.Caller, val string) (string, error) { 150 params := map[string]interface{}{ 151 "arg": val, 152 } 153 result := new(ResultEcho) 154 if err := cl.Call(ctx, "echo", params, result); err != nil { 155 return "", err 156 } 157 return result.Value, nil 158 } 159 160 func echoIntViaHTTP(ctx context.Context, cl client.Caller, val int) (int, error) { 161 params := map[string]interface{}{ 162 "arg": val, 163 } 164 result := new(ResultEchoInt) 165 if err := cl.Call(ctx, "echo_int", params, result); err != nil { 166 return 0, err 167 } 168 return result.Value, nil 169 } 170 171 func echoBytesViaHTTP(ctx context.Context, cl client.Caller, bytes []byte) ([]byte, error) { 172 params := map[string]interface{}{ 173 "arg": bytes, 174 } 175 result := new(ResultEchoBytes) 176 if err := cl.Call(ctx, "echo_bytes", params, result); err != nil { 177 return []byte{}, err 178 } 179 return result.Value, nil 180 } 181 182 func echoDataBytesViaHTTP(ctx context.Context, cl client.Caller, bytes tmbytes.HexBytes) (tmbytes.HexBytes, error) { 183 params := map[string]interface{}{ 184 "arg": bytes, 185 } 186 result := new(ResultEchoDataBytes) 187 if err := cl.Call(ctx, "echo_data_bytes", params, result); err != nil { 188 return []byte{}, err 189 } 190 return result.Value, nil 191 } 192 193 func testWithHTTPClient(ctx context.Context, t *testing.T, cl client.Caller) { 194 val := testVal 195 got, err := echoViaHTTP(ctx, cl, val) 196 require.NoError(t, err) 197 assert.Equal(t, got, val) 198 199 val2 := randBytes(t) 200 got2, err := echoBytesViaHTTP(ctx, cl, val2) 201 require.NoError(t, err) 202 assert.Equal(t, got2, val2) 203 204 val3 := tmbytes.HexBytes(randBytes(t)) 205 got3, err := echoDataBytesViaHTTP(ctx, cl, val3) 206 require.NoError(t, err) 207 assert.Equal(t, got3, val3) 208 209 val4 := mrand.Intn(10000) 210 got4, err := echoIntViaHTTP(ctx, cl, val4) 211 require.NoError(t, err) 212 assert.Equal(t, got4, val4) 213 } 214 215 func echoViaWS(ctx context.Context, cl *client.WSClient, val string) (string, error) { 216 params := map[string]interface{}{ 217 "arg": val, 218 } 219 err := cl.Call(ctx, "echo", params) 220 if err != nil { 221 return "", err 222 } 223 224 msg := <-cl.ResponsesCh 225 if msg.Error != nil { 226 return "", err 227 228 } 229 result := new(ResultEcho) 230 err = json.Unmarshal(msg.Result, result) 231 if err != nil { 232 return "", nil 233 } 234 return result.Value, nil 235 } 236 237 func echoBytesViaWS(ctx context.Context, cl *client.WSClient, bytes []byte) ([]byte, error) { 238 params := map[string]interface{}{ 239 "arg": bytes, 240 } 241 err := cl.Call(ctx, "echo_bytes", params) 242 if err != nil { 243 return []byte{}, err 244 } 245 246 msg := <-cl.ResponsesCh 247 if msg.Error != nil { 248 return []byte{}, msg.Error 249 250 } 251 result := new(ResultEchoBytes) 252 err = json.Unmarshal(msg.Result, result) 253 if err != nil { 254 return []byte{}, nil 255 } 256 return result.Value, nil 257 } 258 259 func testWithWSClient(ctx context.Context, t *testing.T, cl *client.WSClient) { 260 val := testVal 261 got, err := echoViaWS(ctx, cl, val) 262 require.NoError(t, err) 263 assert.Equal(t, got, val) 264 265 val2 := randBytes(t) 266 got2, err := echoBytesViaWS(ctx, cl, val2) 267 require.NoError(t, err) 268 assert.Equal(t, got2, val2) 269 } 270 271 //------------- 272 273 func TestRPC(t *testing.T) { 274 ctx, cancel := context.WithCancel(context.Background()) 275 defer cancel() 276 logger := log.NewNopLogger() 277 278 t.Cleanup(leaktest.Check(t)) 279 require.NoError(t, setup(ctx, t, logger)) 280 t.Run("ServersAndClientsBasic", func(t *testing.T) { 281 serverAddrs := [...]string{tcpAddr, unixAddr} 282 for _, addr := range serverAddrs { 283 t.Run(addr, func(t *testing.T) { 284 tctx, tcancel := context.WithCancel(ctx) 285 defer tcancel() 286 287 logger := log.NewNopLogger() 288 289 cl2, err := client.New(addr) 290 require.NoError(t, err) 291 t.Logf("testing server with JSONRPC client") 292 testWithHTTPClient(tctx, t, cl2) 293 294 cl3, err := client.NewWS(addr, websocketEndpoint) 295 require.NoError(t, err) 296 cl3.Logger = logger 297 err = cl3.Start(tctx) 298 require.NoError(t, err) 299 t.Logf("testing server with WS client") 300 testWithWSClient(tctx, t, cl3) 301 }) 302 } 303 }) 304 t.Run("WSNewWSRPCFunc", func(t *testing.T) { 305 t.Cleanup(leaktest.CheckTimeout(t, 4*time.Second)) 306 307 cl, err := client.NewWS(tcpAddr, websocketEndpoint) 308 require.NoError(t, err) 309 cl.Logger = log.NewNopLogger() 310 tctx, tcancel := context.WithCancel(ctx) 311 defer tcancel() 312 313 require.NoError(t, cl.Start(tctx)) 314 t.Cleanup(func() { 315 if err := cl.Stop(); err != nil { 316 t.Error(err) 317 } 318 }) 319 320 val := testVal 321 params := map[string]interface{}{ 322 "arg": val, 323 } 324 err = cl.Call(tctx, "echo_ws", params) 325 require.NoError(t, err) 326 327 select { 328 case <-tctx.Done(): 329 t.Fatal(tctx.Err()) 330 case msg := <-cl.ResponsesCh: 331 if msg.Error != nil { 332 t.Fatal(err) 333 } 334 result := new(ResultEcho) 335 err = json.Unmarshal(msg.Result, result) 336 require.NoError(t, err) 337 got := result.Value 338 assert.Equal(t, got, val) 339 340 } 341 }) 342 t.Run("WSClientPingPong", func(t *testing.T) { 343 // TestWSClientPingPong checks that a client & server exchange pings 344 // & pongs so connection stays alive. 345 t.Cleanup(leaktest.CheckTimeout(t, 4*time.Second)) 346 347 cl, err := client.NewWS(tcpAddr, websocketEndpoint) 348 require.NoError(t, err) 349 cl.Logger = log.NewNopLogger() 350 351 tctx, tcancel := context.WithCancel(ctx) 352 defer tcancel() 353 354 require.NoError(t, cl.Start(tctx)) 355 t.Cleanup(func() { 356 if err := cl.Stop(); err != nil { 357 t.Error(err) 358 } 359 }) 360 361 time.Sleep(6 * time.Second) 362 }) 363 } 364 365 func randBytes(t *testing.T) []byte { 366 n := mrand.Intn(10) + 2 367 buf := make([]byte, n) 368 _, err := crand.Read(buf) 369 require.NoError(t, err) 370 return bytes.ReplaceAll(buf, []byte("="), []byte{100}) 371 }