Scout/pkg/updater/updater_test.go
2026-08-31 15:34:36 +08:00

235 lines
6.3 KiB
Go

package updater
import (
"archive/zip"
"bytes"
"context"
"encoding/json"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"time"
"scout/internal/httpapi"
"scout/internal/registry"
"scout/pkg/client"
"scout/pkg/scoutpkg"
)
func TestPrepareActivateAndConfirmHealthy(t *testing.T) {
api := newTestClient(t)
packageData := createScoutPackage(t, "1.1.0")
if _, err := api.Upload(context.Background(), client.UploadRequest{
FileName: "demo.scout",
File: bytes.NewReader(packageData),
}); err != nil {
t.Fatal(err)
}
root := t.TempDir()
manager, err := New(Config{
Client: api,
PackageName: "demo-core",
CurrentVersion: "1.0.0",
RootDir: root,
Platform: "linux",
Architecture: "amd64",
KeepReleases: 2,
})
if err != nil {
t.Fatal(err)
}
prepared, err := manager.Prepare(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if prepared.Version != "1.1.0" {
t.Fatalf("\u51c6\u5907\u7248\u672c\u9519\u8bef: %s", prepared.Version)
}
if _, err := os.Stat(filepath.Join(root, "current.json")); !os.IsNotExist(err) {
t.Fatal("Prepare \u4e0d\u5e94\u4fee\u6539\u5f53\u524d\u7248\u672c\u6307\u9488")
}
content, err := os.ReadFile(filepath.Join(prepared.Path, "demo"))
if err != nil || string(content) != "new binary" {
t.Fatalf("\u89e3\u538b\u7ed3\u679c\u9519\u8bef: %q, %v", content, err)
}
activated, err := manager.Activate()
if err != nil {
t.Fatal(err)
}
if activated.Version != prepared.Version {
t.Fatalf("\u6fc0\u6d3b\u7248\u672c\u9519\u8bef: %s", activated.Version)
}
if err := manager.ConfirmHealthy(activated.Version); err != nil {
t.Fatal(err)
}
status, err := manager.Status()
if err != nil {
t.Fatal(err)
}
if status.Current == nil || status.Current.Version != "1.1.0" || !status.State.Healthy {
t.Fatalf("\u5065\u5eb7\u72b6\u6001\u9519\u8bef: %+v", status)
}
if status.Pending != nil {
t.Fatal("\u5065\u5eb7\u786e\u8ba4\u540e\u5e94\u6e05\u9664\u5f85\u6fc0\u6d3b\u6307\u9488")
}
}
func TestActivationPreservesOldReleaseAndStaleActivationRollsBack(t *testing.T) {
api := newTestClient(t)
if _, err := api.Upload(context.Background(), client.UploadRequest{
FileName: "demo.scout",
File: bytes.NewReader(createScoutPackage(t, "2.0.0")),
}); err != nil {
t.Fatal(err)
}
root := t.TempDir()
manager, err := New(Config{
Client: api,
PackageName: "demo-core",
RootDir: root,
Platform: "linux",
Architecture: "amd64",
HealthTimeout: 10 * time.Millisecond,
KeepReleases: 2,
})
if err != nil {
t.Fatal(err)
}
oldDir := filepath.Join(root, "releases", "1.0.0")
if err := os.MkdirAll(oldDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(oldDir, "demo"), []byte("old binary"), 0o755); err != nil {
t.Fatal(err)
}
oldPointer, err := manager.RegisterCurrent("1.0.0", oldDir, "demo")
if err != nil {
t.Fatal(err)
}
if _, err := manager.Prepare(context.Background(), "2.0.0"); err != nil {
t.Fatal(err)
}
if _, err := manager.Activate(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Join(oldPointer.Path, oldPointer.Entrypoint)); err != nil {
t.Fatalf("\u6fc0\u6d3b\u540e\u65e7\u7248\u672c\u4e0d\u5e94\u88ab\u5220\u9664: %v", err)
}
recovered, err := manager.RecoverStaleActivation(time.Now().Add(time.Second))
if err != nil || !recovered {
t.Fatalf("\u8d85\u65f6\u56de\u6eda\u5931\u8d25: %t, %v", recovered, err)
}
status, err := manager.Status()
if err != nil {
t.Fatal(err)
}
if status.Current == nil || status.Current.Version != "1.0.0" || status.State.Stage != "rolled_back" {
t.Fatalf("\u56de\u6eda\u72b6\u6001\u9519\u8bef: %+v", status)
}
}
func TestHealthyCleanupUsesConfiguredRetention(t *testing.T) {
api := newTestClient(t)
if _, err := api.Upload(context.Background(), client.UploadRequest{
FileName: "demo.scout",
File: bytes.NewReader(createScoutPackage(t, "1.1.0")),
}); err != nil {
t.Fatal(err)
}
root := t.TempDir()
manager, err := New(Config{
Client: api,
PackageName: "demo-core",
RootDir: root,
Platform: "linux",
Architecture: "amd64",
KeepReleases: 1,
})
if err != nil {
t.Fatal(err)
}
oldDir := filepath.Join(root, "releases", "1.0.0")
if err := os.MkdirAll(oldDir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(oldDir, "demo"), []byte("old binary"), 0o755); err != nil {
t.Fatal(err)
}
if _, err := manager.RegisterCurrent("1.0.0", oldDir, "demo"); err != nil {
t.Fatal(err)
}
prepared, err := manager.Prepare(context.Background(), "")
if err != nil {
t.Fatal(err)
}
if _, err := manager.Activate(); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(oldDir); err != nil {
t.Fatalf("\u5065\u5eb7\u786e\u8ba4\u524d\u5fc5\u987b\u4fdd\u7559\u65e7\u7248\u672c: %v", err)
}
if err := manager.ConfirmHealthy(prepared.Version); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(oldDir); !os.IsNotExist(err) {
t.Fatalf("\u5065\u5eb7\u786e\u8ba4\u540e\u672a\u6309\u4fdd\u7559\u7b56\u7565\u6e05\u7406\u65e7\u7248\u672c: %v", err)
}
}
func newTestClient(t *testing.T) *client.Client {
t.Helper()
store, err := registry.New(t.TempDir())
if err != nil {
t.Fatal(err)
}
server := httptest.NewServer((&httpapi.Server{Registry: store, DeveloperToken: "test-token"}).Handler())
t.Cleanup(server.Close)
api, err := client.New(server.URL, client.WithDeveloperToken("test-token"))
if err != nil {
t.Fatal(err)
}
return api
}
func createScoutPackage(t *testing.T, version string) []byte {
t.Helper()
buffer := bytes.NewBuffer(nil)
writer := zip.NewWriter(buffer)
manifest := scoutpkg.Manifest{
SchemaVersion: 1,
Name: "demo-core",
Version: version,
Channel: "stable",
PackageType: "full",
Targets: []scoutpkg.Target{{
Platform: "linux",
Architecture: "amd64",
Path: "targets/linux/amd64",
Entrypoint: "targets/linux/amd64/demo",
}},
}
manifestData, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
entry, err := writer.Create(scoutpkg.ManifestName)
if err != nil {
t.Fatal(err)
}
if _, err := entry.Write(manifestData); err != nil {
t.Fatal(err)
}
entry, err = writer.Create("targets/linux/amd64/demo")
if err != nil {
t.Fatal(err)
}
if _, err := entry.Write([]byte("new binary")); err != nil {
t.Fatal(err)
}
if err := writer.Close(); err != nil {
t.Fatal(err)
}
return buffer.Bytes()
}