diff --git a/internal/ota/ota.go b/internal/ota/ota.go index 5617a39b..f5b9fd44 100644 --- a/internal/ota/ota.go +++ b/internal/ota/ota.go @@ -317,6 +317,12 @@ func (s *State) checkUpdateStatus( // GetUpdateStatus returns the current update status (for backwards compatibility) func (s *State) GetUpdateStatus(ctx context.Context, params UpdateParams) (*UpdateStatus, error) { + // if no components are specified, use the default components + // we should remove this once app router feature is released + if len(params.Components) == 0 { + params.Components = defaultComponents + } + appUpdateStatus := componentUpdateStatus{} systemUpdateStatus := componentUpdateStatus{} err := s.checkUpdateStatus(ctx, params, &appUpdateStatus, &systemUpdateStatus) diff --git a/internal/ota/ota_test.go b/internal/ota/ota_test.go index d2b81cdb..4f175edc 100644 --- a/internal/ota/ota_test.go +++ b/internal/ota/ota_test.go @@ -2,30 +2,52 @@ package ota import ( "context" + "crypto/tls" + "crypto/x509" + "fmt" "net/http" "os" "testing" "time" "github.com/Masterminds/semver/v3" + "github.com/gwatts/rootcerts" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" ) -func pseudoGetLocalVersion() (systemVersion *semver.Version, appVersion *semver.Version, err error) { - systemVersion = semver.MustParse("0.2.5") - appVersion = semver.MustParse("0.4.7") - return systemVersion, appVersion, nil +const pseudoDeviceID = "golang-test" + +type otaTestStateParams struct { + LocalSystemVersion string + LocalAppVersion string + WithoutCerts bool } -func newOtaState() *State { - logger := zerolog.New(os.Stdout).Level(zerolog.TraceLevel) +func newOtaState(p otaTestStateParams) *State { + pseudoGetLocalVersion := func() (systemVersion *semver.Version, appVersion *semver.Version, err error) { + appVersion = semver.MustParse(p.LocalAppVersion) + systemVersion = semver.MustParse(p.LocalSystemVersion) + return systemVersion, appVersion, nil + } + + traceLevel := zerolog.InfoLevel + + if os.Getenv("TEST_LOG_TRACE") == "true" { + traceLevel = zerolog.TraceLevel + } + logger := zerolog.New(os.Stdout).Level(traceLevel) otaState := NewState(Options{ SkipConfirmSystem: true, Logger: &logger, ReleaseAPIEndpoint: "https://api.jetkvm.com/releases", GetHTTPClient: func() *http.Client { transport := http.DefaultTransport.(*http.Transport).Clone() + if !p.WithoutCerts { + transport.TLSClientConfig = &tls.Config{RootCAs: rootcerts.ServerCertPool()} + } else { + transport.TLSClientConfig = &tls.Config{RootCAs: x509.NewCertPool()} + } client := &http.Client{ Transport: transport, } @@ -40,18 +62,146 @@ func newOtaState() *State { return otaState } -func TestCheckUpdateComponents(t *testing.T) { - otaState := newOtaState() - updateParams := UpdateParams{ - DeviceID: "test", - IncludePreRelease: false, - Components: map[string]string{"system": "0.2.2"}, - } - info, err := otaState.GetUpdateStatus(context.Background(), updateParams) +func TestCheckUpdateComponentsWithoutCerts(t *testing.T) { + otaState := newOtaState(otaTestStateParams{ + LocalSystemVersion: "0.2.5", + LocalAppVersion: "0.4.7", + WithoutCerts: true, + }) + _, err := otaState.GetUpdateStatus(context.Background(), UpdateParams{}) + assert.ErrorContains(t, err, "certificate signed by unknown authority") +} + +type updateStatusAsserts struct { + system bool + app bool + skip bool +} + +func testGetUpdateStatus(t *testing.T, p otaTestStateParams, u UpdateParams, asserts updateStatusAsserts) *UpdateStatus { + otaState := newOtaState(p) + info, err := otaState.GetUpdateStatus(context.Background(), u) t.Logf("update status: %+v", info) if err != nil { t.Fatalf("failed to check update: %v", err) } - assert.True(t, info.SystemUpdateAvailable) - assert.False(t, info.AppUpdateAvailable) + if asserts.skip { + return info + } + + if asserts.system { + assert.True(t, info.SystemUpdateAvailable, fmt.Sprintf("system update should available, but reason: %s", info.SystemUpdateAvailableReason)) + } else { + assert.False(t, info.SystemUpdateAvailable, fmt.Sprintf("system update should not be available, but reason: %s", info.SystemUpdateAvailableReason)) + } + if asserts.app { + assert.True(t, info.AppUpdateAvailable, fmt.Sprintf("app update should available, but reason: %s", info.AppUpdateAvailableReason)) + } else { + assert.False(t, info.AppUpdateAvailable, fmt.Sprintf("app update should not be available, but reason: %s", info.AppUpdateAvailableReason)) + } + return info +} + +func TestCheckUpdateComponentsSystemOnlyUpgrade(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.2", // remote >= 0.2.5 + LocalAppVersion: "0.4.5", // remote >= 0.4.7 + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + IncludePreRelease: false, + Components: map[string]string{"system": ""}, + }, updateStatusAsserts{ + system: true, + app: false, + }) +} + +func TestCheckUpdateComponentsSystemOnlyDowngrade(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.5", // remote >= 0.2.5 + LocalAppVersion: "0.4.5", // remote >= 0.4.7 + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + Components: map[string]string{"system": "0.2.2"}, + IncludePreRelease: false, + }, updateStatusAsserts{ + system: true, + app: false, + }) +} + +func TestCheckUpdateComponentsAppOnlyUpgrade(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.2", // remote >= 0.2.5 + LocalAppVersion: "0.4.5", // remote >= 0.4.7 + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + IncludePreRelease: false, + Components: map[string]string{"app": ""}, + }, updateStatusAsserts{ + system: false, + app: true, + }) +} + +func TestCheckUpdateComponentsAppOnlyDowngrade(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.2", // remote >= 0.2.5 + LocalAppVersion: "0.4.5", // remote >= 0.4.7 + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + Components: map[string]string{"app": "0.4.6"}, + IncludePreRelease: false, + }, updateStatusAsserts{ + system: false, + app: true, + }) +} + +func TestCheckUpdateComponentsSystemBothUpgrade(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.2", // remote >= 0.2.5 + LocalAppVersion: "0.4.5", // remote >= 0.4.7 + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + IncludePreRelease: false, + Components: map[string]string{"system": "", "app": ""}, + }, updateStatusAsserts{ + system: true, + app: true, + }) +} + +func TestCheckUpdateComponentsSystemBothDowngrade(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.5", // remote >= 0.2.5 + LocalAppVersion: "0.4.5", // remote >= 0.4.7 + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + Components: map[string]string{"system": "0.2.2", "app": "0.4.6"}, + IncludePreRelease: false, + }, updateStatusAsserts{ + system: true, + app: true, + }) +} + +func TestCheckUpdateComponentsNoComponents(t *testing.T) { + _ = testGetUpdateStatus(t, otaTestStateParams{ + LocalSystemVersion: "0.2.2", + LocalAppVersion: "0.4.2", + WithoutCerts: false, + }, UpdateParams{ + DeviceID: pseudoDeviceID, + IncludePreRelease: false, + }, updateStatusAsserts{ + system: true, + app: true, + }) } diff --git a/internal/ota/state.go b/internal/ota/state.go index 2dc0d453..0ee57212 100644 --- a/internal/ota/state.go +++ b/internal/ota/state.go @@ -40,6 +40,10 @@ type UpdateStatus struct { SystemUpdateAvailable bool `json:"systemUpdateAvailable"` AppUpdateAvailable bool `json:"appUpdateAvailable"` + // only available for debugging and won't be exported + SystemUpdateAvailableReason string `json:"-"` + AppUpdateAvailableReason string `json:"-"` + // for backwards compatibility Error string `json:"error,omitempty"` } @@ -138,9 +142,11 @@ func toUpdateStatus(appUpdate *componentUpdateStatus, systemUpdate *componentUpd SystemURL: systemUpdate.url, SystemHash: systemUpdate.hash, }, - SystemUpdateAvailable: systemUpdate.available, - AppUpdateAvailable: appUpdate.available, - Error: error, + SystemUpdateAvailable: systemUpdate.available, + SystemUpdateAvailableReason: systemUpdate.availableReason, + AppUpdateAvailable: appUpdate.available, + AppUpdateAvailableReason: appUpdate.availableReason, + Error: error, } }