package app import ( "os" "path/filepath" "strings" "testing" "time" ) func TestTransportNormalizeBootstrapTarget(t *testing.T) { cases := map[string]string{ "vless://user@example.com:443?type=tcp": "example.com", "example.com:8443": "example.com", "203.0.113.10:443": "203.0.113.10", "[2001:db8::1]:443": "2001:db8::1", "https://api.example.net/path?q=1": "api.example.net", } for in, want := range cases { got := transportNormalizeBootstrapTarget(in) if got != want { t.Fatalf("normalize mismatch for %q: got=%q want=%q", in, got, want) } } } func TestTransportCollectSingBoxConfigCandidates(t *testing.T) { tmp := t.TempDir() cfg := filepath.Join(tmp, "singbox.json") body := `{ "log": {"level":"info"}, "outbounds": [ {"type":"vless","server":"n3.elmprod.tech","server_port":40903}, {"type":"shadowsocks","server":"198.51.100.5","server_port":443} ] }` if err := os.WriteFile(cfg, []byte(body), 0o644); err != nil { t.Fatalf("write config: %v", err) } client := TransportClient{ ID: "sg-test", Kind: TransportClientSingBox, Config: map[string]any{ "config_path": cfg, }, } got := transportCollectSingBoxConfigCandidates(client) joined := strings.Join(got, ",") if !strings.Contains(joined, "n3.elmprod.tech") { t.Fatalf("missing server host in candidates: %#v", got) } if !strings.Contains(joined, "198.51.100.5") { t.Fatalf("missing server ip in candidates: %#v", got) } } func TestTransportSystemdActionAppliesSingBoxBootstrapBypass(t *testing.T) { origRunner := transportRunCommand origPath := transportBootstrapStatePath defer func() { transportRunCommand = origRunner transportBootstrapStatePath = origPath }() tmp := t.TempDir() transportBootstrapStatePath = filepath.Join(tmp, "bootstrap-routes.json") calls := make([]string, 0, 8) transportRunCommand = func(_ time.Duration, name string, args ...string) (string, string, int, error) { cmd := name + " " + strings.Join(args, " ") calls = append(calls, cmd) switch cmd { case "ip -4 route show table main default": return "default via 192.0.2.1 dev eth0\n", "", 0, nil case "ip -4 route replace 203.0.113.10/32 table agvpn via 192.0.2.1 dev eth0": return "", "", 0, nil case "systemctl start singbox@sg-test.service": return "", "", 0, nil default: return "", "", 0, nil } } client := TransportClient{ ID: "sg-test", Kind: TransportClientSingBox, Config: map[string]any{ "runner": "systemd", "unit": "singbox@.service", "bootstrap_host": "203.0.113.10", }, } res := transportSystemdBackend{}.Action(client, "start") if !res.OK { t.Fatalf("expected action success, got %#v", res) } gotCalls := strings.Join(calls, " | ") want := []string{ "ip -4 route show table main default", "ip -4 route replace 203.0.113.10/32 table agvpn via 192.0.2.1 dev eth0", "systemctl start singbox@sg-test.service", } for _, part := range want { if !strings.Contains(gotCalls, part) { t.Fatalf("expected call %q, got %s", part, gotCalls) } } data, err := os.ReadFile(transportBootstrapStatePath) if err != nil { t.Fatalf("read bootstrap state: %v", err) } text := string(data) if !strings.Contains(text, "203.0.113.10") || !strings.Contains(text, "sg-test") { t.Fatalf("unexpected bootstrap state: %s", text) } }