Repository: exercism/cli
Pull request #432 (2017-08-08 17:41:35)

[nextercism] Implement version command
First stab at the version command. I will most likely split the `func showVersion` up so that the command output can be tested. Also need to add mock test for checking latest against current version. 

nywilken: @kytrinyx I saw your test mock, thanks for calling that out. I have working tests and should have an update later tonight. 
nywilken: @kytrinyx @robphoenix I refactored and added tests. When you have a moment can you please take another look. Thanks a million. 
nywilken: @robphoenix I pushed out an update to account for your feedback. I left a comment on your comment regarding the named function. I would like to get your thoughts on my reasoning before making any changes. Thanks for the review. 
nywilken: @kytrinyx @robphoenix I pushed up what I think just might be good to go. Please give it a look over when you get a second. I should be able to pick this back up later this evening. 

Patch:
diff --git a/cli/cli.go b/cli/cli.go
index 293c5a3..1b91fa4 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -79,7 +79,7 @@ func (c *CLI) IsUpToDate() (bool, error) {
 		return false, fmt.Errorf("unable to parse current version (%s): %s", c.Version, err)
 	}
 
-	return rv.EQ(cv), nil
+	return cv.GTE(rv), nil
 }
 
 // Upgrade allows the user to upgrade to the latest version of the CLI.
diff --git a/cmd/version.go b/cmd/version.go
index 3269cec..965561c 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -3,6 +3,7 @@ package cmd
 import (
 	"fmt"
 
+	"github.com/exercism/cli/cli"
 	"github.com/spf13/cobra"
 )
 
@@ -10,6 +11,9 @@ import (
 // It follows semantic versioning.
 const Version = "3.0.0-alpha.1"
 
+// checkLatest flag for version command.
+var checkLatest bool
+
 // versionCmd outputs the version of the CLI.
 var versionCmd = &cobra.Command{
 	Use:     "version",
@@ -17,14 +21,50 @@ var versionCmd = &cobra.Command{
 	Short:   "Version outputs the version of CLI.",
 	Long: `Version outputs the version of the exercism binary that is in use.
 
-To see if there is a more recent version available, call the command with the
+To check for the latest available version, call the command with the
 --latest flag.
 	`,
+
 	Run: func(cmd *cobra.Command, args []string) {
-		fmt.Println(Version)
+		fmt.Println(currentVersion())
+
+		if checkLatest {
+			c := cli.New(Version)
+			l, err := checkForUpdate(c)
+			BailOnError(err)
+
+			fmt.Println(l)
+		}
+
+		return
 	},
 }
 
+// currentVersion returns a formatted version string for the Exercism CLI.
+func currentVersion() string {
+	return fmt.Sprintf("exercism version %s", Version)
+}
+
+// checkForUpdate verifies if the CLI is running the latest version.
+// If the client is out of date, the function returns upgrade instructions.
+func checkForUpdate(c *cli.CLI) (string, error) {
+
+	ok, err := c.IsUpToDate()
+	if err != nil {
+		return "", err
+	}
+
+	if ok {
+		return "Your CLI version is up to date.", nil
+	}
+
+	// Anything but ok is out of date.
+	msg := fmt.Sprintf("A new CLI version is available. Run `exercism upgrade` to update to %s", c.LatestRelease.Version())
+	return msg, nil
+
+}
+
 func init() {
 	RootCmd.AddCommand(versionCmd)
+	versionCmd.Flags().BoolVarP(&checkLatest, "latest", "l", false, "check latest available version")
 }


Test patch:
diff --git a/cli/cli_test.go b/cli/cli_test.go
index 0ea8aca..a525974 100644
--- a/cli/cli_test.go
+++ b/cli/cli_test.go
@@ -15,8 +15,30 @@ func TestIsUpToDate(t *testing.T) {
 		releaseTag string
 		ok         bool
 	}{
-		{"1.0.0", "v1.0.1", false},
-		{"2.0.1", "v2.0.1", true},
+		{
+			// It returns false for versions less than release.
+			cliVersion: "1.0.0",
+			releaseTag: "v1.0.1",
+			ok:         false,
+		},
+		{
+			// It returns false for pre-release versions of release.
+			cliVersion: "1.0.1-alpha.1",
+			releaseTag: "v1.0.1",
+			ok:         false,
+		},
+		{
+			// It returns true for versions equal to release.
+			cliVersion: "2.0.1",
+			releaseTag: "v2.0.1",
+			ok:         true,
+		},
+		{
+			// It returns true for versions greater than release.
+			cliVersion: "2.0.2",
+			releaseTag: "v2.0.1",
+			ok:         true,
+		},
 	}
 
 	for _, test := range tests {
diff --git a/cmd/version_test.go b/cmd/version_test.go
new file mode 100644
index 0000000..d023428
--- /dev/null
+++ b/cmd/version_test.go
@@ -0,0 +1,60 @@
+package cmd
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/exercism/cli/cli"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCurrentVersion(t *testing.T) {
+	expected := fmt.Sprintf("exercism version %s", Version)
+
+	actual := currentVersion()
+	assert.Equal(t, expected, actual)
+}
+
+func TestVersionUpdateCheck(t *testing.T) {
+	fakeEndpoint := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprintln(w, `{"tag_name": "v2.0.0"}`)
+	})
+	ts := httptest.NewServer(fakeEndpoint)
+	defer ts.Close()
+	cli.LatestReleaseURL = ts.URL
+
+	tests := []struct {
+		version  string
+		expected string
+	}{
+		{
+			// It returns new version available for versions older than latest.
+			version:  "1.0.0",
+			expected: "A new CLI version is available. Run `exercism upgrade` to update to 2.0.0",
+		},
+		{
+			// It returns up to date for versions matching latest.
+			version:  "2.0.0",
+			expected: "Your CLI version is up to date.",
+		},
+		{
+			// It returns up to date for versions newer than latest.
+			version:  "2.0.1",
+			expected: "Your CLI version is up to date.",
+		},
+	}
+
+	for _, test := range tests {
+		c := &cli.CLI{
+			Version: test.version,
+		}
+
+		actual, err := checkForUpdate(c)
+
+		assert.NoError(t, err)
+		assert.NotEmpty(t, actual)
+		assert.Equal(t, test.expected, actual)
+	}
+}