Files
pulse/internal/api/runtime_display_test.go
rcourtman 34194e57be Show the real monitoring cadence to non-admin sessions
Non-admin sessions cannot read GET /api/system/settings, so the Settings
General Monitoring Cadence card fell back to the Realtime (10s) preset
regardless of the configured interval; an issue #1601 reporter read that
as the server polling faster for non-admins. Publish the effective
pvePollingInterval on the authenticated runtime-display projection
(runtime config first, persisted value only as fallback, matching the
admin route's precedence), consume it in the viewer fallback of the
settings state, and run that initialization for sessions without
infrastructureRead too, whose ungated General panel previously never
initialized presentation state at all.
2026-08-12 09:20:01 +01:00

180 lines
5.6 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
)
func newRuntimeDisplayHandler(t *testing.T, cfg *config.Config, settings *config.SystemSettings) *SystemSettingsHandler {
t.Helper()
tempDir := t.TempDir()
if cfg == nil {
cfg = &config.Config{}
}
cfg.DataPath = tempDir
cfg.ConfigPath = tempDir
persistence := config.NewConfigPersistence(tempDir)
if settings != nil {
if err := persistence.SaveSystemSettings(*settings); err != nil {
t.Fatalf("save system settings: %v", err)
}
}
return newTestSystemSettingsHandler(cfg, persistence, &mockMonitor{}, func() {}, func() error { return nil })
}
func fetchRuntimeDisplay(t *testing.T, handler *SystemSettingsHandler) (RuntimeDisplayResponse, map[string]any) {
t.Helper()
req := httptest.NewRequest(http.MethodGet, "/api/runtime/display", nil)
rec := httptest.NewRecorder()
handler.HandleGetRuntimeDisplay(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("runtime display = %d, want 200 (%s)", rec.Code, rec.Body.String())
}
var typed RuntimeDisplayResponse
if err := json.Unmarshal(rec.Body.Bytes(), &typed); err != nil {
t.Fatalf("decode runtime display: %v", err)
}
var raw map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &raw); err != nil {
t.Fatalf("decode runtime display as object: %v", err)
}
return typed, raw
}
func TestHandleGetRuntimeDisplay_ProjectsPersistedPresentationValues(t *testing.T) {
settings := config.DefaultSystemSettings()
settings.Theme = "dark"
settings.FullWidthMode = true
settings.ReduceProUpsellNoise = true
handler := newRuntimeDisplayHandler(t, &config.Config{}, settings)
got, _ := fetchRuntimeDisplay(t, handler)
want := RuntimeDisplayResponse{
Theme: "dark",
FullWidthMode: true,
ReduceProUpsellNoise: true,
PVEPollingInterval: config.DefaultSystemSettings().PVEPollingInterval,
}
if got != want {
t.Fatalf("runtime display = %+v, want %+v", got, want)
}
}
// Assert on the serialized response so a future embedded settings struct cannot
// silently widen this session-tier contract.
func TestHandleGetRuntimeDisplay_PublishesOnlyPresentationFields(t *testing.T) {
settings := config.DefaultSystemSettings()
settings.Theme = "light"
settings.AllowedOrigins = "https://pulse.internal"
settings.PublicURL = "https://pulse.example.com"
handler := newRuntimeDisplayHandler(t, &config.Config{}, settings)
_, raw := fetchRuntimeDisplay(t, handler)
allowed := map[string]struct{}{
"theme": {},
"fullWidthMode": {},
"disableDockerUpdateActions": {},
"telemetryEnabled": {},
"reduceProUpsellNoise": {},
"pvePollingInterval": {},
}
for key := range raw {
if _, ok := allowed[key]; !ok {
t.Fatalf("runtime display published %q; the session-tier projection is a whitelist", key)
}
}
for key := range allowed {
if _, ok := raw[key]; !ok {
t.Fatalf("runtime display omitted %q", key)
}
}
}
func TestHandleGetRuntimeDisplay_UsesEffectiveDockerUpdateActionsSetting(t *testing.T) {
settings := config.DefaultSystemSettings()
settings.DisableDockerUpdateActions = false
handler := newRuntimeDisplayHandler(t, &config.Config{
DisableDockerUpdateActions: true,
EnvOverrides: map[string]bool{
"PULSE_DISABLE_DOCKER_UPDATE_ACTIONS": true,
},
}, settings)
got, _ := fetchRuntimeDisplay(t, handler)
if !got.DisableDockerUpdateActions {
t.Fatal("disableDockerUpdateActions = false, want the effective config override to win")
}
}
func TestHandleGetRuntimeDisplay_UsesEffectiveTelemetrySetting(t *testing.T) {
settings := config.DefaultSystemSettings()
enabled := true
settings.TelemetryEnabled = &enabled
handler := newRuntimeDisplayHandler(t, &config.Config{
TelemetryEnabled: false,
EnvOverrides: map[string]bool{
"PULSE_TELEMETRY": true,
},
}, settings)
got, _ := fetchRuntimeDisplay(t, handler)
if got.TelemetryEnabled {
t.Fatal("telemetryEnabled = true, want the effective config override to win")
}
}
// The user-visible failure behind issue #1601: a viewer's Monitoring Cadence
// card claimed Realtime (10s) while the server polled at the operator's slower
// configured interval. The runtime config is the effective value, so it must
// win over the persisted settings file.
func TestHandleGetRuntimeDisplay_UsesEffectivePVEPollingInterval(t *testing.T) {
settings := config.DefaultSystemSettings()
settings.PVEPollingInterval = 60
handler := newRuntimeDisplayHandler(t, &config.Config{
PVEPollingInterval: 30 * time.Second,
EnvOverrides: map[string]bool{
"PVE_POLLING_INTERVAL": true,
},
}, settings)
got, _ := fetchRuntimeDisplay(t, handler)
if got.PVEPollingInterval != 30 {
t.Fatalf("pvePollingInterval = %d, want the effective config value 30", got.PVEPollingInterval)
}
}
func TestHandleGetRuntimeDisplay_FallsBackToPersistedPVEPollingInterval(t *testing.T) {
settings := config.DefaultSystemSettings()
settings.PVEPollingInterval = 300
handler := newRuntimeDisplayHandler(t, &config.Config{}, settings)
got, _ := fetchRuntimeDisplay(t, handler)
if got.PVEPollingInterval != 300 {
t.Fatalf("pvePollingInterval = %d, want persisted fallback 300", got.PVEPollingInterval)
}
}
func TestHandleGetRuntimeDisplay_RejectsNonGET(t *testing.T) {
handler := newRuntimeDisplayHandler(t, &config.Config{}, config.DefaultSystemSettings())
req := httptest.NewRequest(http.MethodPost, "/api/runtime/display", nil)
rec := httptest.NewRecorder()
handler.HandleGetRuntimeDisplay(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Fatalf("POST /api/runtime/display = %d, want 405", rec.Code)
}
}