SEBA-688 add additional unit tests

Change-Id: I361bc872af08b97bccb29f17838940205fed48e9
diff --git a/commands/backup.go b/commands/backup.go
index 287236f..fc5d509 100644
--- a/commands/backup.go
+++ b/commands/backup.go
@@ -37,7 +37,7 @@
 
 type BackupCreate struct {
 	OutputOptions
-	ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Host and port"`
+	ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Chunk size for streaming transfer"`
 	Args      struct {
 		LocalFileName string
 	} `positional-args:"yes" required:"yes"`
@@ -45,9 +45,11 @@
 
 type BackupRestore struct {
 	OutputOptions
-	Args struct {
+	ChunkSize int `short:"h" long:"chunksize" default:"65536" description:"Chunk size for streaming transfer"`
+	Args      struct {
 		LocalFileName string
 	} `positional-args:"yes" required:"yes"`
+	CreateURIFunc func() (string, string) // allow override of CreateURIFunc for easy unit testing
 }
 
 type BackupOpts struct {
@@ -152,6 +154,13 @@
 	return nil
 }
 
+// Create a file:/// URI to use for storing the file in the core
+func CreateDynamicURI() (string, string) {
+	remote_name := "cordctl-restore-" + time.Now().Format("20060102T150405Z")
+	uri := "file:///var/run/xos/backup/local/" + remote_name
+	return remote_name, uri
+}
+
 func (options *BackupRestore) Execute(args []string) error {
 	conn, descriptor, err := InitReflectionClient()
 	if err != nil {
@@ -162,12 +171,17 @@
 	ctx := context.Background() // TODO: Implement a sync timeout
 
 	local_name := options.Args.LocalFileName
-	remote_name := "cordctl-restore-" + time.Now().Format("20060102T150405Z")
-	uri := "file:///var/run/xos/backup/local/" + remote_name
+
+	var remote_name, uri string
+	if options.CreateURIFunc != nil {
+		remote_name, uri = options.CreateURIFunc()
+	} else {
+		remote_name, uri = CreateDynamicURI()
+	}
 
 	// STEP 1: Upload the file
 
-	h, upload_result, err := UploadFile(conn, descriptor, local_name, uri, 65536)
+	h, upload_result, err := UploadFile(conn, descriptor, local_name, uri, options.ChunkSize)
 	if err != nil {
 		return err
 	}
diff --git a/commands/backup_test.go b/commands/backup_test.go
new file mode 100644
index 0000000..357f891
--- /dev/null
+++ b/commands/backup_test.go
@@ -0,0 +1,90 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"bytes"
+	"github.com/opencord/cordctl/testutils"
+	"io/ioutil"
+	"testing"
+)
+
+func TestBackupCreate(t *testing.T) {
+	// use `python -m json.tool` to pretty-print json
+	expected := `[
+		{
+			"bytes": 6,
+			"checksum": "sha256:e9c0f8b575cbfcb42ab3b78ecc87efa3b011d9a5d10b09fa4e96f240bf6a82f5",
+			"chunks": 2,
+			"status": "SUCCESS"
+		}
+	]`
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options BackupOpts
+	options.Create.OutputAs = "json"
+	options.Create.Args.LocalFileName = "/tmp/transfer.down"
+	options.Create.ChunkSize = 3
+	err := options.Create.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+// Mock the CreateURI function in the Restore code to use file:///tmp/transfer.up
+func CreateURI() (string, string) {
+	remote_name := "transfer.up"
+	uri := "file:///tmp/" + remote_name
+	return remote_name, uri
+}
+
+func TestBackupRestore(t *testing.T) {
+	// use `python -m json.tool` to pretty-print json
+	expected := `[
+		{
+			"bytes": 6,
+			"checksum": "sha256:e9c0f8b575cbfcb42ab3b78ecc87efa3b011d9a5d10b09fa4e96f240bf6a82f5",
+			"chunks": 2,
+			"status": "SUCCESS"
+		}
+	]`
+
+	err := ioutil.WriteFile("/tmp/transfer.up", []byte("ABCDEF"), 0644)
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options BackupRestore
+	options.OutputAs = "json"
+	options.Args.LocalFileName = "/tmp/transfer.up"
+	options.ChunkSize = 3
+	options.CreateURIFunc = CreateURI
+	err = options.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertJSONEqual(t, got.String(), expected)
+}
diff --git a/commands/completion.go b/commands/completion.go
index 67c7ad3..65c4883 100644
--- a/commands/completion.go
+++ b/commands/completion.go
@@ -32,6 +32,6 @@
 }
 
 func (options *BashOptions) Execute(args []string) error {
-	fmt.Println(completion.Bash)
+	fmt.Fprintln(OutputStream, completion.Bash)
 	return nil
 }
diff --git a/commands/completion_test.go b/commands/completion_test.go
new file mode 100644
index 0000000..6b9c119
--- /dev/null
+++ b/commands/completion_test.go
@@ -0,0 +1,39 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"bytes"
+	"github.com/opencord/cordctl/completion"
+	"github.com/opencord/cordctl/testutils"
+	"testing"
+)
+
+func TestCompletion(t *testing.T) {
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options CompletionOptions
+	err := options.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertStringEqual(t, got.String(), completion.Bash+"\n")
+}
diff --git a/commands/modeltypes_test.go b/commands/modeltypes_test.go
new file mode 100644
index 0000000..678afba
--- /dev/null
+++ b/commands/modeltypes_test.go
@@ -0,0 +1,110 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"bytes"
+	"github.com/opencord/cordctl/testutils"
+	"testing"
+)
+
+func TestModelTypeList(t *testing.T) {
+	// use `python -m json.tool` to pretty-print json
+	expected := `AddressPool
+AttWorkflowDriverService
+AttWorkflowDriverServiceInstance
+AttWorkflowDriverWhiteListEntry
+BNGPortMapping
+BackupFile
+BackupOperation
+BandwidthProfile
+ComputeServiceInstance
+FabricCrossconnectService
+FabricCrossconnectServiceInstance
+FabricIpAddress
+FabricService
+Flavor
+Image
+InterfaceType
+KubernetesConfigMap
+KubernetesConfigVolumeMount
+KubernetesData
+KubernetesResourceInstance
+KubernetesSecret
+KubernetesSecretVolumeMount
+KubernetesService
+KubernetesServiceInstance
+NNIPort
+Network
+NetworkParameter
+NetworkParameterType
+NetworkSlice
+NetworkTemplate
+Node
+NodeLabel
+NodeToSwitchPort
+OLTDevice
+ONOSApp
+ONOSService
+ONUDevice
+PONONUPort
+PONPort
+Port
+PortBase
+PortInterface
+Principal
+Privilege
+RCORDIpAddress
+RCORDService
+RCORDSubscriber
+Role
+Service
+ServiceAttribute
+ServiceDependency
+ServiceGraphConstraint
+ServiceInstance
+ServiceInstanceAttribute
+ServiceInstanceLink
+ServiceInterface
+ServicePort
+Site
+Slice
+Switch
+SwitchPort
+Tag
+TrustDomain
+UNIPort
+User
+VOLTService
+VOLTServiceInstance
+XOSCore
+XOSGuiExtension
+`
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options ModelTypeOpts
+	err := options.List.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertStringEqual(t, got.String(), expected)
+}
diff --git a/commands/orm_test.go b/commands/orm_test.go
new file mode 100644
index 0000000..aa1b7c4
--- /dev/null
+++ b/commands/orm_test.go
@@ -0,0 +1,179 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"context"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestDecodeOperator(t *testing.T) {
+	value, operator, invert, err := DecodeOperator("=something")
+	assert.Equal(t, value, "something")
+	assert.Equal(t, operator, "EQUAL")
+	assert.Equal(t, invert, false)
+	assert.Equal(t, err, nil)
+
+	value, operator, invert, err = DecodeOperator("!=something")
+	assert.Equal(t, value, "something")
+	assert.Equal(t, operator, "EQUAL")
+	assert.Equal(t, invert, true)
+	assert.Equal(t, err, nil)
+
+	value, operator, invert, err = DecodeOperator(">3")
+	assert.Equal(t, value, "3")
+	assert.Equal(t, operator, "GREATER_THAN")
+	assert.Equal(t, invert, false)
+	assert.Equal(t, err, nil)
+
+	value, operator, invert, err = DecodeOperator(">=3")
+	assert.Equal(t, value, "3")
+	assert.Equal(t, operator, "GREATER_THAN_OR_EQUAL")
+	assert.Equal(t, invert, false)
+	assert.Equal(t, err, nil)
+
+	value, operator, invert, err = DecodeOperator("<3")
+	assert.Equal(t, value, "3")
+	assert.Equal(t, operator, "LESS_THAN")
+	assert.Equal(t, invert, false)
+	assert.Equal(t, err, nil)
+
+	value, operator, invert, err = DecodeOperator("<=3")
+	assert.Equal(t, value, "3")
+	assert.Equal(t, operator, "LESS_THAN_OR_EQUAL")
+	assert.Equal(t, invert, false)
+	assert.Equal(t, err, nil)
+}
+
+func TestCommaSeparatedQueryStringsToMap(t *testing.T) {
+	m, err := CommaSeparatedQueryToMap("foo=7,bar!=stuff, x = 5, y= 27", true)
+	assert.Equal(t, err, nil)
+	assert.Equal(t, m["foo"], "=7")
+	assert.Equal(t, m["bar"], "!=stuff")
+	assert.Equal(t, m["x"], "= 5")
+	assert.Equal(t, m["y"], "= 27")
+}
+
+func TestTypeConvert(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	v, err := TypeConvert(descriptor, "Site", "id", "7")
+	assert.Equal(t, err, nil)
+	assert.Equal(t, v, int32(7))
+
+	v, err = TypeConvert(descriptor, "Site", "name", "foo")
+	assert.Equal(t, err, nil)
+	assert.Equal(t, v, "foo")
+
+	v, err = TypeConvert(descriptor, "Site", "enacted", "123.4")
+	assert.Equal(t, err, nil)
+	assert.Equal(t, v, 123.4)
+}
+
+func TestCheckModelName(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	err = CheckModelName(descriptor, "Slice")
+	assert.Equal(t, err, nil)
+
+	err = CheckModelName(descriptor, "DoesNotExist")
+	assert.Equal(t, err.Error(), "Model DoesNotExist does not exist. Use `cordctl models available` to get a list of available models")
+}
+
+func TestCreateModel(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	m := make(map[string]interface{})
+	m["name"] = "mockslice3"
+	m["site_id"] = int32(1)
+
+	err = CreateModel(conn, descriptor, "Slice", m)
+	assert.Equal(t, err, nil)
+
+	assert.Equal(t, m["id"], int32(3))
+}
+
+func TestUpdateModel(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	m := make(map[string]interface{})
+	m["id"] = int32(1)
+	m["name"] = "mockslice1_newname"
+
+	err = UpdateModel(conn, descriptor, "Slice", m)
+	assert.Equal(t, err, nil)
+}
+
+func TestGetModel(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	m, err := GetModel(context.Background(), conn, descriptor, "Slice", int32(1))
+	assert.Equal(t, err, nil)
+
+	assert.Equal(t, m.GetFieldByName("id").(int32), int32(1))
+	assert.Equal(t, m.GetFieldByName("name").(string), "mockslice1")
+}
+
+func TestListModels(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	m, err := ListModels(context.Background(), conn, descriptor, "Slice")
+	assert.Equal(t, err, nil)
+
+	assert.Equal(t, len(m), 2)
+	assert.Equal(t, m[0].GetFieldByName("id").(int32), int32(1))
+	assert.Equal(t, m[0].GetFieldByName("name").(string), "mockslice1")
+	assert.Equal(t, m[1].GetFieldByName("id").(int32), int32(2))
+	assert.Equal(t, m[1].GetFieldByName("name").(string), "mockslice2")
+}
+
+func TestFilterModels(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	qm := map[string]string{"id": "=1"}
+
+	m, err := FilterModels(context.Background(), conn, descriptor, "Slice", qm)
+	assert.Equal(t, err, nil)
+
+	assert.Equal(t, len(m), 1)
+	assert.Equal(t, m[0].GetFieldByName("id").(int32), int32(1))
+	assert.Equal(t, m[0].GetFieldByName("name").(string), "mockslice1")
+}
+
+func TestDeleteModel(t *testing.T) {
+	conn, descriptor, err := InitReflectionClient()
+	assert.Equal(t, err, nil)
+	defer conn.Close()
+
+	err = DeleteModel(conn, descriptor, "Slice", int32(1))
+	assert.Equal(t, err, nil)
+}
diff --git a/commands/services.go b/commands/services.go
index 99d39c0..e46dac6 100644
--- a/commands/services.go
+++ b/commands/services.go
@@ -21,7 +21,6 @@
 	"github.com/fullstorydev/grpcurl"
 	flags "github.com/jessevdk/go-flags"
 	"github.com/jhump/protoreflect/dynamic"
-	//"github.com/opencord/cordctl/format"
 )
 
 const (
@@ -49,25 +48,19 @@
 }
 
 func (options *ServiceList) Execute(args []string) error {
-
-	conn, err := NewConnection()
+	conn, descriptor, err := InitReflectionClient()
 	if err != nil {
 		return err
 	}
 	defer conn.Close()
 
-	descriptor, method, err := GetReflectionMethod(conn, "xos.dynamicload.GetLoadStatus")
-	if err != nil {
-		return err
-	}
-
 	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
 	defer cancel()
 
 	headers := GenerateHeaders()
 
 	h := &RpcEventHandler{}
-	err = grpcurl.InvokeRPC(ctx, descriptor, conn, method, headers, h, h.GetParams)
+	err = grpcurl.InvokeRPC(ctx, descriptor, conn, "xos.dynamicload.GetLoadStatus", headers, h, h.GetParams)
 	if err != nil {
 		return err
 	}
diff --git a/commands/services_test.go b/commands/services_test.go
new file mode 100644
index 0000000..3b213bf
--- /dev/null
+++ b/commands/services_test.go
@@ -0,0 +1,101 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"bytes"
+	"github.com/opencord/cordctl/testutils"
+	"testing"
+)
+
+func TestServicesList(t *testing.T) {
+	// use `python -m json.tool` to pretty-print json
+	expected := `[
+		{
+			"name": "onos",
+			"state": "present",
+			"version": "2.1.1-dev"
+		},
+		{
+			"name": "kubernetes",
+			"state": "present",
+			"version": "1.2.1"
+		}
+	]`
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options ServiceOpts
+	options.List.OutputAs = "json"
+	err := options.List.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestServicesListTable(t *testing.T) {
+	// We'll use the ServicesList command to be our end-to-end test for
+	// table formatted commands, as the output is relatively simple.
+	expected := `NAME          VERSION      STATE
+onos          2.1.1-dev    present
+kubernetes    1.2.1        present
+`
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options ServiceOpts
+	err := options.List.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertStringEqual(t, got.String(), expected)
+}
+
+func TestServicesListYaml(t *testing.T) {
+	// We'll use the ServicesList command to be our end-to-end test for
+	// yaml formatted commands, as the output is relatively simple.
+	expected := `- name: onos
+  version: 2.1.1-dev
+  state: present
+- name: kubernetes
+  version: 1.2.1
+  state: present
+`
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options ServiceOpts
+	options.List.OutputAs = "yaml"
+	err := options.List.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertStringEqual(t, got.String(), expected)
+}
diff --git a/commands/transfer_test.go b/commands/transfer_test.go
new file mode 100644
index 0000000..71b7d8e
--- /dev/null
+++ b/commands/transfer_test.go
@@ -0,0 +1,83 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-present Ciena Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package commands
+
+import (
+	"bytes"
+	"github.com/opencord/cordctl/testutils"
+	"io/ioutil"
+	"testing"
+)
+
+func TestDownload(t *testing.T) {
+	// use `python -m json.tool` to pretty-print json
+	expected := `[
+		{
+			"bytes": 6,
+			"checksum": "sha256:e9c0f8b575cbfcb42ab3b78ecc87efa3b011d9a5d10b09fa4e96f240bf6a82f5",
+			"chunks": 2,
+			"status": "SUCCESS"
+		}
+	]`
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options TransferOpts
+	options.Download.OutputAs = "json"
+	options.Download.Args.LocalFileName = "/tmp/transfer.down"
+	options.Download.Args.URI = "file:///tmp/transfer.down"
+	err := options.Download.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertJSONEqual(t, got.String(), expected)
+}
+
+func TestUpload(t *testing.T) {
+	// use `python -m json.tool` to pretty-print json
+	expected := `[
+		{
+			"bytes": 6,
+			"checksum": "sha256:e9c0f8b575cbfcb42ab3b78ecc87efa3b011d9a5d10b09fa4e96f240bf6a82f5",
+			"chunks": 2,
+			"status": "SUCCESS"
+		}
+	]`
+
+	err := ioutil.WriteFile("/tmp/transfer.up", []byte("ABCDEF"), 0644)
+
+	got := new(bytes.Buffer)
+	OutputStream = got
+
+	var options TransferOpts
+	options.Upload.OutputAs = "json"
+	options.Upload.Args.LocalFileName = "/tmp/transfer.up"
+	options.Upload.Args.URI = "file:///tmp/transfer.up"
+	options.Upload.ChunkSize = 3
+	err = options.Upload.Execute([]string{})
+
+	if err != nil {
+		t.Errorf("%s: Received error %v", t.Name(), err)
+		return
+	}
+
+	testutils.AssertJSONEqual(t, got.String(), expected)
+}