1
0

node_heartbeat_descendants_test.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. package job
  2. import (
  3. "net/http"
  4. "net/http/httptest"
  5. "path/filepath"
  6. "strconv"
  7. "strings"
  8. "testing"
  9. "github.com/op/go-logging"
  10. "github.com/mhsanaei/3x-ui/v3/internal/database"
  11. "github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
  12. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  13. xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
  14. "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
  15. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  16. )
  17. func transitiveGuids(t *testing.T) []string {
  18. t.Helper()
  19. tree, err := (&service.NodeService{}).GetNodeTree()
  20. if err != nil {
  21. t.Fatalf("GetNodeTree: %v", err)
  22. }
  23. var out []string
  24. for _, n := range tree {
  25. if n.Transitive {
  26. out = append(out, n.Guid)
  27. }
  28. }
  29. return out
  30. }
  31. // The heartbeat skips a disabled node and never sees a deleted one, so the
  32. // sub-nodes it had learned from them stayed on the Nodes page for good.
  33. func TestHeartbeatDropsSubNodesOfNodesItNoLongerProbes(t *testing.T) {
  34. cases := []struct {
  35. name string
  36. retire func(t *testing.T, nodeID int)
  37. }{
  38. {"disabled", func(t *testing.T, nodeID int) {
  39. if err := (&service.NodeService{}).SetEnable(nodeID, false); err != nil {
  40. t.Fatalf("SetEnable: %v", err)
  41. }
  42. }},
  43. {"deleted", func(t *testing.T, nodeID int) {
  44. if err := (&service.NodeService{}).Delete(nodeID); err != nil {
  45. t.Fatalf("Delete: %v", err)
  46. }
  47. }},
  48. }
  49. for _, tc := range cases {
  50. t.Run(tc.name, func(t *testing.T) {
  51. xuilogger.InitLogger(logging.ERROR)
  52. dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
  53. runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
  54. t.Cleanup(func() { runtime.SetManager(nil) })
  55. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  56. w.Header().Set("Content-Type", "application/json")
  57. switch {
  58. case strings.HasSuffix(r.URL.Path, "server/status"):
  59. _, _ = w.Write([]byte(`{"success":true,"obj":{"panelGuid":"direct-guid","xray":{"state":"running"}}}`))
  60. case strings.HasSuffix(r.URL.Path, "server/descendants"):
  61. _, _ = w.Write([]byte(`{"success":true,"obj":[{"guid":"sub-guid","parentGuid":"direct-guid","name":"sub","status":"online"}]}`))
  62. default:
  63. _, _ = w.Write([]byte(`{"success":true}`))
  64. }
  65. }))
  66. t.Cleanup(srv.Close)
  67. host, port, _ := strings.Cut(strings.TrimPrefix(srv.URL, "http://"), ":")
  68. portNum, _ := strconv.Atoi(port)
  69. node := &model.Node{
  70. Name: "direct", Scheme: "http", Address: host, Port: portNum, BasePath: "/", ApiToken: "tok",
  71. Enable: true, Status: "unknown", AllowPrivateAddress: true, TlsVerifyMode: "verify",
  72. }
  73. if err := database.GetDB().Create(node).Error; err != nil {
  74. t.Fatalf("create node: %v", err)
  75. }
  76. hb := NewNodeHeartbeatJob()
  77. hb.Run()
  78. if got := transitiveGuids(t); len(got) != 1 || got[0] != "sub-guid" {
  79. t.Fatalf("sub-nodes after first heartbeat = %v, want [sub-guid]", got)
  80. }
  81. tc.retire(t, node.Id)
  82. hb.Run()
  83. if got := transitiveGuids(t); len(got) != 0 {
  84. t.Fatalf("sub-nodes after the node was %s = %v, want none", tc.name, got)
  85. }
  86. })
  87. }
  88. }