|
|
@@ -0,0 +1,90 @@
|
|
|
+package service
|
|
|
+
|
|
|
+import (
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
|
+ "github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
|
|
|
+ "github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
|
+)
|
|
|
+
|
|
|
+// A snapshot tag that matches neither the central tag nor an alias replaces the
|
|
|
+// inbound in one tick; the client's accumulated usage must survive the swap.
|
|
|
+func TestSetRemoteTraffic_InboundReplacedKeepsClientHistory(t *testing.T) {
|
|
|
+ db := initTrafficTestDB(t)
|
|
|
+ const nodeID = 2
|
|
|
+ if err := db.Create(&model.Node{Id: nodeID, Name: "node", Address: "10.0.0.2", Port: 2053, ApiToken: "t", Guid: "node-guid"}).Error; err != nil {
|
|
|
+ t.Fatalf("create node: %v", err)
|
|
|
+ }
|
|
|
+ const email = "baba"
|
|
|
+ settings := `{"clients":[{"email":"baba","enable":true}]}`
|
|
|
+ createNodeInboundWithClient(t, db, nodeID, "n2-in-2053-tcp", 2053, email)
|
|
|
+ svc := &InboundService{}
|
|
|
+ sync := func(up, down int64) {
|
|
|
+ t.Helper()
|
|
|
+ snap := &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{{
|
|
|
+ Tag: "in-2053-tcp", OriginNodeGuid: "node-guid", Enable: true, Port: 2053, Protocol: model.VLESS,
|
|
|
+ Settings: settings, ClientStats: []xray.ClientTraffic{{Email: email, Up: up, Down: down, Enable: true}},
|
|
|
+ }}}
|
|
|
+ if _, err := svc.setRemoteTrafficLocked(nodeID, snap, false, false); err != nil {
|
|
|
+ t.Fatalf("setRemoteTrafficLocked: %v", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ sync(100, 200)
|
|
|
+ sync(600, 1200)
|
|
|
+ assertUpDown(t, readTraffic(t, db, email), 500, 1000, "before the replace")
|
|
|
+
|
|
|
+ // Desync the central row so the next snapshot neither tag-matches nor aliases it.
|
|
|
+ if err := db.Model(&model.Inbound{}).Where("node_id = ?", nodeID).
|
|
|
+ Updates(map[string]any{"tag": "n2-legacy", "origin_node_guid": "stale-guid"}).Error; err != nil {
|
|
|
+ t.Fatalf("desync central inbound: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ sync(650, 1300)
|
|
|
+ assertUpDown(t, readTraffic(t, db, email), 550, 1100, "replace tick")
|
|
|
+ sync(700, 1400)
|
|
|
+ assertUpDown(t, readTraffic(t, db, email), 600, 1200, "tick after the replace")
|
|
|
+
|
|
|
+ var ib model.Inbound
|
|
|
+ if err := db.Where("node_id = ?", nodeID).First(&ib).Error; err != nil {
|
|
|
+ t.Fatalf("read node inbound: %v", err)
|
|
|
+ }
|
|
|
+ if ib.Tag != "in-2053-tcp" {
|
|
|
+ t.Fatalf("fixture did not replace the inbound: surviving tag %q", ib.Tag)
|
|
|
+ }
|
|
|
+ if ct := readTraffic(t, db, email); ct.InboundId != ib.Id {
|
|
|
+ t.Errorf("client_traffics.inbound_id = %d, want the surviving inbound %d", ct.InboundId, ib.Id)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// A node inbound created on the master has no origin until its first tag match;
|
|
|
+// that empty origin is still this node, so a renamed tag aliases instead of replacing.
|
|
|
+func TestSetRemoteTraffic_AliasesInboundWithEmptyOrigin(t *testing.T) {
|
|
|
+ db := initTrafficTestDB(t)
|
|
|
+ const nodeID = 2
|
|
|
+ if err := db.Create(&model.Node{Id: nodeID, Name: "node", Address: "10.0.0.2", Port: 2053, ApiToken: "t", Guid: "node-guid"}).Error; err != nil {
|
|
|
+ t.Fatalf("create node: %v", err)
|
|
|
+ }
|
|
|
+ createNodeInbound(t, db, nodeID, "n2-in-2053-tcp", 2053)
|
|
|
+ var before model.Inbound
|
|
|
+ if err := db.Where("node_id = ?", nodeID).First(&before).Error; err != nil {
|
|
|
+ t.Fatalf("read node inbound: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ snap := &runtime.TrafficSnapshot{Inbounds: []*model.Inbound{{
|
|
|
+ Tag: "in-2053-tcp-2", OriginNodeGuid: "node-guid", Enable: true, Port: 2053, Protocol: model.VLESS,
|
|
|
+ Settings: `{"clients":[]}`,
|
|
|
+ }}}
|
|
|
+ if _, err := (&InboundService{}).setRemoteTrafficLocked(nodeID, snap, false, false); err != nil {
|
|
|
+ t.Fatalf("setRemoteTrafficLocked: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ var rows []model.Inbound
|
|
|
+ if err := db.Where("node_id = ?", nodeID).Find(&rows).Error; err != nil {
|
|
|
+ t.Fatalf("list node inbounds: %v", err)
|
|
|
+ }
|
|
|
+ if len(rows) != 1 || rows[0].Id != before.Id {
|
|
|
+ t.Fatalf("node inbounds = %#v, want only the original id %d", rows, before.Id)
|
|
|
+ }
|
|
|
+}
|