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) } }