Files
3x-ui-agent/internal/database/model/hysteria_version_test.go
T
Sanaei f727d04f65
Release 3X-UI for Docker / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

152 lines
4.7 KiB
Go

package model
import (
"encoding/json"
"strings"
"testing"
)
func settingsVersion(t *testing.T, settings string) any {
t.Helper()
var parsed map[string]any
if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
t.Fatalf("unmarshal settings: %v", err)
}
return parsed["version"]
}
func TestHealHysteriaVersion(t *testing.T) {
tests := []struct {
name string
settings string
wantChanged bool
wantVersion any
}{
{
name: "legacy v1 row",
settings: `{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`,
wantChanged: true,
wantVersion: float64(2),
},
{
name: "no version at all",
settings: `{"clients":[{"auth":"tok","email":"a@x"}]}`,
wantChanged: true,
wantVersion: float64(2),
},
{
name: "already v2",
settings: `{"version":2,"clients":[]}`,
wantChanged: false,
wantVersion: float64(2),
},
{
name: "version as a string",
settings: `{"version":"1","clients":[]}`,
wantChanged: true,
wantVersion: float64(2),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
healed, changed := HealHysteriaVersion(tt.settings)
if changed != tt.wantChanged {
t.Fatalf("changed = %v, want %v", changed, tt.wantChanged)
}
if got := settingsVersion(t, healed); got != tt.wantVersion {
t.Fatalf("version = %#v, want %#v", got, tt.wantVersion)
}
})
}
}
func TestHealHysteriaVersionKeepsClients(t *testing.T) {
healed, changed := HealHysteriaVersion(`{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`)
if !changed {
t.Fatal("a v1 row must be healed")
}
if !strings.Contains(healed, `"auth": "tok"`) || !strings.Contains(healed, `"email": "a@x"`) {
t.Fatalf("healing dropped client data: %s", healed)
}
}
func TestHealHysteriaVersionLeavesUnparsableSettings(t *testing.T) {
const broken = `{"version":1,`
healed, changed := HealHysteriaVersion(broken)
if changed || healed != broken {
t.Fatalf("unparsable settings must be left alone, got changed=%v %q", changed, healed)
}
if healed, changed := HealHysteriaVersion(""); changed || healed != "" {
t.Fatalf("empty settings must be left alone, got changed=%v %q", changed, healed)
}
}
func TestHealHysteriaStreamVersion(t *testing.T) {
healed, changed := HealHysteriaStreamVersion(`{"network":"hysteria","hysteriaSettings":{"version":1,"udpIdleTimeout":60}}`)
if !changed {
t.Fatal("a v1 transport must be healed")
}
var parsed map[string]any
if err := json.Unmarshal([]byte(healed), &parsed); err != nil {
t.Fatalf("unmarshal: %v", err)
}
hysteria, _ := parsed["hysteriaSettings"].(map[string]any)
if hysteria["version"] != float64(2) {
t.Fatalf("version = %#v, want 2", hysteria["version"])
}
if hysteria["udpIdleTimeout"] != float64(60) {
t.Fatalf("healing dropped transport settings: %#v", hysteria)
}
}
func TestHealHysteriaStreamVersionWithoutHysteriaSettings(t *testing.T) {
const stream = `{"network":"tcp","tcpSettings":{}}`
healed, changed := HealHysteriaStreamVersion(stream)
if changed || healed != stream {
t.Fatalf("a stream without hysteriaSettings must be left alone, got changed=%v %q", changed, healed)
}
}
// TestGenXrayInboundConfigHealsHysteriaVersion is the regression for a stored
// v1 row: xray-core answers "version != 2" and rejects the whole config, so
// every other inbound on the server stays offline until the row is fixed.
func TestGenXrayInboundConfigHealsHysteriaVersion(t *testing.T) {
in := Inbound{
Protocol: 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}}`,
}
cfg := in.GenXrayInboundConfig()
if got := settingsVersion(t, string(cfg.Settings)); got != float64(2) {
t.Fatalf("generated settings.version = %#v, want 2", got)
}
var stream map[string]any
if err := json.Unmarshal(cfg.StreamSettings, &stream); err != nil {
t.Fatalf("unmarshal generated streamSettings: %v", err)
}
hysteria, _ := stream["hysteriaSettings"].(map[string]any)
if hysteria["version"] != float64(2) {
t.Fatalf("generated hysteriaSettings.version = %#v, want 2", hysteria["version"])
}
if !strings.Contains(in.Settings, `"version":1`) {
t.Fatal("the stored row must keep its own value; only the generated config is healed")
}
}
func TestGenXrayInboundConfigLeavesOtherProtocolsAlone(t *testing.T) {
in := Inbound{
Protocol: VLESS,
Port: 443,
Tag: "in-vless",
Settings: `{"clients":[],"decryption":"none"}`,
}
if got := settingsVersion(t, string(in.GenXrayInboundConfig().Settings)); got != nil {
t.Fatalf("a non-hysteria inbound must not gain a version key, got %#v", got)
}
}