89 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|