package pool import ( "testing" "time" proxyDomain "github.com/proxy-pool/proxy-pool/internal/domain/proxy" "github.com/proxy-pool/proxy-pool/internal/domain/upstream" ) func TestPoolReconcilerSignalsOnlyWhenSlotsAreLowAndWholeFetchFits(t *testing.T) { budget, err := NewFetchBudget(FetchBudgetConfig{ UpstreamID: "provider-a", MaxSize: 10, MaxTotal: 20, ExpectedPerFetch: 2, }) if err != nil { t.Fatalf("NewFetchBudget(): %v", err) } notifier := &recordingFetchNotifier{} reconciler, err := NewReconciler(ReconcilePolicy{ MinimumAvailableSlots: 5, ExpectedPerFetch: 2, SafetyMargin: 10 * time.Second, }, budget, notifier) if err != nil { t.Fatalf("NewReconciler(): %v", err) } now := time.Date(2026, 7, 28, 12, 0, 0, 0, time.UTC) inventory := upstream.Inventory{ Proxies: []upstream.ProxyCapacity{{ State: proxyDomain.StateAvailable, ExpiresAt: now.Add(time.Minute), Max: 4, Active: 3, }}, MaxSize: 10, MaxTotal: 20, } decision := reconciler.Reconcile(now, inventory) if !decision.Triggered || decision.AvailableSlots != 1 || decision.FetchAllowance != 2 { t.Fatalf("Reconcile() = %+v, want triggered with one available slot", decision) } if notifier.calls != 1 { t.Fatalf("Notify() calls = %d, want 1", notifier.calls) } } func TestPoolReconcilerUsesBudgetPendingAndCumulativeCounters(t *testing.T) { budget, err := NewFetchBudget(FetchBudgetConfig{ UpstreamID: "provider-a", MaxSize: 2, MaxTotal: 2, ExpectedPerFetch: 2, }) if err != nil { t.Fatalf("NewFetchBudget(): %v", err) } permit, ok, err := budget.ReserveFetch("provider-a") if err != nil || !ok { t.Fatalf("ReserveFetch() = (_, %v, %v), want permit", ok, err) } defer permit.Cancel() notifier := &recordingFetchNotifier{} reconciler, err := NewReconciler(ReconcilePolicy{ MinimumAvailableSlots: 1, ExpectedPerFetch: 2, }, budget, notifier) if err != nil { t.Fatalf("NewReconciler(): %v", err) } decision := reconciler.Reconcile(time.Now(), upstream.Inventory{MaxSize: 2, MaxTotal: 2}) if decision.Triggered || decision.PendingExpected != 2 || decision.FetchAllowance != 0 { t.Fatalf("Reconcile() = %+v, want pending fetch to suppress signal", decision) } if notifier.calls != 0 { t.Fatalf("Notify() calls = %d, want 0", notifier.calls) } } type recordingFetchNotifier struct{ calls int } func (n *recordingFetchNotifier) Notify() { n.calls++ }