proxy-pool/internal/controller/health/scheduler_test.go
youfak 864320a2c8
Some checks are pending
ci / proto (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
ci / integration (push) Waiting to run
feat: support egress probe task contracts
2026-07-31 22:32:49 +08:00

89 lines
3.4 KiB
Go

package health
import (
"errors"
"testing"
"time"
healthDomain "proxy-pool/internal/domain/health"
proxyDomain "proxy-pool/internal/domain/proxy"
)
func TestPlannerPrioritizesAndBoundsOverdueCandidates(t *testing.T) {
planner, err := NewPlanner(SchedulePolicy{
Interval: time.Minute, Jitter: 20, MaxInFlight: 3, Timeout: 4 * time.Second, MaxAttempts: 2,
})
if err != nil {
t.Fatalf("NewPlanner(): %v", err)
}
now := time.Date(2026, 7, 31, 17, 0, 0, 0, time.UTC)
plans, err := planner.Plan(now, 1, 10, []Candidate{
{ProxyID: "available", State: proxyDomain.StateAvailable, Level: healthDomain.LevelBasic, DueAt: now.Add(-time.Second)},
{ProxyID: "suspect", State: proxyDomain.StateSuspect, Level: healthDomain.LevelEgress, TargetURL: "https://egress.example/identity", DueAt: now.Add(-2 * time.Second)},
{ProxyID: "fetched", State: proxyDomain.StateFetched, Level: healthDomain.LevelBasic, DueAt: now.Add(-3 * time.Second)},
{ProxyID: "future", State: proxyDomain.StateFetched, Level: healthDomain.LevelBasic, DueAt: now.Add(time.Second)},
})
if err != nil || len(plans) != 2 {
t.Fatalf("Plan() = (%+v, %v)", plans, err)
}
if plans[0].Candidate.ProxyID != "fetched" || plans[0].Priority != PriorityFetched ||
plans[1].Candidate.ProxyID != "suspect" || plans[1].Priority != PrioritySuspect ||
plans[0].Deadline != now.Add(4*time.Second) || plans[0].Attempts != 2 {
t.Fatalf("plans = %+v", plans)
}
}
func TestPlannerSchedulesUnhealthyForRecovery(t *testing.T) {
planner, err := NewPlanner(SchedulePolicy{
Interval: time.Minute, MaxInFlight: 1, Timeout: time.Second, MaxAttempts: 1,
})
if err != nil {
t.Fatalf("NewPlanner(): %v", err)
}
now := time.Date(2026, 7, 31, 17, 0, 0, 0, time.UTC)
plans, err := planner.Plan(now, 0, 1, []Candidate{{
ProxyID: "unhealthy", State: proxyDomain.StateUnhealthy, Level: healthDomain.LevelBasic, DueAt: now,
}})
if err != nil || len(plans) != 1 || plans[0].Priority != PriorityUnhealthy {
t.Fatalf("Plan(unhealthy) = (%+v, %v)", plans, err)
}
}
func TestPlannerDerivesStableBoundedJitter(t *testing.T) {
planner, err := NewPlanner(SchedulePolicy{
Interval: time.Minute, Jitter: 20, MaxInFlight: 1, Timeout: time.Second, MaxAttempts: 1,
})
if err != nil {
t.Fatalf("NewPlanner(): %v", err)
}
checkedAt := time.Date(2026, 7, 31, 17, 0, 0, 0, time.UTC)
first, err := planner.NextDue(checkedAt, "proxy-a\x00BASIC")
if err != nil {
t.Fatalf("NextDue(first): %v", err)
}
second, err := planner.NextDue(checkedAt, "proxy-a\x00BASIC")
if err != nil || second != first {
t.Fatalf("NextDue(stable) = (%v, %v), want %v", second, err, first)
}
base := checkedAt.Add(time.Minute)
if delta := first.Sub(base); delta < -12*time.Second || delta > 12*time.Second {
t.Fatalf("NextDue jitter delta = %s, want [-12s, 12s]", delta)
}
}
func TestPlannerRejectsInvalidCandidatesWithoutPartialPlan(t *testing.T) {
planner, err := NewPlanner(SchedulePolicy{
Interval: time.Minute, MaxInFlight: 1, Timeout: time.Second, MaxAttempts: 1,
})
if err != nil {
t.Fatalf("NewPlanner(): %v", err)
}
_, err = planner.Plan(time.Now(), 0, 1, []Candidate{{
ProxyID: "proxy-a", State: proxyDomain.StateFetched, Level: healthDomain.LevelTarget,
RoutingName: "route-a", TargetURL: "not-a-url", DueAt: time.Now(),
}})
if !errors.Is(err, ErrInvalidScheduleRequest) {
t.Fatalf("Plan(invalid target) error = %v, want ErrInvalidScheduleRequest", err)
}
}