displayUpdateFields returned avail=false whenever the running build's display version could not be compared (the VV2.1.0 double-letter builds), so the panel swallowed a real update prompt and the native updater refused to run. displayUpdateFieldsFor now takes the current display/raw versions as inputs and, when the display comparison cannot judge, compares the latest tag against the raw base version with the same tolerant display parser — unparseable raw base yields false. (Not isNewerVersion: on this fork's uppercase-V tags it degrades to string inequality, which would phantom-report an update on form difference alone.) Parseable display versions keep the segment rule untouched. Regression tests cover the VV scenario in both raw forms.
102 lines
4.2 KiB
Go
102 lines
4.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")
|
|
}
|
|
}
|
|
|
|
// V2.2.1 ③: when the running build's display version is unparseable
|
|
// (the "VV2.1.0" double-letter builds), the display comparison cannot
|
|
// judge and the verdict must fall back to the raw version comparison
|
|
// instead of swallowing the update. A parseable display version keeps
|
|
// the segment rule — the raw string plays no part there.
|
|
func TestDisplayUpdateFieldsForFallback(t *testing.T) {
|
|
// Unparseable display "VV2.1.0", newer latest → available via raw.
|
|
if disp, avail := displayUpdateFieldsFor("V2.2.0", "VV2.1.0", "2.1.0"); disp != "V2.2.0" || !avail {
|
|
t.Fatalf("VV display, newer latest = %q, %v; want V2.2.0, true", disp, avail)
|
|
}
|
|
// Unparseable display, latest equal to the raw base → not
|
|
// available, in both raw forms (bare and V-prefixed).
|
|
if disp, avail := displayUpdateFieldsFor("V2.1.0", "VV2.1.0", "2.1.0"); disp != "V2.1.0" || avail {
|
|
t.Fatalf("VV display, same latest (bare raw) = %q, %v; want V2.1.0, false", disp, avail)
|
|
}
|
|
if _, avail := displayUpdateFieldsFor("V2.1.0", "VV2.1.0", "V2.1.0"); avail {
|
|
t.Fatal("VV display, same latest (V raw) must not report an update")
|
|
}
|
|
// Unparseable display AND unparseable raw base → no verdict, false.
|
|
if _, avail := displayUpdateFieldsFor("V2.2.0", "VV2.1.0", "3.7.0-agent.1"); avail {
|
|
t.Fatal("unparseable raw base must yield no update verdict")
|
|
}
|
|
// Parseable display: segment rule decides, raw base is ignored
|
|
// even when it disagrees.
|
|
if disp, avail := displayUpdateFieldsFor("V2.2.0", "V2.1.0", "9.9.9"); disp != "V2.2.0" || !avail {
|
|
t.Fatalf("parseable display, newer = %q, %v; want V2.2.0, true", disp, avail)
|
|
}
|
|
if _, avail := displayUpdateFieldsFor("V2.1.0", "V2.1.0", "1.0.0"); avail {
|
|
t.Fatal("parseable display, equal versions must not report an update (raw base must not leak in)")
|
|
}
|
|
// Unparseable latest tag still yields "" so the UI uses raw fields.
|
|
if disp, avail := displayUpdateFieldsFor("3.7.0-agent.1", "VV2.1.0", "2.1.0"); disp != "" || avail {
|
|
t.Fatalf("legacy tag = %q, %v; want empty, false", disp, avail)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|