@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type clock struct{ now time.Time }
|
||||
|
||||
func (c *clock) Now() time.Time { return c.now }
|
||||
|
||||
func testLimiter(t *testing.T, cfg Config, at string) (*Limiter, *clock) {
|
||||
t.Helper()
|
||||
moment, err := time.Parse(time.RFC3339, at)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg.Timezone = "UTC"
|
||||
c := &clock{now: moment}
|
||||
limiter, err := NewLimiter(cfg, "", c.Now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return limiter, c
|
||||
}
|
||||
|
||||
func TestInWindowTable(t *testing.T) {
|
||||
at := func(text string) time.Time {
|
||||
moment, _ := time.Parse("15:04", text)
|
||||
return moment
|
||||
}
|
||||
cases := []struct {
|
||||
from, to, now string
|
||||
want bool
|
||||
}{
|
||||
{"", "", "12:00", true},
|
||||
{"09:00", "17:00", "09:00", true},
|
||||
{"09:00", "17:00", "16:59", true},
|
||||
{"09:00", "17:00", "17:00", false},
|
||||
{"09:00", "17:00", "08:59", false},
|
||||
{"22:00", "06:00", "23:30", true},
|
||||
{"22:00", "06:00", "05:59", true},
|
||||
{"22:00", "06:00", "06:00", false},
|
||||
{"22:00", "06:00", "12:00", false},
|
||||
{"10:00", "10:00", "03:00", true},
|
||||
}
|
||||
for _, item := range cases {
|
||||
if got := InWindow(item.from, item.to, at(item.now)); got != item.want {
|
||||
t.Errorf("%s-%s at %s: got %v", item.from, item.to, item.now, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquireConcurrencyCapAndServerCannotWiden(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.MaxConcurrent = 2
|
||||
limiter, _ := testLimiter(t, cfg, "2026-10-15T12:00:00Z")
|
||||
limiter.ApplyServer(&ServerLimits{MaxConcurrentPerNode: 8}, &ServerSettings{MaxConcurrent: 8})
|
||||
if limiter.Acquire() != "" || limiter.Acquire() != "" {
|
||||
t.Fatal("two slots should be free")
|
||||
}
|
||||
if got := limiter.Acquire(); got != RefuseConcurrency {
|
||||
t.Fatalf("third acquire: %q", got)
|
||||
}
|
||||
limiter.Release()
|
||||
if got := limiter.Acquire(); got != "" {
|
||||
t.Fatalf("after release: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerCanNarrowConcurrency(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.MaxConcurrent = 4
|
||||
limiter, _ := testLimiter(t, cfg, "2026-10-15T12:00:00Z")
|
||||
limiter.ApplyServer(&ServerLimits{MaxConcurrentPerNode: 2}, &ServerSettings{MaxConcurrent: 1})
|
||||
if limiter.Acquire() != "" {
|
||||
t.Fatal("first slot")
|
||||
}
|
||||
if got := limiter.Acquire(); got != RefuseConcurrency {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBandwidthCapLocalAndDayRollover(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.BandwidthMBDay = 1
|
||||
limiter, c := testLimiter(t, cfg, "2026-10-15T23:00:00Z")
|
||||
limiter.ApplyServer(&ServerLimits{MaxDonorMBPerDay: 20480}, &ServerSettings{BandwidthMBDay: 9999})
|
||||
if limiter.Acquire() != "" {
|
||||
t.Fatal("fresh day must allow")
|
||||
}
|
||||
limiter.Release()
|
||||
limiter.AddBytes(mebibyte - 10)
|
||||
if got := limiter.Remaining(); got != 10 {
|
||||
t.Fatalf("remaining %d", got)
|
||||
}
|
||||
limiter.AddBytes(10)
|
||||
if got := limiter.Acquire(); got != RefuseBandwidth {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
c.now = c.now.Add(2 * time.Hour)
|
||||
if got := limiter.Acquire(); got != "" {
|
||||
t.Fatalf("next day: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerCanNarrowBandwidth(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.BandwidthMBDay = 500
|
||||
limiter, _ := testLimiter(t, cfg, "2026-10-15T12:00:00Z")
|
||||
limiter.ApplyServer(nil, &ServerSettings{BandwidthMBDay: 2})
|
||||
if got := limiter.Remaining(); got != 2*mebibyte {
|
||||
t.Fatalf("remaining %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActiveHoursLocalAndServer(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
cfg.ActiveFrom, cfg.ActiveTo = "22:00", "06:00"
|
||||
limiter, c := testLimiter(t, cfg, "2026-10-15T12:00:00Z")
|
||||
if got := limiter.Acquire(); got != RefuseHours {
|
||||
t.Fatalf("noon: %q", got)
|
||||
}
|
||||
c.now = c.now.Add(11 * time.Hour)
|
||||
if got := limiter.Acquire(); got != "" {
|
||||
t.Fatalf("23:00: %q", got)
|
||||
}
|
||||
limiter.Release()
|
||||
limiter.ApplyServer(nil, &ServerSettings{ActiveFrom: "01:00", ActiveTo: "02:00", Timezone: "UTC"})
|
||||
if got := limiter.Acquire(); got != RefuseHours {
|
||||
t.Fatalf("server window: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsagePersistsAcrossRestart(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "usage.json")
|
||||
cfg := DefaultConfig()
|
||||
cfg.Timezone = "UTC"
|
||||
moment := time.Date(2026, 10, 15, 12, 0, 0, 0, time.UTC)
|
||||
c := &clock{now: moment}
|
||||
first, err := NewLimiter(cfg, path, c.Now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
first.AddBytes(12345)
|
||||
first.Flush()
|
||||
second, err := NewLimiter(cfg, path, c.Now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, used, _ := second.Usage(); used != 12345 {
|
||||
t.Fatalf("used %d", used)
|
||||
}
|
||||
c.now = moment.Add(24 * time.Hour)
|
||||
if _, used, _ := second.Usage(); used != 0 {
|
||||
t.Fatalf("next day used %d", used)
|
||||
}
|
||||
if got := ReadUsage(path, time.UTC, moment); got != 12345 {
|
||||
t.Fatalf("read usage %d", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user