77 lines
2.4 KiB
Go
77 lines
2.4 KiB
Go
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++ }
|