1
0

tuic_accounting_identity_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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. "github.com/mhsanaei/3x-ui/v3/internal/tuic"
  7. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  8. )
  9. func audit3CreateTuicClient(t *testing.T, svc *ClientService, inboundSvc *InboundService, ib *model.Inbound, email, id string) model.ClientRecord {
  10. t.Helper()
  11. if _, err := svc.Create(inboundSvc, &ClientCreatePayload{Client: model.Client{Email: email, ID: id, Password: "pw", Enable: true}, InboundIds: []int{ib.Id}}); err != nil {
  12. t.Fatalf("public Create %s: %v", email, err)
  13. }
  14. return lookupClientRecord(t, email)
  15. }
  16. func audit3Traffic(t *testing.T, email string) xray.ClientTraffic {
  17. t.Helper()
  18. var out xray.ClientTraffic
  19. if err := database.GetDB().Where("email = ?", email).First(&out).Error; err != nil {
  20. t.Fatal(err)
  21. }
  22. return out
  23. }
  24. func TestAudit3DeletedIdentityMustNotChargeDuplicateUUID(t *testing.T) {
  25. setupBulkDB(t)
  26. svc, inboundSvc := &ClientService{}, &InboundService{}
  27. ibB := mkInbound(t, 28001, model.TUIC, `{"clients":[]}`)
  28. ibA := mkInbound(t, 28002, model.TUIC, `{"clients":[]}`)
  29. const id = "a0000000-0000-0000-0000-000000000031"
  30. audit3CreateTuicClient(t, svc, inboundSvc, ibB, "b@audit3", id)
  31. a := audit3CreateTuicClient(t, svc, inboundSvc, ibA, "a@audit3", id)
  32. trafficID := audit3RuntimeTrafficID(t, inboundSvc, ibA, "a@audit3")
  33. if _, err := svc.Delete(inboundSvc, a.Id, true); err != nil {
  34. t.Fatalf("public Delete keepTraffic: %v", err)
  35. }
  36. if _, _, err := inboundSvc.AddTraffic(nil, []*xray.ClientTraffic{{Email: "a@audit3", TuicTrafficID: trafficID, TuicUUID: id, TuicInboundId: ibA.Id, Up: 123, Down: 456}}); err != nil {
  37. t.Fatal(err)
  38. }
  39. old, other := audit3Traffic(t, "a@audit3"), audit3Traffic(t, "b@audit3")
  40. if old.Up != 123 || old.Down != 456 || other.Up != 0 || other.Down != 0 {
  41. t.Fatalf("retired A=%d/%d; unrelated B=%d/%d, want A=123/456 and B=0/0", old.Up, old.Down, other.Up, other.Down)
  42. }
  43. }
  44. func TestAudit3RenameAndRotateCredentialsMustPreserveRetiredUsage(t *testing.T) {
  45. setupBulkDB(t)
  46. svc, inboundSvc := &ClientService{}, &InboundService{}
  47. ib := mkInbound(t, 28003, model.TUIC, `{"clients":[]}`)
  48. const oldID = "a0000000-0000-0000-0000-000000000032"
  49. a := audit3CreateTuicClient(t, svc, inboundSvc, ib, "old@audit3", oldID)
  50. trafficID := audit3RuntimeTrafficID(t, inboundSvc, ib, "old@audit3")
  51. if _, err := svc.Update(inboundSvc, a.Id, model.Client{Email: "new@audit3", ID: "a0000000-0000-0000-0000-000000000033", Password: "newpw", Enable: true}, 0); err != nil {
  52. t.Fatalf("public rename + rotate: %v", err)
  53. }
  54. if _, _, err := inboundSvc.AddTraffic(nil, []*xray.ClientTraffic{{Email: "old@audit3", TuicTrafficID: trafficID, TuicUUID: oldID, TuicInboundId: ib.Id, Up: 123, Down: 456}}); err != nil {
  55. t.Fatal(err)
  56. }
  57. got := audit3Traffic(t, "new@audit3")
  58. if got.Up != 123 || got.Down != 456 {
  59. t.Fatalf("new account usage=%d/%d, want retired 123/456", got.Up, got.Down)
  60. }
  61. }
  62. func TestAudit3PublicRenamePreservesRetiredUsage(t *testing.T) {
  63. setupBulkDB(t)
  64. svc, inboundSvc := &ClientService{}, &InboundService{}
  65. ib := mkInbound(t, 28004, model.TUIC, `{"clients":[]}`)
  66. const id = "a0000000-0000-0000-0000-000000000034"
  67. a := audit3CreateTuicClient(t, svc, inboundSvc, ib, "old@audit3", id)
  68. trafficID := audit3RuntimeTrafficID(t, inboundSvc, ib, "old@audit3")
  69. if _, err := svc.Update(inboundSvc, a.Id, model.Client{Email: "new@audit3", ID: id, Password: "pw", Enable: true}, 0); err != nil {
  70. t.Fatalf("public rename: %v", err)
  71. }
  72. if _, _, err := inboundSvc.AddTraffic(nil, []*xray.ClientTraffic{{Email: "old@audit3", TuicTrafficID: trafficID, TuicUUID: id, TuicInboundId: ib.Id, Up: 123, Down: 456}}); err != nil {
  73. t.Fatal(err)
  74. }
  75. got := audit3Traffic(t, "new@audit3")
  76. if got.Up != 123 || got.Down != 456 {
  77. t.Fatalf("new account usage=%d/%d, want retired 123/456", got.Up, got.Down)
  78. }
  79. }
  80. func TestAudit3SameInboundDuplicateUUIDMustBeRejectedOrBillMatchedPrincipal(t *testing.T) {
  81. setupBulkDB(t)
  82. svc, inboundSvc := &ClientService{}, &InboundService{}
  83. ib := mkInbound(t, 28005, model.TUIC, `{"clients":[]}`)
  84. const id = "a0000000-0000-0000-0000-000000000035"
  85. audit3CreateTuicClient(t, svc, inboundSvc, ib, "a@audit3", id)
  86. if _, err := svc.Create(inboundSvc, &ClientCreatePayload{Client: model.Client{Email: "b@audit3", ID: id, Password: "different-pw", Enable: true}, InboundIds: []int{ib.Id}}); err != nil {
  87. t.Logf("duplicate correctly rejected: %v", err)
  88. return
  89. }
  90. t.Fatal("public Create accepted duplicate UUID")
  91. }
  92. func audit3RuntimeTrafficID(t *testing.T, service *InboundService, inbound *model.Inbound, email string) int {
  93. t.Helper()
  94. fresh, err := service.GetInbound(inbound.Id)
  95. if err != nil {
  96. t.Fatal(err)
  97. }
  98. built, err := service.buildInboundForLocalRuntime(database.GetDB(), fresh)
  99. if err != nil {
  100. t.Fatal(err)
  101. }
  102. instance, ok := tuic.InstanceFromInbound(built)
  103. if !ok {
  104. t.Fatal("invalid runtime settings")
  105. }
  106. for _, client := range instance.Clients {
  107. if client.Email == email {
  108. if client.TrafficID == 0 || client.TrafficID != audit3Traffic(t, email).Id {
  109. t.Fatal("runtime snapshot lacks immutable accounting identity")
  110. }
  111. return client.TrafficID
  112. }
  113. }
  114. t.Fatal("runtime client missing")
  115. return 0
  116. }
  117. func TestTuicDeletedTrafficRowCannotBillReusedEmail(t *testing.T) {
  118. setupBulkDB(t)
  119. db := database.GetDB()
  120. old := xray.ClientTraffic{Email: "reused@audit3", Enable: true}
  121. if err := db.Create(&old).Error; err != nil {
  122. t.Fatal(err)
  123. }
  124. if err := db.Delete(&old).Error; err != nil {
  125. t.Fatal(err)
  126. }
  127. replacement := xray.ClientTraffic{Email: old.Email, Enable: true}
  128. if err := db.Create(&replacement).Error; err != nil {
  129. t.Fatal(err)
  130. }
  131. if old.Id == replacement.Id {
  132. t.Fatal("accounting primary key reused")
  133. }
  134. if _, _, err := (&InboundService{}).AddTraffic(nil, []*xray.ClientTraffic{{Email: old.Email, TuicTrafficID: old.Id, Up: 123, Down: 456}}); err != nil {
  135. t.Fatal(err)
  136. }
  137. got := audit3Traffic(t, old.Email)
  138. if got.Up != 0 || got.Down != 0 {
  139. t.Fatalf("retired client billed replacement: %+v", got)
  140. }
  141. }