veyron/tools/mgmt/device: add support for install config override to device tool

Most of the changes are in the unit test. Added a couple more test cases to the
install test, and re-structured the test on the pattern of defining the test
cases as structs.

Change-Id: Ide79df6371a364e6ac52ad616eb84720657e0083
diff --git a/tools/mgmt/device/devicemanager_mock_test.go b/tools/mgmt/device/devicemanager_mock_test.go
index 15b0733..859613b 100644
--- a/tools/mgmt/device/devicemanager_mock_test.go
+++ b/tools/mgmt/device/devicemanager_mock_test.go
@@ -77,6 +77,7 @@
 type InstallStimulus struct {
 	fun     string
 	appName string
+	config  device.Config
 }
 
 type InstallResponse struct {
@@ -85,7 +86,7 @@
 }
 
 func (mni *mockDeviceInvoker) Install(call ipc.ServerContext, appName string, config device.Config) (string, error) {
-	ir := mni.tape.Record(InstallStimulus{"Install", appName})
+	ir := mni.tape.Record(InstallStimulus{"Install", appName, config})
 	r := ir.(InstallResponse)
 	return r.appId, r.err
 }
diff --git a/tools/mgmt/device/impl.go b/tools/mgmt/device/impl.go
index 9a6aaf9..b62fb96 100644
--- a/tools/mgmt/device/impl.go
+++ b/tools/mgmt/device/impl.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"encoding/json"
 	"fmt"
 
 	"v.io/core/veyron2"
@@ -16,19 +17,29 @@
 	Name:     "install",
 	Short:    "Install the given application.",
 	Long:     "Install the given application.",
-	ArgsName: "<device> <application>",
+	ArgsName: "<device> <application> [<config override>]",
 	ArgsLong: `
 <device> is the veyron object name of the device manager's app service.
-<application> is the veyron object name of the application.`,
+
+<application> is the veyron object name of the application.
+
+<config override> is an optional JSON-encoded device.Config object, of the form:
+   '{"flag1":"value1","flag2":"value2"}'.`,
 }
 
 func runInstall(cmd *cmdline.Command, args []string) error {
-	if expected, got := 2, len(args); expected != got {
-		return cmd.UsageErrorf("install: incorrect number of arguments, expected %d, got %d", expected, got)
+	if expectedMin, expectedMax, got := 2, 3, len(args); expectedMin > got || expectedMax < got {
+		return cmd.UsageErrorf("install: incorrect number of arguments, expected between %d and %d, got %d", expectedMin, expectedMax, got)
 	}
 	deviceName, appName := args[0], args[1]
-	// TODO(caprita): Add support for config override.
-	appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, nil)
+	var cfg device.Config
+	if len(args) > 2 {
+		jsonConfig := args[2]
+		if err := json.Unmarshal([]byte(jsonConfig), &cfg); err != nil {
+			return fmt.Errorf("Unmarshal(%v) failed: %v", jsonConfig, err)
+		}
+	}
+	appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, cfg)
 	if err != nil {
 		return fmt.Errorf("Install failed: %v", err)
 	}
diff --git a/tools/mgmt/device/impl_test.go b/tools/mgmt/device/impl_test.go
index 8425bec..a72c411 100644
--- a/tools/mgmt/device/impl_test.go
+++ b/tools/mgmt/device/impl_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"reflect"
 	"strings"
@@ -173,47 +174,81 @@
 	var stdout, stderr bytes.Buffer
 	cmd.Init(nil, &stdout, &stderr)
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
-
-	if err := cmd.Execute([]string{"install", "blech"}); err == nil {
-		t.Fatalf("wrongly failed to receive a non-nil error.")
-	}
-	if got, expected := len(tape.Play()), 0; got != expected {
-		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
-	}
-	tape.Rewind()
-	stdout.Reset()
-
-	if err := cmd.Execute([]string{"install", "blech1", "blech2", "blech3"}); err == nil {
-		t.Fatalf("wrongly failed to receive a non-nil error.")
-	}
-	if got, expected := len(tape.Play()), 0; got != expected {
-		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
-	}
-	tape.Rewind()
-	stdout.Reset()
-
 	appId := "myBestAppID"
-	tape.SetResponses([]interface{}{InstallResponse{
-		appId: appId,
-		err:   nil,
-	}})
-	if err := cmd.Execute([]string{"install", deviceName, "myBestApp"}); err != nil {
-		t.Fatalf("%v", err)
+	cfg := device.Config{"someflag": "somevalue"}
+	for i, c := range []struct {
+		args         []string
+		config       device.Config
+		shouldErr    bool
+		tapeResponse interface{}
+		expectedTape interface{}
+	}{
+		{
+			[]string{"install", "blech"},
+			nil,
+			true,
+			nil,
+			nil,
+		},
+		{
+			[]string{"install", "blech1", "blech2", "blech3", "blech4"},
+			nil,
+			true,
+			nil,
+			nil,
+		},
+		{
+			[]string{"install", deviceName, "myBestApp", "not-valid-json"},
+			nil,
+			true,
+			nil,
+			nil,
+		},
+		{
+			[]string{"install", deviceName, "myBestApp"},
+			nil,
+			false,
+			InstallResponse{appId, nil},
+			InstallStimulus{"Install", "myBestApp", nil},
+		},
+		{
+			[]string{"install", deviceName, "myBestApp"},
+			cfg,
+			false,
+			InstallResponse{appId, nil},
+			InstallStimulus{"Install", "myBestApp", cfg},
+		},
+	} {
+		tape.SetResponses([]interface{}{c.tapeResponse})
+		if c.config != nil {
+			jsonConfig, err := json.Marshal(c.config)
+			if err != nil {
+				t.Fatalf("test case %d: Marshal(%v) failed: %v", i, c.config, err)
+			}
+			c.args = append(c.args, string(jsonConfig))
+		}
+		err := cmd.Execute(c.args)
+		if c.shouldErr {
+			if err == nil {
+				t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
+			}
+			if got, expected := len(tape.Play()), 0; got != expected {
+				t.Errorf("test case %d: invalid call sequence. Got %v, want %v", got, expected)
+			}
+		} else {
+			if err != nil {
+				t.Fatalf("test case %d: %v", i, err)
+			}
+			if expected, got := fmt.Sprintf("Successfully installed: %q", naming.Join(deviceName, appId)), strings.TrimSpace(stdout.String()); got != expected {
+				t.Fatalf("test case %d: Unexpected output from Install. Got %q, expected %q", i, got, expected)
+			}
+			if got, expected := tape.Play(), []interface{}{c.expectedTape}; !reflect.DeepEqual(expected, got) {
+				t.Errorf("test case %d: invalid call sequence. Got %v, want %v", i, got, expected)
+			}
+		}
+		tape.Rewind()
+		stdout.Reset()
 	}
-
-	eb := new(bytes.Buffer)
-	fmt.Fprintf(eb, "Successfully installed: %q", naming.Join(deviceName, appId))
-	if expected, got := eb.String(), strings.TrimSpace(stdout.String()); got != expected {
-		t.Fatalf("Unexpected output from Install. Got %q, expected %q", got, expected)
-	}
-	expected := []interface{}{
-		InstallStimulus{"Install", "myBestApp"},
-	}
-	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
-		t.Errorf("unexpected result. Got %v want %v", got, expected)
-	}
-	tape.Rewind()
-	stdout.Reset()
 }
 
 func TestClaimCommand(t *testing.T) {