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.
84 lines
2.5 KiB
Go
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")
|
|
}
|
|
}
|