精简P2: 下线 infra/conf 交叉验证类测试: golden fixtures 改为面板侧契约(全语料可解析+inbound-full 经 GenXrayInboundConfig 生成后身份保持), 文件更名 golden_fixtures_test.go; XMC 完整性测试由与核心对拍改为面板谓词自身规则表(用户名正则/uuid 可解析/textures 非空); healed-config 测试改为断言面板 heal 结果(version 钉 2/空 packet 被清). 核心接受性今后由节点核心启动时把关

This commit is contained in:
Muse
2026-10-20 11:07:10 +00:00
parent 70d24d2f15
commit 284040e994
4 changed files with 208 additions and 413 deletions
@@ -3,8 +3,6 @@ package service
import (
"encoding/json"
"testing"
"github.com/xtls/xray-core/infra/conf"
)
const completeXmcProfile = `{"username":"Notch","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`
@@ -77,62 +75,54 @@ func TestValidateFinalMaskXmcProfiles(t *testing.T) {
}
}
// TestXmcMaskProfilesCompleteMatchesCoreValidation pins the panel's predicate
// to xray-core's own loader instead of a restatement of it: every profile the
// panel accepts must build, and every one it rejects must fail to build. A
// future core release that tightens or relaxes the rules fails here rather
// than silently producing configs the core refuses to start on.
func TestXmcMaskProfilesCompleteMatchesCoreValidation(t *testing.T) {
// TestXmcMaskProfilesCompleteExpectations pins the panel predicate's own
// contract. Until the lite peel it was cross-checked against xray-core's
// loader (every accepted profile had to build in the core and vice versa);
// with the library gone the predicate stands on its own rules — username
// ^[A-Za-z0-9_]{3,16}$, a parseable uuid, non-empty textures value and
// signature, at least one profile — and this table records them. A core
// release that tightens or relaxes its side is no longer detected here;
// the strip/validate pair errs on the panel's documented rules instead.
func TestXmcMaskProfilesCompleteExpectations(t *testing.T) {
profiles := []struct {
name string
raw string
want bool
}{
{name: "complete", raw: completeXmcProfile},
{name: "undashed uuid", raw: `{"username":"Notch","uuid":"069a79f444e94726a5befca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`},
{name: "username at the 16 char limit", raw: `{"username":"Abcdefghijklmnop","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`},
{name: "username over the limit", raw: `{"username":"Abcdefghijklmnopq","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`},
{name: "username with a hyphen", raw: `{"username":"No-tch","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`},
{name: "empty uuid", raw: `{"username":"Notch","uuid":"","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`},
{name: "missing textures value", raw: `{"username":"Notch","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"","texturesSignature":"c2ln"}`},
{name: "complete", raw: completeXmcProfile, want: true},
{name: "undashed uuid", raw: `{"username":"Notch","uuid":"069a79f444e94726a5befca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`, want: true},
{name: "username at the 16 char limit", raw: `{"username":"Abcdefghijklmnop","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`, want: true},
{name: "username over the limit", raw: `{"username":"Abcdefghijklmnopq","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`, want: false},
{name: "username with a hyphen", raw: `{"username":"No-tch","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`, want: false},
{name: "empty uuid", raw: `{"username":"Notch","uuid":"","texturesValue":"dmFsdWU=","texturesSignature":"c2ln"}`, want: false},
{name: "missing textures value", raw: `{"username":"Notch","uuid":"069a79f4-44e9-4726-a5be-fca90e38aaf5","texturesValue":"","texturesSignature":"c2ln"}`, want: false},
{name: "no profiles at all", raw: ``, want: false},
}
for _, tt := range profiles {
t.Run(tt.name, func(t *testing.T) {
var coreProfile conf.XMCProfile
if err := json.Unmarshal([]byte(tt.raw), &coreProfile); err != nil {
t.Fatalf("unmarshal into conf.XMCProfile: %v", err)
}
_, coreErr := coreProfile.Build()
mask := map[string]any{"type": "xmc"}
var settings map[string]any
if err := json.Unmarshal([]byte(`{"profiles":[`+tt.raw+`]}`), &settings); err != nil {
t.Fatalf("unmarshal settings: %v", err)
raw := tt.raw
if raw == "" {
raw = `{}` // a mask whose settings carry no profiles key at all
if err := json.Unmarshal([]byte(raw), &settings); err != nil {
t.Fatalf("unmarshal settings: %v", err)
}
} else {
if err := json.Unmarshal([]byte(`{"profiles":[`+raw+`]}`), &settings); err != nil {
t.Fatalf("unmarshal settings: %v", err)
}
}
mask["settings"] = settings
panelAccepts := xmcMaskProfilesComplete(mask)
coreAccepts := coreErr == nil
if panelAccepts != coreAccepts {
t.Errorf("xmcMaskProfilesComplete = %v, but conf.XMCProfile.Build() accepts = %v (err %v)", panelAccepts, coreAccepts, coreErr)
if got := xmcMaskProfilesComplete(mask); got != tt.want {
t.Errorf("xmcMaskProfilesComplete = %v, want %v", got, tt.want)
}
})
}
}
// TestXmcEmptyProfilesRejectedByCore covers the rule that lives on XMC rather
// than XMCProfile: v26.7.28 dropped the "default to Dream" fallback, so a mask
// with no profiles at all is now a build failure.
func TestXmcEmptyProfilesRejectedByCore(t *testing.T) {
var core conf.XMC
if err := json.Unmarshal([]byte(`{"hostname":"mc.example.com","password":"pw","profiles":[]}`), &core); err != nil {
t.Fatalf("unmarshal into conf.XMC: %v", err)
}
if _, err := core.Build(); err == nil {
t.Fatal("conf.XMC.Build() accepted an empty profiles list; the panel's strip/validate pair is no longer needed")
}
}
func TestStripIncompleteXmcMasks(t *testing.T) {
tests := []struct {
name string