Files
l-ui/internal/selfupdate/agent_version_test.go
T
Muse e5d42224aa decouple agent version line from panel version
Agent display version now comes from the agent line's own source
(config.agentDisplayVersion injection + defaultAgentDisplayVersion
constant, currently A2.3.0) instead of being derived from the panel's
displayVersion. Masters resolve the agent line's latest from each
release's agent-version.txt marker (selfupdate.ResolveLatestAgentTarget,
legacy tag derivation as fallback for pre-marker releases): update
intents target the marker under the panel tag, panel-only releases
produce no intent, and the nodes page badges agent rows against
their own line via PanelUpdateInfo.displayLatestAgentVersion.

Windows agent frozen at A2.2.1: release discipline in docs/release.md
+ scripts/release-build.sh (agent assets reused verbatim when the
agent code is unchanged, marker always emitted, no Windows exe),
install-agent.ps1 pinned at V2.4.0, and the A2.2.1 protocol shapes
locked by TestAgentLegacyWindows221Contract.

Agent update tests now derive their targets from the live agent
line instead of a hardcoded A2.0.x the line has passed.
2026-10-23 21:47:10 +00:00

84 lines
2.5 KiB
Go

package selfupdate
import (
"context"
"net/http"
"net/http/httptest"
"testing"
)
// The agent-version.txt marker is the master's source of truth for the
// agent line since the version decoupling: a panel release whose agent
// assets are unchanged reuses carries a marker BELOW its own tag
// number, and that marker — never the tag — decides agent update
// availability.
func TestParseAgentVersionMarker(t *testing.T) {
cases := []struct {
body string
want string
ok bool
}{
{"A2.3.0\n", "A2.3.0", true},
{"2.4.0", "A2.4.0", true},
{" A2.10.3 \n", "A2.10.3", true},
{"\n\nA2.3.0\nextra lines ignored\n", "A2.3.0", true},
{"", "", false},
{"\n\n", "", false},
{"dev-latest", "", false},
{"A2.3", "", false},
{"A2.3.0.1", "", false},
}
for _, c := range cases {
got, ok := ParseAgentVersionMarker([]byte(c.body))
if ok != c.ok || got != c.want {
t.Fatalf("ParseAgentVersionMarker(%q) = %q, %v; want %q, %v", c.body, got, ok, c.want, c.ok)
}
}
}
// markerServer serves /<tag>/agent-version.txt from the given map;
// unknown tags 404 like a pre-decoupling release.
func markerServer(t *testing.T, markers map[string]string) *httptest.Server {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for tag, body := range markers {
if r.URL.Path == "/download/"+tag+"/"+AgentVersionMarkerAsset {
_, _ = w.Write([]byte(body))
return
}
}
http.NotFound(w, r)
}))
t.Cleanup(srv.Close)
t.Setenv(BaseURLEnv, srv.URL+"/download/")
return srv
}
func TestResolveLatestAgentTargetPrefersMarker(t *testing.T) {
markerServer(t, map[string]string{"V2.5.0": "A2.3.0\n"})
got, err := ResolveLatestAgentTarget(context.Background(), nil, "V2.5.0")
if err != nil {
t.Fatalf("ResolveLatestAgentTarget: %v", err)
}
// The whole point of the decoupling: panel tag V2.5.0, agent line
// still A2.3.0 — the answer must be the marker, not A2.5.0.
if got != "A2.3.0" {
t.Fatalf("target = %q, want A2.3.0 from the marker", got)
}
}
func TestResolveLatestAgentTargetFallsBackToTag(t *testing.T) {
markerServer(t, map[string]string{}) // no markers anywhere: pre-decoupling world
got, err := ResolveLatestAgentTarget(context.Background(), nil, "V2.4.0")
if err != nil {
t.Fatalf("ResolveLatestAgentTarget: %v", err)
}
if got != "A2.4.0" {
t.Fatalf("target = %q, want A2.4.0 derived from the tag", got)
}
if _, err := ResolveLatestAgentTarget(context.Background(), nil, "dev-latest"); err == nil {
t.Fatal("unparseable tag without a marker must be an error")
}
}