Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Define the port only as custom input.
  • Loading branch information
martacarbone committed Nov 11, 2025
commit 7aba0e15c65baa2def705f0ce30c92b5dfd23b6c
68 changes: 17 additions & 51 deletions cmd/arduino-app-cli/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ package version
import (
"encoding/json"
"fmt"

"net"
"net/http"
"net/url"
"strings"
"time"

"github.com/spf13/cobra"
Expand All @@ -40,73 +40,39 @@ const (
func NewVersionCmd(clientVersion string) *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print the client and server versions for the Arduino App CLI.",
Short: "Print the client and server versions for the Arduino App CLI",
Run: func(cmd *cobra.Command, args []string) {
host, _ := cmd.Flags().GetString("host")
port, _ := cmd.Flags().GetString("port")

validatedHostAndPort, err := validateHost(host)
daemonVersion, err := getDaemonVersion(http.Client{}, port)
if err != nil {
feedback.Fatal("Error: invalid host:port format", feedback.ErrBadArgument)
feedback.Warnf("Warning: cannot get the running daemon version on %s:%s\n", DefaultHostname, port)
}

httpClient := http.Client{
Timeout: time.Second,
result := versionResult{
Name: ProgramName,
Version: clientVersion,
DaemonVersion: daemonVersion,
}

result, err := versionHandler(httpClient, clientVersion, validatedHostAndPort)
if err != nil {
feedback.Warnf("Waning: " + err.Error() + "\n")
}
feedback.PrintResult(result)
},
}
cmd.Flags().String("host", fmt.Sprintf("%s:%s", DefaultHostname, DefaultPort),
"The daemon network address [host]:[port]")
cmd.Flags().String("port", DefaultPort, "The daemon network port")
return cmd
}

func versionHandler(httpClient http.Client, clientVersion string, hostAndPort string) (versionResult, error) {
func getDaemonVersion(httpClient http.Client, port string) (string, error) {

httpClient.Timeout = time.Second

url := url.URL{
Scheme: "http",
Host: hostAndPort,
Host: net.JoinHostPort(DefaultHostname, port),
Path: "/v1/version",
}

daemonVersion, err := getDaemonVersion(httpClient, url.String())

result := versionResult{
Name: ProgramName,
Version: clientVersion,
DaemonVersion: daemonVersion,
}

if err != nil {
return result, fmt.Errorf("error getting daemon version %s", hostAndPort)
}
return result, nil
}

func validateHost(hostPort string) (string, error) {
if !strings.Contains(hostPort, ":") {
hostPort += ":"
}

h, p, err := net.SplitHostPort(hostPort)
if err != nil {
return "", err
}
if h == "" {
h = DefaultHostname
}
if p == "" {
p = DefaultPort
}

return net.JoinHostPort(h, p), nil
}

func getDaemonVersion(httpClient http.Client, url string) (string, error) {
resp, err := httpClient.Get(url)
resp, err := httpClient.Get(url.String())
if err != nil {
return "", err
}
Expand All @@ -133,7 +99,7 @@ type versionResult struct {
}

func (r versionResult) String() string {
resultMessage := fmt.Sprintf("%s client version %s",
resultMessage := fmt.Sprintf("%s version %s",
ProgramName, r.Version)

if r.DaemonVersion != "" {
Expand Down
133 changes: 46 additions & 87 deletions cmd/arduino-app-cli/version/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,115 +25,66 @@ import (
"github.com/stretchr/testify/require"
)

func TestGetValidUrl(t *testing.T) {
func TestDaemonVersion(t *testing.T) {
testCases := []struct {
name string
hostPort string
expectedResult string
name string
serverStub Tripper
port string
expectedResult string
expectedErrorMessage string
}{
{
name: "Valid host and port should return default.",
hostPort: "localhost:8800",
expectedResult: "localhost:8800",
name: "return the server version when the server is up",
serverStub: successServer,
port: "8800",
expectedResult: "3.0-server",
expectedErrorMessage: "",
},
{
name: "Missing host should return default host.",
hostPort: ":8800",
expectedResult: "localhost:8800",
name: "return error if default server is not listening on default port",
serverStub: failureServer,
port: "8800",
expectedResult: "",
expectedErrorMessage: `Get "http://localhost:8800/v1/version": connection refused`,
},
{
name: "Missing port should return default port.",
hostPort: "localhost:",
expectedResult: "localhost:8800",
name: "return error if provided server is not listening on provided port",
serverStub: failureServer,
port: "1234",
expectedResult: "",
expectedErrorMessage: `Get "http://localhost:1234/v1/version": connection refused`,
},
{
name: "Custom host and port should return the provided host:port.",
hostPort: "192.168.100.1:1234",
expectedResult: "192.168.100.1:1234",
name: "return error for server response 500 Internal Server Error",
serverStub: failureInternalServerError,
port: "0",
expectedResult: "",
expectedErrorMessage: "unexpected status code received",
},
{
name: "Host only should return provided input and default port.",
hostPort: "192.168.1.1",
expectedResult: "192.168.1.1:8800",
},
{
name: "Missing host and port should return default.",
hostPort: "",
expectedResult: "localhost:8800",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
url, _ := validateHost(tc.hostPort)
require.Equal(t, tc.expectedResult, url)
})
}
}

func TestServerVersion(t *testing.T) {
clientVersion := "5.1-dev"
unreacheableUrl := "unreacheable:123"
daemonVersion := ""

testCases := []struct {
name string
serverStub Tripper
expectedResult versionResult
hostAndPort string
}{
{
name: "return the server version when the server is up",
serverStub: successServer,
expectedResult: versionResult{
Name: ProgramName,
Version: clientVersion,
DaemonVersion: "3.0",
},
hostAndPort: "localhost:8800",
},
{
name: "return error if default server is not listening",
serverStub: failureServer,
expectedResult: versionResult{
Name: ProgramName,
Version: clientVersion,
DaemonVersion: daemonVersion,
},
hostAndPort: unreacheableUrl,
},
{
name: "return error if provided server is not listening",
serverStub: failureServer,
expectedResult: versionResult{
Name: ProgramName,
Version: clientVersion,
DaemonVersion: daemonVersion,
},
hostAndPort: unreacheableUrl,
},
{
name: "return error for server resopnse 500 Internal Server Error",
serverStub: failureInternalServerError,
expectedResult: versionResult{
Name: ProgramName,
Version: clientVersion,
DaemonVersion: daemonVersion,
},
hostAndPort: unreacheableUrl,
name: "return error for server up and wrong json response",
serverStub: successServerWrongJson,
port: "8800",
expectedResult: "",
expectedErrorMessage: "invalid character '<' looking for beginning of value",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// arrange
httpClient := http.Client{}
httpClient.Transport = tc.serverStub

// act
result, _ := versionHandler(httpClient, clientVersion, tc.hostAndPort)
result, err := getDaemonVersion(httpClient, tc.port)

// assert
require.Equal(t, tc.expectedResult, result)
if err != nil {
require.Equal(t, tc.expectedErrorMessage, err.Error())
}
})
}
}
Expand All @@ -147,15 +98,23 @@ func (t Tripper) RoundTrip(request *http.Request) (*http.Response, error) {
}

var successServer = Tripper(func(*http.Request) (*http.Response, error) {
body := io.NopCloser(strings.NewReader(`{"version":"3.0"}`))
body := io.NopCloser(strings.NewReader(`{"version":"3.0-server"}`))
return &http.Response{
StatusCode: http.StatusOK,
Body: body,
}, nil
})

var successServerWrongJson = Tripper(func(*http.Request) (*http.Response, error) {
body := io.NopCloser(strings.NewReader(`<!doctype html><html lang="en"`))
return &http.Response{
StatusCode: http.StatusOK,
Body: body,
}, nil
})

var failureServer = Tripper(func(*http.Request) (*http.Response, error) {
return nil, errors.New("connetion refused")
return nil, errors.New("connection refused")
})

var failureInternalServerError = Tripper(func(*http.Request) (*http.Response, error) {
Expand Down