package allocator import ( "errors" "sync" "testing" "time" ) func TestFixedWindowBudgetLimitsEachRegionAndResets(t *testing.T) { now := time.Unix(1000, 0).UTC() budget, err := NewFixedWindowBudget(2, time.Minute) if err != nil { t.Fatal(err) } for i := 0; i < 2; i++ { if err := budget.Allow("EU", now); err != nil { t.Fatalf("EU attempt %d: %v", i, err) } } if err := budget.Allow("EU", now); !errors.Is(err, ErrAllocationBudgetExceeded) { t.Fatalf("third EU attempt = %v, want budget error", err) } if err := budget.Allow("NA", now); err != nil { t.Fatalf("NA should have an independent budget: %v", err) } if err := budget.Allow("EU", now.Add(time.Minute)); err != nil { t.Fatalf("EU after window: %v", err) } } func TestFixedWindowBudgetIsAtomicUnderConcurrentAttempts(t *testing.T) { budget, err := NewFixedWindowBudget(7, time.Minute) if err != nil { t.Fatal(err) } now := time.Unix(1000, 0).UTC() var wg sync.WaitGroup var mu sync.Mutex allowed := 0 for i := 0; i < 64; i++ { wg.Add(1) go func() { defer wg.Done() if budget.Allow("EU", now) == nil { mu.Lock() allowed++ mu.Unlock() } }() } wg.Wait() if allowed != 7 { t.Fatalf("allowed=%d, want exactly 7", allowed) } } func TestFixedWindowBudgetRejectsInvalidConfigurationAndInput(t *testing.T) { if _, err := NewFixedWindowBudget(0, time.Minute); err == nil { t.Fatal("zero limit accepted") } if _, err := NewFixedWindowBudget(1, 0); err == nil { t.Fatal("zero window accepted") } budget, _ := NewFixedWindowBudget(1, time.Minute) if err := budget.Allow("APAC", time.Unix(1000, 0)); err == nil { t.Fatal("unknown region accepted") } if err := budget.Allow("EU", time.Time{}); err == nil { t.Fatal("zero time accepted") } }