package updater import ( "runtime" "testing" ) func TestGithubInternal_FilterReleases_Good(t *testing.T) { releases := []Release{ {TagName: "v1.0.0-alpha.1", PreRelease: true}, {TagName: "v1.0.0-beta.1", PreRelease: true}, {TagName: "v1.0.0", PreRelease: false}, } tests := []struct { channel string wantTag string }{ {"stable", "v1.0.0"}, {"alpha", "v1.0.0-alpha.1"}, {"beta", "v1.0.0-beta.1"}, } for _, tt := range tests { t.Run(tt.channel, func(t *testing.T) { got := filterReleases(releases, tt.channel) if got == nil { t.Fatalf("expected release for channel %q, got nil", tt.channel) } if got.TagName != tt.wantTag { t.Errorf("expected tag %q, got %q", tt.wantTag, got.TagName) } }) } } func TestGithubInternal_FilterReleases_Bad(t *testing.T) { releases := []Release{ {TagName: "v1.0.0", PreRelease: false}, } got := filterReleases(releases, "alpha") if got != nil { t.Errorf("expected nil for non-matching channel, got %v", got) } } func TestGithubInternal_FilterReleases_Ugly(t *testing.T) { // Empty releases slice got := filterReleases([]Release{}, "stable") if got != nil { t.Errorf("expected nil for empty releases, got %v", got) } // Pre-release without alpha/beta label maps to beta channel releases := []Release{{TagName: "v2.0.0-rc.1", PreRelease: true}} got = filterReleases(releases, "beta") if got == nil { t.Fatal("expected pre-release without alpha/beta label to match beta channel") } if got.TagName != "v2.0.0-rc.1" { t.Errorf("expected tag %q, got %q", "v2.0.0-rc.1", got.TagName) } } func TestGithubInternal_DetermineChannel_Good(t *testing.T) { tests := []struct { tag string isPreRelease bool want string }{ {"v1.0.0", false, "stable"}, {"v1.0.0-alpha.1", false, "alpha"}, {"v1.0.0-ALPHA.1", false, "alpha"}, {"v1.0.0-beta.1", false, "beta"}, {"v1.0.0-BETA.1", false, "beta"}, {"v1.0.0-rc.1", true, "beta"}, } for _, tt := range tests { t.Run(tt.tag, func(t *testing.T) { got := determineChannel(tt.tag, tt.isPreRelease) if got != tt.want { t.Errorf("determineChannel(%q, %v) = %q, want %q", tt.tag, tt.isPreRelease, got, tt.want) } }) } } func TestGithubInternal_DetermineChannel_Bad(t *testing.T) { // rc tag without prerelease flag resolves to stable (not beta) got := determineChannel("v1.0.0-rc.1", false) if got != "stable" { t.Errorf("expected stable for rc tag without prerelease flag, got %q", got) } } func TestGithubInternal_DetermineChannel_Ugly(t *testing.T) { // Empty tag with prerelease=true — maps to beta got := determineChannel("", true) if got != "beta" { t.Errorf("expected beta for empty tag with prerelease=true, got %q", got) } // Empty tag with prerelease=false — maps to stable got = determineChannel("", false) if got != "stable" { t.Errorf("expected stable for empty tag with prerelease=false, got %q", got) } } func TestGithubInternal_GetDownloadURL_Good(t *testing.T) { osName := runtime.GOOS archName := runtime.GOARCH release := &Release{ TagName: "v1.2.3", Assets: []ReleaseAsset{ {Name: "app-" + osName + "-" + archName, DownloadURL: "https://example.com/full-match"}, {Name: "app-" + osName, DownloadURL: "https://example.com/os-only"}, }, } downloadURL, err := GetDownloadURL(release, "") if err != nil { t.Fatalf("unexpected error: %v", err) } if downloadURL != "https://example.com/full-match" { t.Errorf("expected full match URL, got %q", downloadURL) } } func TestGithubInternal_GetDownloadURL_Bad(t *testing.T) { // nil release _, err := GetDownloadURL(nil, "") if err == nil { t.Error("expected error for nil release") } // No matching assets release := &Release{ TagName: "v1.2.3", Assets: []ReleaseAsset{ {Name: "app-unknownos-unknownarch", DownloadURL: "https://example.com/other"}, }, } _, err = GetDownloadURL(release, "") if err == nil { t.Error("expected error when no suitable asset found") } } func TestGithubInternal_GetDownloadURL_Ugly(t *testing.T) { osName := runtime.GOOS // OS-only fallback when arch does not match release := &Release{ TagName: "v1.2.3", Assets: []ReleaseAsset{ {Name: "app-other-other", DownloadURL: "https://example.com/other"}, {Name: "app-" + osName + "-other", DownloadURL: "https://example.com/os-only"}, }, } downloadURL, err := GetDownloadURL(release, "") if err != nil { t.Fatalf("unexpected error for OS-only fallback: %v", err) } if downloadURL != "https://example.com/os-only" { t.Errorf("expected OS-only fallback URL, got %q", downloadURL) } // URL format template with placeholders release = &Release{TagName: "v1.2.3"} downloadURL, err = GetDownloadURL(release, "https://example.com/{tag}/{os}/{arch}") if err != nil { t.Fatalf("unexpected error for URL format: %v", err) } expected := "https://example.com/v1.2.3/" + runtime.GOOS + "/" + runtime.GOARCH if downloadURL != expected { t.Errorf("expected %q, got %q", expected, downloadURL) } } func TestGithubInternal_FormatVersionForComparison_Good(t *testing.T) { tests := []struct { input string want string }{ {"1.0.0", "v1.0.0"}, {"v1.0.0", "v1.0.0"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { got := formatVersionForComparison(tt.input) if got != tt.want { t.Errorf("formatVersionForComparison(%q) = %q, want %q", tt.input, got, tt.want) } }) } } func TestGithubInternal_FormatVersionForComparison_Bad(t *testing.T) { // Non-semver string still gets v prefix got := formatVersionForComparison("not-a-version") if got != "vnot-a-version" { t.Errorf("expected vnot-a-version, got %q", got) } } func TestGithubInternal_FormatVersionForComparison_Ugly(t *testing.T) { // Empty string returns empty (no v prefix added) if got := formatVersionForComparison(""); got != "" { t.Errorf("expected empty string, got %q", got) } } func TestGithubInternal_FormatVersionForDisplay_Good(t *testing.T) { tests := []struct { version string force bool want string }{ {"1.0.0", true, "v1.0.0"}, {"v1.0.0", true, "v1.0.0"}, {"v1.0.0", false, "1.0.0"}, {"1.0.0", false, "1.0.0"}, } for _, tt := range tests { t.Run(tt.version+"_force_"+boolStr(tt.force), func(t *testing.T) { got := formatVersionForDisplay(tt.version, tt.force) if got != tt.want { t.Errorf("formatVersionForDisplay(%q, %v) = %q, want %q", tt.version, tt.force, got, tt.want) } }) } } func TestGithubInternal_FormatVersionForDisplay_Bad(t *testing.T) { // force=true on already-prefixed version should not double-prefix got := formatVersionForDisplay("vv1.0.0", true) if got != "vv1.0.0" { t.Errorf("expected vv1.0.0 unchanged, got %q", got) } } func TestGithubInternal_FormatVersionForDisplay_Ugly(t *testing.T) { // Empty version with force=true — results in "v" got := formatVersionForDisplay("", true) if got != "v" { t.Errorf("expected \"v\" for empty version with force=true, got %q", got) } } func TestGithubInternal_StartGitHubCheck_Ugly(t *testing.T) { s := &UpdateService{ config: UpdateServiceConfig{ CheckOnStartup: StartupCheckMode(99), }, isGitHub: true, owner: "owner", repo: "repo", } if err := s.Start(); err == nil { t.Error("expected error for unknown startup check mode") } } func TestGithubInternal_StartHTTPCheck_Ugly(t *testing.T) { s := &UpdateService{ config: UpdateServiceConfig{ RepoURL: "https://example.com/updates", CheckOnStartup: StartupCheckMode(99), }, isGitHub: false, } if err := s.Start(); err == nil { t.Error("expected error for unknown startup check mode") } } func boolStr(b bool) string { if b { return "true" } return "false" }