| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- package service
- import (
- "testing"
- "github.com/mhsanaei/3x-ui/v3/database"
- "github.com/mhsanaei/3x-ui/v3/database/model"
- )
- // #4983: a transitive sub-node learned from a direct node must surface as its
- // own read-only entry nested under its parent, and per-GUID counts must split a
- // direct node's own inbounds from its sub-nodes'.
- func TestGetNodeTree_SurfacesTransitiveNodeNestedUnderParent(t *testing.T) {
- setupConflictDB(t)
- db := database.GetDB()
- svc := NodeService{}
- selfGuid, _ := (&SettingService{}).GetPanelGuid()
- if err := db.Create(&model.Node{
- Id: 1, Name: "Node2", Address: "10.0.0.2", Port: 2053,
- ApiToken: "t", Guid: "node2-guid", Status: "online",
- }).Error; err != nil {
- t.Fatalf("create node: %v", err)
- }
- // Node2's own inbound and a transitive inbound physically on Node3
- // (managed through Node2, so node_id = Node2 but origin = Node3).
- nid := 1
- if err := db.Create(&model.Inbound{Tag: "n1-own", Enable: true, Port: 443, Protocol: model.VLESS, Settings: `{"clients":[]}`, NodeID: &nid, OriginNodeGuid: "node2-guid"}).Error; err != nil {
- t.Fatalf("create own inbound: %v", err)
- }
- if err := db.Create(&model.Inbound{Tag: "n1-via", Enable: true, Port: 8443, Protocol: model.VLESS, Settings: `{"clients":[]}`, NodeID: &nid, OriginNodeGuid: "node3-guid"}).Error; err != nil {
- t.Fatalf("create transitive inbound: %v", err)
- }
- // The heartbeat learned that Node2 manages Node3.
- nodeDescendantsMu.Lock()
- nodeDescendantsCache[1] = []model.NodeSummary{{
- Guid: "node3-guid", ParentGuid: "node2-guid", Name: "Node3", Address: "10.0.0.3", Status: "online",
- }}
- nodeDescendantsMu.Unlock()
- t.Cleanup(func() {
- nodeDescendantsMu.Lock()
- nodeDescendantsCache = map[int][]model.NodeSummary{}
- nodeDescendantsMu.Unlock()
- })
- tree, err := svc.GetNodeTree()
- if err != nil {
- t.Fatalf("GetNodeTree: %v", err)
- }
- var node2, node3 *model.Node
- for _, n := range tree {
- switch n.Guid {
- case "node2-guid":
- node2 = n
- case "node3-guid":
- node3 = n
- }
- }
- if node2 == nil || node3 == nil {
- t.Fatalf("expected Node2 + transitive Node3, got %d nodes", len(tree))
- }
- if node2.ParentGuid != selfGuid {
- t.Errorf("Node2 parent = %q, want this panel's GUID %q", node2.ParentGuid, selfGuid)
- }
- if !node3.Transitive || node3.ParentGuid != "node2-guid" {
- t.Errorf("Node3 should be transitive under node2-guid, got transitive=%v parent=%q", node3.Transitive, node3.ParentGuid)
- }
- if node3.Id != 0 {
- t.Errorf("transitive node must be a read-only projection (Id 0), got Id=%d", node3.Id)
- }
- if node2.InboundCount != 1 {
- t.Errorf("Node2 should host only its own inbound, got InboundCount=%d", node2.InboundCount)
- }
- if node3.InboundCount != 1 {
- t.Errorf("transitive Node3 should host its 1 inbound, got %d", node3.InboundCount)
- }
- }
|