Files
wehub-resource-sync ead81af521
Deploy to GitHub Pages / deploy (push) Failing after 0s
CI / go (push) Has been cancelled
CI / build (darwin, macos-14) (push) Has been cancelled
CI / build (windows, windows-2025) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:13 +08:00

267 lines
7.6 KiB
Go

package upgrade
import (
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
)
func testHash(data []byte) string {
sum := sha256.Sum256(data)
return hex.EncodeToString(sum[:])
}
type rewriter struct {
target *url.URL
rt http.RoundTripper
}
func (r rewriter) RoundTrip(req *http.Request) (*http.Response, error) {
clone := req.Clone(req.Context())
clone.URL.Scheme = r.target.Scheme
clone.URL.Host = r.target.Host
clone.Host = r.target.Host
return r.rt.RoundTrip(clone)
}
func installTestClient(t *testing.T, serverURL string) {
t.Helper()
u, err := url.Parse(serverURL)
if err != nil {
t.Fatalf("parse: %v", err)
}
old := httpClient
httpClient = &http.Client{
Timeout: 10 * time.Second,
Transport: rewriter{target: u, rt: http.DefaultTransport},
}
t.Cleanup(func() { httpClient = old })
}
func TestLatestVersionSuccess(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.URL.Path, "/repos/") || !strings.HasSuffix(r.URL.Path, "/releases/latest") {
t.Errorf("unexpected path %q", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"tag_name":"v1.2.3"}`))
}))
defer srv.Close()
installTestClient(t, srv.URL)
tag, err := latestVersion()
if err != nil {
t.Fatalf("latestVersion: %v", err)
}
if tag != "v1.2.3" {
t.Errorf("tag = %q, want v1.2.3", tag)
}
}
func TestLatestVersionHTTPError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "down", http.StatusInternalServerError)
}))
defer srv.Close()
installTestClient(t, srv.URL)
_, err := latestVersion()
if err == nil {
t.Error("latestVersion should error on 500")
}
if !strings.Contains(err.Error(), "GitHub API") {
t.Errorf("error = %q, want to mention 'GitHub API'", err.Error())
}
}
func TestLatestVersionBadJSON(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte(`not valid json`))
}))
defer srv.Close()
installTestClient(t, srv.URL)
_, err := latestVersion()
if err == nil {
t.Error("latestVersion should error on invalid JSON")
}
}
func TestReleaseChecksum(t *testing.T) {
want := strings.Repeat("a", 64)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, want+" cliamp-linux-amd64\n")
}))
defer srv.Close()
installTestClient(t, srv.URL)
got, err := releaseChecksum(srv.URL+"/checksums.txt", "cliamp-linux-amd64")
if err != nil {
t.Fatal(err)
}
if got != want {
t.Errorf("checksum = %q, want %q", got, want)
}
}
func TestReleaseChecksumMissingEntry(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, strings.Repeat("a", 64)+" another-file\n")
}))
defer srv.Close()
installTestClient(t, srv.URL)
if _, err := releaseChecksum(srv.URL+"/checksums.txt", "cliamp-linux-amd64"); err == nil {
t.Fatal("releaseChecksum accepted a missing asset entry")
}
}
func TestDownloadAndReplaceChecksumMismatchPreservesOriginal(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "cliamp")
if err := os.WriteFile(target, []byte("OLD"), 0o755); err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "NEW")
}))
defer srv.Close()
installTestClient(t, srv.URL)
err := downloadAndReplace(srv.URL+"/cliamp", target, testHash([]byte("different")))
if err == nil || !strings.Contains(err.Error(), "SHA-256 mismatch") {
t.Fatalf("downloadAndReplace error = %v, want checksum mismatch", err)
}
got, readErr := os.ReadFile(target)
if readErr != nil || string(got) != "OLD" {
t.Fatalf("original after mismatch = %q, %v", got, readErr)
}
}
func TestDownloadAndReplace(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "cliamp")
// Pre-create target with old contents.
if err := os.WriteFile(target, []byte("OLD"), 0o755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
newContent := []byte("NEW BINARY BYTES")
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/octet-stream")
_, _ = w.Write(newContent)
}))
defer srv.Close()
installTestClient(t, srv.URL)
if err := downloadAndReplace(srv.URL+"/cliamp-linux-amd64", target, testHash(newContent)); err != nil {
t.Fatalf("downloadAndReplace: %v", err)
}
got, err := os.ReadFile(target)
if err != nil {
t.Fatalf("ReadFile: %v", err)
}
if string(got) != string(newContent) {
t.Errorf("target content = %q, want %q", got, newContent)
}
info, err := os.Stat(target)
if err != nil {
t.Fatalf("Stat: %v", err)
}
// Verify executable bit is set where the platform models one.
if runtime.GOOS != "windows" && info.Mode().Perm()&0o111 == 0 {
t.Errorf("target mode = %o, want executable", info.Mode().Perm())
}
}
func TestDownloadAndReplaceHTTPError(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "cliamp")
if err := os.WriteFile(target, []byte("OLD"), 0o755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "missing", http.StatusNotFound)
}))
defer srv.Close()
installTestClient(t, srv.URL)
err := downloadAndReplace(srv.URL+"/cliamp", target, testHash([]byte("unused")))
if err == nil {
t.Error("downloadAndReplace should error on 404")
}
// Original file should remain untouched on failure.
got, _ := os.ReadFile(target)
if string(got) != "OLD" {
t.Errorf("target content = %q, want OLD — file should be untouched on error", got)
}
}
func TestDownloadAndReplaceTruncatesOversize(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, "cliamp")
if err := os.WriteFile(target, []byte("OLD"), 0o755); err != nil {
t.Fatalf("WriteFile: %v", err)
}
// Serve a body larger than maxBinarySize but streamable (cannot easily
// verify the limit without a massive body; we just confirm normal
// operation works with a reasonably sized body).
body := strings.Repeat("Z", 1024)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, body)
}))
defer srv.Close()
installTestClient(t, srv.URL)
if err := downloadAndReplace(srv.URL+"/cliamp", target, testHash([]byte(body))); err != nil {
t.Fatalf("downloadAndReplace: %v", err)
}
got, _ := os.ReadFile(target)
if len(got) != 1024 {
t.Errorf("target len = %d, want 1024", len(got))
}
}
func TestRunAlreadyUpToDate(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"tag_name":"v2.0.0"}`))
}))
defer srv.Close()
installTestClient(t, srv.URL)
// Same current version → no download attempted.
if err := Run("v2.0.0"); err != nil {
t.Errorf("Run(current=latest) = %v, want nil", err)
}
}
func TestRunFailsLatestVersion(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "down", http.StatusInternalServerError)
}))
defer srv.Close()
installTestClient(t, srv.URL)
err := Run("v1.0.0")
if err == nil {
t.Error("Run should propagate latest-version failure")
}
if !strings.Contains(err.Error(), "checking latest version") {
t.Errorf("error = %q, want to mention 'checking latest version'", err.Error())
}
}