host_test.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package controller
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "net/http"
  6. "net/http/httptest"
  7. "path/filepath"
  8. "testing"
  9. "github.com/gin-contrib/sessions"
  10. "github.com/gin-contrib/sessions/cookie"
  11. "github.com/gin-gonic/gin"
  12. "github.com/op/go-logging"
  13. "github.com/mhsanaei/3x-ui/v3/internal/database"
  14. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  15. xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
  16. "github.com/mhsanaei/3x-ui/v3/internal/web/entity"
  17. )
  18. func newHostTestDB(t *testing.T) {
  19. t.Helper()
  20. xuilogger.InitLogger(logging.ERROR)
  21. gin.SetMode(gin.TestMode)
  22. dbDir := t.TempDir()
  23. t.Setenv("XUI_DB_FOLDER", dbDir)
  24. if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
  25. t.Fatalf("InitDB: %v", err)
  26. }
  27. t.Cleanup(func() { _ = database.CloseDB() })
  28. }
  29. type hostEnvelope struct {
  30. Success bool `json:"success"`
  31. Msg string `json:"msg"`
  32. Obj json.RawMessage `json:"obj"`
  33. }
  34. func doHostReq(t *testing.T, engine *gin.Engine, method, path string, body any) hostEnvelope {
  35. t.Helper()
  36. var rdr *bytes.Reader
  37. if body != nil {
  38. b, _ := json.Marshal(body)
  39. rdr = bytes.NewReader(b)
  40. } else {
  41. rdr = bytes.NewReader(nil)
  42. }
  43. req := httptest.NewRequest(method, path, rdr)
  44. req.Header.Set("Content-Type", "application/json")
  45. w := httptest.NewRecorder()
  46. engine.ServeHTTP(w, req)
  47. if w.Code != http.StatusOK {
  48. t.Fatalf("%s %s: status %d, body=%s", method, path, w.Code, w.Body.String())
  49. }
  50. var env hostEnvelope
  51. if err := json.Unmarshal(w.Body.Bytes(), &env); err != nil {
  52. t.Fatalf("%s %s: decode envelope: %v body=%s", method, path, err, w.Body.String())
  53. }
  54. return env
  55. }
  56. func TestHostController_AddListGetDelete(t *testing.T) {
  57. newHostTestDB(t)
  58. engine := gin.New()
  59. NewHostController(engine.Group("/panel/api/hosts"))
  60. ib := &model.Inbound{Tag: "ctl", Enable: true, Port: 5443, Protocol: model.VLESS, Settings: `{"clients":[]}`}
  61. if err := database.GetDB().Create(ib).Error; err != nil {
  62. t.Fatalf("seed inbound: %v", err)
  63. }
  64. add := doHostReq(t, engine, http.MethodPost, "/panel/api/hosts/add", map[string]any{
  65. "inboundIds": []int{ib.Id}, "remark": "h1", "hosts": []string{"h1.example.com"}, "port": 8443,
  66. })
  67. if !add.Success {
  68. t.Fatalf("add not successful: %s", add.Msg)
  69. }
  70. var created []*model.Host
  71. if err := json.Unmarshal(add.Obj, &created); err != nil {
  72. t.Fatalf("decode created hosts: %v", err)
  73. }
  74. if len(created) != 1 || created[0].GroupId == "" || created[0].Remark != "h1" {
  75. t.Fatalf("created hosts = %+v", created)
  76. }
  77. groupId := created[0].GroupId
  78. list := doHostReq(t, engine, http.MethodGet, "/panel/api/hosts/list", nil)
  79. var groups []entity.HostGroup
  80. if err := json.Unmarshal(list.Obj, &groups); err != nil {
  81. t.Fatalf("decode list: %v", err)
  82. }
  83. if len(groups) != 1 || groups[0].GroupId != groupId {
  84. t.Fatalf("list = %+v, want one group groupId=%s", groups, groupId)
  85. }
  86. get := doHostReq(t, engine, http.MethodGet, "/panel/api/hosts/get/"+groupId, nil)
  87. if !get.Success {
  88. t.Fatalf("get not successful: %s", get.Msg)
  89. }
  90. update := doHostReq(t, engine, http.MethodPost, "/panel/api/hosts/update/"+groupId, map[string]any{
  91. "inboundIds": []int{ib.Id}, "remark": "h1-updated", "hosts": []string{"h1.example.com"}, "port": 8443,
  92. })
  93. if !update.Success {
  94. t.Fatalf("update not successful: %s", update.Msg)
  95. }
  96. get2 := doHostReq(t, engine, http.MethodGet, "/panel/api/hosts/get/"+groupId, nil)
  97. var group2 entity.HostGroup
  98. _ = json.Unmarshal(get2.Obj, &group2)
  99. if group2.Remark != "h1-updated" {
  100. t.Fatalf("update did not change remark: %s", group2.Remark)
  101. }
  102. setEn := doHostReq(t, engine, http.MethodPost, "/panel/api/hosts/bulk/setEnable", map[string]any{
  103. "ids": []string{groupId}, "enable": false,
  104. })
  105. if !setEn.Success {
  106. t.Fatalf("bulk/setEnable not successful: %s", setEn.Msg)
  107. }
  108. get3 := doHostReq(t, engine, http.MethodGet, "/panel/api/hosts/get/"+groupId, nil)
  109. var group3 entity.HostGroup
  110. _ = json.Unmarshal(get3.Obj, &group3)
  111. if !group3.IsDisabled {
  112. t.Fatalf("bulk/setEnable did not disable host group")
  113. }
  114. add2 := doHostReq(t, engine, http.MethodPost, "/panel/api/hosts/bulk/add", map[string]any{
  115. "inboundIds": []int{ib.Id}, "remark": "h2", "hosts": []string{"h2.example.com"}, "port": 8443,
  116. })
  117. var created2 []*model.Host
  118. _ = json.Unmarshal(add2.Obj, &created2)
  119. groupId2 := created2[0].GroupId
  120. bulkDel := doHostReq(t, engine, http.MethodPost, "/panel/api/hosts/bulk/del", map[string]any{
  121. "ids": []string{groupId, groupId2},
  122. })
  123. if !bulkDel.Success {
  124. t.Fatalf("bulk/del not successful: %s", bulkDel.Msg)
  125. }
  126. list2 := doHostReq(t, engine, http.MethodGet, "/panel/api/hosts/list", nil)
  127. var groups2 []entity.HostGroup
  128. _ = json.Unmarshal(list2.Obj, &groups2)
  129. if len(groups2) != 0 {
  130. t.Fatalf("after delete, list = %+v, want empty", groups2)
  131. }
  132. }
  133. func TestHostController_AuthInherited(t *testing.T) {
  134. newHostTestDB(t)
  135. engine := gin.New()
  136. store := cookie.NewStore([]byte("host-auth-test-secret"))
  137. engine.Use(sessions.Sessions("3x-ui", store))
  138. a := &APIController{}
  139. api := engine.Group("/panel/api")
  140. api.Use(a.checkAPIAuth)
  141. NewHostController(api.Group("/hosts"))
  142. req := httptest.NewRequest(http.MethodGet, "/panel/api/hosts/list", nil)
  143. req.Header.Set("X-Requested-With", "XMLHttpRequest")
  144. w := httptest.NewRecorder()
  145. engine.ServeHTTP(w, req)
  146. if w.Code != http.StatusUnauthorized {
  147. t.Fatalf("unauthenticated hosts/list = %d, want 401 (auth inherited)", w.Code)
  148. }
  149. }