All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m5s
Add internal/updater package with UpdateService that queries the Gitea generic-package API for newer releases, downloads the installer with SHA256 verification, and launches it to apply the update. Includes semver comparison (CompareVersions) and comprehensive test coverage with httptest-based mock servers. Wire UpdateService into WraithApp (app.go accepts version param) and register as a Wails service in main.go. Frontend StatusBar shows a blue pill notification when an update is available; SettingsModal About section displays the current version and a "Check for Updates" button with idle/checking/found/up-to-date/error states. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
224 lines
6.0 KiB
Go
224 lines
6.0 KiB
Go
package updater
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewUpdateService(t *testing.T) {
|
|
svc := NewUpdateService("0.2.0")
|
|
if svc.currentVersion != "0.2.0" {
|
|
t.Errorf("currentVersion = %q, want %q", svc.currentVersion, "0.2.0")
|
|
}
|
|
if svc.baseURL == "" {
|
|
t.Error("baseURL should not be empty")
|
|
}
|
|
if svc.owner == "" {
|
|
t.Error("owner should not be empty")
|
|
}
|
|
if svc.pkg == "" {
|
|
t.Error("pkg should not be empty")
|
|
}
|
|
if svc.httpClient == nil {
|
|
t.Error("httpClient should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestNewUpdateServiceStripsVPrefix(t *testing.T) {
|
|
svc := NewUpdateService("v1.2.3")
|
|
if svc.currentVersion != "1.2.3" {
|
|
t.Errorf("currentVersion = %q, want %q", svc.currentVersion, "1.2.3")
|
|
}
|
|
}
|
|
|
|
func TestCompareVersions(t *testing.T) {
|
|
tests := []struct {
|
|
a, b string
|
|
want int
|
|
}{
|
|
{"0.2.0", "0.2.1", -1},
|
|
{"0.2.1", "0.2.0", 1},
|
|
{"0.3.0", "0.2.9", 1},
|
|
{"0.2.9", "0.3.0", -1},
|
|
{"1.0.0", "0.9.9", 1},
|
|
{"0.9.9", "1.0.0", -1},
|
|
{"1.0.0", "1.0.0", 0},
|
|
{"0.0.1", "0.0.1", 0},
|
|
{"2.0.0", "1.99.99", 1},
|
|
{"0.1.0", "0.0.99", 1},
|
|
// With "v" prefix
|
|
{"v0.2.0", "v0.2.1", -1},
|
|
{"v1.0.0", "1.0.0", 0},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := CompareVersions(tt.a, tt.b)
|
|
if got != tt.want {
|
|
t.Errorf("CompareVersions(%q, %q) = %d, want %d", tt.a, tt.b, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseSemver(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
want [3]int
|
|
}{
|
|
{"1.2.3", [3]int{1, 2, 3}},
|
|
{"0.0.0", [3]int{0, 0, 0}},
|
|
{"10.20.30", [3]int{10, 20, 30}},
|
|
{"1.2", [3]int{1, 2, 0}},
|
|
{"1", [3]int{1, 0, 0}},
|
|
{"", [3]int{0, 0, 0}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := parseSemver(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("parseSemver(%q) = %v, want %v", tt.input, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseVersionJSON(t *testing.T) {
|
|
raw := `{"version":"0.3.0","sha256":"abcdef1234567890","downloadUrl":"https://example.com/setup.exe"}`
|
|
var v versionJSON
|
|
if err := json.Unmarshal([]byte(raw), &v); err != nil {
|
|
t.Fatalf("Unmarshal error: %v", err)
|
|
}
|
|
if v.Version != "0.3.0" {
|
|
t.Errorf("Version = %q, want %q", v.Version, "0.3.0")
|
|
}
|
|
if v.SHA256 != "abcdef1234567890" {
|
|
t.Errorf("SHA256 = %q, want %q", v.SHA256, "abcdef1234567890")
|
|
}
|
|
if v.DownloadURL != "https://example.com/setup.exe" {
|
|
t.Errorf("DownloadURL = %q, want %q", v.DownloadURL, "https://example.com/setup.exe")
|
|
}
|
|
}
|
|
|
|
func TestCheckForUpdate_NewerAvailable(t *testing.T) {
|
|
// Mock the Gitea API: return a single package version that is newer.
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/v1/packages/vstockwell/generic/wraith", func(w http.ResponseWriter, r *http.Request) {
|
|
versions := []giteaPackageVersion{{Version: "0.3.0"}}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(versions)
|
|
})
|
|
mux.HandleFunc("/api/packages/vstockwell/generic/wraith/0.3.0/version.json", func(w http.ResponseWriter, r *http.Request) {
|
|
v := versionJSON{
|
|
Version: "0.3.0",
|
|
SHA256: "abc123",
|
|
DownloadURL: "https://example.com/wraith-0.3.0-setup.exe",
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(v)
|
|
})
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
svc := NewUpdateService("0.2.0")
|
|
svc.baseURL = server.URL
|
|
|
|
info, err := svc.CheckForUpdate()
|
|
if err != nil {
|
|
t.Fatalf("CheckForUpdate() error: %v", err)
|
|
}
|
|
if !info.Available {
|
|
t.Error("expected Available = true")
|
|
}
|
|
if info.LatestVer != "0.3.0" {
|
|
t.Errorf("LatestVer = %q, want %q", info.LatestVer, "0.3.0")
|
|
}
|
|
if info.SHA256 != "abc123" {
|
|
t.Errorf("SHA256 = %q, want %q", info.SHA256, "abc123")
|
|
}
|
|
if info.DownloadURL != "https://example.com/wraith-0.3.0-setup.exe" {
|
|
t.Errorf("DownloadURL = %q, want %q", info.DownloadURL, "https://example.com/wraith-0.3.0-setup.exe")
|
|
}
|
|
}
|
|
|
|
func TestCheckForUpdate_AlreadyUpToDate(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/v1/packages/vstockwell/generic/wraith", func(w http.ResponseWriter, r *http.Request) {
|
|
versions := []giteaPackageVersion{{Version: "0.2.0"}}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(versions)
|
|
})
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
svc := NewUpdateService("0.2.0")
|
|
svc.baseURL = server.URL
|
|
|
|
info, err := svc.CheckForUpdate()
|
|
if err != nil {
|
|
t.Fatalf("CheckForUpdate() error: %v", err)
|
|
}
|
|
if info.Available {
|
|
t.Error("expected Available = false (same version)")
|
|
}
|
|
}
|
|
|
|
func TestCheckForUpdate_CurrentIsNewer(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/v1/packages/vstockwell/generic/wraith", func(w http.ResponseWriter, r *http.Request) {
|
|
versions := []giteaPackageVersion{{Version: "0.1.0"}}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(versions)
|
|
})
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
svc := NewUpdateService("0.2.0")
|
|
svc.baseURL = server.URL
|
|
|
|
info, err := svc.CheckForUpdate()
|
|
if err != nil {
|
|
t.Fatalf("CheckForUpdate() error: %v", err)
|
|
}
|
|
if info.Available {
|
|
t.Error("expected Available = false (current is newer)")
|
|
}
|
|
}
|
|
|
|
func TestCheckForUpdate_NoVersions(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/v1/packages/vstockwell/generic/wraith", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode([]giteaPackageVersion{})
|
|
})
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
svc := NewUpdateService("0.2.0")
|
|
svc.baseURL = server.URL
|
|
|
|
info, err := svc.CheckForUpdate()
|
|
if err != nil {
|
|
t.Fatalf("CheckForUpdate() error: %v", err)
|
|
}
|
|
if info.Available {
|
|
t.Error("expected Available = false (no versions)")
|
|
}
|
|
}
|
|
|
|
func TestCheckForUpdate_APIError(t *testing.T) {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/v1/packages/vstockwell/generic/wraith", func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
})
|
|
server := httptest.NewServer(mux)
|
|
defer server.Close()
|
|
|
|
svc := NewUpdateService("0.2.0")
|
|
svc.baseURL = server.URL
|
|
|
|
_, err := svc.CheckForUpdate()
|
|
if err == nil {
|
|
t.Error("expected error from API failure")
|
|
}
|
|
}
|