Skip to content

Commit c9d41e4

Browse files
authored
Merge pull request #595 from jshufro/jms/v2/testing
Refactor rocketpool-cli.go to improve code organization, make flag parsing testing easier.
2 parents c97619e + e2b2e71 commit c9d41e4

29 files changed

+663
-286
lines changed

assets/assets_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package assets
2+
3+
import (
4+
"strings"
5+
"testing"
6+
)
7+
8+
func TestLogo(t *testing.T) {
9+
logo := Logo()
10+
if logo == "" {
11+
t.Fatal("Logo wasn't embedded")
12+
}
13+
}
14+
15+
func TestVersionString(t *testing.T) {
16+
// Reset v singleton
17+
v = nil
18+
vers := RocketPoolVersion()
19+
if vers == "" {
20+
t.Fatal("Version string wasn't embedded")
21+
}
22+
23+
if v == nil {
24+
t.Fatalf("v should be initialized")
25+
}
26+
27+
oldV := v
28+
29+
// Make sure subsequent calls to RocketPoolVersion are amortized
30+
RocketPoolVersion()
31+
if oldV != v {
32+
t.Fatalf("v was reallocated")
33+
}
34+
}
35+
36+
func shouldPanic(t *testing.T) {
37+
if r := recover(); r == nil {
38+
t.Fatal("should have panicked!")
39+
} else {
40+
t.Log(r)
41+
}
42+
}
43+
44+
func TestInvalidJsonVersionPanics(t *testing.T) {
45+
// Reset v singleton
46+
v = nil
47+
48+
// Save the version JSON so we can restore it
49+
oldVersionJSON := versionJSON
50+
defer func() {
51+
versionJSON = oldVersionJSON
52+
}()
53+
// We want to panic when version can't be parsed.
54+
defer shouldPanic(t)
55+
56+
versionJSON = []byte("this is not valid json")
57+
_ = RocketPoolVersion()
58+
59+
}
60+
61+
func TestEmptyVersionPanics(t *testing.T) {
62+
// Reset v singleton
63+
v = nil
64+
65+
// Save the version JSON so we can restore it
66+
oldVersionJSON := versionJSON
67+
defer func() {
68+
versionJSON = oldVersionJSON
69+
}()
70+
// We want to panic when version is empty but json is valid
71+
defer shouldPanic(t)
72+
73+
versionJSON = []byte("{}")
74+
_ = RocketPoolVersion()
75+
76+
}
77+
78+
func TestPrintPatchNotes(t *testing.T) {
79+
// Reset v singleton
80+
v = &version{"test"}
81+
notes, err := GetPatchNotes()
82+
if err != nil {
83+
t.Fatal(err)
84+
}
85+
86+
t.Log("Rendered patch notes: ", notes)
87+
88+
// Make sure there are no template directives unpopulated
89+
if strings.Contains(notes, "{{") || strings.Contains(notes, "}}") {
90+
t.Fatal("encountered unexpanded template directive")
91+
}
92+
93+
if !strings.Contains(notes, Logo()) {
94+
t.Fatal("expected logo in patch notes")
95+
}
96+
97+
if !strings.Contains(notes, "test patch notes") {
98+
t.Fatal("expected template body in patch notes")
99+
}
100+
}
101+
102+
// This tests ensures each version has a patchnotes template
103+
func TestPrintCurrentPatchNotes(t *testing.T) {
104+
// Reset v singleton
105+
v = nil
106+
107+
notes, err := GetPatchNotes()
108+
if err != nil {
109+
t.Fatal(err)
110+
}
111+
112+
t.Log("Rendered patch notes: ", notes)
113+
114+
// Make sure there are no template directives unpopulated
115+
if strings.Contains(notes, "{{") || strings.Contains(notes, "}}") {
116+
t.Fatal("encountered unexpanded template directive")
117+
}
118+
119+
if !strings.Contains(notes, Logo()) {
120+
t.Fatal("expected logo in patch notes")
121+
}
122+
}

