package service import ( "encoding/json" "testing" jsonutil "github.com/mhsanaei/3x-ui/v3/internal/util/json_util" "github.com/mhsanaei/3x-ui/v3/internal/xray" ) func testAgentCfg() *xray.Config { return &xray.Config{ OutboundConfigs: jsonutil.RawMessage(`[{"tag":"direct","protocol":"freedom"},{"tag":"blocked","protocol":"blackhole"}]`), RouterConfig: jsonutil.RawMessage(`{"rules":[ {"type":"field","inboundTag":["api"],"outboundTag":"api"}, {"type":"field","ip":["geoip:private"],"outboundTag":"blocked"} ]}`), } } func agentRulesOf(t *testing.T, cfg *xray.Config) []any { t.Helper() var routing map[string]any if err := json.Unmarshal(cfg.RouterConfig, &routing); err != nil { t.Fatalf("unmarshal routing: %v", err) } rules, _ := routing["rules"].([]any) return rules } func hasAPIRule(rules []any) bool { for _, r := range rules { m, ok := r.(map[string]any) if !ok { continue } for _, tag := range inboundTagsOf(m["inboundTag"]) { if tag == "api" { return true } } } return false } func TestApplyAgentEgressOverride(t *testing.T) { cfg := testAgentCfg() outbounds := `[{"tag":"PPFL3-L1","protocol":"socks","settings":{"servers":[{"address":"10.240.64.2","port":23183}]}}]` rules := `[{"type":"field","inboundTag":["in-18692-tcp"],"outboundTag":"PPFL3-L1"}]` if err := applyAgentEgress(cfg, outbounds, rules); err != nil { t.Fatalf("applyAgentEgress: %v", err) } var obs []any if err := json.Unmarshal(cfg.OutboundConfigs, &obs); err != nil { t.Fatalf("unmarshal outbounds: %v", err) } if len(obs) != 1 || obs[0].(map[string]any)["tag"] != "PPFL3-L1" { t.Fatalf("outbounds not replaced: %s", cfg.OutboundConfigs) } rs := agentRulesOf(t, cfg) if len(rs) != 2 { t.Fatalf("expected 2 rules (custom + api), got %d", len(rs)) } if !hasAPIRule(rs) { t.Fatal("api routing rule missing after override") } // The api rule must come first so it wins over user rules. first, _ := rs[0].(map[string]any) if first["outboundTag"] != "api" { t.Fatal("api rule should be prepended") } } func TestApplyAgentEgressKeepsTemplate(t *testing.T) { cfg := testAgentCfg() if err := applyAgentEgress(cfg, "", ""); err != nil { t.Fatalf("applyAgentEgress: %v", err) } var obs []any if err := json.Unmarshal(cfg.OutboundConfigs, &obs); err != nil { t.Fatalf("unmarshal outbounds: %v", err) } if len(obs) != 2 { t.Fatalf("template outbounds should be kept, got %d", len(obs)) } if !hasAPIRule(agentRulesOf(t, cfg)) { t.Fatal("api routing rule missing") } } func TestApplyAgentEgressRejectsBadJSON(t *testing.T) { if err := applyAgentEgress(testAgentCfg(), "not-json", "[]"); err == nil { t.Fatal("expected error for bad outbounds JSON") } if err := applyAgentEgress(testAgentCfg(), "[]", `{"not":"array"}`); err == nil { t.Fatal("expected error for non-array routing rules") } }