mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-14 07:10:58 +00:00
fix(nodes): report probe heartbeat persistence failures (#6207)
Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
@@ -314,7 +314,10 @@ func (a *NodeController) probe(c *gin.Context) {
|
||||
} else {
|
||||
patch.Status = "online"
|
||||
}
|
||||
_ = a.nodeService.UpdateHeartbeat(id, patch)
|
||||
if err := a.nodeService.UpdateHeartbeat(id, patch); err != nil {
|
||||
jsonMsg(c, I18nWeb(c, "pages.nodes.toasts.test"), err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, patch.ToUI(probeErr == nil), nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/web/locale"
|
||||
@@ -66,6 +69,52 @@ func TestNodeControllerResponsesDoNotLeakApiToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeControllerProbeReportsHeartbeatPersistenceFailure(t *testing.T) {
|
||||
engine := newNodeCredentialTestEngine(t)
|
||||
remote := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"success":true,"obj":{"cpu":1,"mem":{"current":1,"total":2},"xray":{"version":"1","state":"running"},"panelVersion":"v3.6.0","panelGuid":"guid","uptime":7,"netIO":{"up":3,"down":4}}}`))
|
||||
}))
|
||||
defer remote.Close()
|
||||
host, portString, err := net.SplitHostPort(strings.TrimPrefix(remote.URL, "http://"))
|
||||
if err != nil {
|
||||
t.Fatalf("split remote addr: %v", err)
|
||||
}
|
||||
port, err := strconv.Atoi(portString)
|
||||
if err != nil {
|
||||
t.Fatalf("parse remote port: %v", err)
|
||||
}
|
||||
node := &model.Node{Scheme: "http", Address: host, Port: port, BasePath: "/", Enable: true, AllowPrivateAddress: true}
|
||||
if err := database.GetDB().Create(node).Error; err != nil {
|
||||
t.Fatalf("seed node: %v", err)
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
const callback = "test:fail_node_heartbeat_update"
|
||||
errInjected := errors.New("injected heartbeat persistence failure")
|
||||
if err := db.Callback().Update().Before("gorm:update").Register(callback, func(tx *gorm.DB) {
|
||||
if tx.Statement != nil && tx.Statement.Table == "nodes" {
|
||||
tx.AddError(errInjected)
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatalf("register update callback: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := db.Callback().Update().Remove(callback); err != nil {
|
||||
t.Errorf("remove update callback: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
engine.ServeHTTP(w, httptest.NewRequest(http.MethodPost, "/panel/api/nodes/probe/"+strconv.Itoa(node.Id), nil))
|
||||
if !strings.Contains(w.Body.String(), `"success":false`) {
|
||||
t.Fatalf("probe reported success despite heartbeat persistence failure: %s", w.Body.String())
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), errInjected.Error()) {
|
||||
t.Fatalf("probe response omitted persistence error: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeControllerAddAcceptsTokenButReturnsView(t *testing.T) {
|
||||
engine := newNodeCredentialTestEngine(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user