package service import ( "encoding/json" "testing" "github.com/mhsanaei/3x-ui/v3/internal/database/model" ) func streamWithNoiseItem(t *testing.T, item map[string]any) map[string]any { t.Helper() return map[string]any{ "network": "tcp", "finalmask": map[string]any{ "udp": []any{map[string]any{ "type": "noise", "settings": map[string]any{ "reset": "60", "noise": []any{item}, }, }}, }, } } func noiseItem(t *testing.T, stream map[string]any) map[string]any { t.Helper() finalmask, _ := stream["finalmask"].(map[string]any) udp, _ := finalmask["udp"].([]any) mask, _ := udp[0].(map[string]any) settings, _ := mask["settings"].(map[string]any) noise, _ := settings["noise"].([]any) item, _ := noise[0].(map[string]any) return item } // TestDropEmptyRandPacketsClearsEditorResidue is the regression for the mask // editor writing packet:[] alongside a rand. xray-core counts the empty array // as a packet and refuses the config, which keeps every inbound offline. func TestDropEmptyRandPacketsClearsEditorResidue(t *testing.T) { stream := streamWithNoiseItem(t, map[string]any{ "type": "array", "rand": "1-8192", "packet": []any{}, "delay": "5", }) if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 1 { t.Fatalf("cleared = %d, want 1", cleared) } item := noiseItem(t, stream) if _, present := item["packet"]; present { t.Fatalf("packet survived: %#v", item) } if item["rand"] != "1-8192" || item["delay"] != "5" { t.Fatalf("healing changed the mask: %#v", item) } } func TestDropEmptyRandPacketsLeavesRealPacketsAlone(t *testing.T) { tests := []struct { name string item map[string]any }{ {"packet without a rand", map[string]any{"type": "array", "packet": []any{1.0, 2.0}, "rand": 0.0}}, {"empty packet without a rand", map[string]any{"type": "array", "packet": []any{}}}, {"empty packet with a zero rand", map[string]any{"type": "array", "packet": []any{}, "rand": 0.0}}, {"empty packet with a zero range", map[string]any{"type": "array", "packet": []any{}, "rand": "0-0"}}, {"string packet", map[string]any{"type": "str", "packet": "ping", "rand": "1-10"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { stream := streamWithNoiseItem(t, tt.item) if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 0 { t.Fatalf("cleared = %d, want the item left alone", cleared) } if _, present := noiseItem(t, stream)["packet"]; !present { t.Fatal("packet was dropped") } }) } } // TestDropEmptyRandPacketsReachesNestedItems covers header-custom, whose items // sit two arrays deep and are subject to the same exclusive-kind rule. func TestDropEmptyRandPacketsReachesNestedItems(t *testing.T) { stream := map[string]any{ "finalmask": map[string]any{ "tcp": []any{map[string]any{ "type": "header-custom", "settings": map[string]any{ "clients": []any{[]any{map[string]any{"type": "array", "rand": 64.0, "packet": []any{}}}}, "servers": []any{[]any{map[string]any{"type": "array", "rand": 32.0, "packet": []any{}}}}, }, }}, }, } if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 2 { t.Fatalf("cleared = %d, want 2", cleared) } } func TestDropEmptyRandPacketsIgnoresMissingFinalMask(t *testing.T) { stream := map[string]any{"network": "tcp"} if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 0 { t.Fatalf("cleared = %d, want 0", cleared) } } // TestHealedConfigsCarryPanelHeals closes the loop on both heals at the // panel level: the generated config must carry the healed shape. Until the // lite peel this test also built the results in xray-core (and asserted the // unhealed rows were refused); with the library gone, the heals themselves // are what's pinned — the core still refuses the unhealed shapes when it // starts on the node, which is exactly why the heals exist. func TestHealedConfigsCarryPanelHeals(t *testing.T) { t.Run("hysteria v1 row", func(t *testing.T) { in := model.Inbound{ Protocol: model.Hysteria, Port: 36715, Listen: "127.0.0.1", Tag: "in-hysteria", Settings: `{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`, StreamSettings: `{"network":"hysteria","hysteriaSettings":{"version":1,"udpIdleTimeout":60}}`, } raw, err := json.Marshal(in.GenXrayInboundConfig()) if err != nil { t.Fatalf("marshal generated inbound: %v", err) } var healed map[string]any if err := json.Unmarshal(raw, &healed); err != nil { t.Fatalf("decode generated inbound: %v", err) } settings, _ := healed["settings"].(map[string]any) if settings["version"] != float64(2) { t.Fatalf("generated settings version = %v, want the heal to pin 2", settings["version"]) } stream, _ := healed["streamSettings"].(map[string]any) hysteriaSettings, _ := stream["hysteriaSettings"].(map[string]any) if hysteriaSettings["version"] != float64(2) { t.Fatalf("generated hysteriaSettings version = %v, want the heal to pin 2", hysteriaSettings["version"]) } }) t.Run("noise item with an empty packet", func(t *testing.T) { item := map[string]any{"type": "array", "rand": "1-8192", "packet": []any{}, "delay": "5"} stream := streamWithNoiseItem(t, item) // The unhealed item carries the rand+packet combination the core // refuses; the heal must remove the empty packet and keep the rest. before := noiseItem(t, stream) if _, present := before["packet"]; !present { t.Fatal("fixture lost its packet key before the heal") } if cleared := dropEmptyRandPackets(stream["finalmask"]); cleared != 1 { t.Fatalf("cleared = %d, want 1", cleared) } after := noiseItem(t, stream) if _, present := after["packet"]; present { t.Fatalf("packet survived the heal: %#v", after) } if after["rand"] != "1-8192" || after["delay"] != "5" { t.Fatalf("healing changed the mask: %#v", after) } }) }