From 7eeef8ef3ab469cc842c8d83f24a729400f02525 Mon Sep 17 00:00:00 2001
From: Manuel Kieweg <mail@manuelkieweg.de>
Date: Thu, 18 Feb 2021 12:50:45 +0000
Subject: [PATCH] PND tests. Still some weird edge cases failing.

---
 cmd/gnmi/gnmi.go                       |  11 +-
 mocks/PrincipalNetworkDomain.go        | 192 -----------
 nucleus/errors.go                      |  13 +-
 nucleus/gnmi_transport_test.go         |  76 +++--
 nucleus/principalNetworkDomain.go      |  46 ++-
 nucleus/principalNetworkDomain_test.go | 438 +++++++++++++++++++++++++
 6 files changed, 527 insertions(+), 249 deletions(-)
 delete mode 100644 mocks/PrincipalNetworkDomain.go

diff --git a/cmd/gnmi/gnmi.go b/cmd/gnmi/gnmi.go
index 29136c249..e0ef58394 100644
--- a/cmd/gnmi/gnmi.go
+++ b/cmd/gnmi/gnmi.go
@@ -28,18 +28,21 @@ func main() {
 		log.Fatal(err)
 	}
 
-	transport := &nucleus.Gnmi{SetNode: sbi.SetNode()}
 	cfg := &gnmi.Config{
-		Addr:     "portainer.danet.fbi.h-da.de:6030",
+		Addr:     "[fdfd::ce05]:6030",
 		Username: "admin",
 		Password: "arista",
 		Encoding: gpb.Encoding_JSON_IETF,
 	}
-	transport.SetConfig(cfg)
+	transport, err := nucleus.NewGnmiTransport(cfg)
+	if err != nil {
+		log.Fatal(err)
+	}
+	transport.SetNode = sbi.SetNode()
 
 	device.Transport = transport
 
