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 }