assets/logo.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package assets
2+
3+
import (
4+
_ "embed"
5+
)
6+
7+
//go:embed logo.txt
8+
var logo string
9+
10+
// Logo returns the rocket pool ascii art, with padding and newlines
11+
func Logo() string {
12+
return logo
13+
}
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
package shared
2-
3-
const RocketPoolVersion string = "2.0.0-b3"
4-
5-
const Logo string = `______ _ _ ______ _
1+
______ _ _ ______ _
62
| ___ \ | | | | | ___ \ | |
73
| |_/ /___ ___| | _____| |_ | |_/ /__ ___ | |
84
| // _ \ / __| |/ / _ \ __| | __/ _ \ / _ \| |
95
| |\ \ (_) | (__| < __/ |_ | | | (_) | (_) | |
10-
\_| \_\___/ \___|_|\_\___|\__| \_| \___/ \___/|_|`
6+
\_| \_\___/ \___|_|\_\___|\__| \_| \___/ \___/|_|
7+

assets/patchnotes.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package assets
2+
3+
import (
4+
"embed"
5+
"fmt"
6+
"strings"
7+
"text/template"
8+
9+
"github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/terminal"
10+
)
11+
12+
var patchnotesTemplateFields = struct {
13+
ColorReset string
14+
ColorBold string
15+
ColorRed string
16+
ColorGreen string
17+
ColorYellow string
18+
ColorBlue string
19+
RocketPoolVersion string
20+
}{
21+
ColorReset: terminal.ColorReset,
22+
ColorBold: terminal.ColorBold,
23+
ColorRed: terminal.ColorRed,
24+
ColorGreen: terminal.ColorGreen,
25+
ColorYellow: terminal.ColorYellow,
26+
ColorBlue: terminal.ColorBlue,
27+
RocketPoolVersion: "",
28+
}
29+
30+
//go:embed patchnotes/*.tmpl
31+
var patchnotesFS embed.FS
32+
33+
func loadTemplate(version string) (*template.Template, error) {
34+
return template.ParseFS(patchnotesFS, fmt.Sprintf("patchnotes/%s.tmpl", version))
35+
}
36+
37+
// PrintPatchNotes looks for a template in ./patchnotes/ with a name matching the current
38+
// version of smartnode and a file extension of .tmpl
39+
//
40+
// If it finds one, it populates it using the PatchNotes struct defined above, and prints it after printing the logo.
41+
// It returns an error when no template exists, or the template could not be populated.
42+
func GetPatchNotes() (string, error) {
43+
version := RocketPoolVersion()
44+
tmpl, err := loadTemplate(version)
45+
if err != nil {
46+
return "", fmt.Errorf("unable to read patch notes: %w", err)
47+
}
48+
49+
// Set RocketPoolVersion before executing
50+
patchnotesTemplateFields.RocketPoolVersion = version
51+
52+
notes := new(strings.Builder)
53+
notes.WriteString("\n")
54+
notes.WriteString(Logo())
55+
err = tmpl.Execute(notes, patchnotesTemplateFields)
56+
if err != nil {
57+
return "", fmt.Errorf("unable to populate patch notes template: %w", err)
58+
}
59+
60+
return notes.String(), nil
61+
}

assets/patchnotes/2.0.0-b3.tmpl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{{.ColorGreen}}=== Smart Node v{{.RocketPoolVersion}} ==={{.ColorReset}}
2+
3+
Changes you should be aware of before starting:
4+
5+
{{.ColorGreen}}=== Welcome to the v2.0 Beta! ==={{.ColorReset}}
6+
Welcome to Smart Node v2! This is a completely redesigned Smart Node from the ground up, taking advantage of years of lessons learned and user feedback. The list of features and changes is far too long to list here, but here are some highlights:
7+
- Support for installing and updating via Debian's `apt` package manager (other package managers coming soon!)
8+
- Support for printing transaction data or signed transactions without submitting them to the network
9+
- Passwordless mode: an opt-in feature that will no longer save your node wallet's password to disk
10+
- Overhauled Smart Node service with an HTTP API, support for batching Ethereum queries and transactions together, a new logging system, and consolidation of the api / node / watchtower containers into one
11+
- And much more!
12+
13+
To learn all about what's changed in Smart Node v2 and how to use it, take a look at our guide: https://github.com/rocket-pool/smartnode/blob/v2/v2.md

assets/patchnotes/test.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{{.RocketPoolVersion}} patch notes.
2+
3+
This file is a test of Patch Note template population and printing.
4+
{{.ColorGreen}} Green Text {{.ColorReset}}

