code.vegaprotocol.io/vega@v0.79.0/wallet/service/service_v2_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 service_test
    17  
    18  import (
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  	"reflect"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"code.vegaprotocol.io/vega/libs/jsonrpc"
    28  	"code.vegaprotocol.io/vega/libs/ptr"
    29  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    30  	"code.vegaprotocol.io/vega/wallet/api"
    31  	v2 "code.vegaprotocol.io/vega/wallet/service/v2"
    32  	"code.vegaprotocol.io/vega/wallet/service/v2/connections"
    33  	"code.vegaprotocol.io/vega/wallet/wallet"
    34  
    35  	"github.com/golang/mock/gomock"
    36  	"github.com/mitchellh/mapstructure"
    37  	"github.com/stretchr/testify/assert"
    38  	"github.com/stretchr/testify/require"
    39  )
    40  
    41  func TestServiceV2(t *testing.T) {
    42  	t.Run("GET /api/v2/health", testServiceV2_GetHealth)
    43  	t.Run("GET /api/v2/methods", testServiceV2_GetMethods)
    44  	t.Run("POST /api/v2/requests", testServiceV2_PostRequests)
    45  }
    46  
    47  func testServiceV2_GetHealth(t *testing.T) {
    48  	t.Run("Checking health succeeds", testServiceV2_GetHealth_CheckingHealthSucceeds)
    49  }
    50  
    51  func testServiceV2_GetHealth_CheckingHealthSucceeds(t *testing.T) {
    52  	// setup
    53  	s := getTestServiceV2(t)
    54  
    55  	// when
    56  	statusCode, _, response := s.serveHTTP(t, buildRequest(t, http.MethodGet, "/api/v2/health", "", nil))
    57  
    58  	// then
    59  	require.Equal(t, http.StatusOK, statusCode)
    60  	assert.Empty(t, response)
    61  }
    62  
    63  func testServiceV2_GetMethods(t *testing.T) {
    64  	t.Run("Listing methods succeeds", testServiceV2_GetMethods_ListingMethodsSucceeds)
    65  }
    66  
    67  func testServiceV2_GetMethods_ListingMethodsSucceeds(t *testing.T) {
    68  	// setup
    69  	s := getTestServiceV2(t)
    70  
    71  	// when
    72  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodGet, "/api/v2/methods", "", nil))
    73  
    74  	// then
    75  	require.Equal(t, http.StatusOK, statusCode)
    76  	response := intoGetMethodsResponse(t, rawResponse)
    77  	assert.Equal(t, []string{
    78  		"client.check_transaction",
    79  		"client.connect_wallet",
    80  		"client.disconnect_wallet",
    81  		"client.get_chain_id",
    82  		"client.list_keys",
    83  		"client.send_transaction",
    84  		"client.sign_transaction",
    85  	}, response.Result.RegisteredMethods)
    86  }
    87  
    88  type getMethodsResponse struct {
    89  	Result struct {
    90  		RegisteredMethods []string `json:"registeredMethods"`
    91  	} `json:"result,omitempty"`
    92  }
    93  
    94  func intoGetMethodsResponse(t *testing.T, response []byte) *getMethodsResponse {
    95  	t.Helper()
    96  	resp := &getMethodsResponse{}
    97  	if err := json.Unmarshal(response, resp); err != nil {
    98  		t.Fatalf("couldn't unmarshal response from /api/v2/methods: %v", err)
    99  	}
   100  	return resp
   101  }
   102  
   103  func testServiceV2_PostRequests(t *testing.T) {
   104  	t.Run("Posting a malformed request fails", testServiceV2_PostRequests_MalformedRequestFails)
   105  	t.Run("Posting an invalid request fails", testServiceV2_PostRequests_InvalidRequestFails)
   106  	t.Run("Posting a request calling an admin method fails", testServiceV2_PostRequests_CallingAdminMethodFails)
   107  	t.Run("Posting a request calling an unknown method fails", testServiceV2_PostRequests_CallingUnknownMethodFails)
   108  	t.Run("`client.get_chain_id` succeeds", testServiceV2_PostRequests_GetChainIDSucceeds)
   109  	t.Run("`client.get_chain_id` as notification returns nothing", testServiceV2_PostRequests_GetChainIDAsNotificationReturnsNothing)
   110  	t.Run("`client.get_chain_id` getting error fails", testServiceV2_PostRequests_GetChainIDGettingErrorFails)
   111  	t.Run("`client.get_chain_id` getting internal error fails", testServiceV2_PostRequests_GetChainIDGettingInternalErrorFails)
   112  	t.Run("`client.connect_wallet` succeeds", testServiceV2_PostRequests_ConnectWalletSucceeds)
   113  	t.Run("`client.connect_wallet` without origin fails", testServiceV2_PostRequests_ConnectWalletWithoutOriginFails)
   114  	t.Run("`client.connect_wallet` with invalid origin fails", testServiceV2_PostRequests_ConnectWalletWithInvalidOriginFails)
   115  	t.Run("`client.connect_wallet` as notification returns nothing", testServiceV2_PostRequests_ConnectWalletAsNotificationReturnsNothing)
   116  	t.Run("`client.connect_wallet` getting error fails", testServiceV2_PostRequests_ConnectWalletGettingErrorFails)
   117  	t.Run("`client.connect_wallet` getting internal error fails", testServiceV2_PostRequests_ConnectWalletGettingInternalErrorFails)
   118  	t.Run("`client.disconnect_wallet` succeeds", testServiceV2_PostRequests_DisconnectWalletSucceeds)
   119  	t.Run("`client.list_keys` succeeds", testServiceV2_PostRequests_ListKeysSucceeds)
   120  	t.Run("`client.list_keys` without origin fails", testServiceV2_PostRequests_ListKeysWithoutOriginFails)
   121  	t.Run("`client.list_keys` without token fails", testServiceV2_PostRequests_ListKeysWithoutTokenFails)
   122  	t.Run("`client.list_keys` with unknown token fails", testServiceV2_PostRequests_ListKeysWithUnknownTokenFails)
   123  	t.Run("`client.list_keys` with origin not matching the original hostname fails", testServiceV2_PostRequests_ListKeysWithMismatchingHostnameFails)
   124  	t.Run("`client.list_keys` as notification returns nothing", testServiceV2_PostRequests_ListKeysAsNotificationReturnsNothing)
   125  	t.Run("`client.list_keys` getting error fails", testServiceV2_PostRequests_ListKeysGettingErrorFails)
   126  	t.Run("`client.list_keys` getting internal error fails", testServiceV2_PostRequests_ListKeysGettingInternalErrorFails)
   127  	t.Run("`client.list_keys` with expired long-living token fails", testServiceV2_PostRequests_ListKeysWithExpiredLongLivingTokenFails)
   128  	t.Run("`client.list_keys` with long-living token succeeds", testServiceV2_PostRequests_ListKeysWithLongLivingTokenSucceeds)
   129  	t.Run("`client.send_transaction` succeeds", testServiceV2_PostRequests_SendTransactionSucceeds)
   130  	t.Run("`client.send_transaction` without origin fails", testServiceV2_PostRequests_SendTransactionWithoutOriginFails)
   131  	t.Run("`client.send_transaction` without token fails", testServiceV2_PostRequests_SendTransactionWithoutTokenFails)
   132  	t.Run("`client.send_transaction` with unknown token fails", testServiceV2_PostRequests_SendTransactionWithUnknownTokenFails)
   133  	t.Run("`client.send_transaction` with origin not matching the original hostname fails", testServiceV2_PostRequests_SendTransactionWithMismatchingHostnameFails)
   134  	t.Run("`client.send_transaction` as notification returns nothing", testServiceV2_PostRequests_SendTransactionAsNotificationReturnsNothing)
   135  	t.Run("`client.send_transaction` getting error fails", testServiceV2_PostRequests_SendTransactionGettingErrorFails)
   136  	t.Run("`client.send_transaction` getting internal error fails", testServiceV2_PostRequests_SendTransactionGettingInternalErrorFails)
   137  	t.Run("`client.send_transaction` with expired long-living token fails", testServiceV2_PostRequests_SendTransactionWithExpiredLongLivingTokenFails)
   138  	t.Run("`client.send_transaction` with long-living token succeeds", testServiceV2_PostRequests_SendTransactionWithLongLivingTokenSucceeds)
   139  }
   140  
   141  func testServiceV2_PostRequests_MalformedRequestFails(t *testing.T) {
   142  	// given
   143  	reqBody := `"not-a-valid-json-rpc-request"`
   144  
   145  	// setup
   146  	s := getTestServiceV2(t)
   147  
   148  	// when
   149  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   150  
   151  	// then
   152  	require.Equal(t, http.StatusBadRequest, statusCode)
   153  	// Since the request can't even be unmarshall, we expect no ID
   154  	rpcErr := intoJSONRPCError(t, rawResponse, "")
   155  	assert.Equal(t, "Parse error", rpcErr.Message)
   156  	assert.Equal(t, jsonrpc.ErrorCodeParseError, rpcErr.Code)
   157  	assert.NotEmpty(t, rpcErr.Data)
   158  }
   159  
   160  func testServiceV2_PostRequests_InvalidRequestFails(t *testing.T) {
   161  	// setup
   162  	s := getTestServiceV2(t)
   163  
   164  	tcs := []struct {
   165  		name    string
   166  		reqBody string
   167  		error   error
   168  	}{
   169  		{
   170  			name:    "with invalid version",
   171  			reqBody: `{"jsonrpc": "1000", "method": "hack_the_world", "id": "123456789"}`,
   172  			error:   jsonrpc.ErrOnlySupportJSONRPC2,
   173  		}, {
   174  			name:    "without method specified",
   175  			reqBody: `{"jsonrpc": "2.0", "method": "", "id": "123456789"}`,
   176  			error:   jsonrpc.ErrMethodIsRequired,
   177  		},
   178  	}
   179  
   180  	for _, tc := range tcs {
   181  		t.Run(tc.name, func(tt *testing.T) {
   182  			// given
   183  			statusCode, _, rawResponse := s.serveHTTP(tt, buildRequest(t, http.MethodPost, "/api/v2/requests", tc.reqBody, nil))
   184  
   185  			// then
   186  			require.Equal(tt, http.StatusBadRequest, statusCode)
   187  			rpcErr := intoJSONRPCError(tt, rawResponse, "123456789")
   188  			assert.Equal(tt, "Invalid Request", rpcErr.Message)
   189  			assert.Equal(tt, jsonrpc.ErrorCodeInvalidRequest, rpcErr.Code)
   190  			assert.Equal(tt, tc.error.Error(), rpcErr.Data)
   191  		})
   192  	}
   193  }
   194  
   195  func testServiceV2_PostRequests_CallingAdminMethodFails(t *testing.T) {
   196  	// given
   197  	reqBody := `{"jsonrpc": "2.0", "method": "admin.create_wallet", "id": "123456789"}`
   198  
   199  	// setup
   200  	s := getTestServiceV2(t)
   201  
   202  	// when
   203  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   204  
   205  	// then
   206  	require.Equal(t, http.StatusBadRequest, statusCode)
   207  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   208  	assert.Equal(t, "Method not found", rpcErr.Message)
   209  	assert.Equal(t, jsonrpc.ErrorCodeMethodNotFound, rpcErr.Code)
   210  	assert.Equal(t, v2.ErrAdminEndpointsNotExposed.Error(), rpcErr.Data)
   211  }
   212  
   213  func testServiceV2_PostRequests_CallingUnknownMethodFails(t *testing.T) {
   214  	// given
   215  	reqBody := `{"jsonrpc": "2.0", "method": "client.create_wallet", "id": "123456789"}`
   216  
   217  	// setup
   218  	s := getTestServiceV2(t)
   219  
   220  	// when
   221  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   222  
   223  	// then
   224  	require.Equal(t, http.StatusBadRequest, statusCode)
   225  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   226  	assert.Equal(t, "Method not found", rpcErr.Message)
   227  	assert.Equal(t, jsonrpc.ErrorCodeMethodNotFound, rpcErr.Code)
   228  	assert.Equal(t, "method \"client.create_wallet\" is not supported", rpcErr.Data)
   229  }
   230  
   231  func testServiceV2_PostRequests_GetChainIDSucceeds(t *testing.T) {
   232  	// given
   233  	expectedChainID := vgrand.RandomStr(5)
   234  	reqBody := `{"jsonrpc": "2.0", "method": "client.get_chain_id", "id": "123456789"}`
   235  
   236  	// setup
   237  	s := getTestServiceV2(t)
   238  	s.clientAPI.EXPECT().GetChainID(gomock.Any()).Times(1).Return(&api.ClientGetChainIDResult{
   239  		ChainID: expectedChainID,
   240  	}, nil)
   241  
   242  	// when
   243  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   244  
   245  	// then
   246  	require.Equal(t, http.StatusOK, statusCode)
   247  	result := intoClientGetChainIDResult(t, rawResponse, "123456789")
   248  	assert.Equal(t, expectedChainID, result.ChainID)
   249  }
   250  
   251  func testServiceV2_PostRequests_GetChainIDAsNotificationReturnsNothing(t *testing.T) {
   252  	// given
   253  	expectedChainID := vgrand.RandomStr(5)
   254  	reqBody := `{"jsonrpc": "2.0", "method": "client.get_chain_id"}`
   255  
   256  	// setup
   257  	s := getTestServiceV2(t)
   258  	s.clientAPI.EXPECT().GetChainID(gomock.Any()).Times(1).Return(&api.ClientGetChainIDResult{
   259  		ChainID: expectedChainID,
   260  	}, nil)
   261  
   262  	// when
   263  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   264  
   265  	// then
   266  	require.Equal(t, http.StatusNoContent, statusCode)
   267  	result := intoJSONRPCResult(t, rawResponse, "")
   268  	assert.Empty(t, result)
   269  }
   270  
   271  func testServiceV2_PostRequests_GetChainIDGettingErrorFails(t *testing.T) {
   272  	// given
   273  	reqBody := `{"jsonrpc": "2.0", "method": "client.get_chain_id", "id": "123456789"}`
   274  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   275  		Code:    123,
   276  		Message: vgrand.RandomStr(10),
   277  		Data:    vgrand.RandomStr(10),
   278  	}
   279  
   280  	// setup
   281  	s := getTestServiceV2(t)
   282  	s.clientAPI.EXPECT().GetChainID(gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
   283  
   284  	// when
   285  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   286  
   287  	// then
   288  	require.Equal(t, http.StatusBadRequest, statusCode)
   289  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   290  	assert.Equal(t, expectedErrorDetails, rpcErr)
   291  }
   292  
   293  func testServiceV2_PostRequests_GetChainIDGettingInternalErrorFails(t *testing.T) {
   294  	// given
   295  	reqBody := `{"jsonrpc": "2.0", "method": "client.get_chain_id", "id": "123456789"}`
   296  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   297  		Code:    123,
   298  		Message: "Internal error",
   299  		Data:    vgrand.RandomStr(10),
   300  	}
   301  
   302  	// setup
   303  	s := getTestServiceV2(t)
   304  	s.clientAPI.EXPECT().GetChainID(gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
   305  
   306  	// when
   307  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   308  
   309  	// then
   310  	require.Equal(t, http.StatusInternalServerError, statusCode)
   311  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   312  	assert.Equal(t, expectedErrorDetails, rpcErr)
   313  }
   314  
   315  func testServiceV2_PostRequests_ConnectWalletSucceeds(t *testing.T) {
   316  	// given
   317  	expectedHostname := vgrand.RandomStr(5)
   318  	reqBody := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   319  	w := newWallet(t)
   320  
   321  	// setup
   322  	s := getTestServiceV2(t)
   323  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   324  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   325  
   326  	// when
   327  	statusCode, responseHeaders, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   328  		"Origin": expectedHostname,
   329  	}))
   330  
   331  	// then
   332  	require.Equal(t, http.StatusOK, statusCode)
   333  	vwt := responseHeaders.Get("Authorization")
   334  	assert.NotEmpty(t, vwt)
   335  	assert.True(t, strings.HasPrefix(vwt, "VWT "))
   336  	assert.True(t, len(vwt) > len("VWT "))
   337  	result := intoJSONRPCResult(t, rawResponse, "123456789")
   338  	assert.Empty(t, result)
   339  }
   340  
   341  func testServiceV2_PostRequests_ConnectWalletWithoutOriginFails(t *testing.T) {
   342  	// given
   343  	reqBody := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   344  
   345  	// setup
   346  	s := getTestServiceV2(t)
   347  
   348  	// when
   349  	statusCode, responseHeaders, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   350  
   351  	// then
   352  	require.Equal(t, http.StatusBadRequest, statusCode)
   353  	vwt := responseHeaders.Get("Authorization")
   354  	assert.Empty(t, vwt)
   355  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   356  	assert.Equal(t, "Server error", rpcErr.Message)
   357  	assert.Equal(t, api.ErrorCodeHostnameResolutionFailure, rpcErr.Code)
   358  	assert.Equal(t, v2.ErrOriginHeaderIsRequired.Error(), rpcErr.Data)
   359  }
   360  
   361  func testServiceV2_PostRequests_ConnectWalletWithInvalidOriginFails(t *testing.T) {
   362  	// given
   363  	reqBody := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   364  
   365  	// setup
   366  	s := getTestServiceV2(t)
   367  
   368  	// when
   369  	statusCode, responseHeaders, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   370  		"Origin": "Contains 世界",
   371  	}))
   372  
   373  	// then
   374  	require.Equal(t, http.StatusBadRequest, statusCode)
   375  	vwt := responseHeaders.Get("Authorization")
   376  	assert.Empty(t, vwt)
   377  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   378  	assert.Equal(t, "Header Origin contains invalid characters", rpcErr.Message)
   379  	assert.Equal(t, jsonrpc.ErrorCodeInvalidRequest, rpcErr.Code)
   380  }
   381  
   382  func testServiceV2_PostRequests_ConnectWalletAsNotificationReturnsNothing(t *testing.T) {
   383  	// given
   384  	expectedHostname := vgrand.RandomStr(5)
   385  	reqBody := `{"jsonrpc": "2.0", "method": "client.connect_wallet"}`
   386  	w := newWallet(t)
   387  
   388  	// setup
   389  	s := getTestServiceV2(t)
   390  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   391  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   392  
   393  	// when
   394  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   395  		"Origin": expectedHostname,
   396  	}))
   397  
   398  	// then
   399  	require.Equal(t, http.StatusNoContent, statusCode)
   400  	result := intoJSONRPCResult(t, rawResponse, "")
   401  	assert.Empty(t, result)
   402  }
   403  
   404  func testServiceV2_PostRequests_ConnectWalletGettingErrorFails(t *testing.T) {
   405  	// given
   406  	reqBody := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   407  	expectedHostname := vgrand.RandomStr(5)
   408  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   409  		Code:    123,
   410  		Message: vgrand.RandomStr(10),
   411  		Data:    vgrand.RandomStr(10),
   412  	}
   413  
   414  	// setup
   415  	s := getTestServiceV2(t)
   416  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(nil, expectedErrorDetails)
   417  
   418  	// when
   419  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   420  		"Origin": expectedHostname,
   421  	}))
   422  
   423  	// then
   424  	require.Equal(t, http.StatusBadRequest, statusCode)
   425  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   426  	assert.Equal(t, expectedErrorDetails, rpcErr)
   427  }
   428  
   429  func testServiceV2_PostRequests_ConnectWalletGettingInternalErrorFails(t *testing.T) {
   430  	// given
   431  	reqBody := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   432  	expectedHostname := vgrand.RandomStr(5)
   433  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   434  		Code:    123,
   435  		Message: "Internal error",
   436  		Data:    vgrand.RandomStr(10),
   437  	}
   438  
   439  	// setup
   440  	s := getTestServiceV2(t)
   441  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(nil, expectedErrorDetails)
   442  
   443  	// when
   444  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   445  		"Origin": expectedHostname,
   446  	}))
   447  
   448  	// then
   449  	require.Equal(t, http.StatusInternalServerError, statusCode)
   450  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   451  	assert.Equal(t, expectedErrorDetails, rpcErr)
   452  }
   453  
   454  func testServiceV2_PostRequests_DisconnectWalletSucceeds(t *testing.T) {
   455  	s := getTestServiceV2(t)
   456  
   457  	// given
   458  	expectedHostname := vgrand.RandomStr(5)
   459  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   460  	w := newWallet(t)
   461  
   462  	// setup
   463  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   464  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   465  
   466  	// when
   467  	connectionStatusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   468  		"Origin": expectedHostname,
   469  	}))
   470  
   471  	// then
   472  	require.Equal(t, http.StatusOK, connectionStatusCode)
   473  
   474  	// given
   475  	reqBodyDisconnectWallet := `{"jsonrpc": "2.0", "method": "client.disconnect_wallet", "id": "123456789"}`
   476  
   477  	// when
   478  	disconnectionStatusCode, _, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyDisconnectWallet, map[string]string{
   479  		"Origin":        expectedHostname,
   480  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   481  	}))
   482  
   483  	// then
   484  	require.Equal(t, http.StatusOK, disconnectionStatusCode)
   485  
   486  	// given
   487  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   488  
   489  	// when
   490  	listStatusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   491  		"Origin":        expectedHostname,
   492  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   493  	}))
   494  
   495  	// then
   496  	require.Equal(t, http.StatusUnauthorized, listStatusCode)
   497  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   498  	assert.Equal(t, "Server error", rpcErr.Message)
   499  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   500  	assert.Equal(t, connections.ErrNoConnectionAssociatedThisAuthenticationToken.Error(), rpcErr.Data)
   501  }
   502  
   503  func testServiceV2_PostRequests_ListKeysSucceeds(t *testing.T) {
   504  	s := getTestServiceV2(t)
   505  
   506  	// given
   507  	expectedHostname := vgrand.RandomStr(5)
   508  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   509  	w := newWallet(t)
   510  
   511  	// setup
   512  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   513  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   514  
   515  	// when
   516  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   517  		"Origin": expectedHostname,
   518  	}))
   519  
   520  	// then
   521  	require.Equal(t, http.StatusOK, statusCode)
   522  
   523  	// given
   524  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   525  	expectedKeys := []api.ClientNamedPublicKey{
   526  		{
   527  			Name:      vgrand.RandomStr(5),
   528  			PublicKey: vgrand.RandomStr(64),
   529  		}, {
   530  			Name:      vgrand.RandomStr(5),
   531  			PublicKey: vgrand.RandomStr(64),
   532  		},
   533  	}
   534  
   535  	// setup
   536  	s.clientAPI.EXPECT().ListKeys(gomock.Any(), gomock.Any()).Times(1).Return(&api.ClientListKeysResult{
   537  		Keys: expectedKeys,
   538  	}, nil)
   539  
   540  	// when
   541  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   542  		"Origin":        expectedHostname,
   543  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   544  	}))
   545  
   546  	// then
   547  	require.Equal(t, http.StatusOK, statusCode)
   548  	result := intoClientListKeysResult(t, rawResponse, "123456789")
   549  	assert.Equal(t, expectedKeys, result.Keys)
   550  }
   551  
   552  func testServiceV2_PostRequests_ListKeysWithoutOriginFails(t *testing.T) {
   553  	// given
   554  	reqBody := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   555  
   556  	// setup
   557  	s := getTestServiceV2(t)
   558  
   559  	// when
   560  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   561  
   562  	// then
   563  	require.Equal(t, http.StatusBadRequest, statusCode)
   564  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   565  	assert.Equal(t, "Server error", rpcErr.Message)
   566  	assert.Equal(t, api.ErrorCodeHostnameResolutionFailure, rpcErr.Code)
   567  	assert.Equal(t, v2.ErrOriginHeaderIsRequired.Error(), rpcErr.Data)
   568  }
   569  
   570  func testServiceV2_PostRequests_ListKeysWithoutTokenFails(t *testing.T) {
   571  	// given
   572  	expectedHostname := vgrand.RandomStr(5)
   573  	reqBody := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   574  
   575  	// setup
   576  	s := getTestServiceV2(t)
   577  
   578  	// when
   579  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   580  		"Origin": expectedHostname,
   581  	}))
   582  
   583  	// then
   584  	require.Equal(t, http.StatusUnauthorized, statusCode)
   585  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   586  	assert.Equal(t, "Server error", rpcErr.Message)
   587  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   588  	assert.Equal(t, v2.ErrAuthorizationHeaderIsRequired.Error(), rpcErr.Data)
   589  }
   590  
   591  func testServiceV2_PostRequests_ListKeysWithUnknownTokenFails(t *testing.T) {
   592  	// given
   593  	expectedHostname := vgrand.RandomStr(5)
   594  	reqBody := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   595  
   596  	// setup
   597  	s := getTestServiceV2(t)
   598  
   599  	// when
   600  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   601  		"Origin":        expectedHostname,
   602  		"Authorization": "VWT " + vgrand.RandomStr(64),
   603  	}))
   604  
   605  	// then
   606  	require.Equal(t, http.StatusUnauthorized, statusCode)
   607  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   608  	assert.Equal(t, "Server error", rpcErr.Message)
   609  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   610  	assert.Equal(t, connections.ErrNoConnectionAssociatedThisAuthenticationToken.Error(), rpcErr.Data)
   611  }
   612  
   613  func testServiceV2_PostRequests_ListKeysWithMismatchingHostnameFails(t *testing.T) {
   614  	s := getTestServiceV2(t)
   615  
   616  	// given
   617  	expectedHostname := vgrand.RandomStr(5)
   618  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   619  	w := newWallet(t)
   620  
   621  	// setup
   622  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   623  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   624  
   625  	// when
   626  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   627  		"Origin": expectedHostname,
   628  	}))
   629  
   630  	// then
   631  	require.Equal(t, http.StatusOK, statusCode)
   632  
   633  	// given
   634  	reqBody := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   635  
   636  	// when
   637  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   638  		"Origin":        vgrand.RandomStr(5),
   639  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   640  	}))
   641  
   642  	// then
   643  	require.Equal(t, http.StatusUnauthorized, statusCode)
   644  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   645  	assert.Equal(t, "Server error", rpcErr.Message)
   646  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   647  	assert.Equal(t, connections.ErrHostnamesMismatchForThisToken.Error(), rpcErr.Data)
   648  }
   649  
   650  func testServiceV2_PostRequests_ListKeysAsNotificationReturnsNothing(t *testing.T) {
   651  	s := getTestServiceV2(t)
   652  
   653  	// given
   654  	expectedHostname := vgrand.RandomStr(5)
   655  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   656  	w := newWallet(t)
   657  
   658  	// setup
   659  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   660  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   661  
   662  	// when
   663  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   664  		"Origin": expectedHostname,
   665  	}))
   666  
   667  	// then
   668  	require.Equal(t, http.StatusOK, statusCode)
   669  
   670  	// given
   671  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys"}`
   672  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   673  		Code:    123,
   674  		Message: vgrand.RandomStr(10),
   675  		Data:    vgrand.RandomStr(10),
   676  	}
   677  
   678  	// setup
   679  	s.clientAPI.EXPECT().ListKeys(gomock.Any(), gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
   680  
   681  	// when
   682  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   683  		"Origin":        expectedHostname,
   684  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   685  	}))
   686  
   687  	// then
   688  	require.Equal(t, http.StatusNoContent, statusCode)
   689  	result := intoJSONRPCResult(t, rawResponse, "")
   690  	assert.Empty(t, result)
   691  }
   692  
   693  func testServiceV2_PostRequests_ListKeysGettingErrorFails(t *testing.T) {
   694  	s := getTestServiceV2(t)
   695  
   696  	// given
   697  	expectedHostname := vgrand.RandomStr(5)
   698  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   699  	w := newWallet(t)
   700  
   701  	// setup
   702  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   703  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   704  
   705  	// when
   706  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   707  		"Origin": expectedHostname,
   708  	}))
   709  
   710  	// then
   711  	require.Equal(t, http.StatusOK, statusCode)
   712  
   713  	// given
   714  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   715  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   716  		Code:    123,
   717  		Message: vgrand.RandomStr(10),
   718  		Data:    vgrand.RandomStr(10),
   719  	}
   720  
   721  	// setup
   722  	s.clientAPI.EXPECT().ListKeys(gomock.Any(), gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
   723  
   724  	// when
   725  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   726  		"Origin":        expectedHostname,
   727  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   728  	}))
   729  
   730  	// then
   731  	require.Equal(t, http.StatusBadRequest, statusCode)
   732  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   733  	assert.Equal(t, expectedErrorDetails, rpcErr)
   734  }
   735  
   736  func testServiceV2_PostRequests_ListKeysGettingInternalErrorFails(t *testing.T) {
   737  	s := getTestServiceV2(t)
   738  
   739  	// given
   740  	expectedHostname := vgrand.RandomStr(5)
   741  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   742  	w := newWallet(t)
   743  
   744  	// setup
   745  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   746  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   747  
   748  	// when
   749  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   750  		"Origin": expectedHostname,
   751  	}))
   752  
   753  	// then
   754  	require.Equal(t, http.StatusOK, statusCode)
   755  
   756  	// given
   757  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   758  	expectedErrorDetails := &jsonrpc.ErrorDetails{
   759  		Code:    123,
   760  		Message: "Internal error",
   761  		Data:    vgrand.RandomStr(10),
   762  	}
   763  
   764  	// setup
   765  	s.clientAPI.EXPECT().ListKeys(gomock.Any(), gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
   766  
   767  	// when
   768  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   769  		"Origin":        expectedHostname,
   770  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   771  	}))
   772  
   773  	// then
   774  	require.Equal(t, http.StatusInternalServerError, statusCode)
   775  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   776  	assert.Equal(t, expectedErrorDetails, rpcErr)
   777  }
   778  
   779  func testServiceV2_PostRequests_ListKeysWithExpiredLongLivingTokenFails(t *testing.T) {
   780  	// given
   781  	token := connections.GenerateToken()
   782  	w := newWallet(t)
   783  	expectedPassphrase := vgrand.RandomStr(5)
   784  	s := getTestServiceV2(t, longLivingTokenSetupForTest{
   785  		tokenDescription: connections.TokenDescription{
   786  			CreationDate:   time.Now().Add(-2 * time.Hour),
   787  			ExpirationDate: ptr.From(time.Now().Add(-1 * time.Hour)),
   788  			Token:          token,
   789  			Wallet: connections.WalletCredentials{
   790  				Name:       w.Name(),
   791  				Passphrase: expectedPassphrase,
   792  			},
   793  		},
   794  		wallet: w,
   795  	})
   796  	expectedHostname := vgrand.RandomStr(5)
   797  
   798  	// given
   799  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   800  
   801  	// setup
   802  
   803  	// when
   804  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   805  		"Origin":        expectedHostname,
   806  		"Authorization": "VWT " + token.String(),
   807  	}))
   808  
   809  	// then
   810  	require.Equal(t, http.StatusUnauthorized, statusCode)
   811  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   812  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   813  	assert.Equal(t, "Server error", rpcErr.Message)
   814  	assert.Equal(t, connections.ErrTokenHasExpired.Error(), rpcErr.Data)
   815  }
   816  
   817  func testServiceV2_PostRequests_ListKeysWithLongLivingTokenSucceeds(t *testing.T) {
   818  	// given
   819  	token := connections.GenerateToken()
   820  	w := newWallet(t)
   821  	expectedPassphrase := vgrand.RandomStr(5)
   822  	s := getTestServiceV2(t, longLivingTokenSetupForTest{
   823  		tokenDescription: connections.TokenDescription{
   824  			CreationDate: time.Now().Add(-2 * time.Hour),
   825  			Token:        token,
   826  			Wallet: connections.WalletCredentials{
   827  				Name:       w.Name(),
   828  				Passphrase: expectedPassphrase,
   829  			},
   830  		},
   831  		wallet: w,
   832  	})
   833  	expectedHostname := vgrand.RandomStr(5)
   834  	reqBodyListKeys := `{"jsonrpc": "2.0", "method": "client.list_keys", "id": "123456789"}`
   835  	expectedKeys := []api.ClientNamedPublicKey{
   836  		{
   837  			Name:      vgrand.RandomStr(5),
   838  			PublicKey: vgrand.RandomStr(64),
   839  		}, {
   840  			Name:      vgrand.RandomStr(5),
   841  			PublicKey: vgrand.RandomStr(64),
   842  		},
   843  	}
   844  
   845  	// setup
   846  	s.clientAPI.EXPECT().ListKeys(gomock.Any(), gomock.Any()).Times(1).Return(&api.ClientListKeysResult{
   847  		Keys: expectedKeys,
   848  	}, nil)
   849  
   850  	// when
   851  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyListKeys, map[string]string{
   852  		"Origin":        expectedHostname,
   853  		"Authorization": "VWT " + token.String(),
   854  	}))
   855  
   856  	// then
   857  	require.Equal(t, http.StatusOK, statusCode)
   858  	result := intoClientListKeysResult(t, rawResponse, "123456789")
   859  	assert.Equal(t, expectedKeys, result.Keys)
   860  }
   861  
   862  func testServiceV2_PostRequests_SendTransactionSucceeds(t *testing.T) {
   863  	s := getTestServiceV2(t)
   864  
   865  	// given
   866  	expectedHostname := vgrand.RandomStr(5)
   867  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   868  	w := newWallet(t)
   869  	kp, err := w.GenerateKeyPair(nil)
   870  	if err != nil {
   871  		t.Fatalf("could not generate a key for wallet in test: %v", err)
   872  	}
   873  
   874  	// setup
   875  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   876  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   877  
   878  	// when
   879  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   880  		"Origin": expectedHostname,
   881  	}))
   882  
   883  	// then
   884  	require.Equal(t, http.StatusOK, statusCode)
   885  
   886  	// given
   887  	reqBodySendTransaction := fmt.Sprintf(`{
   888  		"jsonrpc": "2.0",
   889  		"method": "client.send_transaction",
   890  		"id": "123456789",
   891  		"params": {
   892  			"publicKey": %q,
   893  			"sendingMode": "TYPE_SYNC",
   894  			"transaction": {
   895  		  		"voteSubmission": {
   896  					"proposalId": "eb2d3902fdda9c3eb6e369f2235689b871c7322cf3ab284dde3e9dfc13863a17",
   897  					"value": "VALUE_YES"
   898  		  		}
   899  			}
   900  		}
   901  	}`, kp.PublicKey())
   902  	expectedResult := &api.ClientSendTransactionResult{}
   903  
   904  	// setup
   905  	s.clientAPI.EXPECT().SendTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(expectedResult, nil)
   906  
   907  	// when
   908  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodySendTransaction, map[string]string{
   909  		"Origin":        expectedHostname,
   910  		"Authorization": connectionResponseHeaders.Get("Authorization"),
   911  	}))
   912  
   913  	// then
   914  	require.Equal(t, http.StatusOK, statusCode)
   915  	result := intoClientSendTransactionResult(t, rawResponse, "123456789")
   916  	assert.Equal(t, expectedResult, result)
   917  }
   918  
   919  func testServiceV2_PostRequests_SendTransactionWithoutOriginFails(t *testing.T) {
   920  	// given
   921  	reqBody := `{"jsonrpc": "2.0", "method": "client.send_transaction", "id": "123456789"}`
   922  
   923  	// setup
   924  	s := getTestServiceV2(t)
   925  
   926  	// when
   927  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, nil))
   928  
   929  	// then
   930  	require.Equal(t, http.StatusBadRequest, statusCode)
   931  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   932  	assert.Equal(t, "Server error", rpcErr.Message)
   933  	assert.Equal(t, api.ErrorCodeHostnameResolutionFailure, rpcErr.Code)
   934  	assert.Equal(t, v2.ErrOriginHeaderIsRequired.Error(), rpcErr.Data)
   935  }
   936  
   937  func testServiceV2_PostRequests_SendTransactionWithoutTokenFails(t *testing.T) {
   938  	// given
   939  	expectedHostname := vgrand.RandomStr(5)
   940  	reqBody := `{"jsonrpc": "2.0", "method": "client.send_transaction", "id": "123456789"}`
   941  
   942  	// setup
   943  	s := getTestServiceV2(t)
   944  
   945  	// when
   946  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   947  		"Origin": expectedHostname,
   948  	}))
   949  
   950  	// then
   951  	require.Equal(t, http.StatusUnauthorized, statusCode)
   952  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   953  	assert.Equal(t, "Server error", rpcErr.Message)
   954  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   955  	assert.Equal(t, v2.ErrAuthorizationHeaderIsRequired.Error(), rpcErr.Data)
   956  }
   957  
   958  func testServiceV2_PostRequests_SendTransactionWithUnknownTokenFails(t *testing.T) {
   959  	// given
   960  	expectedHostname := vgrand.RandomStr(5)
   961  	reqBody := `{"jsonrpc": "2.0", "method": "client.send_transaction", "id": "123456789"}`
   962  
   963  	// setup
   964  	s := getTestServiceV2(t)
   965  
   966  	// when
   967  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
   968  		"Origin":        expectedHostname,
   969  		"Authorization": "VWT " + vgrand.RandomStr(64),
   970  	}))
   971  
   972  	// then
   973  	require.Equal(t, http.StatusUnauthorized, statusCode)
   974  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
   975  	assert.Equal(t, "Server error", rpcErr.Message)
   976  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
   977  	assert.Equal(t, connections.ErrNoConnectionAssociatedThisAuthenticationToken.Error(), rpcErr.Data)
   978  }
   979  
   980  func testServiceV2_PostRequests_SendTransactionWithMismatchingHostnameFails(t *testing.T) {
   981  	s := getTestServiceV2(t)
   982  
   983  	// given
   984  	expectedHostname := vgrand.RandomStr(5)
   985  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
   986  	w := newWallet(t)
   987  
   988  	// setup
   989  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
   990  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
   991  
   992  	// when
   993  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
   994  		"Origin": expectedHostname,
   995  	}))
   996  
   997  	// then
   998  	require.Equal(t, http.StatusOK, statusCode)
   999  
  1000  	// given
  1001  	reqBody := `{"jsonrpc": "2.0", "method": "client.send_transaction", "id": "123456789"}`
  1002  
  1003  	// when
  1004  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBody, map[string]string{
  1005  		"Origin":        vgrand.RandomStr(5),
  1006  		"Authorization": connectionResponseHeaders.Get("Authorization"),
  1007  	}))
  1008  
  1009  	// then
  1010  	require.Equal(t, http.StatusUnauthorized, statusCode)
  1011  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
  1012  	assert.Equal(t, "Server error", rpcErr.Message)
  1013  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
  1014  	assert.Equal(t, connections.ErrHostnamesMismatchForThisToken.Error(), rpcErr.Data)
  1015  }
  1016  
  1017  func testServiceV2_PostRequests_SendTransactionAsNotificationReturnsNothing(t *testing.T) {
  1018  	s := getTestServiceV2(t)
  1019  
  1020  	// given
  1021  	expectedHostname := vgrand.RandomStr(5)
  1022  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
  1023  	w := newWallet(t)
  1024  
  1025  	// setup
  1026  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
  1027  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
  1028  
  1029  	// when
  1030  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
  1031  		"Origin": expectedHostname,
  1032  	}))
  1033  
  1034  	// then
  1035  	require.Equal(t, http.StatusOK, statusCode)
  1036  
  1037  	// given
  1038  	reqBodySendTransaction := `{"jsonrpc": "2.0", "method": "client.send_transaction"}`
  1039  	expectedErrorDetails := &jsonrpc.ErrorDetails{
  1040  		Code:    123,
  1041  		Message: vgrand.RandomStr(10),
  1042  		Data:    vgrand.RandomStr(10),
  1043  	}
  1044  
  1045  	// setup
  1046  	s.clientAPI.EXPECT().SendTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
  1047  
  1048  	// when
  1049  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodySendTransaction, map[string]string{
  1050  		"Origin":        expectedHostname,
  1051  		"Authorization": connectionResponseHeaders.Get("Authorization"),
  1052  	}))
  1053  
  1054  	// then
  1055  	require.Equal(t, http.StatusNoContent, statusCode)
  1056  	result := intoJSONRPCResult(t, rawResponse, "")
  1057  	assert.Empty(t, result)
  1058  }
  1059  
  1060  func testServiceV2_PostRequests_SendTransactionGettingErrorFails(t *testing.T) {
  1061  	s := getTestServiceV2(t)
  1062  
  1063  	// given
  1064  	expectedHostname := vgrand.RandomStr(5)
  1065  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
  1066  	w := newWallet(t)
  1067  
  1068  	// setup
  1069  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
  1070  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
  1071  
  1072  	// when
  1073  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
  1074  		"Origin": expectedHostname,
  1075  	}))
  1076  
  1077  	// then
  1078  	require.Equal(t, http.StatusOK, statusCode)
  1079  
  1080  	// given
  1081  	reqBodySendTransaction := `{"jsonrpc": "2.0", "method": "client.send_transaction", "id": "123456789"}`
  1082  	expectedErrorDetails := &jsonrpc.ErrorDetails{
  1083  		Code:    123,
  1084  		Message: vgrand.RandomStr(10),
  1085  		Data:    vgrand.RandomStr(10),
  1086  	}
  1087  
  1088  	// setup
  1089  	s.clientAPI.EXPECT().SendTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
  1090  
  1091  	// when
  1092  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodySendTransaction, map[string]string{
  1093  		"Origin":        expectedHostname,
  1094  		"Authorization": connectionResponseHeaders.Get("Authorization"),
  1095  	}))
  1096  
  1097  	// then
  1098  	require.Equal(t, http.StatusBadRequest, statusCode)
  1099  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
  1100  	assert.Equal(t, expectedErrorDetails, rpcErr)
  1101  }
  1102  
  1103  func testServiceV2_PostRequests_SendTransactionGettingInternalErrorFails(t *testing.T) {
  1104  	s := getTestServiceV2(t)
  1105  
  1106  	// given
  1107  	expectedHostname := vgrand.RandomStr(5)
  1108  	reqBodyConnectWallet := `{"jsonrpc": "2.0", "method": "client.connect_wallet", "id": "123456789"}`
  1109  	w := newWallet(t)
  1110  
  1111  	// setup
  1112  	s.clientAPI.EXPECT().ConnectWallet(gomock.Any(), expectedHostname).Times(1).Return(w, nil)
  1113  	s.sessionStore.EXPECT().TrackSession(gomock.Any()).Times(1).Return(nil)
  1114  
  1115  	// when
  1116  	statusCode, connectionResponseHeaders, _ := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodyConnectWallet, map[string]string{
  1117  		"Origin": expectedHostname,
  1118  	}))
  1119  
  1120  	// then
  1121  	require.Equal(t, http.StatusOK, statusCode)
  1122  
  1123  	// given
  1124  	reqBodySendTransaction := `{"jsonrpc": "2.0", "method": "client.send_transaction", "id": "123456789"}`
  1125  	expectedErrorDetails := &jsonrpc.ErrorDetails{
  1126  		Code:    123,
  1127  		Message: "Internal error",
  1128  		Data:    vgrand.RandomStr(10),
  1129  	}
  1130  
  1131  	// setup
  1132  	s.clientAPI.EXPECT().SendTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(nil, expectedErrorDetails)
  1133  
  1134  	// when
  1135  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodySendTransaction, map[string]string{
  1136  		"Origin":        expectedHostname,
  1137  		"Authorization": connectionResponseHeaders.Get("Authorization"),
  1138  	}))
  1139  
  1140  	// then
  1141  	require.Equal(t, http.StatusInternalServerError, statusCode)
  1142  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
  1143  	assert.Equal(t, expectedErrorDetails, rpcErr)
  1144  }
  1145  
  1146  func testServiceV2_PostRequests_SendTransactionWithExpiredLongLivingTokenFails(t *testing.T) {
  1147  	// given
  1148  	token := connections.GenerateToken()
  1149  	w := newWallet(t)
  1150  	kp, err := w.GenerateKeyPair(nil)
  1151  	if err != nil {
  1152  		t.Fatal(err)
  1153  	}
  1154  	expectedPassphrase := vgrand.RandomStr(5)
  1155  	s := getTestServiceV2(t, longLivingTokenSetupForTest{
  1156  		tokenDescription: connections.TokenDescription{
  1157  			CreationDate:   time.Now().Add(-2 * time.Hour),
  1158  			ExpirationDate: ptr.From(time.Now().Add(-1 * time.Hour)),
  1159  			Token:          token,
  1160  			Wallet: connections.WalletCredentials{
  1161  				Name:       w.Name(),
  1162  				Passphrase: expectedPassphrase,
  1163  			},
  1164  		},
  1165  		wallet: w,
  1166  	})
  1167  	expectedHostname := vgrand.RandomStr(5)
  1168  	reqBodySendTransaction := fmt.Sprintf(`{
  1169  		"jsonrpc": "2.0",
  1170  		"method": "client.send_transaction",
  1171  		"id": "123456789",
  1172  		"params": {
  1173  			"publicKey": %q,
  1174  			"sendingMode": "TYPE_SYNC",
  1175  			"transaction": {
  1176  		  		"voteSubmission": {
  1177  					"proposalId": "eb2d3902fdda9c3eb6e369f2235689b871c7322cf3ab284dde3e9dfc13863a17",
  1178  					"value": "VALUE_YES"
  1179  		  		}
  1180  			}
  1181  		}
  1182  	}`, kp.PublicKey())
  1183  
  1184  	// setup
  1185  
  1186  	// when
  1187  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodySendTransaction, map[string]string{
  1188  		"Origin":        expectedHostname,
  1189  		"Authorization": "VWT " + token.String(),
  1190  	}))
  1191  
  1192  	// then
  1193  	require.Equal(t, http.StatusUnauthorized, statusCode)
  1194  	rpcErr := intoJSONRPCError(t, rawResponse, "123456789")
  1195  	assert.Equal(t, api.ErrorCodeAuthenticationFailure, rpcErr.Code)
  1196  	assert.Equal(t, "Server error", rpcErr.Message)
  1197  	assert.Equal(t, connections.ErrTokenHasExpired.Error(), rpcErr.Data)
  1198  }
  1199  
  1200  func testServiceV2_PostRequests_SendTransactionWithLongLivingTokenSucceeds(t *testing.T) {
  1201  	// given
  1202  	token := connections.GenerateToken()
  1203  	w := newWallet(t)
  1204  	kp, err := w.GenerateKeyPair(nil)
  1205  	if err != nil {
  1206  		t.Fatal(err)
  1207  	}
  1208  	expectedPassphrase := vgrand.RandomStr(5)
  1209  	s := getTestServiceV2(t, longLivingTokenSetupForTest{
  1210  		tokenDescription: connections.TokenDescription{
  1211  			CreationDate: time.Now().Add(-2 * time.Hour),
  1212  			Token:        token,
  1213  			Wallet: connections.WalletCredentials{
  1214  				Name:       w.Name(),
  1215  				Passphrase: expectedPassphrase,
  1216  			},
  1217  		},
  1218  		wallet: w,
  1219  	})
  1220  	expectedHostname := vgrand.RandomStr(5)
  1221  	reqBodySendTransaction := fmt.Sprintf(`{
  1222  		"jsonrpc": "2.0",
  1223  		"method": "client.send_transaction",
  1224  		"id": "123456789",
  1225  		"params": {
  1226  			"publicKey": %q,
  1227  			"sendingMode": "TYPE_SYNC",
  1228  			"transaction": {
  1229  		  		"voteSubmission": {
  1230  					"proposalId": "eb2d3902fdda9c3eb6e369f2235689b871c7322cf3ab284dde3e9dfc13863a17",
  1231  					"value": "VALUE_YES"
  1232  		  		}
  1233  			}
  1234  		}
  1235  	}`, kp.PublicKey())
  1236  	expectedResult := &api.ClientSendTransactionResult{}
  1237  
  1238  	// setup
  1239  	s.clientAPI.EXPECT().SendTransaction(gomock.Any(), gomock.Any(), gomock.Any()).Times(1).Return(expectedResult, nil)
  1240  
  1241  	// when
  1242  	statusCode, _, rawResponse := s.serveHTTP(t, buildRequest(t, http.MethodPost, "/api/v2/requests", reqBodySendTransaction, map[string]string{
  1243  		"Origin":        expectedHostname,
  1244  		"Authorization": "VWT " + token.String(),
  1245  	}))
  1246  
  1247  	// then
  1248  	require.Equal(t, http.StatusOK, statusCode)
  1249  	result := intoClientSendTransactionResult(t, rawResponse, "123456789")
  1250  	assert.Equal(t, expectedResult, result)
  1251  }
  1252  
  1253  func newWallet(t *testing.T) *wallet.HDWallet {
  1254  	t.Helper()
  1255  
  1256  	w, _, err := wallet.NewHDWallet(vgrand.RandomStr(5))
  1257  	if err != nil {
  1258  		t.Fatalf("could not create a wallet for test: %v", err)
  1259  	}
  1260  	return w
  1261  }
  1262  
  1263  func intoClientSendTransactionResult(t *testing.T, rawResponse []byte, id string) *api.ClientSendTransactionResult {
  1264  	t.Helper()
  1265  
  1266  	rpcRes := intoJSONRPCResult(t, rawResponse, id)
  1267  
  1268  	result := &api.ClientSendTransactionResult{}
  1269  	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
  1270  		Metadata:   nil,
  1271  		DecodeHook: mapstructure.ComposeDecodeHookFunc(toTimeHookFunc()),
  1272  		Result:     result,
  1273  	})
  1274  	if err != nil {
  1275  		t.Fatalf("could not create de mapstructure decoder for the client.send_transaction result: %v", err)
  1276  	}
  1277  
  1278  	if err := decoder.Decode(rpcRes); err != nil {
  1279  		t.Fatalf("could not decode the client.send_transaction result: %v", err)
  1280  	}
  1281  	return result
  1282  }
  1283  
  1284  func toTimeHookFunc() mapstructure.DecodeHookFunc {
  1285  	return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
  1286  		if t != reflect.TypeOf(time.Time{}) {
  1287  			return data, nil
  1288  		}
  1289  
  1290  		switch f.Kind() {
  1291  		case reflect.String:
  1292  			return time.Parse(time.RFC3339, data.(string))
  1293  		default:
  1294  			return data, nil
  1295  		}
  1296  	}
  1297  }
  1298  
  1299  func intoClientListKeysResult(t *testing.T, rawResponse []byte, id string) *api.ClientListKeysResult {
  1300  	t.Helper()
  1301  
  1302  	rpcRes := intoJSONRPCResult(t, rawResponse, id)
  1303  	result := &api.ClientListKeysResult{}
  1304  	if err := mapstructure.Decode(rpcRes, result); err != nil {
  1305  		t.Fatalf("could not parse the client.list_keys result: %v", err)
  1306  	}
  1307  	return result
  1308  }
  1309  
  1310  func intoClientGetChainIDResult(t *testing.T, rawResponse []byte, id string) *api.ClientGetChainIDResult {
  1311  	t.Helper()
  1312  
  1313  	rpcRes := intoJSONRPCResult(t, rawResponse, id)
  1314  	result := &api.ClientGetChainIDResult{}
  1315  	if err := mapstructure.Decode(rpcRes, result); err != nil {
  1316  		t.Fatalf("could not parse the client.get_chain_id result: %v", err)
  1317  	}
  1318  	return result
  1319  }
  1320  
  1321  func intoJSONRPCError(t *testing.T, rawResponse []byte, id string) *jsonrpc.ErrorDetails {
  1322  	t.Helper()
  1323  
  1324  	resp := &jsonrpc.Response{}
  1325  	if err := json.Unmarshal(rawResponse, resp); err != nil {
  1326  		t.Fatalf("couldn't unmarshal response from /api/v2/request: %v", err)
  1327  	}
  1328  	assert.Equal(t, "2.0", resp.Version)
  1329  	assert.Equal(t, id, resp.ID)
  1330  	assert.Nil(t, resp.Result)
  1331  	require.NotNil(t, id, resp.Error)
  1332  
  1333  	return resp.Error
  1334  }
  1335  
  1336  func intoJSONRPCResult(t *testing.T, rawResponse []byte, id string) jsonrpc.Result {
  1337  	t.Helper()
  1338  
  1339  	if id == "" {
  1340  		assert.Empty(t, rawResponse)
  1341  		return nil
  1342  	}
  1343  
  1344  	resp := &jsonrpc.Response{}
  1345  	if err := json.Unmarshal(rawResponse, resp); err != nil {
  1346  		t.Fatalf("couldn't unmarshal response from /api/v2/requests: %v", err)
  1347  	}
  1348  	assert.Equal(t, "2.0", resp.Version)
  1349  	assert.Equal(t, id, resp.ID)
  1350  	assert.Nil(t, resp.Error)
  1351  	require.NotNil(t, id, resp.Result)
  1352  
  1353  	return resp.Result
  1354  }