-	p := []string{"/interfaces/interface[name=*]/state/name"}
+	p := []string{"/interfaces/interface"}
 	errors := 0
 	for _, path := range p {
 		err := pnd.RequestAll(path)
diff --git a/mocks/PrincipalNetworkDomain.go b/mocks/PrincipalNetworkDomain.go
deleted file mode 100644
index b7d5a0209..000000000
--- a/mocks/PrincipalNetworkDomain.go
+++ /dev/null
@@ -1,192 +0,0 @@
-// Code generated by mockery v2.6.0. DO NOT EDIT.
-
-package mocks
-
-import (
-	nucleus "code.fbi.h-da.de/cocsn/gosdn/nucleus"
-	mock "github.com/stretchr/testify/mock"
-
-	uuid "github.com/google/uuid"
-)
-
-// PrincipalNetworkDomain is an autogenerated mock type for the PrincipalNetworkDomain type
-type PrincipalNetworkDomain struct {
-	mock.Mock
-}
-
-// AddDevice provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) AddDevice(_a0 *nucleus.Device) error {
-	ret := _m.Called(_a0)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(*nucleus.Device) error); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// AddSbi provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) AddSbi(_a0 nucleus.SouthboundInterface) error {
-	ret := _m.Called(_a0)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(nucleus.SouthboundInterface) error); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// ContainsDevice provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) ContainsDevice(_a0 uuid.UUID) bool {
-	ret := _m.Called(_a0)
-
-	var r0 bool
-	if rf, ok := ret.Get(0).(func(uuid.UUID) bool); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Get(0).(bool)
-	}
-
-	return r0
-}
-
-// Destroy provides a mock function with given fields:
-func (_m *PrincipalNetworkDomain) Destroy() error {
-	ret := _m.Called()
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func() error); ok {
-		r0 = rf()
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// GetDescription provides a mock function with given fields:
-func (_m *PrincipalNetworkDomain) GetDescription() string {
-	ret := _m.Called()
-
-	var r0 string
-	if rf, ok := ret.Get(0).(func() string); ok {
-		r0 = rf()
-	} else {
-		r0 = ret.Get(0).(string)
-	}
-
-	return r0
-}
-
-// GetName provides a mock function with given fields:
-func (_m *PrincipalNetworkDomain) GetName() string {
-	ret := _m.Called()
-
-	var r0 string
-	if rf, ok := ret.Get(0).(func() string); ok {
-		r0 = rf()
-	} else {
-		r0 = ret.Get(0).(string)
-	}
-
-	return r0
-}
-
-// GetSBIs provides a mock function with given fields:
-func (_m *PrincipalNetworkDomain) GetSBIs() map[string]nucleus.SouthboundInterface {
-	ret := _m.Called()
-
-	var r0 map[string]nucleus.SouthboundInterface
-	if rf, ok := ret.Get(0).(func() map[string]nucleus.SouthboundInterface); ok {
-		r0 = rf()
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(map[string]nucleus.SouthboundInterface)
-		}
-	}
-
-	return r0
-}
-
-// MarshalDevice provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) MarshalDevice(_a0 uuid.UUID) (string, error) {
-	ret := _m.Called(_a0)
-
-	var r0 string
-	if rf, ok := ret.Get(0).(func(uuid.UUID) string); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Get(0).(string)
-	}
-
-	var r1 error
-	if rf, ok := ret.Get(1).(func(uuid.UUID) error); ok {
-		r1 = rf(_a0)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
-// RemoveDevice provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) RemoveDevice(_a0 uuid.UUID) error {
-	ret := _m.Called(_a0)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(uuid.UUID) error); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// RemoveSbi provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) RemoveSbi(_a0 string) error {
-	ret := _m.Called(_a0)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(string) error); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// Request provides a mock function with given fields: _a0, _a1
-func (_m *PrincipalNetworkDomain) Request(_a0 uuid.UUID, _a1 string) error {
-	ret := _m.Called(_a0, _a1)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(uuid.UUID, string) error); ok {
-		r0 = rf(_a0, _a1)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// RequestAll provides a mock function with given fields: _a0
-func (_m *PrincipalNetworkDomain) RequestAll(_a0 string) error {
-	ret := _m.Called(_a0)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(string) error); ok {
-		r0 = rf(_a0)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
diff --git a/nucleus/errors.go b/nucleus/errors.go
index 32fdb4773..5da9172de 100644
--- a/nucleus/errors.go
+++ b/nucleus/errors.go
@@ -3,9 +3,16 @@ package nucleus
 import "fmt"
 
 type ErrNilClient struct {
-
 }
 
-func (e *ErrNilClient)Error() string {
+func (e *ErrNilClient) Error() string {
 	return fmt.Sprintf("client cannot be nil")
-}
\ No newline at end of file
+}
+
+type ErrDeviceNotFound struct {
+	id interface{}
+}
+
+func (e *ErrDeviceNotFound) Error() string {
+	return fmt.Sprintf("device not found. requested id: %v", e.id)
+}
diff --git a/nucleus/gnmi_transport_test.go b/nucleus/gnmi_transport_test.go
index ea7229419..dd9e73e7b 100644
--- a/nucleus/gnmi_transport_test.go
+++ b/nucleus/gnmi_transport_test.go
@@ -21,6 +21,7 @@ import (
 // TestMain bootstraps all tests. Humongous beast
 func TestMain(m *testing.M) {
 	testSetupGnmi()
+	testSetupPnd()
 	os.Exit(m.Run())
 }
 
@@ -41,7 +42,6 @@ func testSetupGnmi() {
 		client:   &mocks.GNMIClient{},
 	}
 
-
 	startGnmiTarget = make(chan string)
 	stopGnmiTarget = make(chan bool)
 	go targetRunner()
@@ -60,7 +60,7 @@ var transport *Gnmi
 var gnmiConfig *gnmi.Config
 var startGnmiTarget chan string
 var stopGnmiTarget chan bool
-var mockContext = mock.MatchedBy(func(ctx context.Context) bool { return true})
+var mockContext = mock.MatchedBy(func(ctx context.Context) bool { return true })
 
 func TestGnmi_Capabilities(t *testing.T) {
 	capabilityResponse := &gpb.CapabilityResponse{
@@ -81,7 +81,7 @@ func TestGnmi_Capabilities(t *testing.T) {
 		transport *Gnmi
 	}
 	type args struct {
-		endpoint string
+		endpoint    string
 		runEndpoint bool
 	}
 	tests := []struct {
@@ -95,7 +95,7 @@ func TestGnmi_Capabilities(t *testing.T) {
 			name:   "mock",
 			fields: fields{transport: transport},
 			args: args{
-				endpoint: gnmiConfig.Addr,
+				endpoint:    gnmiConfig.Addr,
 				runEndpoint: false,
 			},
 			want:    capabilityResponse,
@@ -104,7 +104,9 @@ func TestGnmi_Capabilities(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if tt.args.runEndpoint {startGnmiTarget <- tt.args.endpoint}
+			if tt.args.runEndpoint {
+				startGnmiTarget <- tt.args.endpoint
+			}
 
 			got, err := tt.fields.transport.Capabilities(context.Background())
 			if (err != nil) != tt.wantErr {
@@ -114,7 +116,9 @@ func TestGnmi_Capabilities(t *testing.T) {
 			if !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("Get() got = %v, want %v", got, tt.want)
 			}
-			if tt.args.runEndpoint {stopGnmiTarget <- true}
+			if tt.args.runEndpoint {
+				stopGnmiTarget <- true
+			}
 		})
 	}
 }
@@ -156,7 +160,7 @@ func TestGnmi_Get(t *testing.T) {
 		Extension: nil,
 	}
 
-	getResponse := gpb.GetResponse{
+	getResponse := &gpb.GetResponse{
 		Notification: nil,
 		Error:        nil,
 		Extension:    nil,
@@ -170,13 +174,13 @@ func TestGnmi_Get(t *testing.T) {
 		Return(getRequest, nil)
 	transport.client.(*mocks.GNMIClient).
 		On("Get", mockContext, mock.Anything).
-		Return(&getResponse, nil)
+		Return(getResponse, nil)
 
 	type fields struct {
 		transport *Gnmi
 	}
 	type args struct {
-		params []string
+		params      []string
 		runEndpoint bool
 	}
 	tests := []struct {
@@ -187,34 +191,36 @@ func TestGnmi_Get(t *testing.T) {
 		wantErr bool
 	}{
 		{
-			name: "uninitialised",
+			name:   "uninitialised",
 			fields: fields{&Gnmi{}},
 			args: args{
 				params: nil,
 			},
-			want: nil,
+			want:    nil,
 			wantErr: true,
 		},
 		{
-			name: "mock",
-			fields: fields{transport: transport},
-			args: args{},
-			want: getResponse,
+			name:    "mock",
+			fields:  fields{transport: transport},
+			args:    args{},
+			want:    getResponse,
 			wantErr: false,
 		},
 		{
-			name: "endpoint",
+			name:   "endpoint",
 			fields: fields{transport: transport},
 			args: args{
 				runEndpoint: true,
 			},
-			want: getResponse,
+			want:    getResponse,
 			wantErr: false,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			if tt.args.runEndpoint {startGnmiTarget <- tt.fields.transport.config.Addr}
+			if tt.args.runEndpoint {
+				startGnmiTarget <- tt.fields.transport.config.Addr
+			}
 			got, err := tt.fields.transport.Get(context.Background(), tt.args.params...)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
@@ -223,8 +229,10 @@ func TestGnmi_Get(t *testing.T) {
 			if !reflect.DeepEqual(got, tt.want) {
 				t.Errorf("Get() got = %v, want %v", got, tt.want)
 			}
-			if tt.args.runEndpoint {stopGnmiTarget <- true}
-			})
+			if tt.args.runEndpoint {
+				stopGnmiTarget <- true
+			}
+		})
 	}
 }
 
@@ -238,9 +246,9 @@ func TestGnmi_GetConfig(t *testing.T) {
 		want   *gnmi.Config
 	}{
 		{
-			name: "default",
+			name:   "default",
 			fields: fields{transport: transport},
-			want: gnmiConfig,
+			want:   gnmiConfig,
 		},
 	}
 	for _, tt := range tests {
@@ -267,7 +275,7 @@ func TestGnmi_ProcessResponse(t *testing.T) {
 		wantErr bool
 	}{
 		{
-			name: "Arista Full Node",
+			name:   "Arista Full Node",
 			fields: fields{Sbi: &AristaOC{}},
 			args: args{
 				path: "../test_resources/resp-full-node",
@@ -276,7 +284,7 @@ func TestGnmi_ProcessResponse(t *testing.T) {
 			wantErr: false,
 		},
 		{
-			name: "Arista Interfaces Wildcard",
+			name:   "Arista Interfaces Wildcard",
 			fields: fields{Sbi: &AristaOC{}},
 			args: args{
 				path: "../test_resources/resp-interfaces-wildcard",
@@ -285,7 +293,7 @@ func TestGnmi_ProcessResponse(t *testing.T) {
 			wantErr: false,
 		},
 		{
-			name: "OC Full Node",
+			name:   "OC Full Node",
 			fields: fields{Sbi: &OpenConfig{}},
 			args: args{
 				path: "../test_resources/resp-full-node",
@@ -294,7 +302,7 @@ func TestGnmi_ProcessResponse(t *testing.T) {
 			wantErr: false,
 		},
 		{
-			name: "OC Interfaces Wildcard",
+			name:   "OC Interfaces Wildcard",
 			fields: fields{Sbi: &OpenConfig{}},
 			args: args{
 				path: "../test_resources/resp-interfaces-wildcard",
@@ -306,7 +314,7 @@ func TestGnmi_ProcessResponse(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			g := &Gnmi{
-				SetNode:  tt.fields.Sbi.SetNode(),
+				SetNode: tt.fields.Sbi.SetNode(),
 			}
 			s := tt.fields.Sbi.Schema()
 			resp := &gpb.GetResponse{}
@@ -325,7 +333,7 @@ func TestGnmi_Set(t *testing.T) {
 		transport *Gnmi
 	}
 	type args struct {
-		params []string
+		params      []string
 		runEndpoint bool
 	}
 	tests := []struct {
@@ -484,7 +492,7 @@ func TestGnmi_getWithRequest(t *testing.T) {
 		transport *Gnmi
 	}
 	type args struct {
-		request *gpb.GetRequest
+		request     *gpb.GetRequest
 		runEndpoint bool
 	}
 	tests := []struct {
@@ -495,21 +503,21 @@ func TestGnmi_getWithRequest(t *testing.T) {
 		wantErr bool
 	}{
 		{
-			name: "getFullNode",
+			name:   "getFullNode",
 			fields: fields{transport: transport},
 			args: args{
-				request:     reqFullNode,
+				request: reqFullNode,
 			},
-			want: respFullNodeExp,
+			want:    respFullNodeExp,
 			wantErr: false,
 		},
 		{
-			name: "getInterfacesWildcard",
+			name:   "getInterfacesWildcard",
 			fields: fields{transport: transport},
 			args: args{
 				request: reqInterfacesWildcard,
 			},
-			want: respInterfacesWildcardExp,
+			want:    respInterfacesWildcardExp,
 			wantErr: false,
 		},
 	}
diff --git a/nucleus/principalNetworkDomain.go b/nucleus/principalNetworkDomain.go
index bc3307cb5..bda39a39a 100644
--- a/nucleus/principalNetworkDomain.go
+++ b/nucleus/principalNetworkDomain.go
@@ -31,7 +31,7 @@ type pndImplementation struct {
 	devices     map[uuid.UUID]*Device
 }
 
-//NewPND creates a Principle Network Domain
+// NewPND creates a Principle Network Domain
 func NewPND(name, description string, sbi SouthboundInterface) PrincipalNetworkDomain {
 	sbic := make(map[string]SouthboundInterface)
 	sbic["default"] = sbi
@@ -44,41 +44,41 @@ func NewPND(name, description string, sbi SouthboundInterface) PrincipalNetworkD
 	}
 }
 
-//GetName returns the name of the PND
+// GetName returns the name of the PND
 func (pnd *pndImplementation) GetName() string {
 	return pnd.name
 }
 
-//HasDevice checks if the given device uuid is registered for this PND
+// ContainsDevice checks if the given device uuid is registered for this PND
 func (pnd *pndImplementation) ContainsDevice(uuid uuid.UUID) bool {
 	_, exists := pnd.devices[uuid]
 	return exists
 }
 
-//GetDescription returns the current description of the PND
+// GetDescription returns the current description of the PND
 func (pnd *pndImplementation) GetDescription() string {
 	return pnd.description
 }
 
-//GetSBIs returns the registered SBIs
+// GetSBIs returns the registered SBIs
 func (pnd *pndImplementation) GetSBIs() map[string]SouthboundInterface {
 	return pnd.sbi
 }
 
-// Interface satisfaction
+// Destroy destroys the PND
 func (pnd *pndImplementation) Destroy() error {
 	return destroy()
 }
 
-//AddSbi adds a SBI to the PND which will be supported
+// AddSbi adds a SBI to the PND which will be supported
 func (pnd *pndImplementation) AddSbi(sbi SouthboundInterface) error {
 	return pnd.addSbi(sbi)
 }
 
-//AddSbi removes a SBI from the PND
-//TODO: this should to recursivly through
-//devices and remove the devices using
-//this SBI
+// AddSbi removes a SBI from the PND
+// TODO: this should to recursivly through
+// devices and remove the devices using
+// this SBI
 func (pnd *pndImplementation) RemoveSbi(sbiIdentifier string) error {
 	return pnd.removeSbi(sbiIdentifier)
 }
@@ -88,7 +88,7 @@ func (pnd *pndImplementation) AddDevice(device *Device) error {
 	return pnd.addDevice(device)
 }
 
-//RemoveDevice removes a device from the PND
+// RemoveDevice removes a device from the PND
 func (pnd *pndImplementation) RemoveDevice(uuid uuid.UUID) error {
 	return pnd.removeDevice(uuid)
 }
@@ -114,24 +114,38 @@ func (pnd *pndImplementation) addDevice(device *Device) error {
 	return nil
 }
 
+func (pnd *pndImplementation) getDevice(uuid uuid.UUID) (*Device, error) {
+	d, ok := pnd.devices[uuid]
+	if !ok {
+		return nil, &ErrDeviceNotFound{id: uuid}
+	}
+	return d, nil
+}
+
 func (pnd *pndImplementation) removeDevice(uuid uuid.UUID) error {
 	delete(pnd.devices, uuid)
 	return nil
 }
 
 func (pnd *pndImplementation) MarshalDevice(uuid uuid.UUID) (string, error) {
-	d := pnd.devices[uuid]
-	json, err := json.MarshalIndent(d.GoStruct, "", "\t")
+	d, err := pnd.getDevice(uuid)
+	if err != nil {
+		return "", err
+	}
+	jsonTree, err := json.MarshalIndent(d.GoStruct, "", "\t")
 	if err != nil {
 		return "", err
 	}
 
-	return string(json), nil
+	return string(jsonTree), nil
 }
 
 //Request sends a request for a specific device
 func (pnd *pndImplementation) Request(uuid uuid.UUID, path string) error {
-	d := pnd.devices[uuid]
+	d, err := pnd.getDevice(uuid)
+	if err != nil {
+		return err
+	}
 	ctx := context.Background()
 	res, err := d.Transport.Get(ctx, path)
 	if err != nil {
diff --git a/nucleus/principalNetworkDomain_test.go b/nucleus/principalNetworkDomain_test.go
index 0fa4ffa90..784ccab6c 100644
--- a/nucleus/principalNetworkDomain_test.go
+++ b/nucleus/principalNetworkDomain_test.go
@@ -1 +1,439 @@
 package nucleus
+
+import (
+	"code.fbi.h-da.de/cocsn/gosdn/mocks"
+	"code.fbi.h-da.de/cocsn/yang-models/generated/openconfig"
+	"errors"
+	"github.com/google/uuid"
+	log "github.com/sirupsen/logrus"
+	"github.com/stretchr/testify/mock"
+	"reflect"
+	"testing"
+)
+
+func testSetupPnd() {
+	var err error
+	did, err = uuid.Parse("4d8246f8-e884-41d6-87f5-c2c784df9e44")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	mdid, err = uuid.Parse("688a264e-5f85-40f8-bd13-afc42fcd5c7a")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defaultPnd = &pndImplementation{
+		name:        "default",
+		description: "default test pnd",
+		sbi:         map[string]SouthboundInterface{"default": &OpenConfig{}},
+		devices:     map[uuid.UUID]*Device{},
+	}
+
+	deviceWithMockTransport = &Device{
+		GoStruct: nil,
+		SBI:      &OpenConfig{},
+		Config: DeviceConfig{
+			Uuid:     mdid,
+			Address:  "",
+			Username: "",
+			Password: "",
+		},
+		Transport: &mocks.Transport{},
+	}
+}
+
+var defaultPnd *pndImplementation
+var did uuid.UUID
+var mdid uuid.UUID
+var deviceWithMockTransport *Device
+
+func TestNewPND(t *testing.T) {
+	type args struct {
+		name        string
+		description string
+		sbi         SouthboundInterface
+	}
+	tests := []struct {
+		name string
+		args args
+		want PrincipalNetworkDomain
+	}{
+		{
+			name: "default",
+			args: args{
+				name:        "default",
+				description: "default test pnd",
+				sbi:         &OpenConfig{},
+			},
+			want: defaultPnd,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := NewPND(tt.args.name, tt.args.description, tt.args.sbi); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("NewPND() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_destroy(t *testing.T) {
+	tests := []struct {
+		name    string
+		wantErr bool
+	}{
+		{name: "dummy", wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := destroy(); (err != nil) != tt.wantErr {
+				t.Errorf("destroy() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_AddDevice(t *testing.T) {
+	type args struct {
+		device *Device
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default",
+			args: args{device: &Device{
+				Config: DeviceConfig{
+					Uuid: did,
+				},
+			}},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := defaultPnd.AddDevice(tt.args.device); (err != nil) != tt.wantErr {
+				t.Errorf("AddDevice() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			_, ok := defaultPnd.devices[did]
+			if !ok {
+				t.Errorf("AddDevice() Device %v not in device store %v",
+					tt.args.device, defaultPnd.devices)
+			}
+		})
+		delete(defaultPnd.devices, did)
+	}
+}
+
+func Test_pndImplementation_AddSbi(t *testing.T) {
+	type args struct {
+		sbi SouthboundInterface
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{name: "default", args: args{sbi: &OpenConfig{}}, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if err := defaultPnd.AddSbi(tt.args.sbi); (err != nil) != tt.wantErr {
+				t.Errorf("AddSbi() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			_, ok := defaultPnd.GetSBIs()[tt.args.sbi.SbiIdentifier()]
+			if !ok {
+				t.Errorf("AddSbi() SBI %v not in device store %v",
+					tt.args.sbi, defaultPnd.GetSBIs())
+			}
+		})
+		delete(defaultPnd.sbi, tt.args.sbi.SbiIdentifier())
+	}
+}
+
+func Test_pndImplementation_ContainsDevice(t *testing.T) {
+	type args struct {
+		uuid   uuid.UUID
+		device *Device
+	}
+	tests := []struct {
+		name string
+		args args
+		want bool
+	}{
+		{name: "default", args: args{
+			uuid:   did,
+			device: &Device{Config: DeviceConfig{Uuid: did}},
+		}, want: true},
+		{name: "fails", args: args{
+			uuid:   uuid.New(),
+			device: &Device{Config: DeviceConfig{Uuid: did}},
+		}, want: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			defaultPnd.devices[tt.args.uuid] = tt.args.device
+			if got := defaultPnd.ContainsDevice(tt.args.uuid); got != tt.want {
+				t.Errorf("ContainsDevice() = %v, want %v", got, tt.want)
+			}
+		})
+		delete(defaultPnd.devices, did)
+	}
+}
+
+func Test_pndImplementation_Destroy(t *testing.T) {
+	type fields struct {
+		name        string
+		description string
+		sbi         map[string]SouthboundInterface
+		devices     map[uuid.UUID]*Device
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		wantErr bool
+	}{
+		{name: "dummy", fields: fields{}, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			pnd := &pndImplementation{
+				name:        tt.fields.name,
+				description: tt.fields.description,
+				sbi:         tt.fields.sbi,
+				devices:     tt.fields.devices,
+			}
+			if err := pnd.Destroy(); (err != nil) != tt.wantErr {
+				t.Errorf("Destroy() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_GetDescription(t *testing.T) {
+	tests := []struct {
+		name string
+		want string
+	}{
+		{name: "default", want: "default test pnd"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := defaultPnd.GetDescription(); got != tt.want {
+				t.Errorf("GetDescription() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_GetName(t *testing.T) {
+	tests := []struct {
+		name string
+		want string
+	}{
+		{name: "default", want: "default"},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := defaultPnd.GetName(); got != tt.want {
+				t.Errorf("GetName() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_GetSBIs(t *testing.T) {
+	tests := []struct {
+		name string
+		want map[string]SouthboundInterface
+	}{
+		{name: "default", want: defaultPnd.sbi},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := defaultPnd.GetSBIs(); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetSBIs() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_MarshalDevice(t *testing.T) {
+	type args struct {
+		uuid uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    string
+		wantErr bool
+	}{
+		{name: "default", args: args{did}, want: "{\n\t\"Acl\": null,\n\t\"Bgp\": null,\n\t\"Components\": null,\n\t\"Interfaces\": null,\n\t\"LocalRoutes\": null,\n\t\"Messages\": null,\n\t\"NetworkInstances\": null,\n\t\"RoutingPolicy\": null,\n\t\"System\": null\n}", wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			d := &Device{
+				GoStruct: &openconfig.Device{},
+				SBI:      nil,
+				Config: DeviceConfig{
+					Uuid:     tt.args.uuid,
+					Address:  "localhost",
+					Username: "test",
+					Password: "test",
+				},
+				Transport: nil,
+			}
+			_ = defaultPnd.addDevice(d)
+			got, err := defaultPnd.MarshalDevice(tt.args.uuid)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("MarshalDevice() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("MarshalDevice() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_RemoveDevice(t *testing.T) {
+	type args struct {
+		uuid uuid.UUID
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{name: "default", args: args{uuid: did}, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			d := &Device{Config: DeviceConfig{Uuid: did}}
+			_ = defaultPnd.addDevice(d)
+			if err := defaultPnd.RemoveDevice(tt.args.uuid); (err != nil) != tt.wantErr {
+				t.Errorf("RemoveDevice() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			_, ok := defaultPnd.devices[did]
+			if ok {
+				t.Errorf("RemoveDevice() device %v still in device store %v", d, defaultPnd.devices)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_RemoveSbi(t *testing.T) {
+	type args struct {
+		sbiIdentifier string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{name: "default", args: args{sbiIdentifier: "openconfig"}, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			_ = defaultPnd.addSbi(&OpenConfig{})
+			if err := defaultPnd.RemoveSbi(tt.args.sbiIdentifier); (err != nil) != tt.wantErr {
+				t.Errorf("RemoveSbi() error = %v, wantErr %v", err, tt.wantErr)
+			}
+			sbi, ok := defaultPnd.sbi[tt.args.sbiIdentifier]
+			if ok {
+				t.Errorf("RemoveDevice() SBI %v still in SBI store %v", sbi, defaultPnd.sbi)
+			}
+		})
+	}
+}
+
+func Test_pndImplementation_Request(t *testing.T) {
+	type args struct {
+		uuid uuid.UUID
+		path string
+		rErr error
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default",
+			args: args{
+				uuid: mdid,
+				path: "",
+				rErr: nil,
+			},
+			wantErr: false,
+		},
+		{
+			name: "error",
+			args: args{
+				uuid: did,
+				path: "",
+				rErr: errors.New("deliberate test fail"),
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tr := deviceWithMockTransport.Transport.(*mocks.Transport)
+			tr.On("Get", mockContext, mock.Anything).Return(mock.Anything, tt.args.rErr)
+			tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
+			_ = defaultPnd.addDevice(deviceWithMockTransport)
+			if err := defaultPnd.Request(tt.args.uuid, tt.args.path); (err != nil) != tt.wantErr {
+				t.Errorf("Request() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+	delete(defaultPnd.devices, mdid)
+}
+
+func Test_pndImplementation_RequestAll(t *testing.T) {
+	type args struct {
+		uuid uuid.UUID
+		path string
+		rErr error
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default",
+			args: args{
+				uuid: mdid,
+				path: "",
+				rErr: nil,
+			},
+			wantErr: false,
+		},
+		{
+			name: "error",
+			args: args{
+				uuid: uuid.UUID{},
+				path: "",
+				rErr: errors.New("deliberate test fail"),
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tr := deviceWithMockTransport.Transport.(*mocks.Transport)
+			tr.On("Get", mockContext, mock.Anything).Return(mock.Anything, tt.args.rErr)
+			tr.On("ProcessResponse", mock.Anything, mock.Anything, mock.Anything).Return(tt.args.rErr)
+			_ = defaultPnd.addDevice(deviceWithMockTransport)
+			if err := defaultPnd.RequestAll(tt.args.path); (err != nil) != tt.wantErr {
+				t.Errorf("RequestAll() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+		delete(defaultPnd.devices, mdid)
+	}
+}
-- 
GitLab