diff --git a/.vscode/settings.json b/.vscode/settings.json index 41aeee58..5aeb206a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,13 @@ }, "git.ignoreLimitWarning": true, "cmake.sourceDirectory": "/workspaces/kvm-static-ip/internal/native/cgo", - "cmake.ignoreCMakeListsMissing": true + "cmake.ignoreCMakeListsMissing": true, + "json.schemas": [ + { + "fileMatch": [ + "/internal/ota/testdata/ota/*.json" + ], + "url": "./internal/ota/testdata/ota.schema.json" + } + ] } \ No newline at end of file diff --git a/internal/ota/ota.go b/internal/ota/ota.go index f5b9fd44..d0e330fb 100644 --- a/internal/ota/ota.go +++ b/internal/ota/ota.go @@ -12,6 +12,11 @@ import ( "github.com/rs/zerolog" ) +// HttpClient is the interface for the HTTP client +type HttpClient interface { + Do(req *http.Request) (*http.Response, error) +} + // UpdateReleaseAPIEndpoint updates the release API endpoint func (s *State) UpdateReleaseAPIEndpoint(endpoint string) { s.releaseAPIEndpoint = endpoint @@ -215,7 +220,7 @@ func (s *State) doUpdate(ctx context.Context, params UpdateParams) error { // UpdateParams represents the parameters for the update type UpdateParams struct { DeviceID string `json:"deviceID"` - Components map[string]string `json:"components,omitempty"` + Components map[string]string `json:"components"` IncludePreRelease bool `json:"includePreRelease"` CheckOnly bool `json:"checkOnly"` ResetConfig bool `json:"resetConfig"` diff --git a/internal/ota/ota_test.go b/internal/ota/ota_test.go index 4f175edc..2c8ce661 100644 --- a/internal/ota/ota_test.go +++ b/internal/ota/ota_test.go @@ -1,12 +1,18 @@ package ota import ( + "bytes" "context" "crypto/tls" "crypto/x509" + "embed" + "encoding/json" "fmt" + "io" "net/http" + "net/url" "os" + "path/filepath" "testing" "time" @@ -16,34 +22,173 @@ import ( "github.com/stretchr/testify/assert" ) -const pseudoDeviceID = "golang-test" +//go:embed testdata/ota +var testDataFS embed.FS -type otaTestStateParams struct { - LocalSystemVersion string - LocalAppVersion string - WithoutCerts bool +const pseudoDeviceID = "golang-test" +const releaseAPIEndpoint = "https://api.jetkvm.com/releases" + +type testData struct { + Name string `json:"name"` + WithoutCerts bool `json:"withoutCerts"` + RemoteMetadata []struct { + Code int `json:"code"` + Params map[string]string `json:"params"` + Data UpdateMetadata `json:"data"` + } `json:"remoteMetadata"` + LocalMetadata struct { + SystemVersion string `json:"systemVersion"` + AppVersion string `json:"appVersion"` + } `json:"localMetadata"` + UpdateParams UpdateParams `json:"updateParams"` + Expected struct { + System bool `json:"system"` + App bool `json:"app"` + Error string `json:"error,omitempty"` + } `json:"expected"` } -func newOtaState(p otaTestStateParams) *State { +func (d *testData) ToFixtures(t *testing.T) map[string]mockData { + fixtures := make(map[string]mockData) + for _, resp := range d.RemoteMetadata { + url, err := url.Parse(releaseAPIEndpoint) + if err != nil { + t.Fatalf("failed to parse release API endpoint: %v", err) + } + query := url.Query() + query.Set("deviceId", pseudoDeviceID) + for key, value := range resp.Params { + query.Set(key, value) + } + url.RawQuery = query.Encode() + fixtures[url.String()] = mockData{ + Metadata: &resp.Data, + StatusCode: resp.Code, + } + } + return fixtures +} + +func (d *testData) ToUpdateParams() UpdateParams { + d.UpdateParams.DeviceID = pseudoDeviceID + return d.UpdateParams +} + +func loadTestData(t *testing.T, filename string) *testData { + f, err := testDataFS.ReadFile(filepath.Join("testdata", "ota", filename)) + if err != nil { + t.Fatalf("failed to read test data file %s: %v", filename, err) + } + + var testData testData + if err := json.Unmarshal(f, &testData); err != nil { + t.Fatalf("failed to unmarshal test data file %s: %v", filename, err) + } + + return &testData +} + +type mockData struct { + Metadata *UpdateMetadata + StatusCode int +} + +type mockHTTPClient struct { + DoFunc func(req *http.Request) (*http.Response, error) + Fixtures map[string]mockData +} + +func compareURLs(a *url.URL, b *url.URL) bool { + if a.String() == b.String() { + return true + } + if a.Host != b.Host || a.Scheme != b.Scheme || a.Path != b.Path { + return false + } + + // do a quick check to see if the query parameters are the same + queryA := a.Query() + queryB := b.Query() + if len(queryA) != len(queryB) { + return false + } + for key := range queryA { + if queryA.Get(key) != queryB.Get(key) { + return false + } + } + for key := range queryB { + if queryA.Get(key) != queryB.Get(key) { + return false + } + } + return true +} + +func (m *mockHTTPClient) getFixture(expectedURL *url.URL) *mockData { + for u, fixture := range m.Fixtures { + fixtureURL, err := url.Parse(u) + if err != nil { + continue + } + if compareURLs(fixtureURL, expectedURL) { + return &fixture + } + } + return nil +} + +func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) { + fixture := m.getFixture(req.URL) + if fixture == nil { + return &http.Response{ + StatusCode: http.StatusNotFound, + Body: io.NopCloser(bytes.NewBufferString("")), + }, fmt.Errorf("no fixture found for URL: %s", req.URL.String()) + } + + resp := &http.Response{ + StatusCode: fixture.StatusCode, + } + + jsonData, err := json.Marshal(fixture.Metadata) + if err != nil { + return nil, fmt.Errorf("error marshalling metadata: %w", err) + } + + resp.Body = io.NopCloser(bytes.NewBufferString(string(jsonData))) + return resp, nil +} + +func newMockHTTPClient(fixtures map[string]mockData) *mockHTTPClient { + return &mockHTTPClient{ + Fixtures: fixtures, + } +} + +func newOtaState(d *testData, t *testing.T) *State { pseudoGetLocalVersion := func() (systemVersion *semver.Version, appVersion *semver.Version, err error) { - appVersion = semver.MustParse(p.LocalAppVersion) - systemVersion = semver.MustParse(p.LocalSystemVersion) + appVersion = semver.MustParse(d.LocalMetadata.AppVersion) + systemVersion = semver.MustParse(d.LocalMetadata.SystemVersion) return systemVersion, appVersion, nil } traceLevel := zerolog.InfoLevel - if os.Getenv("TEST_LOG_TRACE") == "true" { + if os.Getenv("TEST_LOG_TRACE") == "1" { 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 { + ReleaseAPIEndpoint: releaseAPIEndpoint, + GetHTTPClient: func() HttpClient { + if d.RemoteMetadata != nil { + return newMockHTTPClient(d.ToFixtures(t)) + } transport := http.DefaultTransport.(*http.Transport).Clone() - if !p.WithoutCerts { + if !d.WithoutCerts { transport.TLSClientConfig = &tls.Config{RootCAs: rootcerts.ServerCertPool()} } else { transport.TLSClientConfig = &tls.Config{RootCAs: x509.NewCertPool()} @@ -62,146 +207,55 @@ func newOtaState(p otaTestStateParams) *State { return otaState } -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) +func testUsingJson(t *testing.T, filename string) { + td := loadTestData(t, filename) + otaState := newOtaState(td, t) + info, err := otaState.GetUpdateStatus(context.Background(), td.ToUpdateParams()) if err != nil { - t.Fatalf("failed to check update: %v", err) - } - if asserts.skip { - return info + if td.Expected.Error != "" { + assert.ErrorContains(t, err, td.Expected.Error) + } else { + t.Fatalf("failed to get update status: %v", err) + } } - if asserts.system { + if td.Expected.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 { + + if td.Expected.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, - }) + testUsingJson(t, "system_only_upgrade.json") } 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, - }) + testUsingJson(t, "system_only_downgrade.json") } 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, - }) + testUsingJson(t, "app_only_upgrade.json") } 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, - }) + testUsingJson(t, "app_only_downgrade.json") } 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, - }) + testUsingJson(t, "both_upgrade.json") } 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, - }) + testUsingJson(t, "both_downgrade.json") } 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, - }) + testUsingJson(t, "no_components.json") } diff --git a/internal/ota/state.go b/internal/ota/state.go index 0ee57212..1bb12033 100644 --- a/internal/ota/state.go +++ b/internal/ota/state.go @@ -1,7 +1,6 @@ package ota import ( - "net/http" "sync" "time" @@ -100,7 +99,7 @@ type HwRebootFunc func(force bool, postRebootAction *PostRebootAction, delay tim type ResetConfigFunc func() error // GetHTTPClientFunc is a function that returns the HTTP client -type GetHTTPClientFunc func() *http.Client +type GetHTTPClientFunc func() HttpClient // OnStateUpdateFunc is a function that updates the state of the OTA type OnStateUpdateFunc func(state *RPCState) diff --git a/internal/ota/testdata/ota.schema.json b/internal/ota/testdata/ota.schema.json new file mode 100644 index 00000000..15965850 --- /dev/null +++ b/internal/ota/testdata/ota.schema.json @@ -0,0 +1,159 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "OTA Test Data Schema", + "description": "Schema for OTA update test data", + "type": "object", + "required": ["name", "remoteMetadata", "localMetadata", "updateParams"], + "properties": { + "name": { + "type": "string", + "description": "Name of the test case" + }, + "withoutCerts": { + "type": "boolean", + "default": false, + "description": "Whether to run the test without Root CA certificates" + }, + "remoteMetadata": { + "type": "array", + "description": "Remote metadata responses", + "items": { + "type": "object", + "required": ["params", "code", "data"], + "properties": { + "params": { + "type": "object", + "description": "Query parameters used for the request", + "required": ["prerelease"], + "properties": { + "prerelease": { + "type": "string", + "description": "Whether to include pre-release versions" + }, + "appVersion": { + "type": "string", + "description": "Application version string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "systemVersion": { + "type": "string", + "description": "System version string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + } + }, + "additionalProperties": false + }, + "code": { + "type": "integer", + "description": "HTTP status code" + }, + "data": { + "type": "object", + "required": ["appVersion", "appUrl", "appHash", "systemVersion", "systemUrl", "systemHash"], + "properties": { + "appVersion": { + "type": "string", + "description": "Application version string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "appUrl": { + "type": "string", + "description": "URL to download the application", + "format": "uri" + }, + "appHash": { + "type": "string", + "description": "SHA-256 hash of the application", + "pattern": "^[a-f0-9]{64}$" + }, + "systemVersion": { + "type": "string", + "description": "System version string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "systemUrl": { + "type": "string", + "description": "URL to download the system", + "format": "uri" + }, + "systemHash": { + "type": "string", + "description": "SHA-256 hash of the system", + "pattern": "^[a-f0-9]{64}$" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "localMetadata": { + "type": "object", + "description": "Local metadata containing current installed versions", + "required": ["systemVersion", "appVersion"], + "properties": { + "systemVersion": { + "type": "string", + "description": "Currently installed system version", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "appVersion": { + "type": "string", + "description": "Currently installed application version", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + } + }, + "additionalProperties": false + }, + "updateParams": { + "type": "object", + "description": "Parameters for the update operation", + "required": ["includePreRelease"], + "properties": { + "includePreRelease": { + "type": "boolean", + "description": "Whether to include pre-release versions" + }, + "components": { + "type": "object", + "description": "Component update configuration", + "properties": { + "system": { + "type": "string", + "description": "System component update configuration (empty string to update)" + }, + "app": { + "type": "string", + "description": "App component update configuration (version string to update to)" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "expected": { + "type": "object", + "description": "Expected update results", + "required": [], + "properties": { + "system": { + "type": "boolean", + "description": "Whether system update is expected" + }, + "app": { + "type": "boolean", + "description": "Whether app update is expected" + }, + "error": { + "type": "string", + "description": "Error message if the test case is expected to fail" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} + diff --git a/internal/ota/testdata/ota/app_only_downgrade.json b/internal/ota/testdata/ota/app_only_downgrade.json new file mode 100644 index 00000000..e8e2f7d1 --- /dev/null +++ b/internal/ota/testdata/ota/app_only_downgrade.json @@ -0,0 +1,34 @@ +{ + "name": "Downgrade App Only", + "remoteMetadata": [ + { + "params": { + "prerelease": "false", + "appVersion": "0.4.6" + }, + "code": 200, + "data": { + "appVersion": "0.4.6", + "appUrl": "https://update.jetkvm.com/app/0.4.6/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.5", + "systemUrl": "https://update.jetkvm.com/system/0.2.5/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.2", + "appVersion": "0.4.5" + }, + "updateParams": { + "includePreRelease": false, + "components": { + "app": "0.4.6" + } + }, + "expected": { + "system": false, + "app": true + } +} \ No newline at end of file diff --git a/internal/ota/testdata/ota/app_only_upgrade.json b/internal/ota/testdata/ota/app_only_upgrade.json new file mode 100644 index 00000000..69aa7fb7 --- /dev/null +++ b/internal/ota/testdata/ota/app_only_upgrade.json @@ -0,0 +1,33 @@ +{ + "name": "Upgrade App Only", + "remoteMetadata": [ + { + "params": { + "prerelease": "false" + }, + "code": 200, + "data": { + "appVersion": "0.4.7", + "appUrl": "https://update.jetkvm.com/app/0.4.7/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.5", + "systemUrl": "https://update.jetkvm.com/system/0.2.5/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.2", + "appVersion": "0.4.5" + }, + "updateParams": { + "includePreRelease": false, + "components": { + "app": "" + } + }, + "expected": { + "system": false, + "app": true + } +} \ No newline at end of file diff --git a/internal/ota/testdata/ota/both_downgrade.json b/internal/ota/testdata/ota/both_downgrade.json new file mode 100644 index 00000000..3c57461c --- /dev/null +++ b/internal/ota/testdata/ota/both_downgrade.json @@ -0,0 +1,37 @@ +{ + "name": "Downgrade System & App", + "remoteMetadata": [ + { + "params": { + "prerelease": "false", + "systemVersion": "0.2.2", + "appVersion": "0.4.6" + }, + "code": 200, + "data": { + "appVersion": "0.4.6", + "appUrl": "https://update.jetkvm.com/app/0.4.6/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.2", + "systemUrl": "https://update.jetkvm.com/system/0.2.2/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.5", + "appVersion": "0.4.5" + }, + "updateParams": { + "includePreRelease": false, + "components": { + "system": "0.2.2", + "app": "0.4.6" + } + }, + "expected": { + "system": true, + "app": true + } +} + diff --git a/internal/ota/testdata/ota/both_upgrade.json b/internal/ota/testdata/ota/both_upgrade.json new file mode 100644 index 00000000..c3d3daee --- /dev/null +++ b/internal/ota/testdata/ota/both_upgrade.json @@ -0,0 +1,34 @@ +{ + "name": "Upgrade System & App (components given)", + "remoteMetadata": [ + { + "params": { + "prerelease": "false" + }, + "code": 200, + "data": { + "appVersion": "0.4.7", + "appUrl": "https://update.jetkvm.com/app/0.4.7/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.5", + "systemUrl": "https://update.jetkvm.com/system/0.2.5/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.2", + "appVersion": "0.4.5" + }, + "updateParams": { + "includePreRelease": false, + "components": { + "system": "", + "app": "" + } + }, + "expected": { + "system": true, + "app": true + } +} \ No newline at end of file diff --git a/internal/ota/testdata/ota/no_components.json b/internal/ota/testdata/ota/no_components.json new file mode 100644 index 00000000..9fb8b253 --- /dev/null +++ b/internal/ota/testdata/ota/no_components.json @@ -0,0 +1,32 @@ +{ + "name": "Upgrade System & App (no components given)", + "remoteMetadata": [ + { + "params": { + "prerelease": "false" + }, + "code": 200, + "data": { + "appVersion": "0.4.7", + "appUrl": "https://update.jetkvm.com/app/0.4.7/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.5", + "systemUrl": "https://update.jetkvm.com/system/0.2.5/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.2", + "appVersion": "0.4.2" + }, + "updateParams": { + "includePreRelease": false, + "components": {} + }, + "expected": { + "system": true, + "app": true + } +} + diff --git a/internal/ota/testdata/ota/system_only_downgrade.json b/internal/ota/testdata/ota/system_only_downgrade.json new file mode 100644 index 00000000..007f5279 --- /dev/null +++ b/internal/ota/testdata/ota/system_only_downgrade.json @@ -0,0 +1,34 @@ +{ + "name": "Downgrade System Only", + "remoteMetadata": [ + { + "params": { + "prerelease": "false", + "systemVersion": "0.2.2" + }, + "code": 200, + "data": { + "appVersion": "0.4.7", + "appUrl": "https://update.jetkvm.com/app/0.4.7/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.2", + "systemUrl": "https://update.jetkvm.com/system/0.2.2/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.5", + "appVersion": "0.4.5" + }, + "updateParams": { + "includePreRelease": false, + "components": { + "system": "0.2.2" + } + }, + "expected": { + "system": true, + "app": false + } +} \ No newline at end of file diff --git a/internal/ota/testdata/ota/system_only_upgrade.json b/internal/ota/testdata/ota/system_only_upgrade.json new file mode 100644 index 00000000..b32c9434 --- /dev/null +++ b/internal/ota/testdata/ota/system_only_upgrade.json @@ -0,0 +1,33 @@ +{ + "name": "Upgrade System Only", + "remoteMetadata": [ + { + "params": { + "prerelease": "false" + }, + "code": 200, + "data": { + "appVersion": "0.4.7", + "appUrl": "https://update.jetkvm.com/app/0.4.7/jetkvm_app", + "appHash": "714f33432f17035e38d238bf376e98f3073e6cc2845d269ff617503d12d92bdd", + "systemVersion": "0.2.6", + "systemUrl": "https://update.jetkvm.com/system/0.2.6/system.tar", + "systemHash": "2323463ea8652be767d94514e548f90dd61b1ebcc0fb1834d700fac5b3d88a35" + } + } + ], + "localMetadata": { + "systemVersion": "0.2.5", + "appVersion": "0.4.5" + }, + "updateParams": { + "includePreRelease": false, + "components": { + "system": "" + } + }, + "expected": { + "system": true, + "app": false + } +} \ No newline at end of file diff --git a/internal/ota/testdata/ota/without_certs.json b/internal/ota/testdata/ota/without_certs.json new file mode 100644 index 00000000..d5150896 --- /dev/null +++ b/internal/ota/testdata/ota/without_certs.json @@ -0,0 +1,17 @@ +{ + "name": "Without Certs", + "localMetadata": { + "systemVersion": "0.2.5", + "appVersion": "0.4.7" + }, + "updateParams": { + "includePreRelease": false, + "components": {} + }, + "expected": { + "system": false, + "app": false, + "error": "certificate signed by unknown authority" + } +} + diff --git a/ota.go b/ota.go index b8ae47f1..19ef20fd 100644 --- a/ota.go +++ b/ota.go @@ -19,7 +19,7 @@ func initOta() { otaState = ota.NewState(ota.Options{ Logger: otaLogger, ReleaseAPIEndpoint: config.GetUpdateAPIURL(), - GetHTTPClient: func() *http.Client { + GetHTTPClient: func() ota.HttpClient { transport := http.DefaultTransport.(*http.Transport).Clone() transport.Proxy = config.NetworkConfig.GetTransportProxyFunc()