mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
Route every mode transition through one locked helper so the staged managed version cannot outlive the decision that created it. Both download-only transitions kept pendingVersion, letting Install put on the old enforced release after management disabled updates or the platform fell back to download-only. The helper also bumps a generation counter. handleUpdate and NotifyUI copy the mode under the mutex but publish and install after releasing it, so a reset landing in that window could still emit an enforced notification. Both now recheck the generation right before the side effect and drop the stale work. Reset on every connection attempt instead of once per run: the backoff loop reconnects without re-entering run, and since disconnects no longer force download-only, a fetch in the disconnected gap republished the enforced notification before the new network map arrived. A missing AutoUpdateSettings now logs that it defaults to download-only rather than claiming auto-update is disabled, which contradicted the notifications that mode still emits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
3.6 KiB
Go
114 lines
3.6 KiB
Go
//go:build !windows && !darwin
|
|
|
|
package updater
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"testing"
|
|
"time"
|
|
|
|
v "github.com/hashicorp/go-version"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
"github.com/netbirdio/netbird/client/internal/statemanager"
|
|
)
|
|
|
|
// On Linux, only Mode 1 (downloadOnly) is supported.
|
|
// SetVersion falls back to download-only because auto-update installation is not supported.
|
|
|
|
func Test_LatestVersion_Linux(t *testing.T) {
|
|
testMatrix := []struct {
|
|
name string
|
|
daemonVersion string
|
|
initialLatestVersion *v.Version
|
|
latestVersion *v.Version
|
|
shouldUpdateInit bool
|
|
shouldUpdateLater bool
|
|
}{
|
|
{
|
|
name: "Should notify again when a newer version arrives even within 5 minutes",
|
|
daemonVersion: "1.0.0",
|
|
initialLatestVersion: v.Must(v.NewSemver("1.0.1")),
|
|
latestVersion: v.Must(v.NewSemver("1.0.2")),
|
|
shouldUpdateInit: true,
|
|
shouldUpdateLater: true,
|
|
},
|
|
{
|
|
name: "Shouldn't notify initially, but should notify as soon as latest version is fetched",
|
|
daemonVersion: "1.0.0",
|
|
initialLatestVersion: nil,
|
|
latestVersion: v.Must(v.NewSemver("1.0.1")),
|
|
shouldUpdateInit: false,
|
|
shouldUpdateLater: true,
|
|
},
|
|
}
|
|
|
|
for idx, c := range testMatrix {
|
|
mockUpdate := &versionUpdateMock{latestVersion: c.initialLatestVersion}
|
|
tmpFile := path.Join(t.TempDir(), fmt.Sprintf("update-test-%d.json", idx))
|
|
recorder := peer.NewRecorder("")
|
|
sub := recorder.SubscribeToEvents()
|
|
defer recorder.UnsubscribeFromEvents(sub)
|
|
|
|
m := NewManager(recorder, statemanager.New(tmpFile))
|
|
m.update = mockUpdate
|
|
m.currentVersion = c.daemonVersion
|
|
m.Start(context.Background())
|
|
m.SetDownloadOnly()
|
|
|
|
ver, enforced := waitForUpdateEvent(sub, 500*time.Millisecond)
|
|
triggeredInit := ver != ""
|
|
if enforced {
|
|
t.Errorf("%s: Linux Mode 1 must never have enforced metadata", c.name)
|
|
}
|
|
if triggeredInit != c.shouldUpdateInit {
|
|
t.Errorf("%s: Initial notify mismatch, expected %v, got %v", c.name, c.shouldUpdateInit, triggeredInit)
|
|
}
|
|
if triggeredInit && c.initialLatestVersion != nil && ver != c.initialLatestVersion.String() {
|
|
t.Errorf("%s: Initial version mismatch, expected %v, got %v", c.name, c.initialLatestVersion.String(), ver)
|
|
}
|
|
|
|
mockUpdate.latestVersion = c.latestVersion
|
|
mockUpdate.onUpdate()
|
|
|
|
ver, enforced = waitForUpdateEvent(sub, 500*time.Millisecond)
|
|
triggeredLater := ver != ""
|
|
if enforced {
|
|
t.Errorf("%s: Linux Mode 1 must never have enforced metadata", c.name)
|
|
}
|
|
if triggeredLater != c.shouldUpdateLater {
|
|
t.Errorf("%s: Later notify mismatch, expected %v, got %v", c.name, c.shouldUpdateLater, triggeredLater)
|
|
}
|
|
if triggeredLater && c.latestVersion != nil && ver != c.latestVersion.String() {
|
|
t.Errorf("%s: Later version mismatch, expected %v, got %v", c.name, c.latestVersion.String(), ver)
|
|
}
|
|
|
|
m.Stop()
|
|
}
|
|
}
|
|
|
|
func Test_SetVersion_FallsBackToDownloadOnly_Linux(t *testing.T) {
|
|
tmpFile := path.Join(t.TempDir(), "update-test-fallback.json")
|
|
recorder := peer.NewRecorder("")
|
|
sub := recorder.SubscribeToEvents()
|
|
defer recorder.UnsubscribeFromEvents(sub)
|
|
|
|
m := NewManager(recorder, statemanager.New(tmpFile))
|
|
m.update = &versionUpdateMock{latestVersion: v.Must(v.NewSemver("1.0.5"))}
|
|
m.currentVersion = "1.0.0"
|
|
m.Start(context.Background())
|
|
m.SetVersion("1.0.1", false)
|
|
|
|
ver, enforced := waitForUpdateEvent(sub, 500*time.Millisecond)
|
|
if ver != "1.0.5" {
|
|
t.Fatalf("expected download-only event for fetched 1.0.5, got %q", ver)
|
|
}
|
|
if enforced {
|
|
t.Error("Linux fallback must never have enforced metadata")
|
|
}
|
|
|
|
m.Stop()
|
|
}
|