103 lines
4.3 KiB
Go
103 lines
4.3 KiB
Go
package activitypool
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
healthDomain "proxy-pool/internal/domain/health"
|
|
proxyDomain "proxy-pool/internal/domain/proxy"
|
|
)
|
|
|
|
func TestMemoryPoolAppliesGlobalObservationAtomically(t *testing.T) {
|
|
now := time.Date(2026, 7, 31, 11, 0, 0, 0, time.UTC)
|
|
pool := seededHealthPool(t, now)
|
|
if _, err := pool.ApplyHealth(context.Background(), HealthUpdate{
|
|
ProxyID: "proxy-a", CheckedAt: now.Add(time.Second), NextState: proxyDomain.StateChecking,
|
|
}); err != nil {
|
|
t.Fatalf("ApplyHealth(checking): %v", err)
|
|
}
|
|
failed, err := pool.ApplyGlobalObservation(context.Background(), GlobalHealthCommand{
|
|
Observation: healthObservation("task-1", false, now.Add(2*time.Second)), MaxConsecutiveFailures: 3,
|
|
})
|
|
if err != nil || failed.State != proxyDomain.StateUnhealthy || failed.GlobalHealth.ConsecutiveFailures != 1 {
|
|
t.Fatalf("ApplyGlobalObservation(initial failure) = %+v, %v", failed, err)
|
|
}
|
|
if _, err := pool.ApplyHealth(context.Background(), HealthUpdate{
|
|
ProxyID: "proxy-a", CheckedAt: now.Add(3 * time.Second), NextState: proxyDomain.StateChecking,
|
|
}); err != nil {
|
|
t.Fatalf("ApplyHealth(recheck): %v", err)
|
|
}
|
|
available, err := pool.ApplyGlobalObservation(context.Background(), GlobalHealthCommand{
|
|
Observation: healthObservation("task-2", true, now.Add(4*time.Second)), MaxConsecutiveFailures: 3,
|
|
})
|
|
if err != nil || available.State != proxyDomain.StateAvailable || available.GlobalHealth.ConsecutiveFailures != 0 ||
|
|
available.Proxy.LastSuccessAt == nil || !available.Proxy.LastSuccessAt.Equal(now.Add(4*time.Second)) {
|
|
t.Fatalf("ApplyGlobalObservation(recovery) = %+v, %v", available, err)
|
|
}
|
|
}
|
|
|
|
func TestMemoryPoolGlobalObservationReplayDoesNotIncreaseFailureStreak(t *testing.T) {
|
|
now := time.Date(2026, 7, 31, 11, 0, 0, 0, time.UTC)
|
|
pool := seededHealthPool(t, now)
|
|
if _, err := pool.ApplyHealth(context.Background(), HealthUpdate{
|
|
ProxyID: "proxy-a", CheckedAt: now.Add(time.Second), NextState: proxyDomain.StateChecking,
|
|
}); err != nil {
|
|
t.Fatalf("ApplyHealth(checking): %v", err)
|
|
}
|
|
command := GlobalHealthCommand{Observation: healthObservation("task-1", false, now.Add(2*time.Second)), MaxConsecutiveFailures: 3}
|
|
first, err := pool.ApplyGlobalObservation(context.Background(), command)
|
|
if err != nil {
|
|
t.Fatalf("ApplyGlobalObservation(first): %v", err)
|
|
}
|
|
replayed, err := pool.ApplyGlobalObservation(context.Background(), command)
|
|
if err != nil || replayed.GlobalHealth != first.GlobalHealth {
|
|
t.Fatalf("ApplyGlobalObservation(replay) = %+v, %v", replayed, err)
|
|
}
|
|
conflicting := command
|
|
conflicting.Observation.Success = true
|
|
conflicting.Observation.FailureClass = ""
|
|
if _, err := pool.ApplyGlobalObservation(context.Background(), conflicting); !errors.Is(err, healthDomain.ErrConflictingObservation) {
|
|
t.Fatalf("ApplyGlobalObservation(conflict) error = %v, want ErrConflictingObservation", err)
|
|
}
|
|
}
|
|
|
|
func TestMemoryPoolRejectsTargetObservationFromGlobalStore(t *testing.T) {
|
|
now := time.Date(2026, 7, 31, 11, 0, 0, 0, time.UTC)
|
|
pool := seededHealthPool(t, now)
|
|
_, err := pool.ApplyGlobalObservation(context.Background(), GlobalHealthCommand{Observation: healthDomain.Observation{
|
|
TaskID: "task-target", ProxyID: "proxy-a", Level: healthDomain.LevelTarget, RoutingName: "route-a",
|
|
TargetURL: "https://target.example/", Success: false, FailureClass: "target_403", ObservedAt: now.Add(time.Second),
|
|
}, MaxConsecutiveFailures: 2})
|
|
if !errors.Is(err, ErrInvalidHealthUpdate) {
|
|
t.Fatalf("ApplyGlobalObservation(target) error = %v, want ErrInvalidHealthUpdate", err)
|
|
}
|
|
}
|
|
|
|
func seededHealthPool(t *testing.T, now time.Time) *MemoryPool {
|
|
t.Helper()
|
|
pool := NewMemoryPool()
|
|
_, err := pool.UpsertFetched(context.Background(), "provider-a", FetchedBatch{
|
|
ObservedAt: now, ConfiguredTTL: time.Minute, MaxSize: 1,
|
|
Proxies: []proxyDomain.Proxy{{
|
|
ID: "proxy-a", Scheme: proxyDomain.SchemeHTTP, Host: "192.0.2.10", Port: 8080,
|
|
}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpsertFetched(): %v", err)
|
|
}
|
|
return pool
|
|
}
|
|
|
|
func healthObservation(taskID string, success bool, observedAt time.Time) healthDomain.Observation {
|
|
observation := healthDomain.Observation{
|
|
TaskID: taskID, ProxyID: "proxy-a", Level: healthDomain.LevelBasic,
|
|
Success: success, Latency: 10 * time.Millisecond, ObservedAt: observedAt,
|
|
}
|
|
if !success {
|
|
observation.FailureClass = "timeout"
|
|
}
|
|
return observation
|
|
}
|