From 97c03e871a0a6fb7f91e9a7fc010c8a43d2d497d Mon Sep 17 00:00:00 2001
From: Donatas Kucinskas <donce.lt@gmail.com>
Date: Fri, 26 Apr 2019 11:49:08 +0300
Subject: [PATCH 1/2] Add CLI command for retrieving NAT status

---
 cmd/commands/cli/command.go     | 16 +++++++++++++
 tequilapi/client/client.go      | 13 ++++++++++
 tequilapi/client/client_test.go | 42 +++++++++++++++++++++++++++++++++
 tequilapi/client/dto.go         |  6 +++++
 4 files changed, 77 insertions(+)

diff --git a/cmd/commands/cli/command.go b/cmd/commands/cli/command.go
index 456215ecad..be259ad961 100644
--- a/cmd/commands/cli/command.go
+++ b/cmd/commands/cli/command.go
@@ -144,6 +144,7 @@ func (c *cliApp) handleActions(line string) {
 		{"help", c.help},
 		{"status", c.status},
 		{"healthcheck", c.healthcheck},
+		{"nat", c.natStatus},
 		{"ip", c.ip},
 		{"disconnect", c.disconnect},
 		{"stop", c.stopClient},
@@ -449,6 +450,20 @@ func (c *cliApp) healthcheck() {
 	info(buildString)
 }
 
+func (c *cliApp) natStatus() {
+	status, err := c.tequilapi.NATStatus()
+	if err != nil {
+		warn("Failed to retrieve NAT traversal status:", err)
+		return
+	}
+
+	if status.Error == "" {
+		infof("NAT traversal status: %q\n", status.Status)
+	} else {
+		infof("NAT traversal status: %q (error: %q)\n", status.Status, status.Error)
+	}
+}
+
 func (c *cliApp) proposals(filter string) {
 	proposals := c.fetchProposals()
 	c.fetchedProposals = proposals
@@ -673,6 +688,7 @@ func newAutocompleter(tequilapi *tequilapi_client.Client, proposals []tequilapi_
 		),
 		readline.PcItem("status"),
 		readline.PcItem("healthcheck"),
+		readline.PcItem("nat"),
 		readline.PcItem("proposals"),
 		readline.PcItem("ip"),
 		readline.PcItem("disconnect"),
diff --git a/tequilapi/client/client.go b/tequilapi/client/client.go
index 0cec5e39bd..e12d7538bb 100644
--- a/tequilapi/client/client.go
+++ b/tequilapi/client/client.go
@@ -336,6 +336,19 @@ func (client *Client) ServiceStop(id string) error {
 	return nil
 }
 
+// NATStatus returns status of NAT traversal
+func (client *Client) NATStatus() (NATStatusDTO, error) {
+	status := NATStatusDTO{}
+
+	response, err := client.http.Get("nat/status", nil)
+	if err != nil {
+		return status, err
+	}
+
+	err = parseResponseJSON(response, &status)
+	return status, err
+}
+
 // ServiceSessions returns all currently running sessions
 func (client *Client) ServiceSessions() (ServiceSessionListDTO, error) {
 	sessions := ServiceSessionListDTO{}
diff --git a/tequilapi/client/client_test.go b/tequilapi/client/client_test.go
index f94a950875..45e0375dc1 100644
--- a/tequilapi/client/client_test.go
+++ b/tequilapi/client/client_test.go
@@ -21,6 +21,7 @@ import (
 	"errors"
 	"io"
 	"net/http"
+	"net/http/httptest"
 	"strings"
 	"testing"
 
@@ -33,6 +34,37 @@ const errorMessage = `
 }
 `
 
+func Test_NATStatus_ReturnsStatus(t *testing.T) {
+	httpClient := mockHTTPClient(
+		t,
+		http.MethodGet,
+		"/nat/status",
+		http.StatusOK,
+		`{"status": "failure", "error": "mock error"}`,
+	)
+	client := Client{http: httpClient}
+
+	status, err := client.NATStatus()
+
+	assert.NoError(t, err)
+	assert.Equal(t, "failure", status.Status)
+	assert.Equal(t, "mock error", status.Error)
+}
+
+func Test_NATStatus_ReturnsError(t *testing.T) {
+	httpClient := mockHTTPClient(
+		t,
+		http.MethodGet,
+		"/nat/status",
+		http.StatusInternalServerError,
+		``,
+	)
+	client := Client{http: httpClient}
+
+	_, err := client.NATStatus()
+	assert.Error(t, err)
+}
+
 func TestConnectionErrorIsReturnedByClientInsteadOfDoubleParsing(t *testing.T) {
 	responseBody := &trackingCloser{
 		Reader: strings.NewReader(errorMessage),
@@ -58,6 +90,16 @@ func TestConnectionErrorIsReturnedByClientInsteadOfDoubleParsing(t *testing.T) {
 	assert.True(t, responseBody.Closed)
 }
 
+func mockHTTPClient(t *testing.T, method, url string, statusCode int, response string) httpClientInterface {
+	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, method, r.Method)
+		assert.Equal(t, url, r.URL.Path)
+		w.Write([]byte(response))
+		w.WriteHeader(statusCode)
+	}))
+	return newHTTPClient(server.URL, "", "")
+}
+
 type requestDoer func(req *http.Request) (*http.Response, error)
 
 func (f requestDoer) Do(req *http.Request) (*http.Response, error) {
diff --git a/tequilapi/client/dto.go b/tequilapi/client/dto.go
index 8532832e61..77f13a775b 100644
--- a/tequilapi/client/dto.go
+++ b/tequilapi/client/dto.go
@@ -159,3 +159,9 @@ type ServiceSessionDTO struct {
 type AccessPoliciesRequest struct {
 	IDs []string `json:"ids"`
 }
+
+// NATStatusDTO gives information about NAT traversal success or failure
+type NATStatusDTO struct {
+	Status string `json:"status"`
+	Error  string `json:"error,omitempty"`
+}

From c1651837074c497dee1e2da8cceac65ab26f540d Mon Sep 17 00:00:00 2001
From: Donatas Kucinskas <donce.lt@gmail.com>
Date: Fri, 26 Apr 2019 11:51:17 +0300
Subject: [PATCH 2/2] Refactor CLI actions to unify declaration

---
 cmd/commands/cli/command.go | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/cmd/commands/cli/command.go b/cmd/commands/cli/command.go
index be259ad961..bdde3da62b 100644
--- a/cmd/commands/cli/command.go
+++ b/cmd/commands/cli/command.go
@@ -154,15 +154,15 @@ func (c *cliApp) handleActions(line string) {
 		command string
 		handler func(argsString string)
 	}{
-		{command: "connect", handler: c.connect},
-		{command: "unlock", handler: c.unlock},
-		{command: "identities", handler: c.identities},
-		{command: "payout", handler: c.payout},
-		{command: "version", handler: c.version},
-		{command: "license", handler: c.license},
-		{command: "registration", handler: c.registration},
-		{command: "proposals", handler: c.proposals},
-		{command: "service", handler: c.service},
+		{"connect", c.connect},
+		{"unlock", c.unlock},
+		{"identities", c.identities},
+		{"payout", c.payout},
+		{"version", c.version},
+		{"license", c.license},
+		{"registration", c.registration},
+		{"proposals", c.proposals},
+		{"service", c.service},
 	}
 
 	for _, cmd := range staticCmds {