assets/version.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package assets
2+
3+
import (
4+
_ "embed"
5+
"encoding/json"
6+
)
7+
8+
// Using go-embed to import version means a build pipeline can use the .txt file to tag commits/containers
9+
// Id est, one can simply add $(jq -r .Version assets/version.json) to any command to reference the current version.
10+
// We use json because vim (and other editors) likes to add newlines to the end of files.
11+
//go:embed version.json
12+
var versionJSON []byte
13+
14+
type version struct {
15+
Version string
16+
}
17+
18+
// singleton to hold version after first time parsing
19+
var v *version
20+
21+
func RocketPoolVersion() string {
22+
if v != nil {
23+
return v.Version
24+
}
25+
26+
v = new(version)
27+
if err := json.Unmarshal(versionJSON, v); err != nil {
28+
panic(err)
29+
}
30+
if v.Version == "" {
31+
panic("version must be defined")
32+
}
33+
34+
return v.Version
35+
}

assets/version.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"Version": "2.0.0-b3"
3+
}

rocketpool-cli/client/client.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/fatih/color"
1010
"github.com/rocket-pool/node-manager-core/log"
1111
"github.com/rocket-pool/smartnode/v2/client"
12-
"github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/context"
12+
"github.com/rocket-pool/smartnode/v2/rocketpool-cli/settings"
1313
"github.com/rocket-pool/smartnode/v2/shared/config"
1414
"github.com/urfave/cli/v2"
1515
)
@@ -30,7 +30,7 @@ const (
3030
// Rocket Pool client
3131
type Client struct {
3232
Api *client.ApiClient
33-
Context *context.SmartNodeContext
33+
Context *settings.SmartNodeSettings
3434
Logger *slog.Logger
3535
docker *docker.Client
3636
cfg *config.SmartNodeConfig
@@ -39,27 +39,27 @@ type Client struct {
3939

4040
// Create new Rocket Pool client from CLI context
4141
func NewClientFromCtx(c *cli.Context) (*Client, error) {
42-
snCtx := context.GetSmartNodeContext(c)
43-
logger := log.NewTerminalLogger(snCtx.DebugEnabled, terminalLogColor)
42+
snSettings := settings.GetSmartNodeSettings(c)
43+
logger := log.NewTerminalLogger(snSettings.DebugEnabled, terminalLogColor)
4444

4545
// Create the tracer if required
4646
var tracer *httptrace.ClientTrace
47-
if snCtx.HttpTraceFile != nil {
47+
if snSettings.HttpTraceFile != nil {
4848
var err error
49-
tracer, err = createTracer(snCtx.HttpTraceFile, logger.Logger)
49+
tracer, err = createTracer(snSettings.HttpTraceFile, logger.Logger)
5050
if err != nil {
5151
logger.Error("Error creating HTTP trace", log.Err(err))
5252
}
5353
}
5454

5555
// Make the client
5656
rpClient := &Client{
57-
Context: snCtx,
57+
Context: snSettings,
5858
Logger: logger.Logger,
5959
}
6060

6161
// Get the API URL
62-
url := snCtx.ApiUrl
62+
url := snSettings.ApiUrl
6363
if url == nil {
6464
// Load the config to get the API port
6565
cfg, _, err := rpClient.LoadConfig()

rocketpool-cli/commands/service/config.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77

88
"github.com/rivo/tview"
99
nmc_config "github.com/rocket-pool/node-manager-core/config"
10+
"github.com/rocket-pool/smartnode/v2/assets"
1011
"github.com/rocket-pool/smartnode/v2/rocketpool-cli/client"
1112
cliconfig "github.com/rocket-pool/smartnode/v2/rocketpool-cli/commands/service/config"
1213
"github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils"
1314
"github.com/rocket-pool/smartnode/v2/rocketpool-cli/utils/terminal"
14-
"github.com/rocket-pool/smartnode/v2/shared"
1515
"github.com/rocket-pool/smartnode/v2/shared/config"
1616
"github.com/urfave/cli/v2"
1717
)
@@ -40,7 +40,7 @@ func configureService(c *cli.Context) error {
4040

4141
// Check if this is an update
4242
oldVersion := strings.TrimPrefix(cfg.Version, "v")
43-
currentVersion := strings.TrimPrefix(shared.RocketPoolVersion, "v")
43+
currentVersion := strings.TrimPrefix(assets.RocketPoolVersion(), "v")
4444
isUpdate := c.Bool(installUpdateDefaultsFlag.Name) || (oldVersion != currentVersion)
4545

4646
// For upgrades, move the config to the old one and create a new upgraded copy

0 commit comments

Comments
 (0)