Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e7ac2da

Browse files
martacarbonelucarin91
authored andcommitted
Improve the arduino-app-cli version command by adding the "server version" #31 (#49)
* Add the server version to arduino-app-cli version. * Add copyright header. --------- Co-authored-by: Luca Rinaldi <l.rinaldi@arduino.cc>
1 parent d16133c commit e7ac2da

File tree

2 files changed

+194
-8
lines changed

2 files changed

+194
-8
lines changed

‎cmd/arduino-app-cli/version/version.go‎

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,95 @@
1616
package version
1717

1818
import (
19+
"encoding/json"
1920
"fmt"
21+
"net"
22+
"net/http"
23+
"net/url"
24+
"time"
2025

2126
"github.com/spf13/cobra"
2227

2328
"github.com/arduino/arduino-app-cli/cmd/feedback"
2429
)
2530

26-
func NewVersionCmd(version string) *cobra.Command {
31+
// The actual listening address for the daemon
32+
// is defined in the installation package
33+
const (
34+
DefaultHostname = "localhost"
35+
DefaultPort = "8800"
36+
ProgramName = "Arduino App CLI"
37+
)
38+
39+
func NewVersionCmd(clientVersion string) *cobra.Command {
2740
cmd := &cobra.Command{
2841
Use: "version",
2942
Short: "Print the version number of Arduino App CLI",
3043
Run: func(cmd *cobra.Command, args []string) {
31-
feedback.PrintResult(versionResult{
32-
AppName: "Arduino App CLI",
33-
Version: version,
34-
})
44+
port, _ := cmd.Flags().GetString("port")
45+
46+
daemonVersion, err := getDaemonVersion(http.Client{}, port)
47+
if err != nil {
48+
feedback.Warnf("Warning: cannot get the running daemon version on %s:%s\n", DefaultHostname, port)
49+
}
50+
51+
result := versionResult{
52+
Name: ProgramName,
53+
Version: clientVersion,
54+
DaemonVersion: daemonVersion,
55+
}
56+
57+
feedback.PrintResult(result)
3558
},
3659
}
60+
cmd.Flags().String("port", DefaultPort, "The daemon network port")
3761
return cmd
3862
}
3963

64+
func getDaemonVersion(httpClient http.Client, port string) (string, error) {
65+
66+
httpClient.Timeout = time.Second
67+
68+
url := url.URL{
69+
Scheme: "http",
70+
Host: net.JoinHostPort(DefaultHostname, port),
71+
Path: "/v1/version",
72+
}
73+
74+
resp, err := httpClient.Get(url.String())
75+
if err != nil {
76+
return "", err
77+
}
78+
defer resp.Body.Close()
79+
80+
if resp.StatusCode != http.StatusOK {
81+
return "", fmt.Errorf("unexpected status code received")
82+
}
83+
84+
var daemonResponse struct {
85+
Version string `json:"version"`
86+
}
87+
if err := json.NewDecoder(resp.Body).Decode(&daemonResponse); err != nil {
88+
return "", err
89+
}
90+
91+
return daemonResponse.Version, nil
92+
}
93+
4094
type versionResult struct {
41-
AppName string `json:"appName"`
42-
Version string `json:"version"`
95+
Name string `json:"name"`
96+
Version string `json:"version"`
97+
DaemonVersion string `json:"daemon_version,omitempty"`
4398
}
4499

45100
func (r versionResult) String() string {
46-
return fmt.Sprintf("%s v%s", r.AppName, r.Version)
101+
resultMessage := fmt.Sprintf("%s version %s", ProgramName, r.Version)
102+
103+
if r.DaemonVersion != "" {
104+
resultMessage = fmt.Sprintf("%s\ndaemon version: %s",
105+
resultMessage, r.DaemonVersion)
106+
}
107+
return resultMessage
47108
}
48109

49110
func (r versionResult) Data() interface{} {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// This file is part of arduino-app-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-app-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package version
17+
18+
import (
19+
"errors"
20+
"io"
21+
"net/http"
22+
"strings"
23+
"testing"
24+
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
func TestDaemonVersion(t *testing.T) {
29+
testCases := []struct {
30+
name string
31+
serverStub Tripper
32+
port string
33+
expectedResult string
34+
expectedErrorMessage string
35+
}{
36+
{
37+
name: "return the server version when the server is up",
38+
serverStub: successServer,
39+
port: "8800",
40+
expectedResult: "3.0-server",
41+
expectedErrorMessage: "",
42+
},
43+
{
44+
name: "return error if default server is not listening on default port",
45+
serverStub: failureServer,
46+
port: "8800",
47+
expectedResult: "",
48+
expectedErrorMessage: `Get "http://localhost:8800/v1/version": connection refused`,
49+
},
50+
{
51+
name: "return error if provided server is not listening on provided port",
52+
serverStub: failureServer,
53+
port: "1234",
54+
expectedResult: "",
55+
expectedErrorMessage: `Get "http://localhost:1234/v1/version": connection refused`,
56+
},
57+
{
58+
name: "return error for server response 500 Internal Server Error",
59+
serverStub: failureInternalServerError,
60+
port: "0",
61+
expectedResult: "",
62+
expectedErrorMessage: "unexpected status code received",
63+
},
64+
65+
{
66+
name: "return error for server up and wrong json response",
67+
serverStub: successServerWrongJson,
68+
port: "8800",
69+
expectedResult: "",
70+
expectedErrorMessage: "invalid character '<' looking for beginning of value",
71+
},
72+
}
73+
74+
for _, tc := range testCases {
75+
t.Run(tc.name, func(t *testing.T) {
76+
// arrange
77+
httpClient := http.Client{}
78+
httpClient.Transport = tc.serverStub
79+
80+
// act
81+
result, err := getDaemonVersion(httpClient, tc.port)
82+
83+
// assert
84+
require.Equal(t, tc.expectedResult, result)
85+
if err != nil {
86+
require.Equal(t, tc.expectedErrorMessage, err.Error())
87+
}
88+
})
89+
}
90+
}
91+
92+
// Leverage the http.Client's RoundTripper
93+
// to return a canned response and bypass network calls.
94+
type Tripper func(*http.Request) (*http.Response, error)
95+
96+
func (t Tripper) RoundTrip(request *http.Request) (*http.Response, error) {
97+
return t(request)
98+
}
99+
100+
var successServer = Tripper(func(*http.Request) (*http.Response, error) {
101+
body := io.NopCloser(strings.NewReader(`{"version":"3.0-server"}`))
102+
return &http.Response{
103+
StatusCode: http.StatusOK,
104+
Body: body,
105+
}, nil
106+
})
107+
108+
var successServerWrongJson = Tripper(func(*http.Request) (*http.Response, error) {
109+
body := io.NopCloser(strings.NewReader(`<!doctype html><html lang="en"`))
110+
return &http.Response{
111+
StatusCode: http.StatusOK,
112+
Body: body,
113+
}, nil
114+
})
115+
116+
var failureServer = Tripper(func(*http.Request) (*http.Response, error) {
117+
return nil, errors.New("connection refused")
118+
})
119+
120+
var failureInternalServerError = Tripper(func(*http.Request) (*http.Response, error) {
121+
return &http.Response{
122+
StatusCode: http.StatusInternalServerError,
123+
Body: io.NopCloser(strings.NewReader("")),
124+
}, nil
125+
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /