NodeService.Probe now checks for a bound agent first: agent-managed nodes synthesize the result from agent_nodes.last_seen (MAX over bound agents, ms) — fresh within the same 180s window the heartbeat job expires by = online with the heartbeat age as latency; stale or never-seen = offline with LatencyMs -1. No dial, and LastError is never written for agent nodes (the stored dial error from the placeholder address https://agent:2053 is also healed on next ⚡, since the synthesized patch carries an empty error through UpdateHeartbeat). Traditional-node dial path unchanged; verdict is a pure function with unit tests. Frontend: the ⚡ tooltip/menu used t('pages.nodes.probe'), which since the probe-metadata work resolves to the status-page SECTION object, so i18next fell back to rendering the raw key. The existing pages.nodes.testConnection key (present in all 13 locales) is the right label — point the three usages at it. No new i18n keys.
71 lines
2.4 KiB
Go
71 lines
2.4 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// V2.2.1 ②: the ⚡ probe for an agent-managed node is synthesized
|
|
// from the agent's heartbeat stamp instead of dialing the placeholder
|
|
// address. These tests pin the pure verdict: fresh stamp → online
|
|
// with the heartbeat age as latency; stale / never-seen → offline
|
|
// with LatencyMs -1; LastError empty in every case (a dial error must
|
|
// never be recorded for an agent node again).
|
|
|
|
func TestAgentHeartbeatVerdictFreshIsOnlineWithAgeLatency(t *testing.T) {
|
|
now := time.Unix(1_700_000_000, 0)
|
|
lastSeenMs := now.Add(-30 * time.Second).UnixMilli()
|
|
patch, online := agentHeartbeatVerdict(lastSeenMs, now)
|
|
if !online {
|
|
t.Fatal("30s-old heartbeat must read online")
|
|
}
|
|
if patch.LatencyMs != 30_000 {
|
|
t.Fatalf("LatencyMs = %d, want heartbeat age 30000", patch.LatencyMs)
|
|
}
|
|
if patch.LastHeartbeat != now.Unix() {
|
|
t.Fatalf("LastHeartbeat = %d, want now %d", patch.LastHeartbeat, now.Unix())
|
|
}
|
|
if patch.LastError != "" {
|
|
t.Fatalf("LastError = %q, want empty", patch.LastError)
|
|
}
|
|
}
|
|
|
|
func TestAgentHeartbeatVerdictWindowEdge(t *testing.T) {
|
|
now := time.Unix(1_700_000_000, 0)
|
|
if _, online := agentHeartbeatVerdict(now.Add(-agentOnlineWindow).UnixMilli(), now); !online {
|
|
t.Fatal("heartbeat exactly at the window edge must read online")
|
|
}
|
|
if _, online := agentHeartbeatVerdict(now.Add(-agentOnlineWindow-time.Second).UnixMilli(), now); online {
|
|
t.Fatal("heartbeat past the window must read offline")
|
|
}
|
|
}
|
|
|
|
func TestAgentHeartbeatVerdictStaleIsOffline(t *testing.T) {
|
|
now := time.Unix(1_700_000_000, 0)
|
|
lastSeenMs := now.Add(-10 * time.Minute).UnixMilli()
|
|
patch, online := agentHeartbeatVerdict(lastSeenMs, now)
|
|
if online {
|
|
t.Fatal("10min-old heartbeat must read offline")
|
|
}
|
|
if patch.LatencyMs != -1 {
|
|
t.Fatalf("LatencyMs = %d, want -1", patch.LatencyMs)
|
|
}
|
|
if patch.LastHeartbeat != lastSeenMs/1000 {
|
|
t.Fatalf("LastHeartbeat = %d, want the truthful last-seen %d", patch.LastHeartbeat, lastSeenMs/1000)
|
|
}
|
|
if patch.LastError != "" {
|
|
t.Fatalf("LastError = %q, want empty", patch.LastError)
|
|
}
|
|
}
|
|
|
|
func TestAgentHeartbeatVerdictNeverSeenIsOffline(t *testing.T) {
|
|
now := time.Unix(1_700_000_000, 0)
|
|
patch, online := agentHeartbeatVerdict(0, now)
|
|
if online {
|
|
t.Fatal("never-seen agent must read offline")
|
|
}
|
|
if patch.LatencyMs != -1 || patch.LastHeartbeat != 0 || patch.LastError != "" {
|
|
t.Fatalf("patch = %+v, want zero stamp, latency -1, no error", patch)
|
|
}
|
|
}
|