proxy-pool/internal/adapters/redisactivity/global_health_integration_test.go
youfak 0a904a7b6b
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: add atomic global health reducer
2026-07-31 17:55:06 +08:00

75 lines
3.1 KiB
Go

//go:build integration
package redisactivity
import (
"context"
"errors"
"testing"
"time"
"proxy-pool/internal/domain/activitypool"
healthDomain "proxy-pool/internal/domain/health"
proxyDomain "proxy-pool/internal/domain/proxy"
)
func TestRedisAppliesGlobalHealthObservationAtomically(t *testing.T) {
fixture := newRedisTestFixture(t)
now := redisTestNow()
if _, err := fixture.Adapter.UpsertFetched(context.Background(), "provider-a", activitypool.FetchedBatch{
ObservedAt: now, ConfiguredTTL: time.Minute, MaxSize: 1,
Proxies: []proxyDomain.Proxy{testProxy("proxy-a", "192.0.2.10")},
}); err != nil {
t.Fatalf("UpsertFetched(): %v", err)
}
if _, err := fixture.Adapter.ApplyHealth(context.Background(), activitypool.HealthUpdate{
ProxyID: "proxy-a", CheckedAt: now.Add(time.Second), NextState: proxyDomain.StateChecking,
}); err != nil {
t.Fatalf("ApplyHealth(checking): %v", err)
}
first := redisGlobalObservation("task-1", false, now.Add(2*time.Second))
failed, err := fixture.Adapter.ApplyGlobalObservation(context.Background(), activitypool.GlobalHealthCommand{
Observation: first, MaxConsecutiveFailures: 3,
})
if err != nil || failed.State != proxyDomain.StateUnhealthy || failed.GlobalHealth.ConsecutiveFailures != 1 {
t.Fatalf("ApplyGlobalObservation(initial failure) = %+v, %v", failed, err)
}
replayed, err := fixture.Adapter.ApplyGlobalObservation(context.Background(), activitypool.GlobalHealthCommand{
Observation: first, MaxConsecutiveFailures: 3,
})
if err != nil || replayed.GlobalHealth != failed.GlobalHealth {
t.Fatalf("ApplyGlobalObservation(replay) = %+v, %v", replayed, err)
}
conflicting := first
conflicting.Success = true
conflicting.FailureClass = ""
if _, err := fixture.Adapter.ApplyGlobalObservation(context.Background(), activitypool.GlobalHealthCommand{
Observation: conflicting, MaxConsecutiveFailures: 3,
}); !errors.Is(err, healthDomain.ErrConflictingObservation) {
t.Fatalf("ApplyGlobalObservation(conflict) error = %v, want ErrConflictingObservation", err)
}
if _, err := fixture.Adapter.ApplyHealth(context.Background(), activitypool.HealthUpdate{
ProxyID: "proxy-a", CheckedAt: now.Add(3 * time.Second), NextState: proxyDomain.StateChecking,
}); err != nil {
t.Fatalf("ApplyHealth(recheck): %v", err)
}
recovered, err := fixture.Adapter.ApplyGlobalObservation(context.Background(), activitypool.GlobalHealthCommand{
Observation: redisGlobalObservation("task-2", true, now.Add(4*time.Second)), MaxConsecutiveFailures: 3,
})
if err != nil || recovered.State != proxyDomain.StateAvailable || recovered.GlobalHealth.ConsecutiveFailures != 0 ||
recovered.GlobalHealth.LastObservedAt != now.Add(4*time.Second) {
t.Fatalf("ApplyGlobalObservation(recovery) = %+v, %v", recovered, err)
}
}
func redisGlobalObservation(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
}