212 lines
6.6 KiB
Go
212 lines
6.6 KiB
Go
package app
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRunTransportPolicyHealthCheckSkipsInactiveDraftClient(t *testing.T) {
|
|
now := time.Date(2026, time.March, 15, 12, 0, 0, 0, time.UTC)
|
|
clients := []TransportClient{
|
|
{
|
|
ID: "draft-a",
|
|
Kind: TransportClientSingBox,
|
|
Enabled: false,
|
|
Status: TransportClientDown,
|
|
},
|
|
}
|
|
plan := TransportPolicyCompilePlan{
|
|
Interfaces: []TransportPolicyCompileInterface{
|
|
{IfaceID: "edge-a", ClientIDs: []string{"draft-a"}},
|
|
},
|
|
}
|
|
|
|
res, updated, changed := runTransportPolicyHealthCheck(clients, plan, now)
|
|
if !res.OK {
|
|
t.Fatalf("expected skipped draft client to keep health-check green: %#v", res)
|
|
}
|
|
if res.CheckedCount != 0 || res.FailedCount != 0 {
|
|
t.Fatalf("unexpected counts: %#v", res)
|
|
}
|
|
if len(res.Items) != 1 || res.Items[0].Required {
|
|
t.Fatalf("expected one skipped item: %#v", res.Items)
|
|
}
|
|
if res.InterfaceCount != 1 || len(res.Interfaces) != 1 {
|
|
t.Fatalf("expected one interface summary: %#v", res)
|
|
}
|
|
if !res.Interfaces[0].OK || res.Interfaces[0].CheckedCount != 0 || res.Interfaces[0].SkippedCount != 1 {
|
|
t.Fatalf("unexpected interface summary: %#v", res.Interfaces[0])
|
|
}
|
|
if changed {
|
|
t.Fatalf("inactive draft client should not mutate health snapshot")
|
|
}
|
|
if updated[0].Health.LastCheck != "" {
|
|
t.Fatalf("unexpected health update for skipped client: %#v", updated[0])
|
|
}
|
|
}
|
|
|
|
func TestRunTransportPolicyHealthCheckFailsEnabledDownClient(t *testing.T) {
|
|
now := time.Date(2026, time.March, 15, 12, 0, 0, 0, time.UTC)
|
|
clients := []TransportClient{
|
|
{
|
|
ID: "edge-a-main",
|
|
Kind: TransportClientSingBox,
|
|
Enabled: true,
|
|
Status: TransportClientDown,
|
|
Config: map[string]any{
|
|
"runner": "mock",
|
|
},
|
|
},
|
|
}
|
|
plan := TransportPolicyCompilePlan{
|
|
Interfaces: []TransportPolicyCompileInterface{
|
|
{IfaceID: "edge-a", ClientIDs: []string{"edge-a-main"}},
|
|
},
|
|
}
|
|
|
|
res, updated, changed := runTransportPolicyHealthCheck(clients, plan, now)
|
|
if res.OK {
|
|
t.Fatalf("expected enabled down client to fail health-check: %#v", res)
|
|
}
|
|
if res.CheckedCount != 1 || res.FailedCount != 1 {
|
|
t.Fatalf("unexpected counts: %#v", res)
|
|
}
|
|
if len(res.Items) != 1 || res.Items[0].Code != "TRANSPORT_POLICY_HEALTH_DOWN" {
|
|
t.Fatalf("unexpected health-check item: %#v", res.Items)
|
|
}
|
|
if res.InterfaceCount != 1 || len(res.Interfaces) != 1 {
|
|
t.Fatalf("expected one interface summary: %#v", res)
|
|
}
|
|
if res.Interfaces[0].OK || res.Interfaces[0].CheckedCount != 1 || res.Interfaces[0].FailedCount != 1 {
|
|
t.Fatalf("unexpected interface summary: %#v", res.Interfaces[0])
|
|
}
|
|
if !changed {
|
|
t.Fatalf("expected health snapshot to be updated")
|
|
}
|
|
if updated[0].Health.LastCheck != now.Format(time.RFC3339) {
|
|
t.Fatalf("missing health timestamp update: %#v", updated[0])
|
|
}
|
|
}
|
|
|
|
func TestRunTransportPolicyHealthCheckFailsUnsupportedRuntime(t *testing.T) {
|
|
now := time.Date(2026, time.March, 15, 12, 0, 0, 0, time.UTC)
|
|
clients := []TransportClient{
|
|
{
|
|
ID: "edge-b-embedded",
|
|
Kind: TransportClientPhoenix,
|
|
Enabled: true,
|
|
Status: TransportClientUp,
|
|
Config: map[string]any{
|
|
"runtime_mode": "embedded",
|
|
},
|
|
},
|
|
}
|
|
plan := TransportPolicyCompilePlan{
|
|
Interfaces: []TransportPolicyCompileInterface{
|
|
{IfaceID: "edge-b", ClientIDs: []string{"edge-b-embedded"}},
|
|
},
|
|
}
|
|
|
|
res, updated, changed := runTransportPolicyHealthCheck(clients, plan, now)
|
|
if res.OK {
|
|
t.Fatalf("expected unsupported runtime to fail health-check: %#v", res)
|
|
}
|
|
if len(res.Items) != 1 || res.Items[0].Code != "TRANSPORT_BACKEND_RUNTIME_MODE_UNSUPPORTED" {
|
|
t.Fatalf("unexpected health-check item: %#v", res.Items)
|
|
}
|
|
if res.InterfaceCount != 1 || len(res.Interfaces) != 1 {
|
|
t.Fatalf("expected one interface summary: %#v", res)
|
|
}
|
|
if res.Interfaces[0].OK || res.Interfaces[0].CheckedCount != 1 || res.Interfaces[0].FailedCount != 1 {
|
|
t.Fatalf("unexpected interface summary: %#v", res.Interfaces[0])
|
|
}
|
|
if !changed {
|
|
t.Fatalf("expected unsupported runtime probe to update snapshot")
|
|
}
|
|
if updated[0].Status != TransportClientDegraded {
|
|
t.Fatalf("unexpected degraded status after failed probe: %#v", updated[0])
|
|
}
|
|
if updated[0].Health.LastError == "" {
|
|
t.Fatalf("expected last_error to be persisted after failed probe")
|
|
}
|
|
}
|
|
|
|
func TestRunTransportPolicyHealthCheckBuildsPerInterfaceSummary(t *testing.T) {
|
|
now := time.Date(2026, time.March, 15, 12, 0, 0, 0, time.UTC)
|
|
clients := []TransportClient{
|
|
{
|
|
ID: "edge-a-main",
|
|
Kind: TransportClientSingBox,
|
|
Enabled: true,
|
|
Status: TransportClientUp,
|
|
Config: map[string]any{
|
|
"runner": "mock",
|
|
},
|
|
},
|
|
{
|
|
ID: "edge-a-draft",
|
|
Kind: TransportClientSingBox,
|
|
Enabled: false,
|
|
Status: TransportClientDown,
|
|
},
|
|
{
|
|
ID: "edge-b-main",
|
|
Kind: TransportClientPhoenix,
|
|
Enabled: true,
|
|
Status: TransportClientUp,
|
|
Config: map[string]any{
|
|
"runtime_mode": "embedded",
|
|
},
|
|
},
|
|
}
|
|
plan := TransportPolicyCompilePlan{
|
|
Interfaces: []TransportPolicyCompileInterface{
|
|
{
|
|
IfaceID: "edge-a",
|
|
Mode: "dedicated",
|
|
RuntimeIface: "tun-a",
|
|
NetnsName: "svpn-edge-a",
|
|
RoutingTable: "agvpn_if_edge_a",
|
|
ClientIDs: []string{"edge-a-main", "edge-a-draft"},
|
|
},
|
|
{
|
|
IfaceID: "edge-b",
|
|
Mode: "dedicated",
|
|
RuntimeIface: "tun-b",
|
|
RoutingTable: "agvpn_if_edge_b",
|
|
ClientIDs: []string{"edge-b-main"},
|
|
},
|
|
},
|
|
}
|
|
|
|
res, _, _ := runTransportPolicyHealthCheck(clients, plan, now)
|
|
if res.InterfaceCount != 2 || len(res.Interfaces) != 2 {
|
|
t.Fatalf("expected two interface summaries: %#v", res)
|
|
}
|
|
|
|
ifaceA := res.Interfaces[0]
|
|
if ifaceA.IfaceID != "edge-a" || !ifaceA.OK || ifaceA.ClientCount != 2 || ifaceA.CheckedCount != 1 || ifaceA.SkippedCount != 1 {
|
|
t.Fatalf("unexpected edge-a summary: %#v", ifaceA)
|
|
}
|
|
if ifaceA.RuntimeIface != "tun-a" || ifaceA.NetnsName != "svpn-edge-a" || ifaceA.RoutingTable != "agvpn_if_edge_a" {
|
|
t.Fatalf("edge-a runtime metadata missing: %#v", ifaceA)
|
|
}
|
|
if ifaceA.ActiveClientID != "edge-a-main" || ifaceA.Status != string(TransportClientUp) {
|
|
t.Fatalf("edge-a runtime status fields mismatch: %#v", ifaceA)
|
|
}
|
|
|
|
ifaceB := res.Interfaces[1]
|
|
if ifaceB.IfaceID != "edge-b" || ifaceB.OK || ifaceB.ClientCount != 1 || ifaceB.CheckedCount != 1 || ifaceB.FailedCount != 1 {
|
|
t.Fatalf("unexpected edge-b summary: %#v", ifaceB)
|
|
}
|
|
if ifaceB.RuntimeIface != "tun-b" || ifaceB.RoutingTable != "agvpn_if_edge_b" {
|
|
t.Fatalf("edge-b runtime metadata missing: %#v", ifaceB)
|
|
}
|
|
if ifaceB.ActiveClientID != "edge-b-main" || ifaceB.Status != string(TransportClientDegraded) {
|
|
t.Fatalf("edge-b runtime status fields mismatch: %#v", ifaceB)
|
|
}
|
|
if ifaceB.LastError == "" {
|
|
t.Fatalf("edge-b expected last_error from degraded probe: %#v", ifaceB)
|
|
}
|
|
}
|