package agentd import ( "encoding/json" "os" "path/filepath" "strings" "testing" agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1" ) // writeWarp 把 warp.json 写到 cfg 的 WarpConfigPath。 func writeWarp(t *testing.T, path, body string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(path, []byte(body), 0o600); err != nil { t.Fatal(err) } } const validWarp = `{ "enabled": true, "private_key": "aW52YWxpZC1rZXk=", "peer_public_key": "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=", "endpoint": "162.159.192.1:2408", "address": ["172.16.0.2/32", "2606:4700:110:8abc::/128"], "reserved": [1, 2, 3], "mtu": 1280, "domains": ["reddit.com", "redd.it"] }` // 无 warp.json → 配置里既无 endpoints 也无 route(向后兼容,与旧节点逐字节一致)。 func TestRender_NoWarp_NoRouteSection(t *testing.T) { sb := NewSingBox(testConfig(t), nil) sb.ApplyConfig(sampleSnapshot(&agentv1.Credential{DpUUID: "aaaa", Protocol: agentv1.ProtocolBoth}), true) data, err := sb.RenderConfig() if err != nil { t.Fatal(err) } var cfg map[string]any if err := json.Unmarshal(data, &cfg); err != nil { t.Fatal(err) } if _, ok := cfg["endpoints"]; ok { t.Error("no warp.json but endpoints present") } if _, ok := cfg["route"]; ok { t.Error("no warp.json but route present") } } // 有效 warp.json → 注入 WireGuard endpoint(tag=warp,userspace)+ 域名分流 route。 func TestRender_Warp_InjectsEndpointAndRoute(t *testing.T) { cfg := testConfig(t) writeWarp(t, cfg.WarpConfigPath, validWarp) sb := NewSingBox(cfg, nil) sb.ApplyConfig(sampleSnapshot(&agentv1.Credential{DpUUID: "aaaa", Protocol: agentv1.ProtocolBoth}), true) data, err := sb.RenderConfig() if err != nil { t.Fatal(err) } var m map[string]any if err := json.Unmarshal(data, &m); err != nil { t.Fatalf("rendered config invalid JSON: %v", err) } eps, ok := m["endpoints"].([]any) if !ok || len(eps) != 1 { t.Fatalf("want 1 endpoint, got %v", m["endpoints"]) } ep := eps[0].(map[string]any) if ep["type"] != "wireguard" || ep["tag"] != "warp" { t.Errorf("endpoint type/tag wrong: %v", ep) } if ep["system"] != false { t.Errorf("WARP endpoint must be userspace (system=false), got %v", ep["system"]) } peers := ep["peers"].([]any) peer := peers[0].(map[string]any) if peer["public_key"] != "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo=" { t.Errorf("peer public_key wrong: %v", peer["public_key"]) } if peer["address"] != "162.159.192.1" { t.Errorf("peer address wrong: %v", peer["address"]) } route := m["route"].(map[string]any) if route["final"] != "direct" { t.Errorf("route.final = %v, want direct", route["final"]) } rules := route["rules"].([]any) // 首条必须是 sniff(否则客户端发来的已解析 IP 无域名可匹配)。 if rules[0].(map[string]any)["action"] != "sniff" { t.Errorf("first route rule must be sniff, got %v", rules[0]) } last := rules[len(rules)-1].(map[string]any) if last["outbound"] != "warp" { t.Errorf("domain rule must route to warp, got %v", last) } if !strings.Contains(string(data), "reddit.com") { t.Error("configured domain reddit.com not in route") } } // enabled=false 或域名为空 → 视为未启用,不注入(坏配置宁可全直连)。 func TestRender_Warp_DisabledOrIncomplete(t *testing.T) { cases := map[string]string{ "disabled": strings.Replace(validWarp, `"enabled": true`, `"enabled": false`, 1), "no-domains": strings.Replace(validWarp, `["reddit.com", "redd.it"]`, `[]`, 1), "no-key": strings.Replace(validWarp, `"private_key": "aW52YWxpZC1rZXk=",`, `"private_key": "",`, 1), } for name, body := range cases { t.Run(name, func(t *testing.T) { cfg := testConfig(t) writeWarp(t, cfg.WarpConfigPath, body) sb := NewSingBox(cfg, nil) sb.ApplyConfig(sampleSnapshot(&agentv1.Credential{DpUUID: "aaaa", Protocol: agentv1.ProtocolBoth}), true) data, err := sb.RenderConfig() if err != nil { t.Fatal(err) } var m map[string]any _ = json.Unmarshal(data, &m) if _, ok := m["route"]; ok { t.Errorf("%s: route must be absent", name) } }) } } // 坏 JSON → 渲染不报错、按未启用处理(不产出无法启动的配置)。 func TestRender_Warp_BadJSONDegradesGracefully(t *testing.T) { cfg := testConfig(t) writeWarp(t, cfg.WarpConfigPath, `{ this is not json `) sb := NewSingBox(cfg, nil) sb.ApplyConfig(sampleSnapshot(&agentv1.Credential{DpUUID: "aaaa", Protocol: agentv1.ProtocolBoth}), true) data, err := sb.RenderConfig() if err != nil { t.Fatalf("bad warp.json must not fail render: %v", err) } var m map[string]any if err := json.Unmarshal(data, &m); err != nil { t.Fatal(err) } if _, ok := m["route"]; ok { t.Error("bad warp.json must degrade to no route") } }