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)
+}
diff --git a/mock/README.md b/mock/README.md
index a70b6fc..faf8b01 100644
--- a/mock/README.md
+++ b/mock/README.md
@@ -1,3 +1,9 @@
 This directory contains a mock server that is used when unit-testing the cordctl client. This mock server is implemented in javascript and runs using nodejs inside of a docker container.
 
 Because the nodejs gRPC server implementation does not yet support the gRPC reflection API, a set of statically built protobufs, `xos-core.protoset`, is made available and can be used with the client instead of the reflection API. This protoset is not intended to be an operational substitute for the API retrieved via reflection, but is merely a convenience for unit testing.
+
+To regenerate the protoset from inside a running xos-core container, do this:
+
+```bash
+python -m grpc.tools.protoc --proto_path=. --descriptor_set_out=xos-core.protoset --include_imports xos.proto utility.proto filetransfer.proto dynamicload.proto modeldefs.proto
+```
diff --git a/mock/data.json b/mock/data.json
index 710f423..fd1bb03 100644
--- a/mock/data.json
+++ b/mock/data.json
@@ -119,6 +119,95 @@
         "method": "DeleteSlice",
         "input": {"id": 77},
         "error": { "code": 2, "message": "Slice matching query does not exist."}
+    },
+    {
+        "method": "GetLoadStatus",
+        "input": ".*",
+        "output": {"services": [
+            {"name": "onos",
+             "version": "2.1.1-dev",
+             "state": "present"},
+            {"name": "kubernetes",
+             "version": "1.2.1",
+             "state": "present"}]}
+    },
+    {
+        "method": "Download",
+        "input": ".*",
+        "streamType": "server",
+        "stream": [
+            {"output": { "chunk": "ABC"} },
+            {"output": { "chunk": "DEF"} }
+        ]
+    },
+    {
+        "method": "Upload",
+        "input": ".*",
+        "streamType": "client",
+        "stream": [
+            {"input": { "chunk": "ABC", "uri": "file:///tmp/transfer.up"} },
+            {"input": { "chunk": "DEF", "uri": "file:///tmp/transfer.up"} }
+        ],
+        "output": { "status": 0,
+                    "checksum": "sha256:e9c0f8b575cbfcb42ab3b78ecc87efa3b011d9a5d10b09fa4e96f240bf6a82f5",
+                    "chunks_received": 2,
+                    "bytes_received": 6 }
+    },
+    {
+        "method": "CreateBackupOperation",
+        "input": {"operation": "create"},
+        "output": {
+            "id": 1,
+            "uuid": "uuid1"
+        }
+    },
+    {
+        "method": "GetBackupOperation",
+        "input": {"id": 1},
+        "output": {
+            "id": 1,
+            "operation": "create",
+            "status": "created",
+            "updated": 1234.0,
+            "enacted": 1234.0,
+            "file_id": 33
+        }
+    },
+    {
+        "method": "GetBackupFile",
+        "input": {"id": 33},
+        "output": {
+            "id": 33,
+            "uri": "file:///transfer.down",
+            "checksum": "sha256:e9c0f8b575cbfcb42ab3b78ecc87efa3b011d9a5d10b09fa4e96f240bf6a82f5"
+        }
+    },
+    {
+        "method": "CreateBackupFile",
+        "input": ".*",
+        "output": {
+            "id": 34,
+            "uuid": "uuid34"
+        }
+    },
+    {
+        "method": "CreateBackupOperation",
+        "input": {"operation": "restore"},
+        "output": {
+            "id": 3,
+            "uuid": "uuid3"
+        }
+    },
+    {
+        "method": "FilterBackupOperation",
+        "input": {"elements": [{"operator": 0, "name": "uuid", "sValue": "uuid3"}]},
+        "output": {"items": [{
+            "id": 3,
+            "operation": "restore",
+            "status": "restored",
+            "updated": 1234.0,
+            "enacted": 1234.0,
+            "file_id": 34
+        }]}
     }
-
 ]
diff --git a/mock/xos-core.protoset b/mock/xos-core.protoset
index e1d069f..1515ded 100644
--- a/mock/xos-core.protoset
+++ b/mock/xos-core.protoset
Binary files differ
diff --git a/testutils/testutils.go b/testutils/testutils.go
index 64fdbcb..2f80f79 100644
--- a/testutils/testutils.go
+++ b/testutils/testutils.go
@@ -131,3 +131,11 @@
 	}
 	return nil
 }
+
+func AssertStringEqual(t *testing.T, actual string, expected string) error {
+	if actual != expected {
+		t.Errorf("Expected string '%s' but received actual string '%s'", expected, actual)
+		return errors.New("AssertStringEqual")
+	}
+	return nil
+}