168 lines
5.8 KiB
Go
168 lines
5.8 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|