code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_import_network_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  	"os"
    22  	"path/filepath"
    23  	"testing"
    24  
    25  	"code.vegaprotocol.io/vega/libs/jsonrpc"
    26  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    27  	"code.vegaprotocol.io/vega/wallet/api"
    28  	"code.vegaprotocol.io/vega/wallet/api/mocks"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  func TestAdminImportNetwork(t *testing.T) {
    36  	t.Run("Documentation matches the code", testAdminImportNetworkSchemaCorrect)
    37  	t.Run("Importing a network with invalid params fails", testImportingNetworkWithInvalidParamsFails)
    38  	t.Run("Importing a network that already exists fails", testImportingNetworkThatAlreadyExistsFails)
    39  	t.Run("Getting internal error during verification does not import the network", testGettingInternalErrorDuringVerificationDoesNotImportNetwork)
    40  	t.Run("Importing a network from a file that doesn't exist fails", testImportingANetworkFromAFileThatDoesntExistFails)
    41  	t.Run("Importing a network from a valid file saves", testImportingValidFileSaves)
    42  	t.Run("Importing a network with no name fails", testImportingWithNoNameFails)
    43  	t.Run("Importing a network from a valid file with name in config works", testImportingWithNameInConfig)
    44  	t.Run("Importing a network with a github url suggests better alternative", testImportNetworkWithURL)
    45  	t.Run("Importing a network with a content that is not TOML fails with a user friendly message", testImportNetworkWithNotTOMLContentFailsWithFriendlyMessage)
    46  }
    47  
    48  func testAdminImportNetworkSchemaCorrect(t *testing.T) {
    49  	assertEqualSchema(t, "admin.import_network", api.AdminImportNetworkParams{}, api.AdminImportNetworkResult{})
    50  }
    51  
    52  func testImportingNetworkWithInvalidParamsFails(t *testing.T) {
    53  	tcs := []struct {
    54  		name          string
    55  		params        interface{}
    56  		expectedError error
    57  	}{
    58  		{
    59  			name:          "with nil params",
    60  			params:        nil,
    61  			expectedError: api.ErrParamsRequired,
    62  		}, {
    63  			name:          "with wrong type of params",
    64  			params:        "test",
    65  			expectedError: api.ErrParamsDoNotMatch,
    66  		}, {
    67  			name: "with empty sources",
    68  			params: api.AdminImportNetworkParams{
    69  				Name: "fairground",
    70  				URL:  "",
    71  			},
    72  			expectedError: api.ErrNetworkSourceIsRequired,
    73  		},
    74  	}
    75  
    76  	for _, tc := range tcs {
    77  		t.Run(tc.name, func(tt *testing.T) {
    78  			// given
    79  			ctx := context.Background()
    80  
    81  			// setup
    82  			handler := newImportNetworkHandler(tt)
    83  
    84  			// when
    85  			result, errorDetails := handler.handle(t, ctx, tc.params)
    86  
    87  			// then
    88  			require.Empty(tt, result)
    89  			assertInvalidParams(tt, errorDetails, tc.expectedError)
    90  		})
    91  	}
    92  }
    93  
    94  func testImportingNetworkThatAlreadyExistsFails(t *testing.T) {
    95  	// given
    96  	ctx := context.Background()
    97  	name := vgrand.RandomStr(5)
    98  	d := t.TempDir()
    99  	filePath := filepath.Join(d + "tmp.toml")
   100  	err := os.WriteFile(filePath, []byte("Name = \"local\""), 0o644)
   101  	require.NoError(t, err)
   102  
   103  	// setup
   104  	handler := newImportNetworkHandler(t)
   105  
   106  	// -- expected calls
   107  	handler.networkStore.EXPECT().NetworkExists(gomock.Any()).Times(1).Return(true, nil)
   108  
   109  	// when
   110  	result, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   111  		Name: name,
   112  		URL:  api.FileSchemePrefix + filePath,
   113  	})
   114  
   115  	// then
   116  	require.NotNil(t, errorDetails)
   117  	assert.Empty(t, result)
   118  	assertInvalidParams(t, errorDetails, api.ErrNetworkAlreadyExists)
   119  }
   120  
   121  func testGettingInternalErrorDuringVerificationDoesNotImportNetwork(t *testing.T) {
   122  	// given
   123  	ctx := context.Background()
   124  	name := vgrand.RandomStr(5)
   125  	d := t.TempDir()
   126  	filePath := filepath.Join(d + "tmp.toml")
   127  	err := os.WriteFile(filePath, []byte("Name = \"local\""), 0o644)
   128  	require.NoError(t, err)
   129  
   130  	// setup
   131  	handler := newImportNetworkHandler(t)
   132  	// -- expected calls
   133  	handler.networkStore.EXPECT().NetworkExists(gomock.Any()).Times(1).Return(false, assert.AnError)
   134  
   135  	// when
   136  	result, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   137  		Name: name,
   138  		URL:  api.FileSchemePrefix + filePath,
   139  	})
   140  
   141  	// then
   142  	require.NotNil(t, errorDetails)
   143  	assert.Empty(t, result)
   144  	assertInternalError(t, errorDetails, fmt.Errorf("could not verify the network existence: %w", assert.AnError))
   145  }
   146  
   147  func testImportingANetworkFromAFileThatDoesntExistFails(t *testing.T) {
   148  	// given
   149  	ctx := context.Background()
   150  	name := vgrand.RandomStr(5)
   151  
   152  	// setup
   153  	handler := newImportNetworkHandler(t)
   154  
   155  	// when
   156  	result, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   157  		Name: name,
   158  		URL:  api.FileSchemePrefix + "some-file-path",
   159  	})
   160  
   161  	// then
   162  	require.NotNil(t, errorDetails)
   163  	assert.Empty(t, result)
   164  	assertInvalidParams(t, errorDetails, fmt.Errorf("the network source file does not exist: %w", api.ErrInvalidNetworkSource))
   165  }
   166  
   167  func testImportingValidFileSaves(t *testing.T) {
   168  	// given
   169  	ctx := context.Background()
   170  	name := vgrand.RandomStr(5)
   171  
   172  	d := t.TempDir()
   173  	filePath := filepath.Join(d + "tmp.toml")
   174  	err := os.WriteFile(filePath, []byte("Name = \"local\""), 0o644)
   175  	require.NoError(t, err)
   176  
   177  	// setup
   178  	handler := newImportNetworkHandler(t)
   179  	// -- expected calls
   180  	handler.networkStore.EXPECT().NetworkExists(name).Times(1).Return(false, nil)
   181  	handler.networkStore.EXPECT().SaveNetwork(gomock.Any()).Times(1)
   182  
   183  	// when
   184  	result, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   185  		Name: name,
   186  		URL:  api.FileSchemePrefix + filePath,
   187  	})
   188  
   189  	// then
   190  	require.Nil(t, errorDetails)
   191  	assert.Equal(t, result.Name, name)
   192  }
   193  
   194  func testImportingWithNoNameFails(t *testing.T) {
   195  	// given
   196  	ctx := context.Background()
   197  
   198  	d := t.TempDir()
   199  	filePath := filepath.Join(d + "tmp.toml")
   200  	err := os.WriteFile(filePath, []byte("Address = \"local\""), 0o644)
   201  	require.NoError(t, err)
   202  
   203  	// setup
   204  	handler := newImportNetworkHandler(t)
   205  
   206  	// when the config has no network name, and there is no network name specified in the params
   207  	_, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   208  		URL: api.FileSchemePrefix + filePath,
   209  	})
   210  
   211  	// then
   212  	assertInvalidParams(t, errorDetails, api.ErrNetworkNameIsRequired)
   213  }
   214  
   215  func testImportingWithNameInConfig(t *testing.T) {
   216  	// given
   217  	ctx := context.Background()
   218  
   219  	d := t.TempDir()
   220  	filePath := filepath.Join(d + "tmp.toml")
   221  	err := os.WriteFile(filePath, []byte("Name = \"local\""), 0o644)
   222  	require.NoError(t, err)
   223  
   224  	// setup
   225  	handler := newImportNetworkHandler(t)
   226  	// -- expected calls
   227  	handler.networkStore.EXPECT().NetworkExists("local").Times(1).Return(false, nil)
   228  	handler.networkStore.EXPECT().SaveNetwork(gomock.Any()).Times(1)
   229  
   230  	// when
   231  	result, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   232  		URL: api.FileSchemePrefix + filePath,
   233  	})
   234  
   235  	// then
   236  	require.Nil(t, errorDetails)
   237  	assert.Equal(t, result.Name, "local")
   238  }
   239  
   240  func testImportNetworkWithURL(t *testing.T) {
   241  	// given
   242  	ctx := context.Background()
   243  
   244  	d := t.TempDir()
   245  	filePath := filepath.Join(d + "tmp.toml")
   246  	err := os.WriteFile(filePath, []byte("Name = \"local\""), 0o644)
   247  	require.NoError(t, err)
   248  
   249  	// setup
   250  	_ = "network-path/local.toml"
   251  	handler := newImportNetworkHandler(t)
   252  
   253  	testCases := []struct {
   254  		name       string
   255  		url        string
   256  		suggestion string
   257  		jsonrpcErr jsonrpc.ErrorCode
   258  	}{
   259  		{
   260  			name:       "real-url",
   261  			url:        "https://github.com/vegaprotocol/networks-internal/blob/main/fairground/vegawallet-fairground.toml",
   262  			suggestion: "https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml",
   263  			jsonrpcErr: jsonrpc.ErrorCodeInvalidParams,
   264  		},
   265  		{
   266  			name:       "without s in http",
   267  			url:        "http://github.com/blah/blob/main/fairground/network.toml",
   268  			suggestion: "http://raw.githubusercontent.com/blah/main/fairground/network.toml",
   269  			jsonrpcErr: jsonrpc.ErrorCodeInvalidParams,
   270  		},
   271  		{
   272  			name:       "non-github url tries to fetch",
   273  			url:        "https://example.com",
   274  			jsonrpcErr: jsonrpc.ErrorCodeInternalError,
   275  		},
   276  		{
   277  			name:       "missing .toml tries to fetch",
   278  			url:        "https://github.com/vegaprotocol/vega/issues",
   279  			jsonrpcErr: jsonrpc.ErrorCodeInternalError,
   280  		},
   281  	}
   282  	for _, tc := range testCases {
   283  		t.Run(tc.name, func(t *testing.T) {
   284  			_, errorDetails := handler.handle(t, ctx, api.AdminImportNetworkParams{
   285  				URL: tc.url,
   286  			})
   287  			// then
   288  			require.NotNil(t, errorDetails)
   289  			assert.Equal(t, tc.jsonrpcErr, errorDetails.Code)
   290  			if tc.suggestion != "" {
   291  				require.Contains(t, errorDetails.Data, tc.suggestion)
   292  			}
   293  		})
   294  	}
   295  }
   296  
   297  func testImportNetworkWithNotTOMLContentFailsWithFriendlyMessage(t *testing.T) {
   298  	// given
   299  	ctx := context.Background()
   300  	d := t.TempDir()
   301  
   302  	// setup
   303  	handler := newImportNetworkHandler(t)
   304  
   305  	tcs := []struct {
   306  		name           string
   307  		content        []byte
   308  		identifiedType string
   309  	}{
   310  		{
   311  			name:           "when HTML",
   312  			content:        []byte("<!DOCTYPE html><html></html>"),
   313  			identifiedType: "HTML",
   314  		}, {
   315  			name:           "when JSON",
   316  			content:        []byte("{\"type\":\"JSON\"}"),
   317  			identifiedType: "JSON",
   318  		}, {
   319  			name:           "when JSON",
   320  			content:        []byte("{\"type\":\"JSON\"}"),
   321  			identifiedType: "JSON",
   322  		},
   323  	}
   324  
   325  	for _, tc := range tcs {
   326  		t.Run(tc.name, func(tt *testing.T) {
   327  			filePath := filepath.Join(d + "tmp.toml")
   328  			err := os.WriteFile(filePath, tc.content, 0o644)
   329  			require.NoError(tt, err)
   330  
   331  			// when
   332  			result, errorDetails := handler.handle(tt, ctx, api.AdminImportNetworkParams{
   333  				URL: api.FileSchemePrefix + filePath,
   334  			})
   335  
   336  			// then
   337  			require.NotNil(tt, errorDetails)
   338  			assert.Equal(tt, fmt.Sprintf("could not read the network configuration at %q: the content looks like it contains %s, be sure your file has TOML formatting", filePath, tc.identifiedType), errorDetails.Data)
   339  			assert.Empty(tt, result)
   340  		})
   341  	}
   342  }
   343  
   344  type importNetworkHandler struct {
   345  	*api.AdminImportNetwork
   346  	ctrl         *gomock.Controller
   347  	networkStore *mocks.MockNetworkStore
   348  }
   349  
   350  func (h *importNetworkHandler) handle(t *testing.T, ctx context.Context, params jsonrpc.Params) (api.AdminImportNetworkResult, *jsonrpc.ErrorDetails) {
   351  	t.Helper()
   352  
   353  	rawResult, err := h.Handle(ctx, params)
   354  	if rawResult != nil {
   355  		result, ok := rawResult.(api.AdminImportNetworkResult)
   356  		if !ok {
   357  			t.Fatal("AdminImportWallet handler result is not a AdminImportWalletResult")
   358  		}
   359  		return result, err
   360  	}
   361  	return api.AdminImportNetworkResult{}, err
   362  }
   363  
   364  func newImportNetworkHandler(t *testing.T) *importNetworkHandler {
   365  	t.Helper()
   366  
   367  	ctrl := gomock.NewController(t)
   368  	networkStore := mocks.NewMockNetworkStore(ctrl)
   369  
   370  	return &importNetworkHandler{
   371  		AdminImportNetwork: api.NewAdminImportNetwork(networkStore),
   372  		ctrl:               ctrl,
   373  		networkStore:       networkStore,
   374  	}
   375  }