diff --git a/internal/app/app.go b/internal/app/app.go index 42a417d20d403ae63d90b5845d10cafe76e46722..df57400dfe8e67a63a4e73df017820da367c0e33 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -352,7 +352,7 @@ func (app *App) Shutdown() { func (app *App) checkForUpdates(ctx context.Context) { checkCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - info, err := update.Check(checkCtx) + info, err := update.Check(checkCtx, update.Default) if err != nil || !info.Available() { return } diff --git a/internal/update/update.go b/internal/update/update.go index 100ea63c1f1b589f89b8f32b9a292bcb2fa6bbf8..47377778838045b53b9917c13692f46c77819dc3 100644 --- a/internal/update/update.go +++ b/internal/update/update.go @@ -17,6 +17,9 @@ const ( userAgent = "crush/1.0" ) +// Default is the default [Client]. +var Default Client = &github{} + // Info contains information about an available update. type Info struct { CurrentVersion string @@ -24,10 +27,11 @@ type Info struct { ReleaseURL string } +// Available returns true if there's an update available. func (i Info) Available() bool { return i.CurrentVersion != i.LatestVersion } // Check checks if a new version is available. -func Check(ctx context.Context) (Info, error) { +func Check(ctx context.Context, client Client) (Info, error) { info := Info{ CurrentVersion: version.Version, LatestVersion: version.Version, @@ -37,7 +41,7 @@ func Check(ctx context.Context) (Info, error) { return info, nil } - release, err := fetchLatestRelease(ctx) + release, err := client.Latest(ctx) if err != nil { return info, fmt.Errorf("failed to fetch latest release: %w", err) } @@ -47,14 +51,21 @@ func Check(ctx context.Context) (Info, error) { return info, nil } -// githubRelease represents a GitHub release. -type githubRelease struct { +// Release represents a GitHub release. +type Release struct { TagName string `json:"tag_name"` HTMLURL string `json:"html_url"` } -// fetchLatestRelease fetches the latest release information from GitHub. -func fetchLatestRelease(ctx context.Context) (*githubRelease, error) { +// Client is a client that can get the latest release. +type Client interface { + Latest(ctx context.Context) (*Release, error) +} + +type github struct{} + +// Latest implements [Client]. +func (c *github) Latest(ctx context.Context) (*Release, error) { client := &http.Client{ Timeout: 30 * time.Second, } @@ -77,7 +88,7 @@ func fetchLatestRelease(ctx context.Context) (*githubRelease, error) { return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, string(body)) } - var release githubRelease + var release Release if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { return nil, err } diff --git a/internal/update/update_test.go b/internal/update/update_test.go index 2da3b61fb72de8fe50e0d266bf8ce19805e24536..9ac23d43257289811b5dd4e917fa46745c75f998 100644 --- a/internal/update/update_test.go +++ b/internal/update/update_test.go @@ -1,6 +1,7 @@ package update import ( + "context" "testing" "github.com/charmbracelet/crush/internal/version" @@ -14,7 +15,7 @@ func TestCheckForUpdate_DevelopmentVersion(t *testing.T) { version.Version = originalVersion }) - info, err := Check(t.Context()) + info, err := Check(t.Context(), testClient{}) require.NoError(t, err) require.NotNil(t, info) require.False(t, info.Available()) @@ -26,8 +27,18 @@ func TestCheckForUpdate_Old(t *testing.T) { t.Cleanup(func() { version.Version = originalVersion }) - info, err := Check(t.Context()) + info, err := Check(t.Context(), testClient{}) require.NoError(t, err) require.NotNil(t, info) require.True(t, info.Available()) } + +type testClient struct{} + +// Latest implements Client. +func (t testClient) Latest(ctx context.Context) (*Release, error) { + return &Release{ + TagName: "v0.11.0", + HTMLURL: "https://example.org", + }, nil +}