Newer
Older
wg-portal / internal / wireguard / wireguard_test.go
@Christoph Haas Christoph Haas on 13 Oct 2021 27 KB user and wireguard package
//go:build !integration
// +build !integration

package wireguard

import (
	"reflect"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/vishvananda/netlink"

	"github.com/h44z/wg-portal/internal/persistence"
	"github.com/pkg/errors"
	"github.com/stretchr/testify/mock"
)

func TestWgCtrlManager_GetInterfaces(t *testing.T) {
	tests := []struct {
		name    string
		manager *wgCtrlManager
		want    []*persistence.InterfaceConfig
		wantErr bool
	}{
		{
			name: "NoInterface",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
			},
			want:    []*persistence.InterfaceConfig{},
			wantErr: false,
		},
		{
			name: "Normal",
			manager: &wgCtrlManager{
				mux: sync.RWMutex{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{
					"wg0": {Identifier: "wg0"},
					"wg2": {Identifier: "wg2"},
					"wg1": {Identifier: "wg1"},
				},
			},
			want: []*persistence.InterfaceConfig{
				{Identifier: "wg0"},
				{Identifier: "wg1"},
				{Identifier: "wg2"},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.manager.GetInterfaces()
			if (err != nil) != tt.wantErr {
				t.Errorf("GetInterfaces() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("GetInterfaces() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestWgCtrlManager_CreateInterface(t *testing.T) {
	tests := []struct {
		name      string
		manager   *wgCtrlManager
		mockSetup func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore)
		args      persistence.InterfaceIdentifier
		wantErr   bool
	}{
		{
			name: "AlreadyExisting",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			args:      persistence.InterfaceIdentifier("wg0"),
			wantErr:   true,
		},
		{
			name: "LinkAddFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: nil,
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkAdd", mock.Anything).Return(errors.New("failure"))
			},
			args:    persistence.InterfaceIdentifier("wg0"),
			wantErr: true,
		},
		{
			name: "LinkSetupFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: nil,
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkAdd", mock.Anything).Return(nil)
				nl.On("LinkSetUp", mock.Anything).Return(errors.New("failure"))
			},
			args:    persistence.InterfaceIdentifier("wg0"),
			wantErr: true,
		},
		{
			name: "PersistenceFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: make(map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig),
				peers:      make(map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig),
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkAdd", mock.Anything).Return(nil)
				nl.On("LinkSetUp", mock.Anything).Return(nil)
				st.On("SaveInterface", mock.Anything).Return(errors.New("failure"))
			},
			args:    persistence.InterfaceIdentifier("wg0"),
			wantErr: true,
		},
		{
			name: "Success",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: make(map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig),
				peers:      make(map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig),
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkAdd", mock.Anything).Return(nil)
				nl.On("LinkSetUp", mock.Anything).Return(nil)
				st.On("SaveInterface", mock.Anything).Return(nil)
			},
			args:    persistence.InterfaceIdentifier("wg0"),
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup(
				tt.manager.wg.(*MockWireGuardClient),
				tt.manager.nl.(*MockNetlinkClient),
				tt.manager.store.(*MockWireGuardStore),
			)
			if err := tt.manager.CreateInterface(tt.args); (err != nil) != tt.wantErr {
				t.Errorf("CreateInterface() error = %v, wantErr %v", err, tt.wantErr)
			}
			tt.manager.wg.(*MockWireGuardClient).AssertExpectations(t)
			tt.manager.nl.(*MockNetlinkClient).AssertExpectations(t)
			tt.manager.store.(*MockWireGuardStore).AssertExpectations(t)
		})
	}
}

