platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
395
selective-vpn-api/app/transport_runtime_observability_test.go
Normal file
395
selective-vpn-api/app/transport_runtime_observability_test.go
Normal file
@@ -0,0 +1,395 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user