code.vegaprotocol.io/vega@v0.79.0/libs/jsonrpc/dispatcher_test.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package jsonrpc_test 17 18 import ( 19 "context" 20 "fmt" 21 "sort" 22 "testing" 23 24 "code.vegaprotocol.io/vega/libs/jsonrpc" 25 "code.vegaprotocol.io/vega/libs/jsonrpc/mocks" 26 vgrand "code.vegaprotocol.io/vega/libs/rand" 27 28 "github.com/golang/mock/gomock" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 "go.uber.org/zap" 32 "go.uber.org/zap/zaptest" 33 ) 34 35 func TestDispatcher(t *testing.T) { 36 t.Run("API only supports JSON-RPC 2.0 requests", testAPIOnlySupportsJSONRPC2Request) 37 t.Run("Method is required", testMethodIsRequired) 38 t.Run("Dispatching a request succeeds", testDispatchingRequestSucceeds) 39 t.Run("Dispatching a request with unsatisfied interceptor fails", testDispatchingRequestWithUnsatisfiedInterceptorFails) 40 t.Run("Dispatching a request with satisfied interceptor succeeds", testDispatchingRequestWithSatisfiedInterceptorSucceeds) 41 t.Run("Dispatching an unknown request fails", testDispatchingUnknownRequestFails) 42 t.Run("Failed commands return an error response", testFailedCommandReturnErrorResponse) 43 t.Run("Listing registered methods succeeds", testListingRegisteredMethodsSucceeds) 44 } 45 46 func testAPIOnlySupportsJSONRPC2Request(t *testing.T) { 47 // given 48 dispatcher := newDispatcher(t) 49 ctx := testContextWithTraceID() 50 params := struct { 51 Name string `json:"name"` 52 }{ 53 Name: vgrand.RandomStr(5), 54 } 55 request := jsonrpc.Request{ 56 Version: "1.0", 57 Method: vgrand.RandomStr(5), // Unregistered method. 58 Params: params, 59 ID: vgrand.RandomStr(5), 60 } 61 62 // when 63 response := dispatcher.DispatchRequest(ctx, request) 64 65 // then 66 require.NotNil(t, response) 67 assert.Equal(t, jsonrpc.VERSION2, response.Version) 68 assert.Equal(t, request.ID, response.ID) 69 assert.Nil(t, response.Result) 70 assert.Equal(t, &jsonrpc.ErrorDetails{ 71 Code: jsonrpc.ErrorCodeInvalidRequest, 72 Message: "Invalid Request", 73 Data: jsonrpc.ErrOnlySupportJSONRPC2.Error(), 74 }, response.Error) 75 } 76 77 func testMethodIsRequired(t *testing.T) { 78 // given 79 dispatcher := newDispatcher(t) 80 ctx := testContextWithTraceID() 81 params := struct { 82 Name string `json:"name"` 83 }{ 84 Name: vgrand.RandomStr(5), 85 } 86 request := jsonrpc.Request{ 87 Version: jsonrpc.VERSION2, 88 Method: "", 89 Params: params, 90 ID: vgrand.RandomStr(5), 91 } 92 93 // when 94 response := dispatcher.DispatchRequest(ctx, request) 95 96 // then 97 require.NotNil(t, response) 98 assert.Equal(t, jsonrpc.VERSION2, response.Version) 99 assert.Equal(t, request.ID, response.ID) 100 assert.Nil(t, response.Result) 101 assert.Equal(t, &jsonrpc.ErrorDetails{ 102 Code: jsonrpc.ErrorCodeInvalidRequest, 103 Message: "Invalid Request", 104 Data: jsonrpc.ErrMethodIsRequired.Error(), 105 }, response.Error) 106 } 107 108 func testDispatchingRequestSucceeds(t *testing.T) { 109 // given 110 dispatcher := newDispatcher(t) 111 ctx := testContextWithTraceID() 112 params := struct { 113 Name string `json:"name"` 114 }{ 115 Name: vgrand.RandomStr(5), 116 } 117 request := jsonrpc.Request{ 118 Version: jsonrpc.VERSION2, 119 Method: dispatcher.method1, 120 Params: params, 121 ID: vgrand.RandomStr(5), 122 } 123 expectedResult := vgrand.RandomStr(5) 124 125 // setup 126 dispatcher.command1.EXPECT().Handle(ctx, request.Params).Times(1).Return(expectedResult, nil) 127 128 // when 129 response := dispatcher.DispatchRequest(ctx, request) 130 131 // then 132 require.NotNil(t, response) 133 assert.Equal(t, jsonrpc.VERSION2, response.Version) 134 assert.Equal(t, request.ID, response.ID) 135 assert.Equal(t, expectedResult, response.Result) 136 assert.Nil(t, response.Error) 137 } 138 139 func testDispatchingRequestWithUnsatisfiedInterceptorFails(t *testing.T) { 140 // given 141 dispatcher := newDispatcher(t) 142 ctx := testContextWithTraceID() 143 params := struct { 144 Name string `json:"name"` 145 }{ 146 Name: vgrand.RandomStr(5), 147 } 148 request := jsonrpc.Request{ 149 Version: jsonrpc.VERSION2, 150 Method: vgrand.RandomStr(5), // Unregistered method. 151 Params: params, 152 ID: vgrand.RandomStr(5), 153 } 154 expectedErrDetails := &jsonrpc.ErrorDetails{ 155 Code: jsonrpc.ErrorCode(1234), 156 Message: vgrand.RandomStr(10), 157 Data: vgrand.RandomStr(10), 158 } 159 160 // setup 161 dispatcher.AddInterceptor(func(_ context.Context, _ jsonrpc.Request) *jsonrpc.ErrorDetails { 162 return expectedErrDetails 163 }) 164 165 // when 166 response := dispatcher.DispatchRequest(ctx, request) 167 168 // then 169 require.NotNil(t, response) 170 assert.Equal(t, jsonrpc.VERSION2, response.Version) 171 assert.Equal(t, request.ID, response.ID) 172 assert.Nil(t, response.Result) 173 assert.Equal(t, expectedErrDetails, response.Error) 174 } 175 176 func testDispatchingRequestWithSatisfiedInterceptorSucceeds(t *testing.T) { 177 // given 178 dispatcher := newDispatcher(t) 179 ctx := testContextWithTraceID() 180 params := struct { 181 Name string `json:"name"` 182 }{ 183 Name: vgrand.RandomStr(5), 184 } 185 request := jsonrpc.Request{ 186 Version: jsonrpc.VERSION2, 187 Method: dispatcher.method1, 188 Params: params, 189 ID: vgrand.RandomStr(5), 190 } 191 expectedResult := vgrand.RandomStr(5) 192 193 // setup 194 dispatcher.AddInterceptor(func(_ context.Context, _ jsonrpc.Request) *jsonrpc.ErrorDetails { 195 return nil 196 }) 197 dispatcher.command1.EXPECT().Handle(ctx, request.Params).Times(1).Return(expectedResult, nil) 198 199 // when 200 response := dispatcher.DispatchRequest(ctx, request) 201 202 // then 203 require.NotNil(t, response) 204 assert.Equal(t, jsonrpc.VERSION2, response.Version) 205 assert.Equal(t, request.ID, response.ID) 206 assert.Equal(t, expectedResult, response.Result) 207 assert.Nil(t, response.Error) 208 } 209 210 func testDispatchingUnknownRequestFails(t *testing.T) { 211 // given 212 dispatcher := newDispatcher(t) 213 ctx := testContextWithTraceID() 214 params := struct { 215 Name string `json:"name"` 216 }{ 217 Name: vgrand.RandomStr(5), 218 } 219 request := jsonrpc.Request{ 220 Version: jsonrpc.VERSION2, 221 Method: vgrand.RandomStr(5), // Unregistered method. 222 Params: params, 223 ID: vgrand.RandomStr(5), 224 } 225 226 // when 227 response := dispatcher.DispatchRequest(ctx, request) 228 229 // then 230 require.NotNil(t, response) 231 assert.Equal(t, jsonrpc.VERSION2, response.Version) 232 assert.Equal(t, request.ID, response.ID) 233 assert.Nil(t, response.Result) 234 assert.Equal(t, &jsonrpc.ErrorDetails{ 235 Code: jsonrpc.ErrorCodeMethodNotFound, 236 Message: "Method not found", 237 Data: fmt.Sprintf("method %q is not supported", request.Method), 238 }, response.Error) 239 } 240 241 func testFailedCommandReturnErrorResponse(t *testing.T) { 242 // given 243 dispatcher := newDispatcher(t) 244 ctx := testContextWithTraceID() 245 params := struct { 246 Name string `json:"name"` 247 }{ 248 Name: vgrand.RandomStr(5), 249 } 250 request := jsonrpc.Request{ 251 Version: jsonrpc.VERSION2, 252 Method: dispatcher.method1, // Unregistered method. 253 Params: params, 254 ID: vgrand.RandomStr(5), 255 } 256 expectedError := &jsonrpc.ErrorDetails{ 257 Code: 23456, 258 Message: vgrand.RandomStr(5), 259 Data: vgrand.RandomStr(5), 260 } 261 262 // setup 263 dispatcher.command1.EXPECT().Handle(ctx, request.Params).Times(1).Return(nil, expectedError) 264 265 // when 266 response := dispatcher.DispatchRequest(ctx, request) 267 268 // then 269 require.NotNil(t, response) 270 assert.Equal(t, jsonrpc.VERSION2, response.Version) 271 assert.Equal(t, request.ID, response.ID) 272 assert.Nil(t, response.Result) 273 assert.Equal(t, expectedError, response.Error) 274 } 275 276 func testListingRegisteredMethodsSucceeds(t *testing.T) { 277 // given 278 dispatcher := newDispatcher(t) 279 280 // when 281 methods := dispatcher.RegisteredMethods() 282 283 // then 284 require.NotNil(t, methods) 285 expectedMethods := []string{dispatcher.method1, dispatcher.method2} 286 sort.Strings(expectedMethods) 287 assert.Equal(t, expectedMethods, methods) 288 } 289 290 type testAPI struct { 291 *jsonrpc.Dispatcher 292 method1 string 293 command1 *mocks.MockCommand 294 method2 string 295 command2 *mocks.MockCommand 296 } 297 298 func newDispatcher(t *testing.T) *testAPI { 299 t.Helper() 300 log := newTestLogger(t) 301 ctrl := gomock.NewController(t) 302 method1 := vgrand.RandomStr(5) 303 command1 := mocks.NewMockCommand(ctrl) 304 method2 := vgrand.RandomStr(5) 305 command2 := mocks.NewMockCommand(ctrl) 306 307 // setup 308 dispatcher := jsonrpc.NewDispatcher(log) 309 dispatcher.RegisterMethod(method1, command1) 310 dispatcher.RegisterMethod(method2, command2) 311 312 return &testAPI{ 313 Dispatcher: dispatcher, 314 method1: method1, 315 command1: command1, 316 method2: method2, 317 command2: command2, 318 } 319 } 320 321 func newTestLogger(t *testing.T) *zap.Logger { 322 t.Helper() 323 // Change the level to debug for debugging. 324 // Keep it to Panic otherwise to not pollute tests output. 325 return zaptest.NewLogger(t, zaptest.Level(zap.PanicLevel)) 326 } 327 328 func testContextWithTraceID() context.Context { 329 return context.WithValue(context.Background(), jsonrpc.TraceIDKey, vgrand.RandomStr(64)) 330 }