fix(cloudflare): handle missing nodes in group detail responses

This commit is contained in:
ryan
2026-09-18 14:04:44 +08:00
parent a2404a50ef
commit 1d4533f183
3 changed files with 77 additions and 5 deletions
+21 -5
View File
@@ -255,11 +255,11 @@ func validEdgeNode(ctx context.Context, id uint, requireIPv4 bool) (*model.OpenF
}
func buildGroupItem(ctx context.Context, group *model.CFPointingGroup) (*GroupItem, error) {
primary, err := repository.GetOpenFlareNodeByID(ctx, group.PrimaryNodeID)
primary, err := lookupGroupNode(ctx, group.ID, group.PrimaryNodeID)
if err != nil {
return nil, err
}
active, err := repository.GetOpenFlareNodeByID(ctx, group.ActiveNodeID)
active, err := lookupGroupNode(ctx, group.ID, group.ActiveNodeID)
if err != nil {
return nil, err
}
@@ -267,18 +267,34 @@ func buildGroupItem(ctx context.Context, group *model.CFPointingGroup) (*GroupIt
if err != nil {
return nil, err
}
item := &GroupItem{ID: group.ID, Name: group.Name, PrimaryNode: nodeOption(primary), ActiveNode: nodeOption(active), DefaultProxied: group.DefaultProxied, Enabled: group.Enabled, MemberCount: count, CreatedAt: group.CreatedAt, UpdatedAt: group.UpdatedAt}
item := &GroupItem{ID: group.ID, Name: group.Name, PrimaryNode: nodeOptionForID(group.PrimaryNodeID, primary), ActiveNode: nodeOptionForID(group.ActiveNodeID, active), DefaultProxied: group.DefaultProxied, Enabled: group.Enabled, MemberCount: count, CreatedAt: group.CreatedAt, UpdatedAt: group.UpdatedAt}
if group.BackupNodeID != nil {
backup, backupErr := repository.GetOpenFlareNodeByID(ctx, *group.BackupNodeID)
backup, backupErr := lookupGroupNode(ctx, group.ID, *group.BackupNodeID)
if backupErr != nil {
return nil, backupErr
}
option := nodeOption(backup)
option := nodeOptionForID(*group.BackupNodeID, backup)
item.BackupNode = &option
}
return item, nil
}
func lookupGroupNode(ctx context.Context, groupID, nodeID uint) (*model.OpenFlareNode, error) {
node, err := repository.GetOpenFlareNodeByID(ctx, nodeID)
if errors.Is(err, gorm.ErrRecordNotFound) {
logger.WarnF(ctx, "[Cloudflare] pointing group references missing node: group_id=%d node_id=%d", groupID, nodeID)
return nil, nil
}
return node, err
}
func nodeOptionForID(id uint, node *model.OpenFlareNode) NodeOption {
if node == nil {
return NodeOption{ID: id}
}
return nodeOption(node)
}
func nodeOption(node *model.OpenFlareNode) NodeOption {
return NodeOption{ID: node.ID, Name: node.Name, IP: node.IP}
}
@@ -4,12 +4,14 @@
package cloudflare
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
"github.com/Rain-kl/Wavelet/internal/model"
"github.com/Rain-kl/Wavelet/internal/repository"
"github.com/Rain-kl/Wavelet/internal/shared/response"
"github.com/gin-gonic/gin"
@@ -75,3 +77,56 @@ func TestGetGroupWithOrphanedMemberHealsAndSucceeds(t *testing.T) {
t.Errorf("GetCFPointingMemberByID() should return not found after healing")
}
}
func TestListGroupsHandlerWithMissingNodeStillSucceeds(t *testing.T) {
ctx, _ := setupCloudflareLogicDB(t)
if err := db.DB(ctx).Create(&model.CFPointingGroup{
Name: "KR",
PrimaryNodeID: 12,
ActiveNodeID: 12,
Enabled: true,
}).Error; err != nil {
t.Fatalf("Create(missing-node group) error = %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(response.ErrorHandlerMiddleware())
router.GET("/groups", ListGroupsHandler)
recorder := httptest.NewRecorder()
router.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/groups", nil))
if recorder.Code != http.StatusOK {
t.Fatalf("ListGroupsHandler status = %d, body = %s, want %d", recorder.Code, recorder.Body.String(), http.StatusOK)
}
body := recorder.Body.String()
if !strings.Contains(body, `"name":"KR"`) {
t.Fatalf("ListGroupsHandler body = %s, want group KR", body)
}
if !strings.Contains(body, `"name":"primary"`) {
t.Fatalf("ListGroupsHandler body = %s, want intact group primary", body)
}
}
func TestGetGroupHandlerWithMissingNodeStillSucceeds(t *testing.T) {
ctx, _ := setupCloudflareLogicDB(t)
group := model.CFPointingGroup{Name: "KR", PrimaryNodeID: 12, ActiveNodeID: 12, Enabled: true}
if err := db.DB(ctx).Create(&group).Error; err != nil {
t.Fatalf("Create(missing-node group) error = %v", err)
}
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(response.ErrorHandlerMiddleware())
router.GET("/groups/:id", GetGroupHandler)
recorder := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/groups/%d", group.ID), nil)
router.ServeHTTP(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("GetGroupHandler status = %d, body = %s, want %d", recorder.Code, recorder.Body.String(), http.StatusOK)
}
if !strings.Contains(recorder.Body.String(), `"name":"KR"`) {
t.Fatalf("GetGroupHandler body = %s, want group KR", recorder.Body.String())
}
}