Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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}
}
}
Expand Down
20 changes: 12 additions & 8 deletions internal/update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
41 changes: 40 additions & 1 deletion internal/update/update_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package update

import (
"errors"
"strings"
"testing"
)

Expand Down Expand Up @@ -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)
}
}
Loading