175 lines
4.6 KiB
Go
175 lines
4.6 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
package upgrade
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDownload_Success(t *testing.T) {
|
|
body := "hello world binary content"
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
|
w.Write([]byte(body))
|
|
}))
|
|
defer server.Close()
|
|
|
|
dest := filepath.Join(t.TempDir(), "downloaded")
|
|
n, err := Download(server.URL+"/file.tar.gz", dest)
|
|
if err != nil {
|
|
t.Fatalf("Download() error = %v", err)
|
|
}
|
|
if n != int64(len(body)) {
|
|
t.Errorf("Download() = %d bytes, want %d", n, len(body))
|
|
}
|
|
|
|
got, _ := os.ReadFile(dest)
|
|
if string(got) != body {
|
|
t.Errorf("file content = %q, want %q", string(got), body)
|
|
}
|
|
}
|
|
|
|
func TestDownload_HTTP404(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer server.Close()
|
|
|
|
dest := filepath.Join(t.TempDir(), "notfound")
|
|
_, err := Download(server.URL+"/missing", dest)
|
|
if err == nil {
|
|
t.Fatal("Download() expected error for 404")
|
|
}
|
|
}
|
|
|
|
func TestDownload_HTTP500_RetriesAndFails(t *testing.T) {
|
|
var attempts int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
atomic.AddInt32(&attempts, 1)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer server.Close()
|
|
|
|
dest := filepath.Join(t.TempDir(), "err")
|
|
cfg := &DownloadConfig{
|
|
MaxRetries: 2,
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
_, err := DownloadWithConfig(context.Background(), server.URL+"/fail", dest, cfg)
|
|
if err == nil {
|
|
t.Fatal("expected error after retries")
|
|
}
|
|
|
|
// 500 is not retriable (no "connection reset" pattern), so only 1 attempt
|
|
got := atomic.LoadInt32(&attempts)
|
|
if got != 1 {
|
|
t.Errorf("attempts = %d, want 1 (500 is not retriable)", got)
|
|
}
|
|
}
|
|
|
|
func TestDownload_ContextCancellation(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(2 * time.Second)
|
|
w.Write([]byte("late"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel immediately
|
|
|
|
dest := filepath.Join(t.TempDir(), "cancelled")
|
|
_, err := DownloadWithProgress(ctx, server.URL+"/slow", dest, nil)
|
|
if err == nil {
|
|
t.Fatal("expected error for cancelled context")
|
|
}
|
|
}
|
|
|
|
func TestDownloadWithProgress_Callback(t *testing.T) {
|
|
body := make([]byte, 1024)
|
|
for i := range body {
|
|
body[i] = 'A'
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
|
w.Write(body)
|
|
}))
|
|
defer server.Close()
|
|
|
|
var called int32
|
|
dest := filepath.Join(t.TempDir(), "progress")
|
|
n, err := DownloadWithProgress(context.Background(), server.URL+"/file", dest,
|
|
func(percent float64, downloaded, total int64) {
|
|
atomic.AddInt32(&called, 1)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("DownloadWithProgress() error = %v", err)
|
|
}
|
|
if n != int64(len(body)) {
|
|
t.Errorf("bytes = %d, want %d", n, len(body))
|
|
}
|
|
if atomic.LoadInt32(&called) == 0 {
|
|
t.Error("progress callback was never called")
|
|
}
|
|
}
|
|
|
|
func TestDownload_CreatesParentDirectories(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("data"))
|
|
}))
|
|
defer server.Close()
|
|
|
|
dest := filepath.Join(t.TempDir(), "a", "b", "c", "file")
|
|
_, err := Download(server.URL+"/file", dest)
|
|
if err != nil {
|
|
t.Fatalf("Download() error = %v", err)
|
|
}
|
|
if _, err := os.Stat(dest); err != nil {
|
|
t.Errorf("file not created: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDefaultDownloadConfig(t *testing.T) {
|
|
cfg := DefaultDownloadConfig()
|
|
if cfg.MaxRetries != 3 {
|
|
t.Errorf("MaxRetries = %d, want 3", cfg.MaxRetries)
|
|
}
|
|
if cfg.Timeout != 10*time.Minute {
|
|
t.Errorf("Timeout = %v, want 10m", cfg.Timeout)
|
|
}
|
|
if cfg.ProgressInterval != 200*time.Millisecond {
|
|
t.Errorf("ProgressInterval = %v, want 200ms", cfg.ProgressInterval)
|
|
}
|
|
}
|
|
|
|
func TestIsRetriable(t *testing.T) {
|
|
tests := []struct {
|
|
err error
|
|
want bool
|
|
}{
|
|
{nil, false},
|
|
{fmt.Errorf("connection reset by peer"), true},
|
|
{fmt.Errorf("Connection Refused"), true},
|
|
{fmt.Errorf("request timeout exceeded"), true},
|
|
{fmt.Errorf("temporary failure in name resolution"), true},
|
|
{fmt.Errorf("unexpected EOF"), true},
|
|
{fmt.Errorf("permission denied"), false},
|
|
{fmt.Errorf("file not found"), false},
|
|
}
|
|
for _, tt := range tests {
|
|
got := isRetriable(tt.err)
|
|
if got != tt.want {
|
|
t.Errorf("isRetriable(%q) = %v, want %v", tt.err, got, tt.want)
|
|
}
|
|
}
|
|
}
|