mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-25 04:17:15 +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 {
|
} else {
|
||||||
patch.Status = "online"
|
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)
|
jsonObj(c, patch.ToUI(probeErr == nil), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@@ -12,6 +13,8 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/web/locale"
|
"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) {
|
func TestNodeControllerAddAcceptsTokenButReturnsView(t *testing.T) {
|
||||||
engine := newNodeCredentialTestEngine(t)
|
engine := newNodeCredentialTestEngine(t)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user