func TestWgCtrlManager_DeleteInterface(t *testing.T) {
	tests := []struct {
		name      string
		manager   *wgCtrlManager
		mockSetup func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore)
		args      persistence.InterfaceIdentifier
		wantErr   bool
	}{
		{
			name: "NonExisting",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			args:      "wg0",
			wantErr:   true,
		},
		{
			name: "LowLevelFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkDel", mock.Anything).Return(errors.New("failure"))
			},
			args:    "wg0",
			wantErr: true,
		},
		{
			name: "PersistenceFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkDel", mock.Anything).Return(nil)
				st.On("DeleteInterface", mock.Anything).Return(errors.New("failure"))
			},
			args:    "wg0",
			wantErr: true,
		},
		{
			name: "PeerPersistenceFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {"peer0": {Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}}},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkDel", mock.Anything).Return(nil)
				st.On("DeleteInterface", persistence.InterfaceIdentifier("wg0")).Return(nil)
				st.On("DeletePeer", persistence.PeerIdentifier("peer0")).Return(errors.New("failure"))
			},
			args:    "wg0",
			wantErr: true,
		},
		{
			name: "Success",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {"peer0": {Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}}},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkDel", mock.Anything).Return(nil)
				st.On("DeleteInterface", persistence.InterfaceIdentifier("wg0")).Return(nil)
				st.On("DeletePeer", persistence.PeerIdentifier("peer0")).Return(nil)
			},
			args:    "wg0",
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup(
				tt.manager.wg.(*MockWireGuardClient),
				tt.manager.nl.(*MockNetlinkClient),
				tt.manager.store.(*MockWireGuardStore),
			)
			if err := tt.manager.DeleteInterface(tt.args); (err != nil) != tt.wantErr {
				t.Errorf("DeleteInterface() error = %v, wantErr %v", err, tt.wantErr)
			}
			tt.manager.wg.(*MockWireGuardClient).AssertExpectations(t)
			tt.manager.nl.(*MockNetlinkClient).AssertExpectations(t)
			tt.manager.store.(*MockWireGuardStore).AssertExpectations(t)
		})
	}
}

func TestWgCtrlManager_UpdateInterface(t *testing.T) {
	type args struct {
		cfg *persistence.InterfaceConfig
	}
	tests := []struct {
		name      string
		manager   *wgCtrlManager
		mockSetup func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore)
		args      args
		wantErr   bool
	}{
		{
			name: "NonExistent",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			wantErr:   true,
		},
		{
			name: "NonExistentLowLevel",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				nl.On("LinkByName", "wg0").Return(nil, errors.New("failure"))
			},
			args: args{
				cfg: &persistence.InterfaceConfig{Identifier: "wg0", Type: persistence.InterfaceTypeServer},
			},
			wantErr: true,
		},
		{
			name: "SuccessEnabled",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				virtLink := &netlink.GenericLink{LinkType: "wireguard"}
				nl.On("LinkByName", "wg0").Return(virtLink, nil)
				nl.On("LinkSetMTU", virtLink, 234).Return(nil)
				nl.On("AddrReplace", virtLink, mock.MatchedBy(func(addr *netlink.Addr) bool {
					return addr.String() == "1.2.3.4/24"
				})).Return(nil)
				nl.On("AddrAdd", virtLink, mock.MatchedBy(func(addr *netlink.Addr) bool {
					return addr.String() == "10.0.0.2/24"
				})).Return(nil)
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(nil)
				nl.On("LinkSetUp", virtLink).Return(nil)
				st.On("SaveInterface", mock.Anything).Return(nil)
			},
			args: args{
				cfg: &persistence.InterfaceConfig{
					Identifier: "wg0", Type: persistence.InterfaceTypeServer,
					Mtu: 234, AddressStr: "10.0.0.2/24,1.2.3.4/24", Enabled: true,
					KeyPair: persistence.KeyPair{PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI="},
				},
			},
			wantErr: false,
		},
		{
			name: "SuccessDisabled",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				virtLink := &netlink.GenericLink{LinkType: "wireguard"}
				nl.On("LinkByName", "wg0").Return(virtLink, nil)
				nl.On("LinkSetMTU", virtLink, 234).Return(nil)
				nl.On("AddrReplace", virtLink, mock.MatchedBy(func(addr *netlink.Addr) bool {
					return addr.String() == "1.2.3.4/24"
				})).Return(nil)
				nl.On("AddrAdd", virtLink, mock.MatchedBy(func(addr *netlink.Addr) bool {
					return addr.String() == "10.0.0.2/24"
				})).Return(nil)
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(nil)
				nl.On("LinkSetDown", virtLink).Return(nil)
				st.On("SaveInterface", mock.Anything).Return(nil)
			},
			args: args{
				cfg: &persistence.InterfaceConfig{
					Identifier: "wg0", Type: persistence.InterfaceTypeServer,
					Mtu: 234, AddressStr: "10.0.0.2/24,1.2.3.4/24", Enabled: false,
					KeyPair: persistence.KeyPair{PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI="},
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup(
				tt.manager.wg.(*MockWireGuardClient),
				tt.manager.nl.(*MockNetlinkClient),
				tt.manager.store.(*MockWireGuardStore),
			)
			if err := tt.manager.UpdateInterface(tt.args.cfg); (err != nil) != tt.wantErr {
				t.Errorf("UpdateInterface() error = %v, wantErr %v", err, tt.wantErr)
			}
			tt.manager.wg.(*MockWireGuardClient).AssertExpectations(t)
			tt.manager.nl.(*MockNetlinkClient).AssertExpectations(t)
			tt.manager.store.(*MockWireGuardStore).AssertExpectations(t)
		})
	}
}

