CI / go-test (push) Canceled after 0s
CI / postgres-durable-first (push) Canceled after 0s
CI / codegen (push) Canceled after 0s
CI / govulncheck (push) Canceled after 0s
CI / race (push) Canceled after 0s
CI / fuzz-smoke (push) Canceled after 0s
CI / golangci (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CodeQL Advanced / Analyze (go) (push) Canceled after 0s
CodeQL Advanced / Analyze (actions) (push) Canceled after 0s
CodeQL Advanced / Analyze (javascript-typescript) (push) Canceled after 0s
Docs CI / build (push) Canceled after 0s
Docs Deploy (GitHub Pages) / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / noninteractive-install (ubuntu-latest) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-24.04-arm) (push) Canceled after 0s
Deploy Smoke Tests / release-tag-install (ubuntu-latest) (push) Canceled after 0s
Docs Deploy (GitHub Pages) / deploy (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
108 lines
3.7 KiB
Go
108 lines
3.7 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"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"
|
|
)
|
|
|
|
func seedGlobalTraffic(t *testing.T, email string, up, down int64) {
|
|
t.Helper()
|
|
if err := database.GetDB().Create(&model.ClientGlobalTraffic{
|
|
MasterGuid: "peer-" + email, Email: email, Up: up, Down: down,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed client_global_traffics: %v", err)
|
|
}
|
|
}
|
|
|
|
func countGlobalTraffic(t *testing.T, email string) int64 {
|
|
t.Helper()
|
|
var n int64
|
|
if err := database.GetDB().Model(&model.ClientGlobalTraffic{}).
|
|
Where("email = ?", email).Count(&n).Error; err != nil {
|
|
t.Fatalf("count client_global_traffics: %v", err)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// A capped catch-up keeps its counters and stays expired, so its cross-panel
|
|
// rows still describe a window this client has not spent.
|
|
func TestAutoRenewClients_TruncatedCatchUpKeepsCrossPanelTraffic(t *testing.T) {
|
|
setupBulkDB(t)
|
|
svc := &InboundService{}
|
|
db := database.GetDB()
|
|
|
|
past := time.Now().Add(-150 * 24 * time.Hour).UnixMilli()
|
|
clients := []model.Client{
|
|
{Email: "capped@x", ID: "66666666-6666-6666-6666-666666666666", Enable: false, Reset: 30, ResetMax: 3, ExpiryTime: past},
|
|
}
|
|
ib := mkInbound(t, 30126, model.VLESS, clientsSettings(t, clients))
|
|
if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
|
|
t.Fatalf("SyncInbound: %v", err)
|
|
}
|
|
if err := db.Create(&xray.ClientTraffic{
|
|
InboundId: ib.Id, Email: "capped@x", Enable: false, Reset: 30, ResetMax: 3, ResetCount: 2,
|
|
Up: 111, Down: 222, ExpiryTime: past,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed client_traffics: %v", err)
|
|
}
|
|
seedGlobalTraffic(t, "capped@x", 111, 222)
|
|
|
|
if _, _, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
|
|
t.Fatalf("autoRenewClients: %v", err)
|
|
}
|
|
|
|
var row xray.ClientTraffic
|
|
if err := db.Where("email = ?", "capped@x").First(&row).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if row.Up != 111 || row.Down != 222 {
|
|
t.Fatalf("local counters were reset, so this case no longer exercises the cap: up=%d down=%d", row.Up, row.Down)
|
|
}
|
|
if got := countGlobalTraffic(t, "capped@x"); got != 1 {
|
|
t.Fatalf("cross-panel rows=%d, want 1: the window was dropped for a client that never got it", got)
|
|
}
|
|
}
|
|
|
|
// The counterpart: a real renewal must drop the rows, or the stale pushed
|
|
// totals re-deplete the fresh window at once.
|
|
func TestAutoRenewClients_RenewedClientLosesCrossPanelTraffic(t *testing.T) {
|
|
setupBulkDB(t)
|
|
svc := &InboundService{}
|
|
db := database.GetDB()
|
|
|
|
past := time.Now().Add(-40 * 24 * time.Hour).UnixMilli()
|
|
clients := []model.Client{
|
|
{Email: "rolled@x", ID: "77777777-7777-7777-7777-777777777777", Enable: false, Reset: 30, ExpiryTime: past},
|
|
}
|
|
ib := mkInbound(t, 30127, model.VLESS, clientsSettings(t, clients))
|
|
if err := svc.clientService.SyncInbound(nil, ib.Id, clients); err != nil {
|
|
t.Fatalf("SyncInbound: %v", err)
|
|
}
|
|
if err := db.Create(&xray.ClientTraffic{
|
|
InboundId: ib.Id, Email: "rolled@x", Enable: false, Reset: 30,
|
|
Up: 333, Down: 444, ExpiryTime: past,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed client_traffics: %v", err)
|
|
}
|
|
seedGlobalTraffic(t, "rolled@x", 333, 444)
|
|
|
|
if _, _, err := svc.autoRenewClients(db, newTrafficMutationBatch()); err != nil {
|
|
t.Fatalf("autoRenewClients: %v", err)
|
|
}
|
|
|
|
var row xray.ClientTraffic
|
|
if err := db.Where("email = ?", "rolled@x").First(&row).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if row.Up != 0 || row.Down != 0 {
|
|
t.Fatalf("counters survived a renewal: up=%d down=%d", row.Up, row.Down)
|
|
}
|
|
if got := countGlobalTraffic(t, "rolled@x"); got != 0 {
|
|
t.Fatalf("cross-panel rows=%d, want 0: stale pushed totals would re-deplete the fresh window", got)
|
|
}
|
|
}
|