1
0

clash_info_node_test.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. package sub
  2. import (
  3. "fmt"
  4. "strings"
  5. "testing"
  6. "time"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  10. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  11. )
  12. func TestSubClash_InfoNode_Active(t *testing.T) {
  13. setupInfoNodeTestDB(t)
  14. db := database.GetDB()
  15. ib := &model.Inbound{
  16. Id: 1,
  17. UserId: 1,
  18. Remark: "Germany-VLESS",
  19. Enable: true,
  20. Port: 443,
  21. Protocol: model.VLESS,
  22. Settings: `{"clients":[{"id":"c1-uuid","email":"[email protected]","subId":"sub-clash","enable":true,"totalGB":10737418240}]}`,
  23. StreamSettings: `{"network":"tcp","security":"none"}`,
  24. }
  25. if err := db.Create(ib).Error; err != nil {
  26. t.Fatal(err)
  27. }
  28. rec := &model.ClientRecord{
  29. Id: 1,
  30. Email: "[email protected]",
  31. SubID: "sub-clash",
  32. UUID: "c1-uuid",
  33. Enable: true,
  34. TotalGB: 10737418240,
  35. }
  36. if err := db.Create(rec).Error; err != nil {
  37. t.Fatal(err)
  38. }
  39. if err := db.Create(&model.ClientInbound{InboundId: 1, ClientId: 1}).Error; err != nil {
  40. t.Fatal(err)
  41. }
  42. if err := db.Create(&xray.ClientTraffic{
  43. InboundId: 1,
  44. Email: "[email protected]",
  45. Up: 1073741824,
  46. Down: 1073741824,
  47. Total: 10737418240,
  48. Enable: true,
  49. }).Error; err != nil {
  50. t.Fatal(err)
  51. }
  52. sub := NewSubService("{{EMAIL}}|📊{{TRAFFIC_LEFT}}")
  53. sub.subInfoNodeEnable = true
  54. clash := NewSubClashService(false, "", sub)
  55. out, _, err := clash.GetClash("sub-clash", "sub.example.com")
  56. if err != nil {
  57. t.Fatalf("GetClash: %v", err)
  58. }
  59. if !strings.Contains(out, "type: socks5") || !strings.Contains(out, "server: 127.0.0.1") {
  60. t.Fatalf("expected socks5 dummy node in clash YAML, got:\n%s", out)
  61. }
  62. if !strings.Contains(out, "[email protected]|📊8.00GB") {
  63. t.Fatalf("expected expanded remark on dummy proxy, got:\n%s", out)
  64. }
  65. }
  66. func TestSubClash_InfoNode_Expired(t *testing.T) {
  67. setupInfoNodeTestDB(t)
  68. db := database.GetDB()
  69. expiredTime := time.Now().Add(-24 * time.Hour).UnixMilli()
  70. ib := &model.Inbound{
  71. Id: 1,
  72. UserId: 1,
  73. Remark: "Germany-VLESS",
  74. Enable: true,
  75. Port: 443,
  76. Protocol: model.VLESS,
  77. Settings: fmt.Sprintf(`{"clients":[{"id":"c1-uuid","email":"[email protected]","subId":"sub-clash-exp","enable":true,"expiryTime":%d}]}`, expiredTime),
  78. StreamSettings: `{"network":"tcp","security":"none"}`,
  79. }
  80. if err := db.Create(ib).Error; err != nil {
  81. t.Fatal(err)
  82. }
  83. rec := &model.ClientRecord{
  84. Id: 1,
  85. Email: "[email protected]",
  86. SubID: "sub-clash-exp",
  87. UUID: "c1-uuid",
  88. Enable: true,
  89. ExpiryTime: expiredTime,
  90. }
  91. if err := db.Create(rec).Error; err != nil {
  92. t.Fatal(err)
  93. }
  94. if err := db.Create(&model.ClientInbound{InboundId: 1, ClientId: 1}).Error; err != nil {
  95. t.Fatal(err)
  96. }
  97. if err := db.Create(&xray.ClientTraffic{
  98. InboundId: 1,
  99. Email: "[email protected]",
  100. ExpiryTime: expiredTime,
  101. Enable: true,
  102. }).Error; err != nil {
  103. t.Fatal(err)
  104. }
  105. sub := NewSubService("{{INBOUND}}")
  106. sub.subInfoNodeEnable = true
  107. sub.subExpiredTemplate = service.DefaultSubExpiredTemplate
  108. clash := NewSubClashService(false, "", sub)
  109. out, _, err := clash.GetClash("sub-clash-exp", "sub.example.com")
  110. if err != nil {
  111. t.Fatalf("GetClash: %v", err)
  112. }
  113. if !strings.Contains(out, "type: socks5") || !strings.Contains(out, "server: 127.0.0.1") {
  114. t.Fatalf("expected socks5 dummy node in clash YAML, got:\n%s", out)
  115. }
  116. if !strings.Contains(out, "Expired") || !strings.Contains(out, "[email protected]") {
  117. t.Fatalf("expected expired remark in clash YAML, got:\n%s", out)
  118. }
  119. if strings.Contains(out, "Germany-VLESS") {
  120. t.Fatalf("expired subscription must NOT contain working inbound, got:\n%s", out)
  121. }
  122. }
  123. func TestSubClash_InfoNode_Depleted(t *testing.T) {
  124. setupInfoNodeTestDB(t)
  125. db := database.GetDB()
  126. ib := &model.Inbound{
  127. Id: 1,
  128. UserId: 1,
  129. Remark: "Germany-VLESS",
  130. Enable: true,
  131. Port: 443,
  132. Protocol: model.VLESS,
  133. Settings: `{"clients":[{"id":"c1-uuid","email":"[email protected]","subId":"sub-clash-dep","enable":true,"totalGB":5368709120}]}`,
  134. StreamSettings: `{"network":"tcp","security":"none"}`,
  135. }
  136. if err := db.Create(ib).Error; err != nil {
  137. t.Fatal(err)
  138. }
  139. rec := &model.ClientRecord{
  140. Id: 1,
  141. Email: "[email protected]",
  142. SubID: "sub-clash-dep",
  143. UUID: "c1-uuid",
  144. Enable: true,
  145. TotalGB: 5368709120,
  146. }
  147. if err := db.Create(rec).Error; err != nil {
  148. t.Fatal(err)
  149. }
  150. if err := db.Create(&model.ClientInbound{InboundId: 1, ClientId: 1}).Error; err != nil {
  151. t.Fatal(err)
  152. }
  153. if err := db.Create(&xray.ClientTraffic{
  154. InboundId: 1,
  155. Email: "[email protected]",
  156. Up: 3221225472,
  157. Down: 2147483648,
  158. Total: 5368709120,
  159. Enable: true,
  160. }).Error; err != nil {
  161. t.Fatal(err)
  162. }
  163. sub := NewSubService("{{INBOUND}}")
  164. sub.subInfoNodeEnable = true
  165. sub.subTrafficDepletedTemplate = service.DefaultSubTrafficDepletedTemplate
  166. clash := NewSubClashService(false, "", sub)
  167. out, _, err := clash.GetClash("sub-clash-dep", "sub.example.com")
  168. if err != nil {
  169. t.Fatalf("GetClash: %v", err)
  170. }
  171. if !strings.Contains(out, "type: socks5") || !strings.Contains(out, "server: 127.0.0.1") {
  172. t.Fatalf("expected socks5 dummy node in clash YAML, got:\n%s", out)
  173. }
  174. if !strings.Contains(out, "Traffic Depleted") || !strings.Contains(out, "[email protected]") {
  175. t.Fatalf("expected depleted remark in clash YAML, got:\n%s", out)
  176. }
  177. if strings.Contains(out, "Germany-VLESS") {
  178. t.Fatalf("depleted subscription must NOT contain working inbound, got:\n%s", out)
  179. }
  180. }
  181. func TestSubClash_InfoNode_ProxyGroupOrder_DoesNotDefaultToDummy(t *testing.T) {
  182. setupInfoNodeTestDB(t)
  183. db := database.GetDB()
  184. ib := &model.Inbound{
  185. Id: 1,
  186. UserId: 1,
  187. Remark: "Germany-VLESS",
  188. Enable: true,
  189. Port: 443,
  190. Protocol: model.VLESS,
  191. Settings: `{"clients":[{"id":"c1-uuid","email":"[email protected]","subId":"sub-clash","enable":true,"totalGB":10737418240}]}`,
  192. StreamSettings: `{"network":"tcp","security":"none"}`,
  193. }
  194. if err := db.Create(ib).Error; err != nil {
  195. t.Fatal(err)
  196. }
  197. if err := db.Create(&model.ClientRecord{Id: 1, Email: "[email protected]", SubID: "sub-clash", UUID: "c1-uuid", Enable: true}).Error; err != nil {
  198. t.Fatal(err)
  199. }
  200. if err := db.Create(&model.ClientInbound{InboundId: 1, ClientId: 1}).Error; err != nil {
  201. t.Fatal(err)
  202. }
  203. if err := db.Create(&xray.ClientTraffic{InboundId: 1, Email: "[email protected]", Enable: true}).Error; err != nil {
  204. t.Fatal(err)
  205. }
  206. sub := NewSubService("{{EMAIL}}|📊{{TRAFFIC_LEFT}}|{{STATUS}}")
  207. sub.subInfoNodeEnable = true
  208. clash := NewSubClashService(false, "", sub)
  209. out, _, err := clash.GetClash("sub-clash", "sub.example.com")
  210. if err != nil {
  211. t.Fatalf("GetClash: %v", err)
  212. }
  213. // Status token must be rendered as active
  214. if !strings.Contains(out, "active") {
  215. t.Fatalf("expected active status in clash dummy remark, got:\n%s", out)
  216. }
  217. // In proxy-groups, PROXY select group must NOT have the dummy node as first member
  218. // PROXY group proxies should begin with real proxy
  219. if strings.Contains(out, "[email protected]|active") && strings.Contains(out, "proxies:\n - [email protected]|active") {
  220. t.Fatalf("PROXY group must NOT contain dummy info node as member, got:\n%s", out)
  221. }
  222. if !strings.Contains(out, "proxies:\n - Germany-VLESS\n - DIRECT") {
  223. t.Fatalf("expected real proxy Germany-VLESS in PROXY group, got:\n%s", out)
  224. }
  225. }