NodeView carried only the category counts, so the nodes page chips could hover to nothing but the category title. GetAll/recountByGuid now emit online/active/disabled/depleted email lists from the same bucketing pass as the counts (statuses gain the client email; the online list is the counted set), and the chips pop the list over with the inbounds page's popover pattern.
98 lines
3.5 KiB
Go
98 lines
3.5 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
)
|
|
|
|
// The nodes page category chips used to hover to a bare category title:
|
|
// the API carried only the counts, never WHO is in each category. GetAll
|
|
// must now emit the per-category email lists from the exact same bucketing
|
|
// pass as the counts (statuses by node id, online set by attribution GUID),
|
|
// and the tree view must carry them through to the wire contract.
|
|
func TestGetAll_ClientEmailListsMatchCounts(t *testing.T) {
|
|
setupConflictDB(t)
|
|
db := database.GetDB()
|
|
nodeSvc := NodeService{}
|
|
inboundSvc := InboundService{}
|
|
|
|
const nodeID = 8301
|
|
if err := db.Create(&model.Node{Id: nodeID, Name: "agent-node", Address: "10.0.8.3"}).Error; err != nil {
|
|
t.Fatalf("create node: %v", err)
|
|
}
|
|
nid := nodeID
|
|
ib := &model.Inbound{Tag: "lists-in", Enable: true, Port: 443, Protocol: model.VLESS, Settings: `{"clients":[]}`, NodeID: &nid}
|
|
if err := db.Create(ib).Error; err != nil {
|
|
t.Fatalf("create inbound: %v", err)
|
|
}
|
|
|
|
mk := func(id int, email string, enable bool) {
|
|
if err := db.Create(&model.ClientRecord{Id: id, Email: email, Enable: enable}).Error; err != nil {
|
|
t.Fatalf("create client %s: %v", email, err)
|
|
}
|
|
if !enable {
|
|
if err := db.Model(&model.ClientRecord{}).Where("id = ?", id).Update("enable", false).Error; err != nil {
|
|
t.Fatalf("disable client %s: %v", email, err)
|
|
}
|
|
}
|
|
if err := db.Create(&model.ClientInbound{ClientId: id, InboundId: ib.Id}).Error; err != nil {
|
|
t.Fatalf("attach client %s: %v", email, err)
|
|
}
|
|
}
|
|
mk(8301, "online@x", true)
|
|
mk(8302, "active@x", true)
|
|
mk(8303, "disabled@x", false)
|
|
mk(8304, "depleted@x", true)
|
|
if err := db.Create(&xray.ClientTraffic{InboundId: 999, Email: "depleted@x", Enable: true, Total: 100, Up: 60, Down: 60}).Error; err != nil {
|
|
t.Fatalf("create traffic: %v", err)
|
|
}
|
|
|
|
// The agent's online report — same entry the agent controller calls.
|
|
inboundSvc.SetAgentOnlineClients(nodeID, []string{"online@x"})
|
|
t.Cleanup(func() { inboundSvc.ClearNodeOnlineClients(nodeID) })
|
|
|
|
nodes, err := nodeSvc.GetAll()
|
|
if err != nil {
|
|
t.Fatalf("GetAll: %v", err)
|
|
}
|
|
var n *model.Node
|
|
for _, x := range nodes {
|
|
if x.Id == nodeID {
|
|
n = x
|
|
}
|
|
}
|
|
if n == nil {
|
|
t.Fatal("node not found")
|
|
}
|
|
if n.ClientCount != 4 || n.ActiveCount != 2 || n.DisabledCount != 1 || n.DepletedCount != 1 || n.OnlineCount != 1 {
|
|
t.Fatalf("counts = total %d active %d disabled %d depleted %d online %d, want 4/2/1/1/1",
|
|
n.ClientCount, n.ActiveCount, n.DisabledCount, n.DepletedCount, n.OnlineCount)
|
|
}
|
|
assertStringSet(t, "active emails", n.ActiveEmails, []string{"online@x", "active@x"})
|
|
assertStringSet(t, "disabled emails", n.DisabledEmails, []string{"disabled@x"})
|
|
assertStringSet(t, "depleted emails", n.DepletedEmails, []string{"depleted@x"})
|
|
assertStringSet(t, "online emails", n.OnlineEmails, []string{"online@x"})
|
|
|
|
// The wire contract (nodes page) must carry the same lists.
|
|
views, err := nodeSvc.GetNodeTreeView()
|
|
if err != nil {
|
|
t.Fatalf("GetNodeTreeView: %v", err)
|
|
}
|
|
var v *NodeView
|
|
for _, x := range views {
|
|
if x.Id == nodeID {
|
|
v = x
|
|
}
|
|
}
|
|
if v == nil {
|
|
t.Fatal("node view not found")
|
|
}
|
|
assertStringSet(t, "view online emails", v.OnlineEmails, []string{"online@x"})
|
|
assertStringSet(t, "view active emails", v.ActiveEmails, []string{"online@x", "active@x"})
|
|
assertStringSet(t, "view disabled emails", v.DisabledEmails, []string{"disabled@x"})
|
|
assertStringSet(t, "view depleted emails", v.DepletedEmails, []string{"depleted@x"})
|
|
}
|