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  }