Files
elmprodvpn/selective-vpn-api/app/transport_runtime_observability_test.go

396 lines
11 KiB
Go

package app
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestBuildTransportRuntimeObservabilityItemsAggregatesInterfaces(t *testing.T) {
now := time.Date(2026, time.March, 16, 12, 0, 0, 0, time.UTC)
ifaces := []TransportInterface{
{
ID: transportDefaultIfaceID,
Name: "Shared interface",
Mode: TransportInterfaceModeShared,
RuntimeIface: "tun-shared",
},
{
ID: "edge-a",
Name: "Edge A",
Mode: TransportInterfaceModeDedicated,
RuntimeIface: "tun-edge",
NetnsName: "svpn-edge-a",
RoutingTable: "agvpn_if_edge_a",
},
}
clients := []TransportClient{
{
ID: "sb-up",
Kind: TransportClientSingBox,
Enabled: true,
IfaceID: "edge-a",
Iface: "tun-edge0",
Status: TransportClientUp,
Health: TransportClientHealth{
LastCheck: "2026-03-16T11:59:00Z",
LatencyMS: 81,
},
},
{
ID: "dn-down",
Kind: TransportClientDNSTT,
Enabled: false,
IfaceID: "edge-a",
Status: TransportClientDown,
},
{
ID: "ph-shared",
Kind: TransportClientPhoenix,
Enabled: true,
IfaceID: transportDefaultIfaceID,
Iface: "tun-shared0",
Status: TransportClientDegraded,
Health: TransportClientHealth{
LastCheck: "2026-03-16T11:58:00Z",
LatencyMS: 320,
LastError: "upstream timeout",
},
},
}
plan := TransportPolicyCompilePlan{
Interfaces: []TransportPolicyCompileInterface{
{IfaceID: "edge-a", RuleCount: 3},
{IfaceID: transportDefaultIfaceID, RuleCount: 1},
},
}
egressByClient := map[string]EgressIdentity{
"sb-up": {
Scope: "transport:sb-up",
Source: "transport",
SourceID: "sb-up",
IP: "203.0.113.10",
CountryCode: "SG",
},
"ph-shared": {
Scope: "transport:ph-shared",
Source: "transport",
SourceID: "ph-shared",
IP: "198.51.100.44",
CountryCode: "NL",
},
}
items := buildTransportRuntimeObservabilityItems(
ifaces,
clients,
plan,
func(clientID string) EgressIdentity { return egressByClient[clientID] },
now,
)
if len(items) != 2 {
t.Fatalf("expected 2 items, got %d", len(items))
}
edge := items[0]
if edge.IfaceID != "edge-a" {
t.Fatalf("expected edge-a item first, got %q", edge.IfaceID)
}
if edge.ClientID != "sb-up" {
t.Fatalf("unexpected edge-a primary client: %#v", edge)
}
if edge.ActiveIface != "tun-edge0" || edge.RuntimeIface != "tun-edge" {
t.Fatalf("unexpected edge-a iface binding: %#v", edge)
}
if edge.Status != string(TransportClientUp) {
t.Fatalf("unexpected edge-a status: %#v", edge)
}
if edge.Counters.ClientCount != 2 || edge.Counters.UpCount != 1 || edge.Counters.DownCount != 1 || edge.Counters.RuleCount != 3 {
t.Fatalf("unexpected edge-a counters: %#v", edge.Counters)
}
if edge.Egress.IP != "203.0.113.10" || edge.Egress.CountryCode != "SG" {
t.Fatalf("unexpected edge-a egress: %#v", edge.Egress)
}
if len(edge.EngineCounts) != 2 || edge.EngineCounts[0].Kind != "dnstt" || edge.EngineCounts[1].Kind != "singbox" {
t.Fatalf("unexpected edge-a engine counts: %#v", edge.EngineCounts)
}
shared := items[1]
if shared.IfaceID != transportDefaultIfaceID {
t.Fatalf("expected shared item second, got %q", shared.IfaceID)
}
if shared.ClientID != "ph-shared" || shared.Status != string(TransportClientDegraded) {
t.Fatalf("unexpected shared snapshot: %#v", shared)
}
if shared.LatencyMS != 320 || shared.LastError != "upstream timeout" || shared.LastCheck != "2026-03-16T11:58:00Z" {
t.Fatalf("unexpected shared health snapshot: %#v", shared)
}
if shared.Counters.ClientCount != 1 || shared.Counters.DegradedCount != 1 || shared.Counters.RuleCount != 1 {
t.Fatalf("unexpected shared counters: %#v", shared.Counters)
}
}
func TestBuildTransportRuntimeObservabilityItemsKeepsActiveClientAndAggregateError(t *testing.T) {
now := time.Date(2026, time.March, 16, 12, 5, 0, 0, time.UTC)
ifaces := []TransportInterface{
{
ID: "edge-a",
Name: "Edge A",
Mode: TransportInterfaceModeDedicated,
RuntimeIface: "tun-edge",
},
}
clients := []TransportClient{
{
ID: "sb-up",
Kind: TransportClientSingBox,
Enabled: true,
IfaceID: "edge-a",
Iface: "tun-edge0",
Status: TransportClientUp,
Health: TransportClientHealth{
LastCheck: "2026-03-16T12:04:00Z",
LatencyMS: 55,
},
},
{
ID: "ph-bad",
Kind: TransportClientPhoenix,
Enabled: true,
IfaceID: "edge-a",
Status: TransportClientDegraded,
Health: TransportClientHealth{
LastCheck: "2026-03-16T12:04:30Z",
LastError: "probe failed",
},
},
}
items := buildTransportRuntimeObservabilityItems(
ifaces,
clients,
TransportPolicyCompilePlan{},
func(clientID string) EgressIdentity {
if clientID == "sb-up" {
return EgressIdentity{IP: "203.0.113.55", CountryCode: "DE"}
}
return EgressIdentity{}
},
now,
)
if len(items) != 1 {
t.Fatalf("expected 1 item, got %d", len(items))
}
item := items[0]
if item.ClientID != "sb-up" {
t.Fatalf("expected active client to stay sb-up, got %#v", item)
}
if item.Status != string(TransportClientDegraded) {
t.Fatalf("expected aggregate degraded status, got %#v", item)
}
if item.LatencyMS != 55 {
t.Fatalf("expected latency from active client, got %#v", item)
}
if item.LastError != "probe failed" {
t.Fatalf("expected aggregate last_error from degraded peer, got %#v", item)
}
if item.LastCheck != "2026-03-16T12:04:00Z" {
t.Fatalf("expected active client last_check to stay stable, got %#v", item)
}
}
func TestHandleTransportRuntimeObservability(t *testing.T) {
withTransportPolicyMutationTestPaths(t)
prevEgress := egressIdentitySWR
egressIdentitySWR = newEgressIdentityService(1)
t.Cleanup(func() {
egressIdentitySWR = prevEgress
})
if err := saveTransportClientsState(transportClientsState{
Version: transportStateVersion,
Items: []TransportClient{
{
ID: "sb-one",
Kind: TransportClientSingBox,
Enabled: true,
IfaceID: "edge-a",
Iface: "tun-edge0",
Status: TransportClientUp,
Health: TransportClientHealth{
LastCheck: "2026-03-16T12:09:00Z",
LatencyMS: 64,
},
},
},
}); err != nil {
t.Fatalf("save clients state: %v", err)
}
if err := saveTransportInterfacesState(transportInterfacesState{
Version: transportStateVersion,
Items: []TransportInterface{
{
ID: "edge-a",
Name: "Edge A",
Mode: TransportInterfaceModeDedicated,
RuntimeIface: "tun-edge",
RoutingTable: "agvpn_if_edge_a",
},
},
}); err != nil {
t.Fatalf("save interfaces state: %v", err)
}
if err := saveTransportPolicyState(TransportPolicyState{
Version: transportStateVersion,
Revision: 5,
UpdatedAt: "2026-03-16T12:00:00Z",
Intents: []TransportPolicyIntent{
{SelectorType: "domain", SelectorValue: "example.org", ClientID: "sb-one"},
{SelectorType: "cidr", SelectorValue: "10.0.0.0/24", ClientID: "sb-one"},
},
}); err != nil {
t.Fatalf("save policy state: %v", err)
}
if err := saveTransportPolicyCompilePlan(TransportPolicyCompilePlan{
GeneratedAt: "2026-03-16T12:00:00Z",
PolicyRevision: 5,
InterfaceCount: 1,
RuleCount: 2,
Interfaces: []TransportPolicyCompileInterface{
{IfaceID: "edge-a", RuleCount: 2, ClientIDs: []string{"sb-one"}},
},
}); err != nil {
t.Fatalf("save policy plan: %v", err)
}
egressIdentitySWR.mu.Lock()
egressIdentitySWR.entries["transport:sb-one"] = &egressIdentityEntry{
item: EgressIdentity{
Scope: "transport:sb-one",
Source: "transport",
SourceID: "sb-one",
IP: "198.51.100.8",
CountryCode: "NL",
},
}
egressIdentitySWR.mu.Unlock()
req := httptest.NewRequest(http.MethodGet, "/api/v1/transport/runtime/observability", nil)
rec := httptest.NewRecorder()
handleTransportRuntimeObservability(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("unexpected status: %d body=%s", rec.Code, rec.Body.String())
}
var resp TransportRuntimeObservabilityResponse
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
t.Fatalf("decode response: %v", err)
}
if !resp.OK || resp.Count < 2 || resp.Count != len(resp.Items) || resp.GeneratedAt == "" {
t.Fatalf("unexpected response envelope: %#v", resp)
}
var item TransportRuntimeObservabilityItem
found := false
for _, current := range resp.Items {
if current.IfaceID != "edge-a" {
continue
}
item = current
found = true
break
}
if !found {
t.Fatalf("edge-a snapshot not found: %#v", resp.Items)
}
if item.IfaceID != "edge-a" || item.ClientID != "sb-one" {
t.Fatalf("unexpected runtime item identity: %#v", item)
}
if item.ActiveIface != "tun-edge0" || item.Counters.RuleCount != 2 {
t.Fatalf("unexpected runtime item details: %#v", item)
}
if item.Egress.IP != "198.51.100.8" || item.Egress.CountryCode != "NL" {
t.Fatalf("unexpected runtime item egress: %#v", item.Egress)
}
}
func TestBuildTransportRuntimeObservabilityItemsAddsVirtualAdGuardRow(t *testing.T) {
now := time.Date(2026, time.March, 23, 19, 10, 0, 0, time.UTC)
ifaces := []TransportInterface{
{
ID: transportDefaultIfaceID,
Name: "Shared interface",
Mode: TransportInterfaceModeShared,
},
}
virtual := buildTransportPolicyAdGuardTargetFromObservation(
"active",
"CONNECTED",
"after connect: CONNECTED; raw: Connected to HELSINKI in TUN mode, running on tun0",
now,
)
clients := []TransportClient{
{
ID: "sb-one",
Kind: TransportClientSingBox,
Enabled: true,
IfaceID: transportDefaultIfaceID,
Status: TransportClientDown,
},
virtual,
}
plan := TransportPolicyCompilePlan{
Interfaces: []TransportPolicyCompileInterface{
{
IfaceID: transportDefaultIfaceID,
RuleCount: 2,
Rules: []TransportPolicyCompileRule{
{ClientID: "sb-one"},
{ClientID: "adguardvpn"},
},
},
},
}
items := buildTransportRuntimeObservabilityItems(
ifaces,
clients,
plan,
func(clientID string) EgressIdentity {
if clientID == "adguardvpn" {
return EgressIdentity{Scope: "adguardvpn", IP: "185.77.216.28", CountryCode: "EE"}
}
return EgressIdentity{}
},
now,
)
if len(items) != 2 {
t.Fatalf("expected 2 rows, got %d", len(items))
}
var found bool
for _, item := range items {
if item.IfaceID != "adguardvpn" {
continue
}
found = true
if item.ClientID != "adguardvpn" || item.Status != string(TransportClientUp) {
t.Fatalf("unexpected virtual row identity: %#v", item)
}
if item.RuntimeIface != "tun0" || item.ActiveIface != "tun0" {
t.Fatalf("unexpected virtual iface binding: %#v", item)
}
if item.Counters.ClientCount != 1 || item.Counters.RuleCount != 1 {
t.Fatalf("unexpected virtual counters: %#v", item.Counters)
}
if item.Egress.IP != "185.77.216.28" || item.Egress.CountryCode != "EE" {
t.Fatalf("unexpected virtual egress: %#v", item.Egress)
}
}
if !found {
t.Fatalf("virtual adguard row not found: %#v", items)
}
}