code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_import_wallet_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 api_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"testing"
    22  
    23  	"code.vegaprotocol.io/vega/libs/jsonrpc"
    24  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    25  	"code.vegaprotocol.io/vega/wallet/api"
    26  	"code.vegaprotocol.io/vega/wallet/api/mocks"
    27  	"code.vegaprotocol.io/vega/wallet/wallet"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestAdminImportWallet(t *testing.T) {
    35  	t.Run("Documentation matches the code", testAdminImportWalletSchemaCorrect)
    36  	t.Run("Importing a wallet with invalid params fails", testImportingWalletWithInvalidParamsFails)
    37  	t.Run("Importing a wallet with valid params succeeds", testImportingWalletWithValidParamsSucceeds)
    38  	t.Run("Importing a wallet that already exists fails", testImportingWalletThatAlreadyExistsFails)
    39  	t.Run("Getting internal error during verification does not import the wallet", testGettingInternalErrorDuringVerificationDoesNotImportWallet)
    40  	t.Run("Getting internal error during saving does not import the wallet", testGettingInternalErrorDuringSavingDoesNotImportWallet)
    41  }
    42  
    43  func testAdminImportWalletSchemaCorrect(t *testing.T) {
    44  	assertEqualSchema(t, "admin.import_wallet", api.AdminImportWalletParams{}, api.AdminImportWalletResult{})
    45  }
    46  
    47  func testImportingWalletWithInvalidParamsFails(t *testing.T) {
    48  	tcs := []struct {
    49  		name          string
    50  		params        interface{}
    51  		expectedError error
    52  	}{
    53  		{
    54  			name:          "with nil params",
    55  			params:        nil,
    56  			expectedError: api.ErrParamsRequired,
    57  		}, {
    58  			name:          "with wrong type of params",
    59  			params:        "test",
    60  			expectedError: api.ErrParamsDoNotMatch,
    61  		}, {
    62  			name: "with empty name",
    63  			params: api.AdminImportWalletParams{
    64  				Wallet:               "",
    65  				RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
    66  				KeyDerivationVersion: 2,
    67  				Passphrase:           vgrand.RandomStr(5),
    68  			},
    69  			expectedError: api.ErrWalletIsRequired,
    70  		}, {
    71  			name: "with empty passphrase",
    72  			params: api.AdminImportWalletParams{
    73  				Wallet:               vgrand.RandomStr(5),
    74  				RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
    75  				KeyDerivationVersion: 2,
    76  				Passphrase:           "",
    77  			},
    78  			expectedError: api.ErrPassphraseIsRequired,
    79  		}, {
    80  			name: "with empty recovery phrase",
    81  			params: api.AdminImportWalletParams{
    82  				Wallet:               vgrand.RandomStr(5),
    83  				RecoveryPhrase:       "",
    84  				KeyDerivationVersion: 2,
    85  				Passphrase:           vgrand.RandomStr(5),
    86  			},
    87  			expectedError: api.ErrRecoveryPhraseIsRequired,
    88  		}, {
    89  			name: "with unset key derivation version",
    90  			params: api.AdminImportWalletParams{
    91  				Wallet:               vgrand.RandomStr(5),
    92  				RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
    93  				KeyDerivationVersion: 0,
    94  				Passphrase:           vgrand.RandomStr(5),
    95  			},
    96  			expectedError: api.ErrWalletKeyDerivationVersionIsRequired,
    97  		}, {
    98  			name: "with unset key derivation version",
    99  			params: api.AdminImportWalletParams{
   100  				Wallet:         vgrand.RandomStr(5),
   101  				RecoveryPhrase: "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
   102  				Passphrase:     vgrand.RandomStr(5),
   103  			},
   104  			expectedError: api.ErrWalletKeyDerivationVersionIsRequired,
   105  		},
   106  	}
   107  
   108  	for _, tc := range tcs {
   109  		t.Run(tc.name, func(tt *testing.T) {
   110  			// given
   111  			ctx := context.Background()
   112  
   113  			// setup
   114  			handler := newImportWalletHandler(tt)
   115  
   116  			// when
   117  			result, errorDetails := handler.handle(t, ctx, tc.params)
   118  
   119  			// then
   120  			require.Empty(tt, result)
   121  			assertInvalidParams(tt, errorDetails, tc.expectedError)
   122  		})
   123  	}
   124  }
   125  
   126  func testImportingWalletWithValidParamsSucceeds(t *testing.T) {
   127  	// given
   128  	ctx := context.Background()
   129  	passphrase := vgrand.RandomStr(5)
   130  	name := vgrand.RandomStr(5)
   131  	var importedWallet wallet.Wallet
   132  
   133  	// setup
   134  	handler := newImportWalletHandler(t)
   135  	// -- expected calls
   136  	handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(false, nil)
   137  	handler.walletStore.EXPECT().CreateWallet(ctx, gomock.Any(), passphrase).Times(1).DoAndReturn(func(_ context.Context, w wallet.Wallet, passphrase string) error {
   138  		importedWallet = w
   139  		return nil
   140  	})
   141  
   142  	// when
   143  	result, errorDetails := handler.handle(t, ctx, api.AdminImportWalletParams{
   144  		Wallet:               name,
   145  		Passphrase:           passphrase,
   146  		RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
   147  		KeyDerivationVersion: 2,
   148  	})
   149  
   150  	// then
   151  	require.Nil(t, errorDetails)
   152  	// Verify imported wallet.
   153  	assert.Equal(t, name, importedWallet.Name())
   154  	// Verify the first generated key.
   155  	assert.Len(t, importedWallet.ListKeyPairs(), 1)
   156  	keyPair := importedWallet.ListKeyPairs()[0]
   157  	assert.Equal(t, []wallet.Metadata{{Key: "name", Value: "Key 1"}}, keyPair.Metadata())
   158  	// Verify the result.
   159  	assert.Equal(t, name, result.Wallet.Name)
   160  	assert.Equal(t, keyPair.PublicKey(), result.Key.PublicKey)
   161  	assert.Equal(t, keyPair.AlgorithmName(), result.Key.Algorithm.Name)
   162  	assert.Equal(t, keyPair.AlgorithmVersion(), result.Key.Algorithm.Version)
   163  	assert.Equal(t, keyPair.Metadata(), result.Key.Metadata)
   164  }
   165  
   166  func testImportingWalletThatAlreadyExistsFails(t *testing.T) {
   167  	// given
   168  	ctx := context.Background()
   169  	passphrase := vgrand.RandomStr(5)
   170  	name := vgrand.RandomStr(5)
   171  
   172  	// setup
   173  	handler := newImportWalletHandler(t)
   174  	// -- expected calls
   175  	handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(true, nil)
   176  
   177  	// when
   178  	result, errorDetails := handler.handle(t, ctx, api.AdminImportWalletParams{
   179  		Wallet:               name,
   180  		Passphrase:           passphrase,
   181  		RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
   182  		KeyDerivationVersion: 2,
   183  	})
   184  
   185  	// then
   186  	require.NotNil(t, errorDetails)
   187  	assert.Empty(t, result)
   188  	assertInvalidParams(t, errorDetails, api.ErrWalletAlreadyExists)
   189  }
   190  
   191  func testGettingInternalErrorDuringVerificationDoesNotImportWallet(t *testing.T) {
   192  	// given
   193  	ctx := context.Background()
   194  	passphrase := vgrand.RandomStr(5)
   195  	name := vgrand.RandomStr(5)
   196  
   197  	// setup
   198  	handler := newImportWalletHandler(t)
   199  	// -- expected calls
   200  	handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(false, assert.AnError)
   201  
   202  	// when
   203  	result, errorDetails := handler.handle(t, ctx, api.AdminImportWalletParams{
   204  		Wallet:               name,
   205  		Passphrase:           passphrase,
   206  		RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
   207  		KeyDerivationVersion: 2,
   208  	})
   209  
   210  	// then
   211  	require.NotNil(t, errorDetails)
   212  	assert.Empty(t, result)
   213  	assertInternalError(t, errorDetails, fmt.Errorf("could not verify the wallet exists: %w", assert.AnError))
   214  }
   215  
   216  func testGettingInternalErrorDuringSavingDoesNotImportWallet(t *testing.T) {
   217  	// given
   218  	ctx := context.Background()
   219  	passphrase := vgrand.RandomStr(5)
   220  	name := vgrand.RandomStr(5)
   221  
   222  	// setup
   223  	handler := newImportWalletHandler(t)
   224  	// -- expected calls
   225  	handler.walletStore.EXPECT().WalletExists(ctx, name).Times(1).Return(false, nil)
   226  	handler.walletStore.EXPECT().CreateWallet(ctx, gomock.Any(), passphrase).Times(1).Return(assert.AnError)
   227  
   228  	// when
   229  	result, errorDetails := handler.handle(t, ctx, api.AdminImportWalletParams{
   230  		Wallet:               name,
   231  		Passphrase:           passphrase,
   232  		RecoveryPhrase:       "swing ceiling chaos green put insane ripple desk match tip melt usual shrug turkey renew icon parade veteran lens govern path rough page render",
   233  		KeyDerivationVersion: 2,
   234  	})
   235  
   236  	// then
   237  	require.NotNil(t, errorDetails)
   238  	assert.Empty(t, result)
   239  	assertInternalError(t, errorDetails, fmt.Errorf("could not save the wallet: %w", assert.AnError))
   240  }
   241  
   242  type importWalletHandler struct {
   243  	*api.AdminImportWallet
   244  	ctrl        *gomock.Controller
   245  	walletStore *mocks.MockWalletStore
   246  }
   247  
   248  func (h *importWalletHandler) handle(t *testing.T, ctx context.Context, params jsonrpc.Params) (api.AdminImportWalletResult, *jsonrpc.ErrorDetails) {
   249  	t.Helper()
   250  
   251  	rawResult, err := h.Handle(ctx, params)
   252  	if rawResult != nil {
   253  		result, ok := rawResult.(api.AdminImportWalletResult)
   254  		if !ok {
   255  			t.Fatal("AdminImportWallet handler result is not a AdminImportWalletResult")
   256  		}
   257  		return result, err
   258  	}
   259  	return api.AdminImportWalletResult{}, err
   260  }
   261  
   262  func newImportWalletHandler(t *testing.T) *importWalletHandler {
   263  	t.Helper()
   264  
   265  	ctrl := gomock.NewController(t)
   266  	walletStore := mocks.NewMockWalletStore(ctrl)
   267  
   268  	return &importWalletHandler{
   269  		AdminImportWallet: api.NewAdminImportWallet(walletStore),
   270  		ctrl:              ctrl,
   271  		walletStore:       walletStore,
   272  	}
   273  }