Files
pulse/internal/api/connections_aggregator_test.go
T
2026-05-14 14:37:24 +01:00

860 lines
29 KiB
Go

package api
import (
"reflect"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/remoteconfig"
)
func ptrTime(t time.Time) *time.Time { return &t }
func ptrBool(v bool) *bool { return &v }
func healthEntry(lastSuccess *time.Time, errMessage, errCategory string, breakerState string) monitoring.InstanceHealth {
ps := monitoring.InstancePollStatus{LastSuccess: lastSuccess}
if errMessage != "" {
ps.LastError = &monitoring.ErrorDetail{
At: time.Now(),
Message: errMessage,
Category: errCategory,
}
}
return monitoring.InstanceHealth{
PollStatus: ps,
Breaker: monitoring.InstanceBreaker{State: breakerState},
}
}
func desiredAgentConfigFingerprint(t *testing.T, commandsEnabled *bool, settings map[string]interface{}) ConnectionFleetConfigFingerprint {
t.Helper()
desired := desiredAgentConfig(t, commandsEnabled, settings)
if desired.Fingerprint == nil {
t.Fatal("expected desired config fingerprint")
}
return *desired.Fingerprint
}
func desiredAgentConfig(t *testing.T, commandsEnabled *bool, settings map[string]interface{}) connectionAgentDesiredConfig {
t.Helper()
metadata, err := remoteconfig.BuildDesiredConfigMetadata(commandsEnabled, settings)
if err != nil {
t.Fatalf("BuildDesiredConfigMetadata: %v", err)
}
fingerprint := ConnectionFleetConfigFingerprint{
Version: metadata.Version,
Hash: metadata.Hash,
}
return connectionAgentDesiredConfig{
Fingerprint: &fingerprint,
CommandsEnabled: cloneBoolPtr(commandsEnabled),
}
}
func TestDeriveConnectionState_Paused(t *testing.T) {
state, reason, _, _ := deriveConnectionState(false, monitoring.InstanceHealth{}, time.Now())
if state != ConnectionStatePaused {
t.Fatalf("got %q, want %q", state, ConnectionStatePaused)
}
if reason == "" {
t.Fatal("expected non-empty reason for paused state")
}
}
func TestDeriveConnectionState_Pending(t *testing.T) {
state, _, lastSeen, lastError := deriveConnectionState(true, monitoring.InstanceHealth{}, time.Now())
if state != ConnectionStatePending {
t.Fatalf("got %q, want %q", state, ConnectionStatePending)
}
if lastSeen != nil || lastError != nil {
t.Fatal("expected nil lastSeen and lastError in pending state")
}
}
func TestDeriveConnectionState_Unauthorized(t *testing.T) {
now := time.Now()
h := healthEntry(ptrTime(now.Add(-30*time.Second)), "401 Unauthorized: token invalid", "auth", "closed")
state, reason, _, err := deriveConnectionState(true, h, now)
if state != ConnectionStateUnauthorized {
t.Fatalf("got %q, want %q", state, ConnectionStateUnauthorized)
}
if reason == "" {
t.Fatal("expected reason to include error message")
}
if err == nil || err.Message != "401 Unauthorized: token invalid" {
t.Fatalf("unexpected lastError: %+v", err)
}
}
func TestDeriveConnectionState_Unreachable(t *testing.T) {
now := time.Now()
h := healthEntry(ptrTime(now.Add(-30*time.Second)), "connection refused", "network", "open")
state, _, _, _ := deriveConnectionState(true, h, now)
if state != ConnectionStateUnreachable {
t.Fatalf("got %q, want %q", state, ConnectionStateUnreachable)
}
}
func TestDeriveConnectionState_Stale(t *testing.T) {
now := time.Now()
stale := now.Add(-5 * time.Minute)
h := healthEntry(ptrTime(stale), "", "", "closed")
state, reason, _, _ := deriveConnectionState(true, h, now)
if state != ConnectionStateStale {
t.Fatalf("got %q, want %q", state, ConnectionStateStale)
}
if reason == "" {
t.Fatal("expected reason to describe staleness")
}
}
func TestDeriveConnectionState_Active(t *testing.T) {
now := time.Now()
h := healthEntry(ptrTime(now.Add(-10*time.Second)), "", "", "closed")
state, _, _, _ := deriveConnectionState(true, h, now)
if state != ConnectionStateActive {
t.Fatalf("got %q, want %q", state, ConnectionStateActive)
}
}
func TestBuildConnections_SortsByTypeThenName(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
pveInstances: []config.PVEInstance{
{Name: "beta", Host: "https://b.lan:8006", MonitorVMs: true},
{Name: "alpha", Host: "https://a.lan:8006", MonitorVMs: true},
},
pbsInstances: []config.PBSInstance{
{Name: "backups", Host: "https://bkp.lan:8007"},
},
now: now,
}
got := buildConnections(in)
if len(got) != 3 {
t.Fatalf("expected 3 connections, got %d", len(got))
}
if got[0].Type != ConnectionTypePBS {
t.Fatalf("expected PBS first, got %q", got[0].Type)
}
if got[1].Name != "alpha" || got[2].Name != "beta" {
t.Fatalf("PVE order wrong: %q, %q", got[1].Name, got[2].Name)
}
}
func TestBuildConnections_PVEPausedRespectsDisabled(t *testing.T) {
in := aggregatorInputs{
pveInstances: []config.PVEInstance{{Name: "pve1", Host: "https://pve1.lan:8006", Disabled: true}},
now: time.Now(),
}
got := buildConnections(in)
if len(got) != 1 {
t.Fatalf("expected 1 connection, got %d", len(got))
}
if got[0].State != ConnectionStatePaused {
t.Fatalf("expected paused, got %q", got[0].State)
}
if got[0].Enabled {
t.Fatal("expected Enabled=false for Disabled PVE instance")
}
}
func TestBuildConnections_AgentStateFromLastSeen(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
hosts: []models.Host{
{ID: "fresh", Hostname: "h1", LastSeen: now.Add(-10 * time.Second)},
{ID: "stale", Hostname: "h2", LastSeen: now.Add(-5 * time.Minute)},
{ID: "never", Hostname: "h3"},
},
now: now,
}
got := buildConnections(in)
byID := map[string]Connection{}
for _, c := range got {
byID[c.ID] = c
}
if byID["agent:fresh"].State != ConnectionStateActive {
t.Fatalf("fresh agent: got %q, want active", byID["agent:fresh"].State)
}
if byID["agent:stale"].State != ConnectionStateStale {
t.Fatalf("stale agent: got %q, want stale", byID["agent:stale"].State)
}
if byID["agent:never"].State != ConnectionStatePending {
t.Fatalf("never-reported agent: got %q, want pending", byID["agent:never"].State)
}
for _, c := range got {
if c.Capabilities.SupportsPause || c.Capabilities.SupportsScope {
t.Fatalf("agents must not advertise pause/scope capabilities: %+v", c)
}
}
}
func TestBuildConnections_AvailabilityTargetStateFromProbeStatus(t *testing.T) {
checkedAt := time.Date(2026, 5, 6, 10, 0, 0, 0, time.UTC)
in := aggregatorInputs{
availabilityTargets: []config.AvailabilityTarget{
{
ID: "sensor-1",
Name: "Energy monitor",
Address: "192.0.2.10",
Protocol: config.AvailabilityProbeICMP,
Enabled: true,
FailureThreshold: 2,
},
},
availabilityStatuses: map[string]monitoring.AvailabilityProbeStatus{
"sensor-1": {
TargetID: "sensor-1",
Available: false,
LastChecked: checkedAt,
ConsecutiveFailures: 1,
LastError: "timeout",
},
},
now: checkedAt.Add(5 * time.Second),
}
got := buildConnections(in)
if len(got) != 1 {
t.Fatalf("expected 1 connection, got %d", len(got))
}
connection := got[0]
if connection.ID != "availability:sensor-1" {
t.Fatalf("ID = %q, want availability:sensor-1", connection.ID)
}
if connection.Type != ConnectionTypeAvailability {
t.Fatalf("Type = %q, want availability", connection.Type)
}
if connection.State != ConnectionStateUnreachable {
t.Fatalf("State = %q, want unreachable", connection.State)
}
if connection.LastError == nil || connection.LastError.Category != "availability" {
t.Fatalf("LastError = %+v, want availability category", connection.LastError)
}
if !reflect.DeepEqual(connection.Surfaces, []string{"availability"}) {
t.Fatalf("Surfaces = %+v, want [availability]", connection.Surfaces)
}
if !connection.Capabilities.SupportsPause || connection.Capabilities.SupportsScope || !connection.Capabilities.SupportsTest {
t.Fatalf("Capabilities = %+v, want pause/test without scope", connection.Capabilities)
}
if !reflect.DeepEqual(connection.HostAliases, []string{"192.0.2.10"}) {
t.Fatalf("HostAliases = %+v, want endpoint address", connection.HostAliases)
}
}
func TestBuildConnections_AgentHostAliasesIncludeReportedIdentityHints(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
hosts: []models.Host{
{
ID: "pi",
Hostname: "pi",
Platform: "slackware",
OSName: "Unraid",
OSVersion: "7.1.0",
KernelVersion: "6.12.0",
Architecture: "x86_64",
ReportIP: "192.168.0.2",
LastSeen: now,
AgentVersion: "6.0.0",
CommandsEnabled: true,
NetworkInterfaces: []models.HostNetworkInterface{
{Name: "eth0", Addresses: []string{"192.168.0.2/24", "fe80::1/64"}},
},
},
},
expectedAgentVersion: "6.0.1",
now: now,
}
got := buildConnections(in)
if len(got) != 1 {
t.Fatalf("expected 1 connection, got %d", len(got))
}
if !reflect.DeepEqual(got[0].HostAliases, []string{"pi", "192.168.0.2", "fe80::1"}) {
t.Fatalf("agent host aliases = %+v, want %+v", got[0].HostAliases, []string{"pi", "192.168.0.2", "fe80::1"})
}
if got[0].AgentIdentity == nil {
t.Fatal("expected agent identity metadata")
}
if !reflect.DeepEqual(
got[0].AgentIdentity,
&ConnectionAgentIdentity{
Hostname: "pi",
Platform: "linux",
HostProfile: "unraid",
OSName: "Unraid",
OSVersion: "7.1.0",
KernelVersion: "6.12.0",
Architecture: "x86_64",
ReportIP: "192.168.0.2",
CommandsEnabled: true,
},
) {
t.Fatalf("agent identity = %+v", got[0].AgentIdentity)
}
}
func TestBuildConnections_AgentHostProfileFromUnraidStorageFacts(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
hosts: []models.Host{
{
ID: "tower",
Hostname: "tower",
Platform: "linux",
Unraid: &models.HostUnraidStorage{ArrayStarted: true},
LastSeen: now,
AgentVersion: "6.12.10",
CommandsEnabled: false,
},
},
now: now,
}
got := buildConnections(in)
if len(got) != 1 {
t.Fatalf("expected 1 connection, got %d", len(got))
}
identity := got[0].AgentIdentity
if identity == nil {
t.Fatal("expected agent identity metadata")
}
if identity.HostProfile != "unraid" {
t.Fatalf("host profile = %q, want %q", identity.HostProfile, "unraid")
}
if identity.Platform != "linux" {
t.Fatalf("platform = %q, want %q", identity.Platform, "linux")
}
if identity.OSName != "" {
t.Fatalf("os name = %q, want empty when source identity is absent", identity.OSName)
}
}
func TestBuildConnections_AgentVersionUpdateAvailability(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
hosts: []models.Host{
{ID: "current", Hostname: "current", LastSeen: now, AgentVersion: "6.0.2"},
{ID: "outdated", Hostname: "outdated", LastSeen: now, AgentVersion: "6.0.0"},
{ID: "unknown", Hostname: "unknown", LastSeen: now},
},
expectedAgentVersion: "6.0.2",
now: now,
}
got := buildConnections(in)
byID := map[string]Connection{}
for _, connection := range got {
byID[connection.ID] = connection
}
if byID["agent:current"].AgentVersion != "6.0.2" {
t.Fatalf("current agent version = %q, want %q", byID["agent:current"].AgentVersion, "6.0.2")
}
if byID["agent:current"].ExpectedAgentVersion != "6.0.2" {
t.Fatalf(
"current expected agent version = %q, want %q",
byID["agent:current"].ExpectedAgentVersion,
"6.0.2",
)
}
if byID["agent:current"].AgentUpdateAvailable {
t.Fatal("current agent should not report an update available")
}
if byID["agent:outdated"].AgentVersion != "6.0.0" {
t.Fatalf(
"outdated agent version = %q, want %q",
byID["agent:outdated"].AgentVersion,
"6.0.0",
)
}
if !byID["agent:outdated"].AgentUpdateAvailable {
t.Fatal("outdated agent should report an update available")
}
if byID["agent:unknown"].AgentVersion != "" {
t.Fatalf("unknown agent version = %q, want empty", byID["agent:unknown"].AgentVersion)
}
if byID["agent:unknown"].AgentUpdateAvailable {
t.Fatal("agent without a reported version should not raise an update flag")
}
}
func TestBuildConnections_AgentFleetGovernance(t *testing.T) {
now := time.Now()
currentDesiredConfig := desiredAgentConfig(t, ptrBool(true), nil)
currentDesired := *currentDesiredConfig.Fingerprint
in := aggregatorInputs{
hosts: []models.Host{
{
ID: "current",
Hostname: "current",
LastSeen: now,
AgentVersion: "6.0.2",
CommandsEnabled: true,
},
{
ID: "outdated",
Hostname: "outdated",
LastSeen: now,
AgentVersion: "6.0.0",
},
{
ID: "pending",
Hostname: "pending",
},
},
agentDesiredConfigs: map[string]connectionAgentDesiredConfig{
"current": currentDesiredConfig,
},
expectedAgentVersion: "6.0.2",
now: now,
}
got := buildConnections(in)
byID := map[string]Connection{}
for _, connection := range got {
byID[connection.ID] = connection
}
current := byID["agent:current"].Fleet
if current.EnrollmentState != fleetStateEnrolled ||
current.LivenessState != fleetStateActive ||
current.VersionDrift != fleetStateCurrent ||
current.AdapterHealth != fleetStateHealthy ||
current.ConfigRollout != fleetStateReported ||
current.CredentialStatus != fleetStateVerified ||
current.UpdateStatus != fleetStateCurrent ||
current.RemoteControl != fleetStateEnabled {
t.Fatalf("current agent fleet governance = %+v", current)
}
if current.ConfigDrift == nil ||
current.ConfigDrift.Status != fleetStatePending ||
current.ConfigDrift.Desired == nil ||
current.ConfigDrift.Applied != nil ||
current.ConfigDrift.Desired.Version != connectionAgentConfigFingerprintVersion ||
current.ConfigDrift.Desired.Hash != currentDesired.Hash {
t.Fatalf("current agent config drift = %+v", current.ConfigDrift)
}
if current.Rollout == nil || current.Rollout.Status != fleetStatePending || current.Rollout.Stage != fleetRolloutStagePending {
t.Fatalf("current agent rollout = %+v", current.Rollout)
}
if current.CommandPolicy == nil ||
current.CommandPolicy.Status != fleetStateEnabled ||
current.CommandPolicy.Desired != fleetStateEnabled ||
current.CommandPolicy.Applied != fleetStateEnabled ||
current.CommandPolicy.Enforcement != fleetCommandPolicyInSync {
t.Fatalf("current agent command policy = %+v", current.CommandPolicy)
}
outdated := byID["agent:outdated"].Fleet
if outdated.VersionDrift != fleetStateBehind ||
outdated.UpdateStatus != fleetStateUpdateAvailable ||
outdated.RemoteControl != fleetStateDisabled {
t.Fatalf("outdated agent fleet governance = %+v", outdated)
}
if outdated.CommandPolicy == nil ||
outdated.CommandPolicy.Status != fleetStateDisabled ||
outdated.CommandPolicy.Desired != fleetStateUnknown ||
outdated.CommandPolicy.Applied != fleetStateDisabled ||
outdated.CommandPolicy.Enforcement != fleetCommandPolicyNotApplicable {
t.Fatalf("outdated agent command policy = %+v", outdated.CommandPolicy)
}
pending := byID["agent:pending"].Fleet
if pending.EnrollmentState != fleetStatePending ||
pending.LivenessState != string(ConnectionStatePending) ||
pending.AdapterHealth != fleetStateDegraded ||
pending.ConfigRollout != fleetStateUnknown ||
pending.CredentialStatus != fleetStateUnknown {
t.Fatalf("pending agent fleet governance = %+v", pending)
}
if pending.ConfigDrift == nil || pending.ConfigDrift.Status != fleetStateUnknown {
t.Fatalf("pending agent config drift = %+v", pending.ConfigDrift)
}
if pending.Rollout == nil || pending.Rollout.Status != fleetStatePending {
t.Fatalf("pending agent rollout = %+v", pending.Rollout)
}
if pending.CommandPolicy == nil ||
pending.CommandPolicy.Desired != fleetStateUnknown ||
pending.CommandPolicy.Applied != fleetStateUnknown ||
pending.CommandPolicy.Enforcement != fleetStatePending {
t.Fatalf("pending agent command policy = %+v", pending.CommandPolicy)
}
}
func TestBuildConnections_AgentWithoutManagedDesiredConfigDoesNotReportRolloutPending(t *testing.T) {
now := time.Now()
in := aggregatorInputs{
hosts: []models.Host{
{
ID: "default-agent",
Hostname: "default-agent",
LastSeen: now,
CommandsEnabled: false,
},
},
agentDesiredConfigs: map[string]connectionAgentDesiredConfig{
"default-agent": {},
},
now: now,
}
got := buildConnections(in)
if len(got) != 1 {
t.Fatalf("expected 1 connection, got %d", len(got))
}
fleet := got[0].Fleet
if fleet.ConfigDrift == nil ||
fleet.ConfigDrift.Status != fleetConfigDriftNotApplicable ||
fleet.ConfigDrift.Desired != nil ||
fleet.ConfigDrift.Applied != nil {
t.Fatalf("default agent config drift = %+v", fleet.ConfigDrift)
}
if fleet.Rollout == nil ||
fleet.Rollout.Status != fleetStateCurrent ||
fleet.Rollout.Stage != fleetRolloutStageApplied {
t.Fatalf("default agent rollout = %+v", fleet.Rollout)
}
}
func TestBuildConnections_AgentCommandPolicyDesiredAppliedConvergence(t *testing.T) {
now := time.Now()
desiredDisabled := desiredAgentConfig(t, ptrBool(false), nil)
desiredEnabled := desiredAgentConfig(t, ptrBool(true), nil)
in := aggregatorInputs{
hosts: []models.Host{
{
ID: "desired-disabled-applied-enabled",
Hostname: "desired-disabled-applied-enabled",
LastSeen: now,
CommandsEnabled: true,
},
{
ID: "desired-enabled-applied-disabled",
Hostname: "desired-enabled-applied-disabled",
LastSeen: now,
CommandsEnabled: false,
},
{
ID: "desired-disabled-unreported",
Hostname: "desired-disabled-unreported",
},
},
agentDesiredConfigs: map[string]connectionAgentDesiredConfig{
"desired-disabled-applied-enabled": desiredDisabled,
"desired-enabled-applied-disabled": desiredEnabled,
"desired-disabled-unreported": desiredDisabled,
},
now: now,
}
got := buildConnections(in)
byID := map[string]Connection{}
for _, connection := range got {
byID[connection.ID] = connection
}
disabledDesired := byID["agent:desired-disabled-applied-enabled"].Fleet
if disabledDesired.RemoteControl != fleetStateEnabled {
t.Fatalf("remote control for desired-disabled/applied-enabled = %q, want applied enabled", disabledDesired.RemoteControl)
}
if disabledDesired.CommandPolicy == nil ||
disabledDesired.CommandPolicy.Status != fleetStateEnabled ||
disabledDesired.CommandPolicy.Desired != fleetStateDisabled ||
disabledDesired.CommandPolicy.Applied != fleetStateEnabled ||
disabledDesired.CommandPolicy.Enforcement != fleetCommandPolicyDrifted {
t.Fatalf("desired-disabled/applied-enabled command policy = %+v", disabledDesired.CommandPolicy)
}
enabledDesired := byID["agent:desired-enabled-applied-disabled"].Fleet
if enabledDesired.RemoteControl != fleetStateDisabled {
t.Fatalf("remote control for desired-enabled/applied-disabled = %q, want applied disabled", enabledDesired.RemoteControl)
}
if enabledDesired.CommandPolicy == nil ||
enabledDesired.CommandPolicy.Status != fleetStateDisabled ||
enabledDesired.CommandPolicy.Desired != fleetStateEnabled ||
enabledDesired.CommandPolicy.Applied != fleetStateDisabled ||
enabledDesired.CommandPolicy.Enforcement != fleetCommandPolicyDrifted {
t.Fatalf("desired-enabled/applied-disabled command policy = %+v", enabledDesired.CommandPolicy)
}
unreported := byID["agent:desired-disabled-unreported"].Fleet
if unreported.RemoteControl != fleetStateUnknown {
t.Fatalf("remote control for unreported agent = %q, want unknown", unreported.RemoteControl)
}
if unreported.CommandPolicy == nil ||
unreported.CommandPolicy.Status != fleetStateUnknown ||
unreported.CommandPolicy.Desired != fleetStateDisabled ||
unreported.CommandPolicy.Applied != fleetStateUnknown ||
unreported.CommandPolicy.Enforcement != fleetStatePending {
t.Fatalf("unreported command policy = %+v", unreported.CommandPolicy)
}
}
func TestBuildConnections_AgentConfigDriftUsesCanonicalDesiredMetadataWithoutSelfComparing(t *testing.T) {
now := time.Now()
desiredConfig := desiredAgentConfig(t, ptrBool(true), map[string]interface{}{
"interval": "10s",
})
desired := *desiredConfig.Fingerprint
in := aggregatorInputs{
hosts: []models.Host{
{
ID: "agent-1",
Hostname: "agent-1",
LastSeen: now,
CommandsEnabled: false,
DiskExclude: []string{"/dev/loop*"},
},
},
agentDesiredConfigs: map[string]connectionAgentDesiredConfig{
"agent-1": desiredConfig,
},
now: now,
}
got := buildConnections(in)
if len(got) != 1 {
t.Fatalf("expected 1 connection, got %d", len(got))
}
drift := got[0].Fleet.ConfigDrift
if drift == nil {
t.Fatal("expected agent config drift metadata")
}
if drift.Status != fleetStatePending {
t.Fatalf("config drift status = %q, want pending", drift.Status)
}
if drift.Desired == nil || *drift.Desired != desired {
t.Fatalf("desired config drift fingerprint = %+v, want %+v", drift.Desired, desired)
}
if drift.Applied != nil {
t.Fatalf("applied config fingerprint should be absent until agent reports a comparable fingerprint, got %+v", drift.Applied)
}
selfCompared := connectionConfigFingerprint(connectionAgentConfigFingerprintVersion, map[string]any{
"commandsEnabled": false,
"diskExclude": []string{"/dev/loop*"},
})
if selfCompared == nil {
t.Fatal("expected local self-comparison fingerprint to be derivable")
}
if drift.Desired.Hash == selfCompared.Hash {
t.Fatalf("desired config hash reused report-field fingerprint %q", drift.Desired.Hash)
}
if got[0].Fleet.Rollout == nil || got[0].Fleet.Rollout.Status == fleetStateCurrent {
t.Fatalf("rollout should not claim current without an applied config comparison, got %+v", got[0].Fleet.Rollout)
}
}
func TestConnectionAgentDesiredConfigFingerprintsSkipsEmptyDefaultConfig(t *testing.T) {
monitor, err := monitoring.New(&config.Config{DataPath: t.TempDir()})
if err != nil {
t.Fatalf("monitoring.New: %v", err)
}
t.Cleanup(func() { monitor.Stop() })
hostID := "default-agent"
got := connectionAgentDesiredConfigFingerprints(monitor, []models.Host{{ID: hostID}})
desired, ok := got[hostID]
if !ok {
t.Fatalf("expected resolved desired config entry for %q, got %+v", hostID, got)
}
if desired.Fingerprint != nil {
t.Fatalf("empty default config should not create rollout fingerprint, got %+v", desired.Fingerprint)
}
if desired.CommandsEnabled != nil {
t.Fatalf("empty default config should not set command override, got %+v", desired.CommandsEnabled)
}
commandsEnabled := true
if err := monitor.UpdateHostAgentConfig(hostID, &commandsEnabled); err != nil {
t.Fatalf("UpdateHostAgentConfig: %v", err)
}
got = connectionAgentDesiredConfigFingerprints(monitor, []models.Host{{ID: hostID}})
desired = got[hostID]
if desired.Fingerprint == nil {
t.Fatalf("managed command override should create desired config fingerprint")
}
if desired.CommandsEnabled == nil || !*desired.CommandsEnabled {
t.Fatalf("managed command override = %+v, want true", desired.CommandsEnabled)
}
}
func TestConnectionFleetAgentConfigDriftComparesAppliedFingerprintsWhenAvailable(t *testing.T) {
now := time.Now()
conn := Connection{
Type: ConnectionTypeAgent,
State: ConnectionStateActive,
Enabled: true,
LastSeen: &now,
}
desired := &ConnectionFleetConfigFingerprint{Version: connectionAgentConfigFingerprintVersion, Hash: "sha256:desired"}
applied := &ConnectionFleetConfigFingerprint{Version: connectionAgentConfigFingerprintVersion, Hash: "sha256:applied"}
drifted := connectionFleetAgentConfigDriftForFingerprints(conn, desired, applied)
if drifted.Status != fleetConfigDriftDrifted ||
drifted.Desired != desired ||
drifted.Applied != applied ||
drifted.LastObservedAt == nil {
t.Fatalf("drifted config comparison = %+v", drifted)
}
matchingApplied := &ConnectionFleetConfigFingerprint{Version: desired.Version, Hash: desired.Hash}
current := connectionFleetAgentConfigDriftForFingerprints(conn, desired, matchingApplied)
if current.Status != fleetConfigDriftCurrent ||
current.Desired != desired ||
current.Applied != matchingApplied ||
current.LastObservedAt == nil {
t.Fatalf("current config comparison = %+v", current)
}
}
func TestBuildConnections_PlatformFleetGovernance(t *testing.T) {
now := time.Now()
lastSuccess := now.Add(-30 * time.Second)
in := aggregatorInputs{
pveInstances: []config.PVEInstance{
{Name: "healthy", Host: "https://healthy.lan:8006"},
{Name: "bad-token", Host: "https://bad-token.lan:8006"},
{Name: "paused", Host: "https://paused.lan:8006", Disabled: true},
},
instanceHealth: map[string]monitoring.InstanceHealth{
"pve::healthy": healthEntry(&lastSuccess, "", "", "closed"),
"pve::bad-token": healthEntry(&lastSuccess, "403 forbidden", "auth", "closed"),
},
now: now,
}
got := buildConnections(in)
byID := map[string]Connection{}
for _, connection := range got {
byID[connection.ID] = connection
}
healthy := byID["pve:healthy"].Fleet
if healthy.EnrollmentState != fleetStateConfigured ||
healthy.LivenessState != fleetStateActive ||
healthy.VersionDrift != fleetStateNotApplicable ||
healthy.AdapterHealth != fleetStateHealthy ||
healthy.ConfigRollout != fleetStateConfigured ||
healthy.CredentialStatus != fleetStateVerified ||
healthy.UpdateStatus != fleetStateNotApplicable ||
healthy.RemoteControl != fleetStateNotApplicable {
t.Fatalf("healthy platform fleet governance = %+v", healthy)
}
if healthy.ConfigDrift == nil ||
healthy.ConfigDrift.Status != fleetConfigDriftCurrent ||
healthy.ConfigDrift.Desired == nil ||
healthy.ConfigDrift.Applied == nil ||
healthy.ConfigDrift.Desired.Hash != healthy.ConfigDrift.Applied.Hash {
t.Fatalf("healthy platform config drift = %+v", healthy.ConfigDrift)
}
if healthy.Rollout == nil || healthy.Rollout.Status != fleetStateCurrent {
t.Fatalf("healthy platform rollout = %+v", healthy.Rollout)
}
if healthy.CredentialHealth == nil || healthy.CredentialHealth.Status != fleetStateVerified {
t.Fatalf("healthy platform credential health = %+v", healthy.CredentialHealth)
}
badToken := byID["pve:bad-token"].Fleet
if badToken.AdapterHealth != fleetStateBlocked ||
badToken.CredentialStatus != fleetStateInvalid ||
badToken.LivenessState != string(ConnectionStateUnauthorized) {
t.Fatalf("bad token fleet governance = %+v", badToken)
}
if badToken.CredentialHealth == nil ||
badToken.CredentialHealth.Status != fleetStateInvalid ||
badToken.CredentialHealth.LastFailedAt == nil {
t.Fatalf("bad token credential health = %+v", badToken.CredentialHealth)
}
if badToken.Rollout == nil || badToken.Rollout.Status != fleetRolloutBlocked {
t.Fatalf("bad token rollout = %+v", badToken.Rollout)
}
paused := byID["pve:paused"].Fleet
if paused.EnrollmentState != fleetStatePaused ||
paused.AdapterHealth != fleetStatePaused ||
paused.ConfigRollout != fleetStatePaused ||
paused.CredentialStatus != fleetStatePaused {
t.Fatalf("paused platform fleet governance = %+v", paused)
}
if paused.ConfigDrift == nil || paused.ConfigDrift.Status != fleetStatePaused {
t.Fatalf("paused config drift = %+v", paused.ConfigDrift)
}
if paused.Rollout == nil || paused.Rollout.Status != fleetStatePaused {
t.Fatalf("paused rollout = %+v", paused.Rollout)
}
}
func TestBuildConnections_VMwareAndTrueNASEnabledFlag(t *testing.T) {
in := aggregatorInputs{
vmwareInstances: []config.VMwareVCenterInstance{{
ID: "vc1", Name: "vc", Host: "vc.lan", Enabled: false,
MonitorVMs: true, MonitorHosts: true, MonitorDatastores: false,
}},
truenasInstances: []config.TrueNASInstance{{
ID: "tn1", Name: "tn", Host: "tn.lan", Enabled: true, UseHTTPS: true,
MonitorDatasets: true, MonitorPools: false, MonitorReplication: true,
}},
now: time.Now(),
}
got := buildConnections(in)
var vmw, tn Connection
for _, c := range got {
switch c.Type {
case ConnectionTypeVMware:
vmw = c
case ConnectionTypeTrueNAS:
tn = c
}
}
if vmw.State != ConnectionStatePaused || vmw.Enabled {
t.Fatalf("vmware with Enabled=false should be paused, got state=%q enabled=%v", vmw.State, vmw.Enabled)
}
if !vmw.Capabilities.SupportsScope {
t.Fatal("vmware connections must advertise scope capability")
}
if vmw.Scope["datastores"] || !vmw.Scope["vms"] || !vmw.Scope["hosts"] {
t.Fatalf("vmware scope map should reflect Monitor* fields, got %+v", vmw.Scope)
}
if tn.State != ConnectionStatePending {
t.Fatalf("truenas with no health yet should be pending, got %q", tn.State)
}
if !tn.Enabled {
t.Fatal("truenas with Enabled=true should surface enabled=true")
}
if !tn.Capabilities.SupportsScope {
t.Fatal("truenas connections must advertise scope capability")
}
if tn.Scope["pools"] || !tn.Scope["datasets"] || !tn.Scope["replication"] {
t.Fatalf("truenas scope map should reflect Monitor* fields, got %+v", tn.Scope)
}
}
func TestBuildConnections_UsesHealthLookup(t *testing.T) {
now := time.Now()
ls := now.Add(-15 * time.Second)
in := aggregatorInputs{
pveInstances: []config.PVEInstance{{Name: "pve1", Host: "https://pve1.lan:8006"}},
instanceHealth: map[string]monitoring.InstanceHealth{
"pve::pve1": healthEntry(&ls, "", "", "closed"),
},
now: now,
}
got := buildConnections(in)
if got[0].State != ConnectionStateActive {
t.Fatalf("expected active, got %q", got[0].State)
}
if got[0].LastSeen == nil || !got[0].LastSeen.Equal(ls) {
t.Fatalf("lastSeen not propagated: %+v", got[0].LastSeen)
}
}