// SPDX-License-Identifier: MIT package netdetect import ( "context" "strings" "testing" "prx/internal/execx" ) func fake(handler func(name string, args []string) (string, error)) *execx.Fake { return &execx.Fake{Handler: func(name string, args []string, _ []byte) (execx.Result, error) { out, err := handler(name, args) if err != nil { return execx.Result{}, err } return execx.Result{Stdout: []byte(out)}, nil }} } func TestDetect(t *testing.T) { r := fake(func(name string, args []string) (string, error) { joined := strings.Join(args, " ") switch { case joined == "-j route show default": return `[{"dst":"default","gateway":"192.168.1.1","dev":"wlan0","metric":600}]`, nil case joined == "-6 -j route show default": return `[]`, nil case joined == "-j addr show dev wlan0": return `[{"ifname":"wlan0","addr_info":[ {"family":"inet","local":"192.168.1.100","prefixlen":24,"scope":"global"}, {"family":"inet6","local":"fe80::abcd","prefixlen":64,"scope":"link"}]}]`, nil } return "", nil }) wan, err := Detect(context.Background(), r, "", "", "") if err != nil { t.Fatalf("Detect: %v", err) } if wan.Interface != "wlan0" { t.Errorf("interface = %q", wan.Interface) } if wan.Gateway4 != "192.168.1.1" { t.Errorf("gateway4 = %q", wan.Gateway4) } if wan.Gateway6 != "" { t.Errorf("gateway6 = %q, want empty", wan.Gateway6) } if len(wan.Local4) != 1 || wan.Local4[0] != "192.168.1.100" { t.Errorf("local4 = %v", wan.Local4) } if len(wan.Local6) != 0 { t.Errorf("local6 = %v, want none (link-local excluded)", wan.Local6) } } func TestDetectPrefersLowestMetric(t *testing.T) { r := fake(func(_ string, args []string) (string, error) { joined := strings.Join(args, " ") if joined == "-j route show default" { return `[{"dst":"default","gateway":"10.0.0.1","dev":"eth9","metric":900}, {"dst":"default","gateway":"192.168.1.1","dev":"wlan0","metric":100}]`, nil } if strings.Contains(joined, "addr show dev wlan0") { return `[{"ifname":"wlan0","addr_info":[{"family":"inet","local":"192.168.1.100","prefixlen":24,"scope":"global"}]}]`, nil } return `[]`, nil }) wan, err := Detect(context.Background(), r, "", "", "") if err != nil { t.Fatalf("Detect: %v", err) } if wan.Interface != "wlan0" || wan.Gateway4 != "192.168.1.1" { t.Fatalf("picked %s/%s, want wlan0/192.168.1.1", wan.Interface, wan.Gateway4) } } func TestDetectOverride(t *testing.T) { r := fake(func(_ string, args []string) (string, error) { if strings.Join(args, " ") == "-j route show default" { return `[{"dst":"default","gateway":"192.168.1.1","dev":"wlan0","metric":100}]`, nil } if strings.Contains(strings.Join(args, " "), "addr show dev wlan0") { return `[{"ifname":"wlan0","addr_info":[{"family":"inet","local":"192.168.1.100","prefixlen":24,"scope":"global"}]}]`, nil } return `[]`, nil }) wan, err := Detect(context.Background(), r, "wlan0", "10.9.9.9", "") if err != nil { t.Fatalf("Detect: %v", err) } if wan.Gateway4 != "10.9.9.9" { t.Fatalf("gateway override not applied: %q", wan.Gateway4) } } func TestDetectNoDefaultRoute(t *testing.T) { r := fake(func(_ string, _ []string) (string, error) { return `[]`, nil }) if _, err := Detect(context.Background(), r, "", "", ""); err == nil { t.Fatal("expected error when no default route exists") } } func TestDetectIPv6OnlyUplink(t *testing.T) { r := fake(func(_ string, args []string) (string, error) { joined := strings.Join(args, " ") switch { case joined == "-j route show default": return `[]`, nil case joined == "-6 -j route show default": return `[{"dst":"default","gateway":"2001:db8::1","dev":"eth0","metric":100}]`, nil case joined == "-j addr show dev eth0": return `[{"ifname":"eth0","addr_info":[{"family":"inet6","local":"2001:db8::5","prefixlen":64,"scope":"global"}]}]`, nil } return `[]`, nil }) wan, err := Detect(context.Background(), r, "", "", "") if err != nil { t.Fatalf("Detect: %v", err) } if wan.Interface != "eth0" || wan.Gateway4 != "" || wan.Gateway6 != "2001:db8::1" { t.Fatalf("ipv6-only detection = %+v", wan) } }