host_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. package service
  2. import (
  3. "testing"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. "github.com/mhsanaei/3x-ui/v3/internal/web/entity"
  7. )
  8. func mkHost(t *testing.T, svc *HostService, inboundId int, remark string, order int) *entity.HostGroup {
  9. t.Helper()
  10. created, err := svc.AddHostGroup(&entity.HostGroup{
  11. InboundIds: []int{inboundId},
  12. Remark: remark,
  13. SortOrder: order,
  14. Hosts: []string{remark + ".example.com"},
  15. Port: 8443,
  16. })
  17. if err != nil {
  18. t.Fatalf("AddHostGroup %s: %v", remark, err)
  19. }
  20. g, err := svc.GetHostGroup(created[0].GroupId)
  21. if err != nil {
  22. t.Fatalf("GetHostGroup %s: %v", remark, err)
  23. }
  24. return g
  25. }
  26. func TestAddHost_GetHostsByInbound(t *testing.T) {
  27. setupBulkDB(t)
  28. svc := &HostService{}
  29. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  30. h1 := mkHost(t, svc, ib.Id, "b", 2)
  31. h2 := mkHost(t, svc, ib.Id, "a", 1)
  32. got, err := svc.GetHostsByInbound(ib.Id)
  33. if err != nil {
  34. t.Fatalf("GetHostsByInbound: %v", err)
  35. }
  36. if len(got) != 2 {
  37. t.Fatalf("len = %d, want 2", len(got))
  38. }
  39. if got[0].GroupId != h2.GroupId || got[1].GroupId != h1.GroupId {
  40. t.Fatalf("order = [%s,%s], want [%s,%s] (sort_order asc)", got[0].GroupId, got[1].GroupId, h2.GroupId, h1.GroupId)
  41. }
  42. if got[0].Hosts[0] != "a.example.com:8443" {
  43. t.Fatalf("address not persisted: %q", got[0].Hosts[0])
  44. }
  45. }
  46. func TestAddHost_RejectsUnknownInbound(t *testing.T) {
  47. setupBulkDB(t)
  48. svc := &HostService{}
  49. if _, err := svc.AddHostGroup(&entity.HostGroup{InboundIds: []int{99999}, Remark: "x", Hosts: []string{"test.com"}}); err == nil {
  50. t.Fatalf("expected error adding host to unknown inbound")
  51. }
  52. }
  53. func TestReorderHosts(t *testing.T) {
  54. setupBulkDB(t)
  55. svc := &HostService{}
  56. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  57. h1 := mkHost(t, svc, ib.Id, "h1", 0)
  58. h2 := mkHost(t, svc, ib.Id, "h2", 0)
  59. h3 := mkHost(t, svc, ib.Id, "h3", 0)
  60. want := []string{h3.GroupId, h1.GroupId, h2.GroupId}
  61. if err := svc.ReorderHostGroups(want); err != nil {
  62. t.Fatalf("ReorderHostGroups: %v", err)
  63. }
  64. got, _ := svc.GetHostsByInbound(ib.Id)
  65. for i, g := range got {
  66. if g.GroupId != want[i] {
  67. t.Fatalf("position %d = %s, want %s", i, g.GroupId, want[i])
  68. }
  69. if g.SortOrder != i {
  70. t.Fatalf("host %s sort_order = %d, want %d", g.GroupId, g.SortOrder, i)
  71. }
  72. }
  73. }
  74. func TestSetHostEnableAndBulk(t *testing.T) {
  75. setupBulkDB(t)
  76. svc := &HostService{}
  77. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  78. h1 := mkHost(t, svc, ib.Id, "h1", 0)
  79. h2 := mkHost(t, svc, ib.Id, "h2", 1)
  80. if err := svc.SetHostGroupEnable(h1.GroupId, false); err != nil {
  81. t.Fatalf("SetHostGroupEnable: %v", err)
  82. }
  83. if g, _ := svc.GetHostGroup(h1.GroupId); g == nil || !g.IsDisabled {
  84. t.Fatalf("h1 should be disabled after SetHostGroupEnable(false)")
  85. }
  86. if err := svc.SetHostsGroupEnable([]string{h1.GroupId, h2.GroupId}, true); err != nil {
  87. t.Fatalf("SetHostsGroupEnable(true): %v", err)
  88. }
  89. for _, gid := range []string{h1.GroupId, h2.GroupId} {
  90. if g, _ := svc.GetHostGroup(gid); g == nil || g.IsDisabled {
  91. t.Fatalf("host %s should be enabled", gid)
  92. }
  93. }
  94. if err := svc.SetHostsGroupEnable([]string{h1.GroupId, h2.GroupId}, false); err != nil {
  95. t.Fatalf("SetHostsGroupEnable(false): %v", err)
  96. }
  97. for _, gid := range []string{h1.GroupId, h2.GroupId} {
  98. if g, _ := svc.GetHostGroup(gid); g == nil || !g.IsDisabled {
  99. t.Fatalf("host %s should be disabled", gid)
  100. }
  101. }
  102. }
  103. func TestDeleteHosts(t *testing.T) {
  104. setupBulkDB(t)
  105. svc := &HostService{}
  106. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  107. h1 := mkHost(t, svc, ib.Id, "h1", 0)
  108. h2 := mkHost(t, svc, ib.Id, "h2", 1)
  109. h3 := mkHost(t, svc, ib.Id, "h3", 2)
  110. if err := svc.DeleteHostsGroup([]string{h1.GroupId, h3.GroupId}); err != nil {
  111. t.Fatalf("DeleteHostsGroup: %v", err)
  112. }
  113. got, _ := svc.GetHostsByInbound(ib.Id)
  114. if len(got) != 1 || got[0].GroupId != h2.GroupId {
  115. t.Fatalf("remaining = %v, want only h2 (%s)", got, h2.GroupId)
  116. }
  117. }
  118. func TestDeleteInboundCascadesHosts(t *testing.T) {
  119. setupBulkDB(t)
  120. svc := &HostService{}
  121. inboundSvc := &InboundService{}
  122. ib := &model.Inbound{Tag: "casc", Enable: false, Port: 4443, Protocol: model.VLESS, Settings: `{"clients":[]}`}
  123. if err := database.GetDB().Create(ib).Error; err != nil {
  124. t.Fatalf("create inbound: %v", err)
  125. }
  126. h1 := mkHost(t, svc, ib.Id, "h1", 0)
  127. if _, err := inboundSvc.DelInbound(ib.Id); err != nil {
  128. t.Fatalf("DelInbound: %v", err)
  129. }
  130. got, _ := svc.GetHostsByInbound(ib.Id)
  131. if len(got) != 0 {
  132. t.Fatalf("hosts not cascaded on inbound delete, len = %d", len(got))
  133. }
  134. if _, err := svc.GetHostGroup(h1.GroupId); err == nil {
  135. t.Fatalf("expected group to be deleted after cascading")
  136. }
  137. }
  138. func TestGetAllTags(t *testing.T) {
  139. setupBulkDB(t)
  140. svc := &HostService{}
  141. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  142. if _, err := svc.AddHostGroup(&entity.HostGroup{InboundIds: []int{ib.Id}, Remark: "h1", Hosts: []string{"h1.com"}, Tags: []string{"EU", "CDN"}}); err != nil {
  143. t.Fatalf("AddHostGroup: %v", err)
  144. }
  145. if _, err := svc.AddHostGroup(&entity.HostGroup{InboundIds: []int{ib.Id}, Remark: "h2", Hosts: []string{"h2.com"}, Tags: []string{"CDN", "FAST"}}); err != nil {
  146. t.Fatalf("AddHostGroup: %v", err)
  147. }
  148. tags, err := svc.GetAllTags()
  149. if err != nil {
  150. t.Fatalf("GetAllTags: %v", err)
  151. }
  152. want := []string{"CDN", "EU", "FAST"}
  153. if len(tags) != len(want) {
  154. t.Fatalf("tags = %v, want %v", tags, want)
  155. }
  156. for i := range want {
  157. if tags[i] != want[i] {
  158. t.Fatalf("tags = %v, want %v", tags, want)
  159. }
  160. }
  161. }
  162. func TestAddHostsGroup(t *testing.T) {
  163. setupBulkDB(t)
  164. svc := &HostService{}
  165. ib1 := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  166. ib2 := mkInbound(t, 80, model.VLESS, `{"clients":[]}`)
  167. req := &entity.HostGroup{
  168. InboundIds: []int{ib1.Id, ib2.Id},
  169. Hosts: []string{"h1.com", "h2.com:443", "[2001:db8::1]:80"},
  170. Remark: "BulkRemark",
  171. Port: 8443,
  172. Security: "same",
  173. }
  174. created, err := svc.AddHostGroup(req)
  175. if err != nil {
  176. t.Fatalf("AddHostGroup: %v", err)
  177. }
  178. if len(created) != 6 {
  179. t.Fatalf("expected 6 created hosts, got %d", len(created))
  180. }
  181. got1, _ := svc.GetHostsByInbound(ib1.Id)
  182. if len(got1) != 1 {
  183. t.Fatalf("expected 1 group for inbound 1, got %d", len(got1))
  184. }
  185. g := got1[0]
  186. if g.Remark != "BulkRemark" {
  187. t.Errorf("expected remark BulkRemark, got %s", g.Remark)
  188. }
  189. var foundH2Port443 bool
  190. var foundIPv6Port80 bool
  191. var foundH1DefaultPort8443 bool
  192. for _, hostStr := range g.Hosts {
  193. if hostStr == "h2.com:443" {
  194. foundH2Port443 = true
  195. }
  196. if hostStr == "[2001:db8::1]:80" {
  197. foundIPv6Port80 = true
  198. }
  199. if hostStr == "h1.com:8443" {
  200. foundH1DefaultPort8443 = true
  201. }
  202. }
  203. if !foundH2Port443 {
  204. t.Error("missing custom port override host h2.com:443")
  205. }
  206. if !foundIPv6Port80 {
  207. t.Error("missing IPv6 host with port override [2001:db8::1]:80")
  208. }
  209. if !foundH1DefaultPort8443 {
  210. t.Error("missing default port fallback host h1.com:8443")
  211. }
  212. }
  213. func TestParseHostAndPort_IPv6EdgeCases(t *testing.T) {
  214. tests := []struct {
  215. input string
  216. defaultPort int
  217. wantAddr string
  218. wantPort int
  219. }{
  220. {"2001:db8::1", 8443, "2001:db8::1", 8443},
  221. {"[2001:db8::1]:80", 8443, "2001:db8::1", 80},
  222. {"h1.com:443", 8443, "h1.com", 443},
  223. {"h1.com", 8443, "h1.com", 8443},
  224. }
  225. for _, tc := range tests {
  226. addr, port := parseHostAndPort(tc.input, tc.defaultPort)
  227. if addr != tc.wantAddr || port != tc.wantPort {
  228. t.Errorf("parseHostAndPort(%q, %d) = (%q, %d); want (%q, %d)",
  229. tc.input, tc.defaultPort, addr, port, tc.wantAddr, tc.wantPort)
  230. }
  231. }
  232. }
  233. func TestParseHostAndPort_AdversarialStressCases(t *testing.T) {
  234. tests := []struct {
  235. input string
  236. defaultPort int
  237. wantAddr string
  238. wantPort int
  239. }{
  240. {"", 8443, "", 8443},
  241. {" ", 8443, "", 8443},
  242. {"h1.com: ", 8443, "h1.com:", 8443},
  243. {"h1.com: -1", 8443, "h1.com: -1", 8443},
  244. {"h1.com:-1", 8443, "h1.com:-1", 8443},
  245. {"h1.com:0", 8443, "h1.com", 0},
  246. {"h1.com:65535", 8443, "h1.com", 65535},
  247. {"h1.com:65536", 8443, "h1.com:65536", 8443},
  248. {"h1.com:80a", 8443, "h1.com:80a", 8443},
  249. {"h1.com:123:456", 8443, "h1.com:123:456", 8443},
  250. {"[2001:db8::1]", 8443, "2001:db8::1", 8443},
  251. {"[2001:db8::1]:80", 8443, "2001:db8::1", 80},
  252. {"2001:db8::1", 8443, "2001:db8::1", 8443},
  253. {"[2001:db8::1]:65536", 8443, "[2001:db8::1]:65536", 8443},
  254. {"[]:80", 8443, "", 80},
  255. {"[:]::80", 8443, "[:]:", 80},
  256. {"h1.com:", 8443, "h1.com:", 8443},
  257. {"h1.com:123:", 8443, "h1.com:123:", 8443},
  258. {" h1.com : 80 ", 8443, "h1.com : 80", 8443},
  259. {" [2001:db8::1]:80 ", 8443, "2001:db8::1", 80},
  260. {"[2001:db8::1]:+80", 8443, "2001:db8::1", 80},
  261. {"[2001:db8::1]:080", 8443, "2001:db8::1", 80},
  262. {"[2001:db8::1]80", 8443, "[2001:db8::1]80", 8443},
  263. {"[::1]", 8443, "::1", 8443},
  264. {"[2001:db8::1", 8443, "[2001:db8:", 1},
  265. {"[2001:db8::1]:-80", 8443, "[2001:db8::1]:-80", 8443},
  266. {"h1.com:443:80", 8443, "h1.com:443:80", 8443},
  267. {"[2001:db8::1]::80", 8443, "[2001:db8::1]:", 80},
  268. }
  269. for _, tc := range tests {
  270. addr, port := parseHostAndPort(tc.input, tc.defaultPort)
  271. if addr != tc.wantAddr || port != tc.wantPort {
  272. t.Errorf("parseHostAndPort(%q, %d) = (%q, %d); want (%q, %d)",
  273. tc.input, tc.defaultPort, addr, port, tc.wantAddr, tc.wantPort)
  274. }
  275. }
  276. }
  277. func TestAddHostGroup_OptionalAddress(t *testing.T) {
  278. setupBulkDB(t)
  279. svc := &HostService{}
  280. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  281. created, err := svc.AddHostGroup(&entity.HostGroup{
  282. InboundIds: []int{ib.Id},
  283. Remark: "OptionalAddressHost",
  284. Hosts: nil,
  285. Port: 8443,
  286. })
  287. if err != nil {
  288. t.Fatalf("AddHostGroup with nil Hosts failed: %v", err)
  289. }
  290. if len(created) != 1 {
  291. t.Fatalf("expected 1 host created, got %d", len(created))
  292. }
  293. g, err := svc.GetHostGroup(created[0].GroupId)
  294. if err != nil {
  295. t.Fatalf("GetHostGroup failed: %v", err)
  296. }
  297. if len(g.Hosts) != 1 || g.Hosts[0] != ":8443" {
  298. t.Fatalf("expected Hosts list to contain default port fallback ':8443', got %v", g.Hosts)
  299. }
  300. }
  301. func TestUpdateHostGroup_ValidateBeforeDelete(t *testing.T) {
  302. setupBulkDB(t)
  303. svc := &HostService{}
  304. ib := mkInbound(t, 443, model.VLESS, `{"clients":[]}`)
  305. h1 := mkHost(t, svc, ib.Id, "h1", 0)
  306. req := &entity.HostGroup{
  307. InboundIds: []int{99999},
  308. Remark: "h1-updated",
  309. Hosts: []string{"h1.com"},
  310. }
  311. if _, err := svc.UpdateHostGroup(h1.GroupId, req); err == nil {
  312. t.Fatalf("expected error updating host group with invalid inbound")
  313. }
  314. got, err := svc.GetHostGroup(h1.GroupId)
  315. if err != nil {
  316. t.Fatalf("original host group should not be deleted: %v", err)
  317. }
  318. if got.Remark != "h1" {
  319. t.Fatalf("original host group remark changed: %s", got.Remark)
  320. }
  321. req.InboundIds = []int{ib.Id}
  322. if _, err := svc.UpdateHostGroup(h1.GroupId, req); err != nil {
  323. t.Fatalf("valid update failed: %v", err)
  324. }
  325. got2, _ := svc.GetHostGroup(h1.GroupId)
  326. if got2.Remark != "h1-updated" {
  327. t.Fatalf("remark not updated: %s", got2.Remark)
  328. }
  329. }