Files
l-ui/internal/web/service/probe_public_stack_test.go
T

99 lines
3.6 KiB
Go

package service
import (
"encoding/json"
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service/probe"
)
// Rider (B6): the anonymous public view must carry ipv4/ipv6 stack
// existence markers so the theme's IpStackBadges light up, without
// ever disclosing a real address. Existence = agent-reported address
// or master-observed (seen) source; pins do not count.
const (
stackReal4 = "203.0.113.77"
stackReal6 = "2001:db8:77::1"
stackSeen4 = "198.51.100.88"
stackSeen6 = "2001:db8:88::1"
stackPin4 = "192.0.2.99"
stackPin6 = "2001:db8:99::1"
)
func stackSource(agent model.AgentNode, meta probe.Meta) probeNodeSource {
return probeNodeSource{agent: agent, meta: meta, name: "stack-node"}
}
func TestProbeAnonViewStackMarkers(t *testing.T) {
// Agent-reported addresses light both markers.
src := stackSource(model.AgentNode{IPv4: stackReal4, IPv6: stackReal6}, probe.Meta{Public: true})
view := buildProbeNodeView(src, false)
if view["ipv4"] != "1" || view["ipv6"] != "1" {
t.Fatalf("reported stacks: ipv4=%v ipv6=%v, want both \"1\"", view["ipv4"], view["ipv6"])
}
// Seen-only (NAT node) lights the marker too.
src = stackSource(model.AgentNode{SeenIPv4: stackSeen4}, probe.Meta{Public: true})
view = buildProbeNodeView(src, false)
if view["ipv4"] != "1" {
t.Fatalf("seen-only v4: ipv4=%v, want \"1\"", view["ipv4"])
}
if view["ipv6"] != "" {
t.Fatalf("no v6 anywhere: ipv6=%v, want empty", view["ipv6"])
}
// No stack anywhere → both empty.
src = stackSource(model.AgentNode{}, probe.Meta{Public: true})
view = buildProbeNodeView(src, false)
if view["ipv4"] != "" || view["ipv6"] != "" {
t.Fatalf("no stacks: ipv4=%v ipv6=%v, want both empty", view["ipv4"], view["ipv6"])
}
// A pin alone is not a stack: markers stay empty.
src = stackSource(model.AgentNode{}, probe.Meta{Public: true, IPv4Pin: stackPin4, IPv6Pin: stackPin6})
view = buildProbeNodeView(src, false)
if view["ipv4"] != "" || view["ipv6"] != "" {
t.Fatalf("pin-only: ipv4=%v ipv6=%v, want both empty (pins do not count)", view["ipv4"], view["ipv6"])
}
}
func TestProbeAnonViewNeverLeaksAddresses(t *testing.T) {
src := stackSource(
model.AgentNode{IPv4: stackReal4, IPv6: stackReal6, SeenIPv4: stackSeen4, SeenIPv6: stackSeen6},
probe.Meta{Public: true, IPv4Pin: stackPin4, IPv6Pin: stackPin6},
)
view := buildProbeNodeView(src, false)
raw, err := json.Marshal(view)
if err != nil {
t.Fatalf("marshal anon view: %v", err)
}
for _, addr := range []string{stackReal4, stackReal6, stackSeen4, stackSeen6, stackPin4, stackPin6} {
if strings.Contains(string(raw), addr) {
t.Fatalf("anonymous view leaks address %s: %s", addr, raw)
}
}
// The admin-only address keys stay absent in the anonymous shape.
for _, key := range []string{"addresses", "ipv4_pin", "ipv6_pin", "ipv4_auto", "ipv6_auto", "hostname", "ip", "remark"} {
if _, ok := view[key]; ok {
t.Fatalf("anonymous view unexpectedly carries admin key %q", key)
}
}
}
func TestProbeFullViewAddressesUnchanged(t *testing.T) {
src := stackSource(
model.AgentNode{IPv4: stackReal4, IPv6: stackReal6, SeenIPv4: stackSeen4, SeenIPv6: stackSeen6},
probe.Meta{Public: true, IPv4Pin: stackPin4, IPv6Pin: stackPin6},
)
view := buildProbeNodeView(src, true)
if view["ipv4"] != stackReal4 || view["ipv6"] != stackReal6 {
t.Fatalf("full view addresses changed: ipv4=%v ipv6=%v", view["ipv4"], view["ipv6"])
}
if view["ipv4_pin"] != stackPin4 || view["ipv6_pin"] != stackPin6 {
t.Fatalf("full view pins changed: ipv4_pin=%v ipv6_pin=%v", view["ipv4_pin"], view["ipv6_pin"])
}
}