Repository: vegaprotocol/vegawallet (go)

Issue:
Add command to delete a network
It should work like the `vegawallet delete --wallet <WALLET>`, especially the safety mechanism and the `--force` flag.

Example:

```sh
vegawallet network delete --network <NETWORK>
```

Pull request:
Add command to delete a network
Closes #400 
Add a command to delete a network from the store, along with tests for the store, the handler, and an end-to-end test.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6565f9b..4b75289 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
 - [385](https://github.com/vegaprotocol/vegawallet/pull/385) - Add command `delete` to delete a wallet
 - [398](https://github.com/vegaprotocol/vegawallet/pull/398) - More idiomatic test suite
 - [396](https://github.com/vegaprotocol/vegawallet/pull/396) - Update the "important" section of `key generate`
+- [405](https://github.com/vegaprotocol/vegawallet/pull/405) - Add command `network delete` to delete a network
 
 ### 🔥 Removal
 - [387](https://github.com/vegaprotocol/vegawallet/pull/387) - Remove network initialisation in favor of `network import`
diff --git a/cmd/network.go b/cmd/network.go
index d8587e0..b0bf791 100644
--- a/cmd/network.go
+++ b/cmd/network.go
@@ -17,5 +17,6 @@ func NewCmdNetwork(w io.Writer, rf *RootFlags) *cobra.Command {
 	cmd.AddCommand(NewCmdListNetworks(w, rf))
 	cmd.AddCommand(NewCmdImportNetwork(w, rf))
 	cmd.AddCommand(NewCmdDescribeNetwork(w, rf))
+	cmd.AddCommand(NewCmdDeleteNetwork(w, rf))
 	return cmd
 }
diff --git a/cmd/network_delete.go b/cmd/network_delete.go
new file mode 100644
index 0000000..b29e7fe
--- /dev/null
+++ b/cmd/network_delete.go
@@ -0,0 +1,98 @@
+package cmd
+
+import (
+	"fmt"
+	"io"
+
+	"code.vegaprotocol.io/shared/paths"
+	"code.vegaprotocol.io/vegawallet/cmd/cli"
+	"code.vegaprotocol.io/vegawallet/cmd/flags"
+	"code.vegaprotocol.io/vegawallet/cmd/printer"
+	"code.vegaprotocol.io/vegawallet/network"
+	netstore "code.vegaprotocol.io/vegawallet/network/store/v1"
+	"github.com/spf13/cobra"
+)
+
+var (
+	deleteNetworkLong = cli.LongDesc(`
+	    Delete the specified network
+	`)
+
+	deleteNetworkExample = cli.Examples(`
+		# Delete a network
+		vegawallet network delete --network NETWORK
+	`)
+)
+
+func NewCmdDeleteNetwork(w io.Writer, rf *RootFlags) *cobra.Command {
+	h := func(req *network.DeleteNetworkRequest) error {
+		vegaPaths := paths.New(rf.Home)
+
+		netStore, err := netstore.InitialiseStore(vegaPaths)
+		if err != nil {
+			return fmt.Errorf("couldn't initialise networks store: %w", err)
+		}
+
+		return network.DeleteNetwork(netStore, req)
+	}
+
+	return BuildCmdDeleteNetwork(w, h, rf)
+}
+
+type DeleteNetworkFlags struct {
+	Network string
+}
+
+func (f *DeleteNetworkFlags) Validate() (*network.DeleteNetworkRequest, error) {
+	req := &network.DeleteNetworkRequest{}
+
+	if len(f.Network) == 0 {
+		return nil, flags.FlagMustBeSpecifiedError("network")
+	}
+	req.Name = f.Network
+
+	return req, nil
+}
+
+func BuildCmdDeleteNetwork(w io.Writer, handler DeleteNetworkHandler, rf *RootFlags) *cobra.Command {
+	f := &DeleteNetworkFlags{}
+	cmd := &cobra.Command{
+		Use:     "delete",
+		Short:   "Delete the specified network",
+		Long:    deleteNetworkLong,
+		Example: deleteNetworkExample,
+		RunE: func(_ *cobra.Command, _ []string) error {
+			req, err := f.Validate()
+			if err != nil {
+				return err
+			}
+			err = handler(req)
+			if err != nil {
+				return err
+			}
+
+			if rf.Output == flags.InteractiveOutput {
+				PrintDeleteNetworkResponse(w, f.Network)
+			}
+
+			return nil
+		},
+	}
+
+	cmd.Flags().StringVarP(&f.Network,
+		"network", "n",
+		"",
+		"Network to delete",
+	)
+
+	return cmd
+}
+
+type DeleteNetworkHandler func(*network.DeleteNetworkRequest) error
+
+func PrintDeleteNetworkResponse(w io.Writer, networkName string) {
+	p := printer.NewInteractivePrinter(w)
+	p.NextLine().Text("Network ")
+	p.WarningText(networkName)
+	p.Text(" deleted")
+}
diff --git a/cmd/wallet_delete.go b/cmd/wallet_delete.go
index ad78053..d0c2423 100644
--- a/cmd/wallet_delete.go
+++ b/cmd/wallet_delete.go
@@ -74,7 +74,7 @@ func BuildCmdDeleteWallet(w io.Writer, handler DeleteWalletHandler, rf *RootFlag
 
 			switch rf.Output {
 			case flags.InteractiveOutput:
-				PrintDeleteWalletResponse(w)
+				PrintDeleteWalletResponse(w, f.Wallet)
 			case flags.JSONOutput:
 				return nil
 			}
@@ -114,8 +114,10 @@ func (f *DeleteWalletFlags) Validate() error {
 	return nil
 }
 
-func PrintDeleteWalletResponse(w io.Writer) {
+func PrintDeleteWalletResponse(w io.Writer, walletName string) {
 	p := printer.NewInteractivePrinter(w)
 
-	p.CheckMark().SuccessText("Deletion succeeded").NextSection()
+	p.CheckMark().SuccessText("Wallet ")
+	p.WarningText(walletName)
+	p.Text(" deleted")
 }
diff --git a/network/handler.go b/network/handler.go
index 179e39f..45be945 100644
--- a/network/handler.go
+++ b/network/handler.go
@@ -13,6 +13,7 @@ type Store interface {
 	SaveNetwork(*Network) error
 	ListNetworks() ([]string, error)
 	GetNetworkPath(string) string
+	DeleteNetwork(string) error
 }
 
 //go:generate go run github.com/golang/mock/mockgen -destination mocks/reader_mock.go -package mocks code.vegaprotocol.io/vegawallet/network Reader
@@ -165,3 +166,27 @@ type DescribeNetworkResponse struct {
 		LocalPort int    `json:"localPort"`
 	}
 }
+
+type DeleteNetworkRequest struct {
+	Name string
+}
+
+type DeleteNetworkResponse struct {
+	Name string
+}
+
+func DeleteNetwork(store Store, req *DeleteNetworkRequest) error {
+	exists, err := store.NetworkExists(req.Name)
+	if err != nil {
+		return fmt.Errorf("couldn't verify network existence: %w", err)
+	}
+	if !exists {
+		return NewNetworkDoesNotExistError(req.Name)
+	}
+	err = store.DeleteNetwork(req.Name)
+	if err != nil {
+		return fmt.Errorf("couldn't delete network: %w", err)
+	}
+
+	return nil
+}
diff --git a/network/mocks/store_mock.go b/network/mocks/store_mock.go
index 2f2dd8f..90a2210 100644
--- a/network/mocks/store_mock.go
+++ b/network/mocks/store_mock.go
@@ -33,6 +33,20 @@ func (m *MockStore) EXPECT() *MockStoreMockRecorder {
 	return m.recorder
 }
 
+// DeleteNetwork mocks base method
+func (m *MockStore) DeleteNetwork(arg0 string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteNetwork", arg0)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteNetwork indicates an expected call of DeleteNetwork
+func (mr *MockStoreMockRecorder) DeleteNetwork(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNetwork", reflect.TypeOf((*MockStore)(nil).DeleteNetwork), arg0)
+}
+
 // GetNetwork mocks base method
 func (m *MockStore) GetNetwork(arg0 string) (*network.Network, error) {
 	m.ctrl.T.Helper()
diff --git a/network/store/v1/store.go b/network/store/v1/store.go
index 1c5d79d..ce326d3 100644
--- a/network/store/v1/store.go
+++ b/network/store/v1/store.go
@@ -76,6 +76,11 @@ func (s *Store) SaveNetwork(net *network.Network) error {
 	return nil
 }
 
+func (s *Store) DeleteNetwork(name string) error {
+	path := s.GetNetworkPath(name)
+	return os.Remove(path)
+}
+
 func (s *Store) nameToFilePath(network string) string {
 	return filepath.Join(s.networksHome, network+fileExt)
 }
diff --git a/wallet/wallet.go b/wallet/wallet.go
index 0a9bd17..c14d251 100644
--- a/wallet/wallet.go
+++ b/wallet/wallet.go
@@ -41,7 +41,6 @@ type PublicKey interface {
 	Index() uint32
 	AlgorithmVersion() uint32
 	AlgorithmName() string
-
 	MarshalJSON() ([]byte, error)
 	UnmarshalJSON(data []byte) error
 }
@@ -51,7 +50,6 @@ type MasterKeyPair interface {
 	PrivateKey() string
 	AlgorithmVersion() uint32
 	AlgorithmName() string
-
 	SignAny(data []byte) ([]byte, error)
 	Sign(data []byte) (*Signature, error)
 }


Test patch:
diff --git a/network/handler_test.go b/network/handler_test.go
index 38f01b2..c554056 100644
--- a/network/handler_test.go
+++ b/network/handler_test.go
@@ -36,6 +36,49 @@ func TestImportNetwork(t *testing.T) {
 	t.Run("Importing existing network fails", testImportingExistingNetworkFails)
 	t.Run("Importing by overwriting existing network succeeds", testImportingByOverwritingNetworkSucceeds)
 	t.Run("Importing network with errors when saving fails", testImportingNetworkWithErrorsWhenSavingFails)
+	t.Run("Deleting a network", testDeletingNetwork)
+	t.Run("Deleting a network which doesn't exist fails", testDeletingNonExistantNetworkFails)
+}
+
+func testDeletingNonExistantNetworkFails(t *testing.T) {
+	ts := getTestConfig(t)
+
+	// given
+	net := &network.Network{
+		Name: "test",
+	}
+	ts.store.EXPECT().
+		NetworkExists("test").
+		Times(1).
+		Return(false, nil)
+
+	err := network.DeleteNetwork(ts.store, &network.DeleteNetworkRequest{Name: net.Name})
+	require.EqualError(t, err, "network \"test\" doesn't exist")
+}
+
+func testDeletingNetwork(t *testing.T) {
+	ts := getTestConfig(t)
+
+	// given
+	net := &network.Network{
+		Name: "test",
+	}
+
+	// setup
+	ts.store.EXPECT().
+		NetworkExists("test").
+		Times(1).
+		Return(true, nil)
+	ts.store.EXPECT().
+		DeleteNetwork("test").
+		Times(1).
+		Return(nil)
+
+	// when
+	err := network.DeleteNetwork(ts.store, &network.DeleteNetworkRequest{Name: net.Name})
+
+	// then
+	require.Nil(t, err)
 }
 
 func testImportingNetworkSucceeds(t *testing.T) {
diff --git a/network/store/v1/store_test.go b/network/store/v1/store_test.go
index a70d98e..7d97187 100644
--- a/network/store/v1/store_test.go
+++ b/network/store/v1/store_test.go
@@ -27,6 +27,37 @@ func TestFileStoreV1(t *testing.T) {
 	t.Run("Getting network path succeeds", testFileStoreV1GetNetworkPathSucceeds)
 	t.Run("Getting networks path succeeds", testFileStoreV1GetNetworksPathSucceeds)
 	t.Run("Listing networks succeeds", testFileStoreV1ListingNetworksSucceeds)
+	t.Run("Deleting network succeeds", testFileStoreV1DeleteNetworkSucceeds)
+}
+
+func testFileStoreV1DeleteNetworkSucceeds(t *testing.T) {
+	vegaHome := newVegaHome(t)
+
+	// Create a network for us to delete
+	s, err := v1.InitialiseStore(vegaHome)
+	require.NoError(t, err)
+	assert.NotNil(t, s)
+
+	net := &network.Network{
+		Name: "test",
+	}
+
+	err = s.SaveNetwork(net)
+	require.NoError(t, err)
+
+	// Check it's really there
+	returnedNet, err := s.GetNetwork("test")
+	require.NoError(t, err)
+	assert.Equal(t, net, returnedNet)
+
+	// Now delete it
+	err = s.DeleteNetwork("test")
+	require.NoError(t, err)
+
+	// Check it's no longer there
+	returnedNet, err = s.GetNetwork("test")
+	require.Error(t, err)
+	assert.Nil(t, returnedNet)
 }
 
 func testNewStoreSucceeds(t *testing.T) {
diff --git a/tests/commands_for_test.go b/tests/commands_for_test.go
index 8b291f6..c3c07dc 100644
--- a/tests/commands_for_test.go
+++ b/tests/commands_for_test.go
@@ -439,6 +439,14 @@ func NetworkList(t *testing.T, args []string) (*ListNetworksResponse, error) {
 	return resp, nil
 }
 
+func NetworkDelete(t *testing.T, args []string) error {
+	t.Helper()
+	argsWithCmd := []string{"network", "delete"}
+	argsWithCmd = append(argsWithCmd, args...)
+	_, err := ExecuteCmd(t, argsWithCmd)
+	return err
+}
+
 type DescribeNetworkResponse struct {
 	Name        string `json:"name"`
 	Level       string `json:"logLevel"`
diff --git a/tests/sc_delete_network_test.go b/tests/sc_delete_network_test.go
new file mode 100644
index 0000000..792c735
--- /dev/null
+++ b/tests/sc_delete_network_test.go
@@ -0,0 +1,59 @@
+package tests_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestDeleteNetwork(t *testing.T) {
+	// given
+	home := t.TempDir()
+
+	networkFile := NewFile(t, home, "my-network-1.toml", FakeNetwork("my-network-1"))
+
+	// when (import network)
+	importNetworkResp, err := NetworkImport(t, []string{
+		"--home", home,
+		"--output", "json",
+		"--from-file", networkFile,
+	})
+
+	// then
+	require.NoError(t, err)
+	AssertImportNetwork(t, importNetworkResp).
+		WithName("my-network-1").
+		LocatedUnder(home)
+
+	// when (list networks)
+	listNetsResp1, err := NetworkList(t, []string{
+		"--home", home,
+		"--output", "json",
+	})
+
+	// then
+	require.NoError(t, err)
+	require.NotNil(t, listNetsResp1)
+	require.Equal(t, []string{"my-network-1"}, listNetsResp1.Networks)
+
+	// when (delete the network)
+	err = NetworkDelete(t, []string{
+		"--home", home,
+		"--output", "json",
+		"--network", "my-network-1",
+	})
+
+	// then
+	require.NoError(t, err)
+
+	// when (list networks again)
+	listNetsResp2, err := NetworkList(t, []string{
+		"--home", home,
+		"--output", "json",
+	})
+
+	// then
+	require.NoError(t, err)
+	require.NotNil(t, listNetsResp2)
+	require.Equal(t, []string{}, listNetsResp2.Networks)
+}