proxy-pool/internal/controller/pool/reconciler_test.go
youfak 4de3ffb85f
Some checks are pending
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
feat: add ephemeral proxy activity pool
2026-07-29 12:51:18 +08:00

77 lines
2.3 KiB
Go

package pool
import (
"testing"
"time"
proxyDomain "proxy-pool/internal/domain/proxy"
"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++ }