func TestWgCtrlManager_ApplyDefaultConfigs(t *testing.T) {
	type args struct {
		id persistence.InterfaceIdentifier
	}
	tests := []struct {
		name      string
		manager   *wgCtrlManager
		mockSetup func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore)
		args      args
		wantErr   bool
	}{
		{
			name: "NoInterface",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			args: args{
				id: "wg0",
			},
			wantErr: true,
		},
		{
			name: "PersistenceFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {Identifier: "wg0"}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {
						"peer0": {Identifier: "peer0", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}},
					},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				st.On("SavePeer", mock.Anything).Return(errors.New("failure"))
			},
			args: args{
				id: "wg0",
			},
			wantErr: true,
		},
		{
			name: "Success",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {Identifier: "wg0"}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {
						"peer0": {Identifier: "peer0", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}},
					},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				st.On("SavePeer", mock.Anything).Return(nil)
			},
			args: args{
				id: "wg0",
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup(
				tt.manager.wg.(*MockWireGuardClient),
				tt.manager.nl.(*MockNetlinkClient),
				tt.manager.store.(*MockWireGuardStore),
			)
			if err := tt.manager.ApplyDefaultConfigs(tt.args.id); (err != nil) != tt.wantErr {
				t.Errorf("ApplyDefaultConfigs() error = %v, wantErr %v", err, tt.wantErr)
			}
			tt.manager.wg.(*MockWireGuardClient).AssertExpectations(t)
			tt.manager.nl.(*MockNetlinkClient).AssertExpectations(t)
			tt.manager.store.(*MockWireGuardStore).AssertExpectations(t)
		})
	}
}

func TestWgCtrlManager_GetPeers(t *testing.T) {
	tests := []struct {
		name        string
		manager     *wgCtrlManager
		interfaceId persistence.InterfaceIdentifier
		want        []*persistence.PeerConfig
		wantErr     bool
	}{
		{
			name: "NoInterface",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
			},
			interfaceId: "wg0",
			want:        nil,
			wantErr:     true,
		},
		{
			name: "Normal",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {
						"peer0": &persistence.PeerConfig{Identifier: "peer0", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}},
						"peer1": &persistence.PeerConfig{Identifier: "peer1", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg1"}},
					},
				},
			},
			interfaceId: "wg0",
			want: []*persistence.PeerConfig{
				{Identifier: "peer0", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}},
				{Identifier: "peer1", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg1"}},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := tt.manager.GetPeers(tt.interfaceId)
			if (err != nil) != tt.wantErr {
				t.Errorf("GetPeers() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !assert.Equal(t, got, tt.want) {
				t.Errorf("GetPeers() got = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestWgCtrlManager_SavePeers(t *testing.T) {
	tests := []struct {
		name      string
		manager   *wgCtrlManager
		mockSetup func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore)
		args      []*persistence.PeerConfig
		wantErr   bool
	}{
		{
			name: "NoInterface",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			args:      []*persistence.PeerConfig{{Identifier: "peer0", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}}},
			wantErr:   true,
		},
		{
			name: "ConfigGenerationFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			args:      []*persistence.PeerConfig{{Identifier: "peer0", Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"}}},
			wantErr:   true,
		},
		{
			name: "WireGuardFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers:      nil,
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(errors.New("failure"))
			},
			args: []*persistence.PeerConfig{
				{
					Identifier: "peer0",
					KeyPair:    persistence.KeyPair{PublicKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=", PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI="},
					Interface:  &persistence.PeerInterfaceConfig{Identifier: "wg0"},
				},
			},
			wantErr: true,
		},
		{
			name: "PersistenceFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(nil)
				st.On("SavePeer", mock.Anything).Return(errors.New("failure"))
			},
			args: []*persistence.PeerConfig{
				{
					Identifier: "peer0",
					KeyPair:    persistence.KeyPair{PublicKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=", PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI="},
					Interface:  &persistence.PeerInterfaceConfig{Identifier: "wg0"},
				},
			},
			wantErr: true,
		},
		{
			name: "Success",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(nil)
				st.On("SavePeer", mock.Anything).Return(nil)
			},
			args: []*persistence.PeerConfig{
				{
					Identifier: "peer0",
					KeyPair:    persistence.KeyPair{PublicKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=", PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI="},
					Interface:  &persistence.PeerInterfaceConfig{Identifier: "wg0"},
				},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup(
				tt.manager.wg.(*MockWireGuardClient),
				tt.manager.nl.(*MockNetlinkClient),
				tt.manager.store.(*MockWireGuardStore),
			)
			if err := tt.manager.SavePeers(tt.args...); (err != nil) != tt.wantErr {
				t.Errorf("SavePeers() error = %v, wantErr %v", err, tt.wantErr)
			}
			tt.manager.wg.(*MockWireGuardClient).AssertExpectations(t)
			tt.manager.nl.(*MockNetlinkClient).AssertExpectations(t)
			tt.manager.store.(*MockWireGuardStore).AssertExpectations(t)
		})
	}
}

