| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- package service
- import (
- "testing"
- "github.com/mhsanaei/3x-ui/v3/database"
- "github.com/mhsanaei/3x-ui/v3/database/model"
- "github.com/mhsanaei/3x-ui/v3/web/runtime"
- )
- // While a node is config-dirty (a local edit committed before it could be
- // mirrored to the node), the traffic pull must not overwrite the central
- // inbound's config columns from the node's stale snapshot — only traffic
- // counters may advance. Otherwise a reconnecting node reverts the edit.
- func TestSetRemoteTraffic_DirtyPreservesConfig(t *testing.T) {
- setupConflictDB(t)
- db := database.GetDB()
- node := &model.Node{Name: "n1", Address: "127.0.0.1", Port: 2096, ApiToken: "tok", Enable: true, Status: "online"}
- if err := db.Create(node).Error; err != nil {
- t.Fatalf("create node: %v", err)
- }
- id := node.Id
- const desiredSettings = `{"clients":[{"email":"a@x"}]}`
- central := &model.Inbound{
- UserId: 1,
- NodeID: &id,
- Tag: "in-443-tcp",
- Enable: true,
- Port: 443,
- Protocol: model.VLESS,
- Settings: desiredSettings,
- }
- if err := db.Create(central).Error; err != nil {
- t.Fatalf("create inbound: %v", err)
- }
- snap := &runtime.TrafficSnapshot{
- Inbounds: []*model.Inbound{{
- Tag: "in-443-tcp",
- Enable: true,
- Port: 443,
- Protocol: model.VLESS,
- Settings: `{"clients":[{"email":"b@x"}]}`,
- Up: 500,
- Down: 700,
- }},
- }
- svc := InboundService{}
- if _, err := svc.setRemoteTrafficLocked(id, snap, true); err != nil {
- t.Fatalf("setRemoteTrafficLocked dirty: %v", err)
- }
- var got model.Inbound
- if err := db.First(&got, central.Id).Error; err != nil {
- t.Fatalf("reload inbound: %v", err)
- }
- if got.Settings != desiredSettings {
- t.Fatalf("dirty pull overwrote settings: want %q got %q", desiredSettings, got.Settings)
- }
- if got.Up != 500 || got.Down != 700 {
- t.Fatalf("traffic counters not applied while dirty: up=%d down=%d", got.Up, got.Down)
- }
- }
- // ClearNodeDirty must be a compare-and-swap on config_dirty_at so a concurrent
- // edit that re-dirties the node during a reconcile is not silently cleared.
- func TestNodeDirty_ClearIsCASOnDirtyAt(t *testing.T) {
- setupConflictDB(t)
- db := database.GetDB()
- node := &model.Node{Name: "n2", Address: "127.0.0.1", Port: 2096, ApiToken: "tok", Enable: true, Status: "online"}
- if err := db.Create(node).Error; err != nil {
- t.Fatalf("create node: %v", err)
- }
- nodeSvc := NodeService{}
- if err := nodeSvc.MarkNodeDirty(node.Id); err != nil {
- t.Fatalf("MarkNodeDirty: %v", err)
- }
- _, _, dirty, dirtyAt, err := nodeSvc.NodeSyncState(node.Id)
- if err != nil {
- t.Fatalf("NodeSyncState: %v", err)
- }
- if !dirty {
- t.Fatal("node should be dirty after MarkNodeDirty")
- }
- if err := nodeSvc.ClearNodeDirty(node.Id, dirtyAt-1); err != nil {
- t.Fatalf("ClearNodeDirty stale token: %v", err)
- }
- if _, _, stillDirty, _, _ := nodeSvc.NodeSyncState(node.Id); !stillDirty {
- t.Fatal("stale-token clear must not clear the dirty flag")
- }
- if err := nodeSvc.ClearNodeDirty(node.Id, dirtyAt); err != nil {
- t.Fatalf("ClearNodeDirty matching token: %v", err)
- }
- if _, _, stillDirty, _, _ := nodeSvc.NodeSyncState(node.Id); stillDirty {
- t.Fatal("matching-token clear must clear the dirty flag")
- }
- }
|