Skip to content

Commit 6fd5ece

Browse files
committed
Add unit tests
1 parent 59ae92c commit 6fd5ece

File tree

10 files changed

+262
-31
lines changed

10 files changed

+262
-31
lines changed

client/internal/updatemanager/manager.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ type UpdateManager struct {
3030
mgmUpdateChan chan struct{}
3131
updateChannel chan struct{}
3232
wg sync.WaitGroup
33+
currentVersion string
34+
updateFunc func(ctx context.Context, targetVersion string) error
3335

3436
cancel context.CancelFunc
35-
update *version.Update
37+
update version.UpdateInterface
3638

3739
expectedVersion *v.Version
3840
updateToLatestVersion bool
@@ -44,18 +46,26 @@ func NewUpdateManager(statusRecorder *peer.Status) *UpdateManager {
4446
statusRecorder: statusRecorder,
4547
mgmUpdateChan: make(chan struct{}, 1),
4648
updateChannel: make(chan struct{}, 1),
49+
currentVersion: version.NetbirdVersion(),
50+
updateFunc: triggerUpdate,
51+
update: version.NewUpdate("nb/client"),
4752
}
4853
return manager
4954
}
5055

56+
func (u *UpdateManager) WithCustomVersionUpdate(versionUpdate version.UpdateInterface) *UpdateManager {
57+
u.update = versionUpdate
58+
return u
59+
}
60+
5161
func (u *UpdateManager) Start(ctx context.Context) {
5262
if u.cancel != nil {
5363
log.Errorf("UpdateManager already started")
5464
return
5565
}
5666

57-
u.update = version.NewUpdate("nb/client")
58-
u.update.SetDaemonVersion(version.NetbirdVersion())
67+
go u.update.StartFetcher()
68+
u.update.SetDaemonVersion(u.currentVersion)
5969
u.update.SetOnUpdateListener(func() {
6070
select {
6171
case u.updateChannel <- struct{}{}:
@@ -162,7 +172,7 @@ func (u *UpdateManager) handleUpdate(ctx context.Context) {
162172
defer cancel()
163173

164174
u.lastTrigger = time.Now()
165-
log.Debugf("Auto-update triggered, current version: %s, target version: %s", version.NetbirdVersion(), updateVersion)
175+
log.Debugf("Auto-update triggered, current version: %s, target version: %s", u.currentVersion, updateVersion)
166176
u.statusRecorder.PublishEvent(
167177
cProto.SystemEvent_INFO,
168178
cProto.SystemEvent_SYSTEM,
@@ -171,21 +181,20 @@ func (u *UpdateManager) handleUpdate(ctx context.Context) {
171181
nil,
172182
)
173183

174-
err := u.triggerUpdate(ctx, updateVersion.String())
184+
err := u.updateFunc(ctx, updateVersion.String())
175185
if err != nil {
176186
log.Errorf("Error triggering auto-update: %v", err)
177187
}
178188
}
179189

180190
func (u *UpdateManager) shouldUpdate(updateVersion *v.Version) bool {
181-
currentVersionString := version.NetbirdVersion()
182-
currentVersion, err := v.NewVersion(currentVersionString)
191+
currentVersion, err := v.NewVersion(u.currentVersion)
183192
if err != nil {
184-
log.Errorf("Error checking for update, error parsing version `%s`: %v", currentVersionString, err)
193+
log.Errorf("Error checking for update, error parsing version `%s`: %v", u.currentVersion, err)
185194
return false
186195
}
187196
if currentVersion.GreaterThanOrEqual(updateVersion) {
188-
log.Debugf("Current version (%s) is equal to or higher than auto-update version (%s)", currentVersionString, updateVersion)
197+
log.Debugf("Current version (%s) is equal to or higher than auto-update version (%s)", u.currentVersion, updateVersion)
189198
return false
190199
}
191200

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package updatemanager
2+
3+
import (
4+
"context"
5+
v "github.com/hashicorp/go-version"
6+
"github.com/netbirdio/netbird/client/internal/peer"
7+
"testing"
8+
"time"
9+
)
10+
11+
type versionUpdateMock struct {
12+
latestVersion *v.Version
13+
onUpdate func()
14+
}
15+
16+
func (v versionUpdateMock) StopWatch() {}
17+
18+
func (v versionUpdateMock) SetDaemonVersion(newVersion string) bool {
19+
return false
20+
}
21+
22+
func (v *versionUpdateMock) SetOnUpdateListener(updateFn func()) {
23+
v.onUpdate = updateFn
24+
}
25+
26+
func (v versionUpdateMock) LatestVersion() *v.Version {
27+
return v.latestVersion
28+
}
29+
30+
func (v versionUpdateMock) StartFetcher() {}
31+
32+
func Test_LatestVersion(t *testing.T) {
33+
testMatrix := []struct {
34+
name string
35+
daemonVersion string
36+
initialLatestVersion *v.Version
37+
latestVersion *v.Version
38+
shouldUpdateInit bool
39+
shouldUpdateLater bool
40+
}{
41+
{
42+
name: "Should only trigger update once due to time between triggers being < 5 Minutes",
43+
daemonVersion: "1.0.0",
44+
initialLatestVersion: v.Must(v.NewSemver("1.0.1")),
45+
latestVersion: v.Must(v.NewSemver("1.0.2")),
46+
shouldUpdateInit: true,
47+
shouldUpdateLater: false,
48+
},
49+
{
50+
name: "Shouldn't update initially, but should update as soon as latest version is fetched",
51+
daemonVersion: "1.0.0",
52+
initialLatestVersion: nil,
53+
latestVersion: v.Must(v.NewSemver("1.0.1")),
54+
shouldUpdateInit: false,
55+
shouldUpdateLater: true,
56+
},
57+
}
58+
59+
for _, c := range testMatrix {
60+
mockUpdate := &versionUpdateMock{latestVersion: c.initialLatestVersion}
61+
m := NewUpdateManager(peer.NewRecorder("")).WithCustomVersionUpdate(mockUpdate)
62+
63+
targetVersionChan := make(chan string, 1)
64+
65+
m.updateFunc = func(ctx context.Context, targetVersion string) error {
66+
targetVersionChan <- targetVersion
67+
return nil
68+
}
69+
m.currentVersion = c.daemonVersion
70+
m.Start(t.Context())
71+
m.SetVersion("latest")
72+
var triggeredInit bool
73+
select {
74+
case targetVersion := <-targetVersionChan:
75+
if targetVersion != c.initialLatestVersion.String() {
76+
t.Errorf("%s: Initial update version mismatch, expected %v, got %v", c.name, c.initialLatestVersion.String(), targetVersion)
77+
}
78+
triggeredInit = true
79+
case <-time.After(10 * time.Millisecond):
80+
triggeredInit = false
81+
}
82+
if triggeredInit != c.shouldUpdateInit {
83+
t.Errorf("%s: Initial update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdateInit, triggeredInit)
84+
}
85+
86+
mockUpdate.latestVersion = c.latestVersion
87+
mockUpdate.onUpdate()
88+
89+
var triggeredLater bool
90+
select {
91+
case targetVersion := <-targetVersionChan:
92+
if targetVersion != c.latestVersion.String() {
93+
t.Errorf("%s: Update version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), targetVersion)
94+
}
95+
triggeredLater = true
96+
case <-time.After(10 * time.Millisecond):
97+
triggeredLater = false
98+
}
99+
if triggeredLater != c.shouldUpdateLater {
100+
t.Errorf("%s: Update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdateLater, triggeredLater)
101+
}
102+
103+
m.Stop()
104+
}
105+
}
106+
107+
func Test_HandleUpdate(t *testing.T) {
108+
testMatrix := []struct {
109+
name string
110+
daemonVersion string
111+
latestVersion *v.Version
112+
expectedVersion string
113+
shouldUpdate bool
114+
}{
115+
{
116+
name: "Update to a specific version should update regardless of if latestVersion is available yet",
117+
daemonVersion: "0.55.0",
118+
latestVersion: nil,
119+
expectedVersion: "0.56.0",
120+
shouldUpdate: true,
121+
},
122+
{
123+
name: "Update to specific version should not update if version matches",
124+
daemonVersion: "0.55.0",
125+
latestVersion: nil,
126+
expectedVersion: "0.55.0",
127+
shouldUpdate: false,
128+
},
129+
{
130+
name: "Update to specific version should not update if current version is newer",
131+
daemonVersion: "0.55.0",
132+
latestVersion: nil,
133+
expectedVersion: "0.54.0",
134+
shouldUpdate: false,
135+
},
136+
{
137+
name: "Update to latest version should update if latest is newer",
138+
daemonVersion: "0.55.0",
139+
latestVersion: v.Must(v.NewSemver("0.56.0")),
140+
expectedVersion: "latest",
141+
shouldUpdate: true,
142+
},
143+
{
144+
name: "Update to latest version should not update if latest == current",
145+
daemonVersion: "0.56.0",
146+
latestVersion: v.Must(v.NewSemver("0.56.0")),
147+
expectedVersion: "latest",
148+
shouldUpdate: false,
149+
},
150+
{
151+
name: "Should not update if daemon version is invalid",
152+
daemonVersion: "development",
153+
latestVersion: v.Must(v.NewSemver("1.0.0")),
154+
expectedVersion: "latest",
155+
shouldUpdate: false,
156+
},
157+
{
158+
name: "Should not update if expecting latest and latest version is unavailable",
159+
daemonVersion: "0.55.0",
160+
latestVersion: nil,
161+
expectedVersion: "latest",
162+
shouldUpdate: false,
163+
},
164+
{
165+
name: "Should not update if expected version is invalid",
166+
daemonVersion: "0.55.0",
167+
latestVersion: nil,
168+
expectedVersion: "development",
169+
shouldUpdate: false,
170+
},
171+
}
172+
for _, c := range testMatrix {
173+
m := NewUpdateManager(peer.NewRecorder("")).WithCustomVersionUpdate(&versionUpdateMock{latestVersion: c.latestVersion})
174+
targetVersionChan := make(chan string, 1)
175+
176+
m.updateFunc = func(ctx context.Context, targetVersion string) error {
177+
targetVersionChan <- targetVersion
178+
return nil
179+
}
180+
181+
m.currentVersion = c.daemonVersion
182+
m.Start(t.Context())
183+
m.SetVersion(c.expectedVersion)
184+
185+
var updateTriggered bool
186+
select {
187+
case targetVersion := <-targetVersionChan:
188+
if c.expectedVersion == "latest" && targetVersion != c.latestVersion.String() {
189+
t.Errorf("%s: Update version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), targetVersion)
190+
} else if c.expectedVersion != "latest" && targetVersion != c.expectedVersion {
191+
t.Errorf("%s: Update version mismatch, expected %v, got %v", c.name, c.expectedVersion, targetVersion)
192+
}
193+
updateTriggered = true
194+
case <-time.After(10 * time.Millisecond):
195+
updateTriggered = false
196+
}
197+
198+
if updateTriggered != c.shouldUpdate {
199+
t.Errorf("%s: Update trigger mismatch, expected %v, got %v", c.name, c.shouldUpdate, updateTriggered)
200+
}
201+
m.Stop()
202+
}
203+
}

client/internal/updatemanager/update_darwin.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ const (
1616
pkgDownloadURL = "https://github.com/netbirdio/netbird/releases/download/v%version/netbird_%version_darwin_%arch.pkg"
1717
)
1818

19-
func (u *UpdateManager) triggerUpdate(ctx context.Context, targetVersion string) error {
19+
func triggerUpdate(ctx context.Context, targetVersion string) error {
2020
cmd := exec.CommandContext(ctx, "pkgutil", "--pkg-info", "io.netbird.client")
2121
outBytes, err := cmd.Output()
2222
if err != nil && cmd.ProcessState.ExitCode() == 1 {
2323
// Not installed using pkg file, thus installed using Homebrew
2424

25-
return u.updateHomeBrew(ctx)
25+
return updateHomeBrew(ctx)
2626
}
2727
// Installed using pkg file
2828
path, err := downloadFileToTemporaryDir(ctx, urlWithVersionArch(pkgDownloadURL, targetVersion))
@@ -49,7 +49,7 @@ func (u *UpdateManager) triggerUpdate(ctx context.Context, targetVersion string)
4949
return err
5050
}
5151

52-
func (u *UpdateManager) updateHomeBrew(ctx context.Context) error {
52+
func updateHomeBrew(ctx context.Context) error {
5353
// Homebrew must be run as a non-root user
5454
// To find out which user installed NetBird using HomeBrew we can check the owner of our brew tap directory
5555
fileInfo, err := os.Stat("/opt/homebrew/Library/Taps/netbirdio/homebrew-tap/")

client/internal/updatemanager/update_freebsd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package updatemanager
44

55
import "context"
66

7-
func (u *UpdateManager) triggerUpdate(ctx context.Context, targetVersion string) error {
7+
func triggerUpdate(ctx context.Context, targetVersion string) error {
88
// TODO: Implement
99
return nil
1010
}

client/internal/updatemanager/update_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ package updatemanager
44

55
import "context"
66

7-
func (u *UpdateManager) triggerUpdate(ctx context.Context, targetVersion string) error {
7+
func triggerUpdate(ctx context.Context, targetVersion string) error {
88
// TODO: Implement
99
return nil
1010
}

client/internal/updatemanager/update_windows.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func installationMethod() string {
4040
return "EXE"
4141
}
4242

43-
func (u *UpdateManager) updateMSI(ctx context.Context, targetVersion string) error {
43+
func updateMSI(ctx context.Context, targetVersion string) error {
4444
path, err := downloadFileToTemporaryDir(ctx, urlWithVersionArch(msiDownloadURL, targetVersion))
4545
if err != nil {
4646
return err
@@ -50,7 +50,7 @@ func (u *UpdateManager) updateMSI(ctx context.Context, targetVersion string) err
5050
return err
5151
}
5252

53-
func (u *UpdateManager) updateEXE(ctx context.Context, targetVersion string) error {
53+
func updateEXE(ctx context.Context, targetVersion string) error {
5454
path, err := downloadFileToTemporaryDir(ctx, urlWithVersionArch(exeDownloadURL, targetVersion))
5555
if err != nil {
5656
return err
@@ -65,12 +65,12 @@ func (u *UpdateManager) updateEXE(ctx context.Context, targetVersion string) err
6565
return err
6666
}
6767

68-
func (u *UpdateManager) triggerUpdate(ctx context.Context, targetVersion string) error {
68+
func triggerUpdate(ctx context.Context, targetVersion string) error {
6969
switch installationMethod() {
7070
case "EXE":
71-
return u.updateEXE(ctx, targetVersion)
71+
return updateEXE(ctx, targetVersion)
7272
case "MSI":
73-
return u.updateMSI(ctx, targetVersion)
73+
return updateMSI(ctx, targetVersion)
7474
default:
7575
return fmt.Errorf("unsupported installation method: %s", installationMethod())
7676
}

client/ui/client_ui.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ func newServiceClient(args *newServiceClientArgs) *serviceClient {
330330

331331
showAdvancedSettings: args.showSettings,
332332
showNetworks: args.showNetworks,
333-
update: version.NewUpdate("nb/client-ui"),
333+
update: version.NewUpdateAndStart("nb/client-ui"),
334334
}
335335

336336
s.eventHandler = newEventHandler(s)
@@ -529,7 +529,7 @@ func (s *serviceClient) getSettingsForm() *widget.Form {
529529
var req proto.SetConfigRequest
530530
req.ProfileName = activeProf.Name
531531
req.Username = currUser.Username
532-
532+
533533
if iMngURL != "" {
534534
req.ManagementUrl = iMngURL
535535
}

management/internals/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func (s *BaseServer) Start(ctx context.Context) error {
180180
log.WithContext(ctx).Infof("running HTTP server and gRPC server on the same port: %s", s.listener.Addr().String())
181181
s.serveGRPCWithHTTP(ctx, s.listener, rootHandler, tlsEnabled)
182182

183-
s.update = version.NewUpdate("nb/management")
183+
s.update = version.NewUpdateAndStart("nb/management")
184184
s.update.SetDaemonVersion(version.NetbirdVersion())
185185
s.update.SetOnUpdateListener(func() {
186186
log.WithContext(ctx).Infof("your management version, \"%s\", is outdated, a new management version is available. Learn more here: https://github.com/netbirdio/netbird/releases", version.NetbirdVersion())

0 commit comments

Comments
 (0)