| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- package service
- import (
- "encoding/json"
- "os"
- "testing"
- xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
- "github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
- "github.com/mhsanaei/3x-ui/v3/internal/xray"
- "github.com/op/go-logging"
- )
- func TestMain(m *testing.M) {
- // injectPanelEgress logs when it skips injection; the package logger must
- // exist before any test exercises a skipped path.
- xuilogger.InitLogger(logging.ERROR)
- os.Exit(m.Run())
- }
- func TestEnsureAPIServices(t *testing.T) {
- // legacy template without RoutingService gets it injected
- out := ensureAPIServices(json_util.RawMessage(`{"services":["HandlerService","LoggerService","StatsService"],"tag":"api"}`))
- var parsed struct {
- Services []string `json:"services"`
- Tag string `json:"tag"`
- }
- if err := json.Unmarshal(out, &parsed); err != nil {
- t.Fatal(err)
- }
- want := map[string]bool{"HandlerService": true, "StatsService": true, "RoutingService": true, "LoggerService": true}
- if len(parsed.Services) != 4 {
- t.Fatalf("expected 4 services, got %v", parsed.Services)
- }
- for _, svc := range parsed.Services {
- if !want[svc] {
- t.Fatalf("unexpected service %q", svc)
- }
- }
- if parsed.Tag != "api" {
- t.Fatalf("tag must be preserved, got %q", parsed.Tag)
- }
- // complete api block is returned unchanged (no marshal churn)
- full := json_util.RawMessage(`{"services":["HandlerService","StatsService","RoutingService"],"tag":"api"}`)
- if got := ensureAPIServices(full); string(got) != string(full) {
- t.Fatalf("complete api block must pass through untouched, got %s", got)
- }
- // absent api block stays absent
- if got := ensureAPIServices(nil); got != nil {
- t.Fatalf("nil api block must stay nil, got %s", got)
- }
- }
- func egressTestConfig() *xray.Config {
- return &xray.Config{
- RouterConfig: json_util.RawMessage(`{"domainStrategy":"AsIs","rules":[{"type":"field","inboundTag":["api"],"outboundTag":"api"}]}`),
- InboundConfigs: []xray.InboundConfig{
- {Port: 62789, Protocol: "tunnel", Tag: "api", Listen: json_util.RawMessage(`"127.0.0.1"`)},
- },
- }
- }
- type egressRouting struct {
- DomainStrategy string `json:"domainStrategy"`
- Rules []struct {
- InboundTag []string `json:"inboundTag"`
- OutboundTag string `json:"outboundTag"`
- Type string `json:"type"`
- } `json:"rules"`
- }
- func TestInjectPanelEgress(t *testing.T) {
- cfg := egressTestConfig()
- injectPanelEgress(cfg, "warp")
- if len(cfg.InboundConfigs) != 2 {
- t.Fatalf("expected the egress inbound to be appended, got %d inbounds", len(cfg.InboundConfigs))
- }
- ib := cfg.InboundConfigs[1]
- if ib.Tag != PanelEgressInboundTag || ib.Protocol != "socks" || ib.Port != panelEgressBasePort {
- t.Fatalf("unexpected egress inbound: %+v", ib)
- }
- if string(ib.Listen) != `"127.0.0.1"` {
- t.Fatalf("egress inbound must listen on loopback, got %s", ib.Listen)
- }
- var routing egressRouting
- if err := json.Unmarshal(cfg.RouterConfig, &routing); err != nil {
- t.Fatal(err)
- }
- if routing.DomainStrategy != "AsIs" {
- t.Fatalf("routing keys outside rules must be preserved, got %+v", routing)
- }
- if len(routing.Rules) != 2 {
- t.Fatalf("expected egress rule + existing rule, got %+v", routing.Rules)
- }
- first := routing.Rules[0]
- if first.Type != "field" || first.OutboundTag != "warp" ||
- len(first.InboundTag) != 1 || first.InboundTag[0] != PanelEgressInboundTag {
- t.Fatalf("egress rule must be prepended, got %+v", first)
- }
- }
- func TestInjectPanelEgress_PortCollision(t *testing.T) {
- cfg := egressTestConfig()
- cfg.InboundConfigs = append(cfg.InboundConfigs,
- xray.InboundConfig{Port: panelEgressBasePort, Protocol: "vless", Tag: "in-1"},
- xray.InboundConfig{Port: panelEgressBasePort + 1, Protocol: "vless", Tag: "in-2"},
- )
- injectPanelEgress(cfg, "direct")
- got := cfg.InboundConfigs[len(cfg.InboundConfigs)-1]
- if got.Tag != PanelEgressInboundTag || got.Port != panelEgressBasePort+2 {
- t.Fatalf("egress inbound must skip taken ports, got %+v", got)
- }
- }
- func TestInjectPanelEgress_TagCollisionSkips(t *testing.T) {
- cfg := egressTestConfig()
- cfg.InboundConfigs = append(cfg.InboundConfigs,
- xray.InboundConfig{Port: 1234, Protocol: "socks", Tag: PanelEgressInboundTag},
- )
- before := string(cfg.RouterConfig)
- injectPanelEgress(cfg, "direct")
- if len(cfg.InboundConfigs) != 2 || string(cfg.RouterConfig) != before {
- t.Fatal("a user inbound owning the egress tag must make injection a no-op")
- }
- }
- func TestInjectPanelEgress_NoRoutingSection(t *testing.T) {
- cfg := egressTestConfig()
- cfg.RouterConfig = nil
- injectPanelEgress(cfg, "direct")
- var routing egressRouting
- if err := json.Unmarshal(cfg.RouterConfig, &routing); err != nil {
- t.Fatal(err)
- }
- if len(routing.Rules) != 1 || routing.Rules[0].OutboundTag != "direct" {
- t.Fatalf("a routing section must be created with the egress rule, got %+v", routing)
- }
- if len(cfg.InboundConfigs) != 2 {
- t.Fatal("egress inbound must still be appended")
- }
- }
- func TestInjectPanelEgress_BadRoutingSkips(t *testing.T) {
- cfg := egressTestConfig()
- cfg.RouterConfig = json_util.RawMessage(`{not json`)
- injectPanelEgress(cfg, "direct")
- if len(cfg.InboundConfigs) != 1 {
- t.Fatal("unparsable routing must skip the whole injection, inbound included")
- }
- if string(cfg.RouterConfig) != `{not json` {
- t.Fatal("unparsable routing must be left untouched")
- }
- }
|