test(pbs): protect node metrics permission outage recovery

Limited tokens intentionally omit node metrics, but later gateway failures must remain visible rather than being classified from permission text. Exercise restriction, outage and recovery on the same client to protect this monitoring boundary.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-05 14:47:04 +01:00
parent f5c6520b1c
commit 8a91c82dea
+52
View File
@@ -817,3 +817,55 @@ func TestClient_GetNodeName_ConcurrentTransientFailureIsSingleFlight(t *testing.
t.Fatalf("/nodes hit %d times after recovery and cache read, want 2", got)
}
}
// Restricted node metrics must not turn a later outage into a permission
// fallback, nor prevent recovery on the same long-lived polling client.
func TestClient_GetNodeStatus_PermissionOutageRecovery(t *testing.T) {
var responseCode atomic.Int32
responseCode.Store(http.StatusForbidden)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api2/json/nodes/localhost/status" {
t.Errorf("unexpected path: %s", r.URL.Path)
http.NotFound(w, r)
return
}
code := int(responseCode.Load())
w.WriteHeader(code)
if code != http.StatusOK {
// Deliberately misleading body: classification must use HTTP status.
_, _ = w.Write([]byte("permission denied: upstream authentication error"))
return
}
_, _ = w.Write([]byte(`{"data":{"cpu":0.25}}`))
}))
defer server.Close()
client, err := NewClient(ClientConfig{
Host: server.URL, TokenName: "monitor@pbs!pulse",
TokenValue: "test-only", Timeout: 2 * time.Second,
})
if err != nil {
t.Fatal(err)
}
status, err := client.GetNodeStatus(context.Background())
if err != nil || status != nil {
t.Fatalf("restricted metrics = (%+v, %v), want (nil, nil)", status, err)
}
for _, code := range []int{http.StatusServiceUnavailable, http.StatusBadGateway} {
responseCode.Store(int32(code))
status, err = client.GetNodeStatus(context.Background())
if status != nil || err == nil {
t.Fatalf("outage %d = (%+v, %v), want nil status and error", code, status, err)
}
if got, ok := pbsHTTPStatus(err); !ok || got != code {
t.Fatalf("outage status = (%d, %v), want %d", got, ok, code)
}
}
responseCode.Store(http.StatusOK)
status, err = client.GetNodeStatus(context.Background())
if err != nil || status == nil {
t.Fatalf("recovery = (%+v, %v), want metrics", status, err)
}
if status.CPU != 0.25 {
t.Fatalf("recovered CPU = %v, want 0.25", status.CPU)
}
}