feat: add manual server binary upload and confirmation upgrade process

- Implemented UploadManualServerBinary endpoint for uploading server binaries and checking their versions.
- Added ConfirmManualServerUpgrade endpoint to confirm the upgrade with the uploaded binary.
- Updated API routes to include manual upload and upgrade confirmation.
- Enhanced service layer to handle manual binary uploads, version detection, and upgrade execution.
- Introduced new types for handling uploaded binary information.
- Updated frontend components to support manual binary upload and confirmation, including UI feedback for users.
- Modified documentation to reflect new manual upload and upgrade features.
This commit is contained in:
ryan
2026-03-12 10:34:31 +08:00
parent 29a0c64ba3
commit 93e43fb3b0
12 changed files with 1004 additions and 28 deletions
+128
View File
@@ -2,9 +2,31 @@ package service
import (
"atsflare/common"
"bytes"
"context"
"os"
"runtime"
"testing"
"time"
)
func resetServerUpgradeTestState(t *testing.T) {
t.Helper()
serverUpgradeState.Lock()
serverUpgradeState.inProgress = false
serverUpgradeState.Unlock()
manualServerBinaryState.Lock()
cleanupManualServerBinaryCandidateLocked()
manualServerBinaryState.Unlock()
}
func fakeServerBinaryFixture(version string) (string, []byte) {
if runtime.GOOS == "windows" {
return "atsflare-server-test.cmd", []byte("@echo off\r\necho " + version + "\r\n")
}
return "atsflare-server-test.sh", []byte("#!/bin/sh\necho " + version + "\n")
}
func TestIsVersionNewer(t *testing.T) {
testCases := []struct {
name string
@@ -85,3 +107,109 @@ func TestBuildLatestServerReleaseViewDevBuild(t *testing.T) {
t.Fatal("expected dev build not to support self-upgrade")
}
}
func TestUploadManualServerBinary(t *testing.T) {
originalVersion := common.Version
common.Version = "v0.4.0"
t.Cleanup(func() {
common.Version = originalVersion
resetServerUpgradeTestState(t)
})
fileName, content := fakeServerBinaryFixture("v0.5.0")
info, err := UploadManualServerBinary(context.Background(), fileName, bytes.NewReader(content))
if err != nil {
t.Fatalf("expected upload to succeed: %v", err)
}
if !info.ReadyToUpgrade {
t.Fatal("expected uploaded binary to be ready for upgrade")
}
if info.UploadToken == "" {
t.Fatal("expected upload token to be returned")
}
if info.DetectedVersion != "v0.5.0" {
t.Fatalf("unexpected detected version: %s", info.DetectedVersion)
}
manualServerBinaryState.Lock()
candidate := manualServerBinaryState.candidate
manualServerBinaryState.Unlock()
if candidate == nil {
t.Fatal("expected manual upgrade candidate to be stored")
}
if _, err := os.Stat(candidate.TempPath); err != nil {
t.Fatalf("expected temporary binary to exist: %v", err)
}
if candidate.UploadToken != info.UploadToken {
t.Fatalf("unexpected stored upload token: %s", candidate.UploadToken)
}
}
func TestUploadManualServerBinaryRejectsSameVersion(t *testing.T) {
originalVersion := common.Version
common.Version = "v0.5.0"
t.Cleanup(func() {
common.Version = originalVersion
resetServerUpgradeTestState(t)
})
fileName, content := fakeServerBinaryFixture("v0.5.0")
info, err := UploadManualServerBinary(context.Background(), fileName, bytes.NewReader(content))
if err != nil {
t.Fatalf("expected upload to succeed: %v", err)
}
if info.ReadyToUpgrade {
t.Fatal("expected same-version upload not to be upgradeable")
}
if info.UploadToken != "" {
t.Fatal("expected same-version upload not to issue a token")
}
manualServerBinaryState.Lock()
defer manualServerBinaryState.Unlock()
if manualServerBinaryState.candidate != nil {
t.Fatal("expected no pending manual upgrade candidate")
}
}
func TestConfirmManualServerUpgrade(t *testing.T) {
originalVersion := common.Version
originalExecutor := ServerBinaryUpgradeExecutorForTest()
originalDelay := ServerUpgradeDispatchDelayForTest()
common.Version = "v0.4.0"
called := make(chan string, 1)
SetServerBinaryUpgradeExecutorForTest(func(execPath string, tempPath string) error {
called <- tempPath
return nil
})
SetServerUpgradeDispatchDelayForTest(0)
t.Cleanup(func() {
common.Version = originalVersion
SetServerBinaryUpgradeExecutorForTest(originalExecutor)
SetServerUpgradeDispatchDelayForTest(originalDelay)
resetServerUpgradeTestState(t)
})
fileName, content := fakeServerBinaryFixture("v0.5.0")
info, err := UploadManualServerBinary(context.Background(), fileName, bytes.NewReader(content))
if err != nil {
t.Fatalf("expected upload to succeed: %v", err)
}
confirmed, err := ConfirmManualServerUpgrade(info.UploadToken)
if err != nil {
t.Fatalf("expected confirm to succeed: %v", err)
}
if confirmed.UploadToken != info.UploadToken {
t.Fatalf("unexpected confirmed upload token: %s", confirmed.UploadToken)
}
select {
case tempPath := <-called:
if tempPath == "" {
t.Fatal("expected upgrade executor to receive temp path")
}
case <-time.After(time.Second):
t.Fatal("expected manual upgrade executor to be called")
}
}