Files
3x-ui-agent/internal/web/service/panel/panel_native_update_test.go
Muse 2596880327 feat(update): 第20条 主控面板一键自更新(Go 原生换名 + systemd 重启)
- PanelUpdateInfo 增 displayCurrentVersion/displayLatestVersion/
  displayUpdateAvailable:V 系按个位进位段比较,大于当前才算新版;
  原 currentVersion/latestVersion/updateAvailable 语义不动(节点页
  与传统节点继续用)
- 稳定渠道自更新改 Go 原生路径:查最新 tag→按架构下载发行资产→
  sha256 旁车校验→原子换名(旧二进制留 .bak)→写既有状态文件
  (与 update.sh 同形状,前端轮询协议不变)→脱离进程 systemctl
  restart 本单元(单元名 XUI_SERVICE_NAME 覆盖 / /proc/self/cgroup
  解析 / 回退 x-ui);dev 渠道保留 update.sh 原路径
- 仅 systemd 环境启用(/run/systemd/system 存在或
  XUI_SELFUPDATE_SYSTEMD=1 沙箱钩子),否则报错提示手动更新
- 单测:显示版本比较、cgroup 单元名解析、状态文件往返、systemd
  门拦截
2026-10-22 13:16:35 +00:00

66 lines
2.2 KiB
Go

package panel
import (
"strings"
"testing"
)
// Item 20: the stable channel compares V-series display versions with
// the segment rule; the native self-update is systemd-gated and records
// its outcome in the shared update status file.
func TestDisplayUpdateFields(t *testing.T) {
// The test binary runs the default display version V1.0.0.
if disp, avail := displayUpdateFields("V1.0.1"); disp != "V1.0.1" || !avail {
t.Fatalf("V1.0.1 vs V1.0.0 = %q, %v; want V1.0.1, true", disp, avail)
}
if disp, avail := displayUpdateFields("V1.0.0"); disp != "V1.0.0" || avail {
t.Fatalf("V1.0.0 vs V1.0.0 = %q, %v; want V1.0.0, false", disp, avail)
}
if disp, avail := displayUpdateFields("3.7.0-agent.1"); disp != "" || avail {
t.Fatalf("legacy tag = %q, %v; want empty, false", disp, avail)
}
if _, avail := displayUpdateFields("V1.0.9"); !avail {
t.Fatal("V1.0.9 must be newer than V1.0.0")
}
}
func TestServiceUnitFromCgroup(t *testing.T) {
content := "0::/system.slice/x-ui.service\n"
if got := serviceUnitFromCgroup(content); got != "x-ui" {
t.Fatalf("unit = %q, want x-ui", got)
}
if got := serviceUnitFromCgroup("0::/user.slice/session-3.scope\n"); got != "" {
t.Fatalf("unit = %q, want empty", got)
}
if got := serviceUnitFromCgroup(""); got != "" {
t.Fatalf("unit = %q, want empty", got)
}
}
func TestNativeUpdateStatusRoundtrip(t *testing.T) {
t.Setenv("XUI_DB_FOLDER", t.TempDir())
writeUpdateStatus(424242, updateStateSuccess, 0)
st := (&PanelService{}).GetUpdateStatus()
if st.RunID != "424242" || st.State != updateStateSuccess || st.ExitCode != 0 {
t.Fatalf("status roundtrip = %+v", st)
}
writeUpdateStatus(424243, updateStateFailed, 1)
st = (&PanelService{}).GetUpdateStatus()
if st.RunID != "424243" || st.State != updateStateFailed || st.ExitCode != 1 {
t.Fatalf("status roundtrip = %+v", st)
}
}
func TestStartUpdateStableRequiresSystemd(t *testing.T) {
// Force the gate closed regardless of the host running the test.
orig := nativeSystemdAvailable
nativeSystemdAvailable = func() bool { return false }
t.Cleanup(func() { nativeSystemdAvailable = orig })
_, err := (&PanelService{}).startNativeUpdate()
if err == nil || !strings.Contains(err.Error(), "systemd") {
t.Fatalf("want systemd gate error, got %v", err)
}
}