Files
3x-ui/internal/web/service/node_client_breakdown_test.go
T
n0ctal f431e9cc03 fix(inbounds): apply runtime changes after the DB commit (#5768)
* fix(inbounds): apply runtime changes after commit

* ci: fix staticcheck findings
2026-07-08 22:12:28 +02:00

89 lines
2.9 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 node-page client breakdown must classify by EMAIL, not by
// client_traffics.inbound_id — that column goes stale after an inbound is
// delete+recreated, so filtering by it drops almost every row and undercounts
// active/disabled/ended. Here the traffic rows carry a bogus inbound_id (999)
// yet must still be matched to the node's clients by email.
func TestGetAll_ClientBreakdownMatchesByEmailNotStaleInboundId(t *testing.T) {
setupConflictDB(t)
db := database.GetDB()
svc := NodeService{}
if err := db.Create(&model.Node{Id: 1, Name: "n", Address: "10.0.0.1", Port: 2053, ApiToken: "t", Guid: "g"}).Error; err != nil {
t.Fatalf("create node: %v", err)
}
nid := 1
ib := &model.Inbound{Tag: "n1-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 {
// Enable has gorm:"default:true", so a zero-value (false) create is
// dropped and the DB applies true — force the disabled state explicitly.
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(1, "active@x", true)
mk(2, "disabled@x", false)
mk(3, "depleted@x", true)
// Traffic rows carry a STALE inbound_id (999), unrelated to the node inbound.
const staleInboundID = 999
rows := []*xray.ClientTraffic{
{InboundId: staleInboundID, Email: "active@x", Enable: true},
{InboundId: staleInboundID, Email: "disabled@x", Enable: false},
{InboundId: staleInboundID, Email: "depleted@x", Enable: true, Total: 100, Up: 60, Down: 60}, // exhausted
}
for _, r := range rows {
if err := db.Create(r).Error; err != nil {
t.Fatalf("create traffic %s: %v", r.Email, err)
}
}
nodes, err := svc.GetAll()
if err != nil {
t.Fatalf("GetAll: %v", err)
}
var n *model.Node
for _, x := range nodes {
if x.Id == 1 {
n = x
}
}
if n == nil {
t.Fatal("node 1 not found")
return
}
if n.ClientCount != 3 {
t.Errorf("ClientCount = %d, want 3", n.ClientCount)
}
if n.ActiveCount != 1 {
t.Errorf("ActiveCount = %d, want 1 (matched by email despite stale inbound_id)", n.ActiveCount)
}
if n.DisabledCount != 1 {
t.Errorf("DisabledCount = %d, want 1", n.DisabledCount)
}
if n.DepletedCount != 1 {
t.Errorf("DepletedCount = %d, want 1", n.DepletedCount)
}
}