func TestWgCtrlManager_RemovePeer(t *testing.T) {
	tests := []struct {
		name      string
		manager   *wgCtrlManager
		mockSetup func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore)
		args      persistence.PeerIdentifier
		wantErr   bool
	}{
		{
			name: "NoPeer",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{},
				peers:      map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {},
			args:      "peer0",
			wantErr:   true,
		},
		{
			name: "WireGuardFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {"peer0": {
						KeyPair: persistence.KeyPair{
							PublicKey:  "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=",
							PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=",
						},
						Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"},
					}},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(errors.New("failure"))
			},
			args:    "peer0",
			wantErr: true,
		},
		{
			name: "PersistenceFailure",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {"peer0": {
						KeyPair: persistence.KeyPair{
							PublicKey:  "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=",
							PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=",
						},
						Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"},
					}},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(nil)
				st.On("DeletePeer", persistence.PeerIdentifier("peer0")).Return(errors.New("failure"))
			},
			args:    "peer0",
			wantErr: true,
		},
		{
			name: "Success",
			manager: &wgCtrlManager{
				mux:        sync.RWMutex{},
				wg:         &MockWireGuardClient{},
				nl:         &MockNetlinkClient{},
				store:      &MockWireGuardStore{},
				interfaces: map[persistence.InterfaceIdentifier]*persistence.InterfaceConfig{"wg0": {}},
				peers: map[persistence.InterfaceIdentifier]map[persistence.PeerIdentifier]*persistence.PeerConfig{
					"wg0": {"peer0": {
						KeyPair: persistence.KeyPair{
							PublicKey:  "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=",
							PrivateKey: "pcDxSxSZp5x87cNoRJaHdAOzxrxDfDUn7pGmrY/AmzI=",
						},
						Interface: &persistence.PeerInterfaceConfig{Identifier: "wg0"},
					}},
				},
			},
			mockSetup: func(wg *MockWireGuardClient, nl *MockNetlinkClient, st *MockWireGuardStore) {
				wg.On("ConfigureDevice", "wg0", mock.Anything).Return(nil)
				st.On("DeletePeer", persistence.PeerIdentifier("peer0")).Return(nil)
			},
			args:    "peer0",
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.mockSetup(
				tt.manager.wg.(*MockWireGuardClient),
				tt.manager.nl.(*MockNetlinkClient),
				tt.manager.store.(*MockWireGuardStore),
			)
			if err := tt.manager.RemovePeer(tt.args); (err != nil) != tt.wantErr {
				t.Errorf("RemovePeer() error = %v, wantErr %v", err, tt.wantErr)
			}
			tt.manager.wg.(*MockWireGuardClient).AssertExpectations(t)
			tt.manager.nl.(*MockNetlinkClient).AssertExpectations(t)
			tt.manager.store.(*MockWireGuardStore).AssertExpectations(t)
		})
	}
}