diff --git a/internal/app/app.go b/internal/app/app.go index 389ed57..8d3bbd2 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -12,6 +12,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "unic/internal/config" "unic/internal/domain" + uniclog "unic/internal/log" awsservice "unic/internal/services/aws" "unic/internal/update" ) @@ -293,7 +294,10 @@ var appLoadCallerIdentityFn = func(m Model) tea.Cmd { func (m Model) checkForUpdate() tea.Cmd { return func() tea.Msg { method := update.DetectInstallMethod() - newVersion := update.CheckForUpdate(m.currentVersion) + newVersion, err := update.CheckForUpdate(m.currentVersion) + if err != nil { + uniclog.Info("app", "update check failed", "error", err.Error()) + } return updateAvailableMsg{version: newVersion, method: method} } } diff --git a/internal/update/update.go b/internal/update/update.go index 30cb5e0..d1512bb 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -147,34 +147,38 @@ func CheckLatestVersion() (string, error) { return release.TagName, nil } +// checkLatestVersionFn is a test seam for the GitHub releases API call. +var checkLatestVersionFn = CheckLatestVersion + // CheckForUpdate checks if a newer version is available, using cache when possible. // Returns the new version string if available, or empty string if up to date. -func CheckForUpdate(currentVersion string) string { +// A non-nil error means the check itself failed and no result is known. +func CheckForUpdate(currentVersion string) (string, error) { if currentVersion == "dev" { - return "" + return "", nil } // Try cache first if !ShouldCheck() { c, err := readCache() if err == nil && IsNewer(currentVersion, c.Version) { - return c.Version + return c.Version, nil } - return "" + return "", nil } - latest, err := CheckLatestVersion() + latest, err := checkLatestVersionFn() if err != nil { - return "" + return "", fmt.Errorf("update check failed: %w", err) } // Update cache regardless of result _ = writeCache(latest) if IsNewer(currentVersion, latest) { - return latest + return latest, nil } - return "" + return "", nil } // IsNewer returns true if latest is a newer version than current. diff --git a/internal/update/update_test.go b/internal/update/update_test.go index 9bcc7ba..099ebb9 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -1,6 +1,8 @@ package update import ( + "errors" + "strings" "testing" ) @@ -113,8 +115,45 @@ func TestWriteAndReadCache(t *testing.T) { func TestCheckForUpdate_DevVersion(t *testing.T) { // dev version should never trigger update check - result := CheckForUpdate("dev") + result, err := CheckForUpdate("dev") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } if result != "" { t.Errorf("expected empty string for dev version, got %q", result) } } + +func TestCheckForUpdate_PropagatesCheckError(t *testing.T) { + t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + orig := checkLatestVersionFn + t.Cleanup(func() { checkLatestVersionFn = orig }) + checkLatestVersionFn = func() (string, error) { + return "", errors.New("api down") + } + + result, err := CheckForUpdate("0.1.0") + if err == nil || !strings.Contains(err.Error(), "api down") { + t.Fatalf("expected wrapped check error, got %v", err) + } + if result != "" { + t.Errorf("expected empty version on error, got %q", result) + } +} + +func TestCheckForUpdate_ReturnsNewerVersion(t *testing.T) { + t.Setenv("XDG_CONFIG_HOME", t.TempDir()) + orig := checkLatestVersionFn + t.Cleanup(func() { checkLatestVersionFn = orig }) + checkLatestVersionFn = func() (string, error) { + return "v0.2.0", nil + } + + result, err := CheckForUpdate("0.1.0") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result != "v0.2.0" { + t.Errorf("expected v0.2.0, got %q", result) + } +}