mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-05 20:55:29 +03:00
dbb6212988
Restore usable clients when renewed, avoid duplicate reset updates, and prioritize operator writes in the serialized traffic writer. Preserve manual disables and keep every inbound consistent after traffic resets.
92 lines
2.7 KiB
Go
92 lines
2.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"
|
|
)
|
|
|
|
// A reset of a quota-disabled client must end enabled even when a traffic tick
|
|
// runs between its steps: enabling before zeroing lets the tick re-disable it.
|
|
func TestResetTrafficOfDepletedClientSurvivesTickMidReset(t *testing.T) {
|
|
resets := []struct {
|
|
name string
|
|
run func() error
|
|
}{
|
|
{"single", func() error {
|
|
_, err := (&ClientService{}).ResetTrafficByEmail(&InboundService{}, "d@stale")
|
|
return err
|
|
}},
|
|
{"bulk", func() error {
|
|
_, err := (&ClientService{}).BulkResetTraffic(&InboundService{}, []string{"d@stale"})
|
|
return err
|
|
}},
|
|
}
|
|
for _, reset := range resets {
|
|
t.Run(reset.name, func(t *testing.T) {
|
|
requireResetSurvivesTick(t, reset.run)
|
|
})
|
|
}
|
|
}
|
|
|
|
func requireResetSurvivesTick(t *testing.T, reset func() error) {
|
|
t.Helper()
|
|
setupBulkDB(t)
|
|
clients := []model.Client{{Email: "d@stale", ID: "aaaaaaaa-0000-0000-0000-00000000000d", SubID: "sub-d", Enable: false, TotalGB: 1000}}
|
|
ib := mkInbound(t, 23140, model.VLESS, clientsSettings(t, clients))
|
|
if err := (&ClientService{}).SyncInbound(nil, ib.Id, clients); err != nil {
|
|
t.Fatalf("SyncInbound: %v", err)
|
|
}
|
|
row := xray.ClientTraffic{InboundId: ib.Id, Email: "d@stale", Enable: false, Up: 600, Down: 400, Total: 1000}
|
|
if err := database.GetDB().Create(&row).Error; err != nil {
|
|
t.Fatalf("seed client_traffics: %v", err)
|
|
}
|
|
|
|
resetTrafficWriterForTest(t)
|
|
StartTrafficWriter()
|
|
parked := make(chan struct{})
|
|
release := make(chan struct{})
|
|
go func() {
|
|
_ = submitTrafficWrite(func() error {
|
|
close(parked)
|
|
<-release
|
|
return nil
|
|
})
|
|
}()
|
|
<-parked
|
|
|
|
resetDone := make(chan error, 1)
|
|
go func() { resetDone <- reset() }()
|
|
waitTrafficWriterQueued(t)
|
|
tickDone := make(chan error, 1)
|
|
go func() {
|
|
_, _, err := (&InboundService{}).AddTraffic(nil, nil)
|
|
tickDone <- err
|
|
}()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for trafficWriterQueueLen() < 2 && time.Now().Before(deadline) {
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
close(release)
|
|
if err := <-resetDone; err != nil {
|
|
t.Fatalf("reset: %v", err)
|
|
}
|
|
if err := <-tickDone; err != nil {
|
|
t.Fatalf("AddTraffic: %v", err)
|
|
}
|
|
|
|
var after xray.ClientTraffic
|
|
if err := database.GetDB().Where("email = ?", "d@stale").First(&after).Error; err != nil {
|
|
t.Fatalf("read client_traffics: %v", err)
|
|
}
|
|
c, _ := settingsClient(t, ib.Id, "d@stale")
|
|
rec := lookupClientRecord(t, "d@stale")
|
|
if !after.Enable || !c.Enable || !rec.Enable || after.Up+after.Down != 0 {
|
|
t.Fatalf("after reset: traffic enable=%v used=%d, settings enable=%v, record enable=%v; want all enabled at 0",
|
|
after.Enable, after.Up+after.Down, c.Enable, rec.Enable)
|
|
}
|
|
}
|