mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-12 13:10:05 +00:00
455 lines
12 KiB
Go
455 lines
12 KiB
Go
package job
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"x-ui/database/model"
|
|
)
|
|
|
|
func TestShouldResetDaily(t *testing.T) {
|
|
job := NewPeriodicTrafficResetJob()
|
|
|
|
tests := []struct {
|
|
name string
|
|
now time.Time
|
|
lastReset time.Time
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "first time reset at midnight",
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Time{}, // zero time
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "first time reset not at midnight",
|
|
now: time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC),
|
|
lastReset: time.Time{}, // zero time
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 24 hours at midnight",
|
|
now: time.Date(2024, 1, 16, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "reset after 24 hours but not at midnight",
|
|
now: time.Date(2024, 1, 16, 12, 0, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset same day",
|
|
now: time.Date(2024, 1, 15, 0, 8, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 23 hours",
|
|
now: time.Date(2024, 1, 15, 23, 5, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "midnight but minute > 10",
|
|
now: time.Date(2024, 1, 16, 0, 15, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "edge case: exactly at minute 10",
|
|
now: time.Date(2024, 1, 16, 0, 10, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "edge case: minute 9 (last valid minute)",
|
|
now: time.Date(2024, 1, 16, 0, 9, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := job.shouldResetDaily(tt.now, tt.lastReset)
|
|
if result != tt.expected {
|
|
t.Errorf("shouldResetDaily() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShouldResetWeekly(t *testing.T) {
|
|
job := NewPeriodicTrafficResetJob()
|
|
|
|
tests := []struct {
|
|
name string
|
|
now time.Time
|
|
lastReset time.Time
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "first time reset on Sunday at midnight",
|
|
now: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC), // Sunday
|
|
lastReset: time.Time{}, // zero time
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "first time reset on Monday",
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC), // Monday
|
|
lastReset: time.Time{}, // zero time
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 7 days on Sunday at midnight",
|
|
now: time.Date(2024, 1, 21, 0, 5, 0, 0, time.UTC), // Sunday
|
|
lastReset: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC), // Previous Sunday
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "reset after 7 days on Sunday but not at midnight",
|
|
now: time.Date(2024, 1, 21, 12, 0, 0, 0, time.UTC), // Sunday
|
|
lastReset: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 6 days on Saturday",
|
|
now: time.Date(2024, 1, 20, 0, 5, 0, 0, time.UTC), // Saturday
|
|
lastReset: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset same Sunday",
|
|
now: time.Date(2024, 1, 14, 0, 8, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 8 days but on Monday",
|
|
now: time.Date(2024, 1, 22, 0, 5, 0, 0, time.UTC), // Monday
|
|
lastReset: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC), // Previous Sunday
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "edge case: Sunday but minute > 10",
|
|
now: time.Date(2024, 1, 21, 0, 15, 0, 0, time.UTC), // Sunday
|
|
lastReset: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := job.shouldResetWeekly(tt.now, tt.lastReset)
|
|
if result != tt.expected {
|
|
t.Errorf("shouldResetWeekly() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShouldResetMonthly(t *testing.T) {
|
|
job := NewPeriodicTrafficResetJob()
|
|
|
|
tests := []struct {
|
|
name string
|
|
now time.Time
|
|
lastReset time.Time
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "first time reset on 1st day at midnight",
|
|
now: time.Date(2024, 2, 1, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Time{}, // zero time
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "first time reset on 15th day",
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Time{}, // zero time
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 28 days on 1st day at midnight",
|
|
now: time.Date(2024, 2, 1, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 4, 0, 5, 0, 0, time.UTC), // 28 days ago
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "reset after 32 days on 1st day at midnight",
|
|
now: time.Date(2024, 3, 1, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 29, 0, 5, 0, 0, time.UTC), // 32 days ago
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "reset after 28 days on 1st day but not at midnight",
|
|
now: time.Date(2024, 2, 1, 12, 0, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 4, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 27 days",
|
|
now: time.Date(2024, 1, 31, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 4, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset same day (1st)",
|
|
now: time.Date(2024, 1, 1, 0, 8, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 1, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "reset after 28 days but on 2nd day",
|
|
now: time.Date(2024, 2, 2, 0, 5, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 5, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "leap year February edge case",
|
|
now: time.Date(2024, 3, 1, 0, 5, 0, 0, time.UTC), // March 1st in leap year
|
|
lastReset: time.Date(2024, 2, 1, 0, 5, 0, 0, time.UTC), // February 1st (29 days ago)
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "edge case: 1st day but minute > 10",
|
|
now: time.Date(2024, 2, 1, 0, 15, 0, 0, time.UTC),
|
|
lastReset: time.Date(2024, 1, 4, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := job.shouldResetMonthly(tt.now, tt.lastReset)
|
|
if result != tt.expected {
|
|
t.Errorf("shouldResetMonthly() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShouldResetTraffic(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inbound *model.Inbound
|
|
now time.Time
|
|
setup func(*PeriodicTrafficResetJob, *model.Inbound)
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "never reset type",
|
|
inbound: &model.Inbound{
|
|
Id: 1,
|
|
Tag: "test1",
|
|
PeriodicTrafficReset: "never",
|
|
},
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "invalid reset type",
|
|
inbound: &model.Inbound{
|
|
Id: 2,
|
|
Tag: "test2",
|
|
PeriodicTrafficReset: "invalid",
|
|
},
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "daily reset should trigger",
|
|
inbound: &model.Inbound{
|
|
Id: 3,
|
|
Tag: "test3",
|
|
PeriodicTrafficReset: "daily",
|
|
},
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "weekly reset should trigger",
|
|
inbound: &model.Inbound{
|
|
Id: 4,
|
|
Tag: "test4",
|
|
PeriodicTrafficReset: "weekly",
|
|
},
|
|
now: time.Date(2024, 1, 14, 0, 5, 0, 0, time.UTC), // Sunday
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "monthly reset should trigger",
|
|
inbound: &model.Inbound{
|
|
Id: 5,
|
|
Tag: "test5",
|
|
PeriodicTrafficReset: "monthly",
|
|
},
|
|
now: time.Date(2024, 2, 1, 0, 5, 0, 0, time.UTC),
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "daily reset with existing reset time - should not trigger",
|
|
inbound: &model.Inbound{
|
|
Id: 6,
|
|
Tag: "test6",
|
|
PeriodicTrafficReset: "daily",
|
|
},
|
|
now: time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC),
|
|
setup: func(j *PeriodicTrafficResetJob, inbound *model.Inbound) {
|
|
j.updateLastResetTime(inbound, time.Date(2024, 1, 15, 0, 3, 0, 0, time.UTC))
|
|
},
|
|
expected: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create new job for each test to avoid state interference
|
|
testJob := NewPeriodicTrafficResetJob()
|
|
|
|
if tt.setup != nil {
|
|
tt.setup(testJob, tt.inbound)
|
|
}
|
|
|
|
result := testJob.shouldResetTraffic(tt.inbound, tt.now)
|
|
if result != tt.expected {
|
|
t.Errorf("shouldResetTraffic() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetResetKey(t *testing.T) {
|
|
job := NewPeriodicTrafficResetJob()
|
|
|
|
tests := []struct {
|
|
name string
|
|
inbound *model.Inbound
|
|
expected string
|
|
}{
|
|
{
|
|
name: "daily reset key",
|
|
inbound: &model.Inbound{
|
|
Tag: "test-inbound",
|
|
PeriodicTrafficReset: "daily",
|
|
},
|
|
expected: "daily_test-inbound",
|
|
},
|
|
{
|
|
name: "weekly reset key",
|
|
inbound: &model.Inbound{
|
|
Tag: "proxy-1",
|
|
PeriodicTrafficReset: "weekly",
|
|
},
|
|
expected: "weekly_proxy-1",
|
|
},
|
|
{
|
|
name: "monthly reset key",
|
|
inbound: &model.Inbound{
|
|
Tag: "vless-tcp",
|
|
PeriodicTrafficReset: "monthly",
|
|
},
|
|
expected: "monthly_vless-tcp",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := job.getResetKey(tt.inbound)
|
|
if result != tt.expected {
|
|
t.Errorf("getResetKey() = %v, expected %v", result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLastResetTimeOperations(t *testing.T) {
|
|
job := NewPeriodicTrafficResetJob()
|
|
|
|
inbound := &model.Inbound{
|
|
Id: 1,
|
|
Tag: "test-inbound",
|
|
PeriodicTrafficReset: "daily",
|
|
}
|
|
|
|
// Test getting non-existent reset time (should return zero time)
|
|
resetTime := job.getLastResetTime(job.getResetKey(inbound))
|
|
if !resetTime.IsZero() {
|
|
t.Errorf("Expected zero time for non-existent key, got %v", resetTime)
|
|
}
|
|
|
|
// Test updating and getting reset time
|
|
testTime := time.Date(2024, 1, 15, 0, 5, 0, 0, time.UTC)
|
|
job.updateLastResetTime(inbound, testTime)
|
|
|
|
retrievedTime := job.getLastResetTime(job.getResetKey(inbound))
|
|
if !retrievedTime.Equal(testTime) {
|
|
t.Errorf("Expected %v, got %v", testTime, retrievedTime)
|
|
}
|
|
|
|
// Test updating existing reset time
|
|
newTime := time.Date(2024, 1, 16, 0, 5, 0, 0, time.UTC)
|
|
job.updateLastResetTime(inbound, newTime)
|
|
|
|
retrievedTime = job.getLastResetTime(job.getResetKey(inbound))
|
|
if !retrievedTime.Equal(newTime) {
|
|
t.Errorf("Expected %v, got %v", newTime, retrievedTime)
|
|
}
|
|
}
|
|
|
|
// Test for concurrent access to lastResetTimes map
|
|
func TestConcurrentAccess(t *testing.T) {
|
|
job := NewPeriodicTrafficResetJob()
|
|
|
|
inbound := &model.Inbound{
|
|
Id: 1,
|
|
Tag: "concurrent-test",
|
|
PeriodicTrafficReset: "daily",
|
|
}
|
|
|
|
// Run multiple goroutines to test concurrent access
|
|
done := make(chan bool, 20)
|
|
|
|
// 10 writers
|
|
for i := 0; i < 10; i++ {
|
|
go func(i int) {
|
|
testTime := time.Date(2024, 1, 15+i, 0, 5, 0, 0, time.UTC)
|
|
job.updateLastResetTime(inbound, testTime)
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
// 10 readers
|
|
for i := 0; i < 10; i++ {
|
|
go func() {
|
|
_ = job.getLastResetTime(job.getResetKey(inbound))
|
|
done <- true
|
|
}()
|
|
}
|
|
|
|
// Wait for all goroutines to complete
|
|
for i := 0; i < 20; i++ {
|
|
<-done
|
|
}
|
|
|
|
// If we reach here without panic/deadlock, the concurrent access is safe
|
|
t.Log("Concurrent access test passed")
|
|
}
|
|
|
|
func TestValidResetTypes(t *testing.T) {
|
|
expectedTypes := []string{"never", "daily", "weekly", "monthly"}
|
|
|
|
for _, resetType := range expectedTypes {
|
|
if !validResetTypes[resetType] {
|
|
t.Errorf("Expected reset type %s to be valid", resetType)
|
|
}
|
|
}
|
|
|
|
// Test invalid type
|
|
if validResetTypes["invalid"] {
|
|
t.Error("Expected 'invalid' to not be a valid reset type")
|
|
}
|
|
}
|