services/application/applicationd: return newest envelope

Given multiple applications with different versions, return the
highest one by lexiographic sort order if no version is provied.

Change-Id: I17ab9a95eab557814b115c431de0010d4e7e83a6
diff --git a/services/application/applicationd/impl_test.go b/services/application/applicationd/impl_test.go
index 781403c..aa53303 100644
--- a/services/application/applicationd/impl_test.go
+++ b/services/application/applicationd/impl_test.go
@@ -64,8 +64,10 @@
 
 	// Create client stubs for talking to the server.
 	stub := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search"))
+	stubV0 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v0"))
 	stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
 	stubV2 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v2"))
+	stubV3 := repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v3"))
 
 	blessings, sig := newPublisherSignature(t, ctx, []byte("binarycontents"))
 
@@ -88,6 +90,15 @@
 		},
 		Publisher: blessings,
 	}
+	envelopeV3 := application.Envelope{
+		Args: []string{"--verbose", "--spiffynewflag"},
+		Env:  []string{"DEBUG=0"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
 
 	// Test Put(), adding a number of application envelopes.
 	if err := stubV1.Put(ctx, []string{"base", "media"}, envelopeV1); err != nil {
@@ -121,8 +132,45 @@
 	if _, err := stubV2.Match(ctx, []string{}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
 		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
 	}
-	if _, err := stub.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != appd.ErrInvalidSuffix.ID {
-		t.Fatalf("Unexpected error: expected %v, got %v", appd.ErrInvalidSuffix, err)
+
+	// Test that Match() against a name without a version suffix returns the latest.
+	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV2, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
+	}
+
+	// Test that we can add another envelope in sort order and we still get the
+	// correct (i.e. newest) version.
+	if err := stubV3.Put(ctx, []string{"base"}, envelopeV3); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
+	}
+
+	// Test that this is not based on time but on sort order.
+	envelopeV0 := application.Envelope{
+		Args: []string{"--help", "--zeroth"},
+		Env:  []string{"DEBUG=1"},
+		Binary: application.SignedFile{
+			File:      "/v23/name/of/binary",
+			Signature: sig,
+		},
+		Publisher: blessings,
+	}
+	if err := stubV0.Put(ctx, []string{"base"}, envelopeV0); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
+		t.Fatalf("Match() failed: %v", err)
+	}
+	if !reflect.DeepEqual(envelopeV3, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
 	}
 
 	// Test Glob
@@ -133,8 +181,10 @@
 	expected := []string{
 		"",
 		"search",
+		"search/v0",
 		"search/v1",
 		"search/v2",
+		"search/v3",
 	}
 	if !reflect.DeepEqual(matches, expected) {
 		t.Errorf("unexpected Glob results. Got %q, want %q", matches, expected)
diff --git a/services/application/applicationd/service.go b/services/application/applicationd/service.go
index e79ff03..6a8ad11 100644
--- a/services/application/applicationd/service.go
+++ b/services/application/applicationd/service.go
@@ -5,6 +5,7 @@
 package main
 
 import (
+	"sort"
 	"strings"
 
 	"v.io/x/ref/services/internal/fs"
@@ -66,13 +67,22 @@
 	if err != nil {
 		return empty, err
 	}
-	if version == "" {
-		return empty, verror.New(ErrInvalidSuffix, ctx)
-	}
 
 	i.store.Lock()
 	defer i.store.Unlock()
 
+	if version == "" {
+		versions, err := i.allAppVersions(name)
+		if err != nil {
+			return empty, err
+		}
+		if len(versions) < 1 {
+			return empty, verror.New(ErrInvalidSuffix, ctx)
+		}
+		sort.Strings(versions)
+		version = versions[len(versions)-1]
+	}
+
 	for _, profile := range profiles {
 		path := naming.Join("/applications", name, profile, version)
 		entry, err := i.store.BindObject(path).Get(call)