hysteria_version_test.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package model
  2. import (
  3. "encoding/json"
  4. "strings"
  5. "testing"
  6. )
  7. func settingsVersion(t *testing.T, settings string) any {
  8. t.Helper()
  9. var parsed map[string]any
  10. if err := json.Unmarshal([]byte(settings), &parsed); err != nil {
  11. t.Fatalf("unmarshal settings: %v", err)
  12. }
  13. return parsed["version"]
  14. }
  15. func TestHealHysteriaVersion(t *testing.T) {
  16. tests := []struct {
  17. name string
  18. settings string
  19. wantChanged bool
  20. wantVersion any
  21. }{
  22. {
  23. name: "legacy v1 row",
  24. settings: `{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`,
  25. wantChanged: true,
  26. wantVersion: float64(2),
  27. },
  28. {
  29. name: "no version at all",
  30. settings: `{"clients":[{"auth":"tok","email":"a@x"}]}`,
  31. wantChanged: true,
  32. wantVersion: float64(2),
  33. },
  34. {
  35. name: "already v2",
  36. settings: `{"version":2,"clients":[]}`,
  37. wantChanged: false,
  38. wantVersion: float64(2),
  39. },
  40. {
  41. name: "version as a string",
  42. settings: `{"version":"1","clients":[]}`,
  43. wantChanged: true,
  44. wantVersion: float64(2),
  45. },
  46. }
  47. for _, tt := range tests {
  48. t.Run(tt.name, func(t *testing.T) {
  49. healed, changed := HealHysteriaVersion(tt.settings)
  50. if changed != tt.wantChanged {
  51. t.Fatalf("changed = %v, want %v", changed, tt.wantChanged)
  52. }
  53. if got := settingsVersion(t, healed); got != tt.wantVersion {
  54. t.Fatalf("version = %#v, want %#v", got, tt.wantVersion)
  55. }
  56. })
  57. }
  58. }
  59. func TestHealHysteriaVersionKeepsClients(t *testing.T) {
  60. healed, changed := HealHysteriaVersion(`{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`)
  61. if !changed {
  62. t.Fatal("a v1 row must be healed")
  63. }
  64. if !strings.Contains(healed, `"auth": "tok"`) || !strings.Contains(healed, `"email": "a@x"`) {
  65. t.Fatalf("healing dropped client data: %s", healed)
  66. }
  67. }
  68. func TestHealHysteriaVersionLeavesUnparsableSettings(t *testing.T) {
  69. const broken = `{"version":1,`
  70. healed, changed := HealHysteriaVersion(broken)
  71. if changed || healed != broken {
  72. t.Fatalf("unparsable settings must be left alone, got changed=%v %q", changed, healed)
  73. }
  74. if healed, changed := HealHysteriaVersion(""); changed || healed != "" {
  75. t.Fatalf("empty settings must be left alone, got changed=%v %q", changed, healed)
  76. }
  77. }
  78. func TestHealHysteriaStreamVersion(t *testing.T) {
  79. healed, changed := HealHysteriaStreamVersion(`{"network":"hysteria","hysteriaSettings":{"version":1,"udpIdleTimeout":60}}`)
  80. if !changed {
  81. t.Fatal("a v1 transport must be healed")
  82. }
  83. var parsed map[string]any
  84. if err := json.Unmarshal([]byte(healed), &parsed); err != nil {
  85. t.Fatalf("unmarshal: %v", err)
  86. }
  87. hysteria, _ := parsed["hysteriaSettings"].(map[string]any)
  88. if hysteria["version"] != float64(2) {
  89. t.Fatalf("version = %#v, want 2", hysteria["version"])
  90. }
  91. if hysteria["udpIdleTimeout"] != float64(60) {
  92. t.Fatalf("healing dropped transport settings: %#v", hysteria)
  93. }
  94. }
  95. func TestHealHysteriaStreamVersionWithoutHysteriaSettings(t *testing.T) {
  96. const stream = `{"network":"tcp","tcpSettings":{}}`
  97. healed, changed := HealHysteriaStreamVersion(stream)
  98. if changed || healed != stream {
  99. t.Fatalf("a stream without hysteriaSettings must be left alone, got changed=%v %q", changed, healed)
  100. }
  101. }
  102. // TestGenXrayInboundConfigHealsHysteriaVersion is the regression for a stored
  103. // v1 row: xray-core answers "version != 2" and rejects the whole config, so
  104. // every other inbound on the server stays offline until the row is fixed.
  105. func TestGenXrayInboundConfigHealsHysteriaVersion(t *testing.T) {
  106. in := Inbound{
  107. Protocol: Hysteria,
  108. Port: 36715,
  109. Listen: "127.0.0.1",
  110. Tag: "in-hysteria",
  111. Settings: `{"version":1,"clients":[{"auth":"tok","email":"a@x"}]}`,
  112. StreamSettings: `{"network":"hysteria","hysteriaSettings":{"version":1,"udpIdleTimeout":60}}`,
  113. }
  114. cfg := in.GenXrayInboundConfig()
  115. if got := settingsVersion(t, string(cfg.Settings)); got != float64(2) {
  116. t.Fatalf("generated settings.version = %#v, want 2", got)
  117. }
  118. var stream map[string]any
  119. if err := json.Unmarshal(cfg.StreamSettings, &stream); err != nil {
  120. t.Fatalf("unmarshal generated streamSettings: %v", err)
  121. }
  122. hysteria, _ := stream["hysteriaSettings"].(map[string]any)
  123. if hysteria["version"] != float64(2) {
  124. t.Fatalf("generated hysteriaSettings.version = %#v, want 2", hysteria["version"])
  125. }
  126. if !strings.Contains(in.Settings, `"version":1`) {
  127. t.Fatal("the stored row must keep its own value; only the generated config is healed")
  128. }
  129. }
  130. func TestGenXrayInboundConfigLeavesOtherProtocolsAlone(t *testing.T) {
  131. in := Inbound{
  132. Protocol: VLESS,
  133. Port: 443,
  134. Tag: "in-vless",
  135. Settings: `{"clients":[],"decryption":"none"}`,
  136. }
  137. if got := settingsVersion(t, string(in.GenXrayInboundConfig().Settings)); got != nil {
  138. t.Fatalf("a non-hysteria inbound must not gain a version key, got %#v", got)
  139. }
  140. }