1
0

node_shared_guid_test.go 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. package service
  2. import (
  3. "testing"
  4. "github.com/mhsanaei/3x-ui/v3/internal/database"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  6. )
  7. // Cloned node servers ship an identical panelGuid in their copied settings.
  8. // effectiveNodeGuid must keep each physical node in its own attribution bucket
  9. // by falling back to the node-unique id when a GUID is shared, while leaving a
  10. // uniquely-identified node on its real GUID.
  11. func TestEffectiveNodeGuid_DisambiguatesSharedGuids(t *testing.T) {
  12. nodes := []*model.Node{
  13. {Id: 1, Guid: "dup"},
  14. {Id: 2, Guid: "dup"},
  15. {Id: 3, Guid: "uniq"},
  16. {Id: 4, Guid: ""},
  17. {Id: 0, Guid: "transitive"},
  18. }
  19. shared := sharedNodeGuids(nodes)
  20. if _, ok := shared["dup"]; !ok {
  21. t.Fatalf("dup must be flagged shared, got %v", shared)
  22. }
  23. if _, ok := shared["uniq"]; ok {
  24. t.Fatalf("uniq must not be shared, got %v", shared)
  25. }
  26. if _, ok := shared["transitive"]; ok {
  27. t.Fatalf("transitive (Id 0) must not count toward sharing, got %v", shared)
  28. }
  29. cases := map[*model.Node]string{
  30. nodes[0]: "node:1",
  31. nodes[1]: "node:2",
  32. nodes[2]: "uniq",
  33. nodes[3]: "node:4",
  34. nodes[4]: "transitive",
  35. }
  36. for n, want := range cases {
  37. if got := effectiveNodeGuid(n, shared); got != want {
  38. t.Errorf("effectiveNodeGuid(Id=%d, Guid=%q) = %q, want %q", n.Id, n.Guid, got, want)
  39. }
  40. }
  41. }
  42. // recountByGuid must split per-node counts even when two direct nodes share a
  43. // GUID and their inbounds still carry that shared GUID as origin (pre-backfill).
  44. func TestRecountByGuid_SplitsClonedNodesWithSharedGuid(t *testing.T) {
  45. setupConflictDB(t)
  46. db := database.GetDB()
  47. svc := NodeService{}
  48. selfGuid, _ := (&SettingService{}).GetPanelGuid()
  49. n1 := &model.Node{Id: 1, Name: "A", Address: "10.0.0.1", Port: 2053, ApiToken: "t", Guid: "dup", Status: "online"}
  50. n2 := &model.Node{Id: 2, Name: "B", Address: "10.0.0.2", Port: 2053, ApiToken: "t", Guid: "dup", Status: "online"}
  51. n3 := &model.Node{Id: 3, Name: "C", Address: "10.0.0.3", Port: 2053, ApiToken: "t", Guid: "uniq", Status: "online"}
  52. for _, n := range []*model.Node{n1, n2, n3} {
  53. if err := db.Create(n).Error; err != nil {
  54. t.Fatalf("create node %s: %v", n.Name, err)
  55. }
  56. }
  57. id1, id2, id3 := 1, 2, 3
  58. inbounds := []*model.Inbound{
  59. {Tag: "a", Port: 1001, Protocol: model.VLESS, Settings: `{"clients":[]}`, Enable: true, NodeID: &id1, OriginNodeGuid: "dup"},
  60. {Tag: "b", Port: 1002, Protocol: model.VLESS, Settings: `{"clients":[]}`, Enable: true, NodeID: &id2, OriginNodeGuid: "dup"},
  61. {Tag: "c", Port: 1003, Protocol: model.VLESS, Settings: `{"clients":[]}`, Enable: true, NodeID: &id3, OriginNodeGuid: "uniq"},
  62. }
  63. for _, ib := range inbounds {
  64. if err := db.Create(ib).Error; err != nil {
  65. t.Fatalf("create inbound %s: %v", ib.Tag, err)
  66. }
  67. }
  68. nodes := []*model.Node{n1, n2, n3}
  69. svc.recountByGuid(nodes, selfGuid)
  70. if n1.InboundCount != 1 || n2.InboundCount != 1 {
  71. t.Errorf("cloned nodes must not share inbound counts: n1=%d n2=%d, want 1,1", n1.InboundCount, n2.InboundCount)
  72. }
  73. if n3.InboundCount != 1 {
  74. t.Errorf("unique node InboundCount = %d, want 1", n3.InboundCount)
  75. }
  76. }