proxy-pool/internal/controller/provider/stats_test.go
2026-08-02 13:55:23 +08:00

111 lines
3.7 KiB
Go

package provider
import (
"sync"
"testing"
"proxy-pool/internal/domain/upstream"
)
func TestStatsRecorderTracksEmptyResetAndErrors(t *testing.T) {
recorder, err := NewStatsRecorder(2)
if err != nil {
t.Fatalf("NewStatsRecorder(): %v", err)
}
for _, class := range []upstream.FetchClass{
upstream.FetchEmpty,
upstream.FetchEmpty,
upstream.FetchError,
} {
recorder.Record(Result{UpstreamID: "provider-a", Class: class})
}
stats := recorder.ReadProviderStats([]string{"provider-a"})[0]
if stats.ConsecutiveEmptyFetch != 2 || stats.FetchErrorCount != 1 {
t.Fatalf("stats = %+v, want empty=2 errors=1", stats)
}
recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchDuplicateOnly})
if got := recorder.ReadProviderStats([]string{"provider-a"})[0].ConsecutiveEmptyFetch; got != 0 {
t.Fatalf("consecutive empty after duplicate = %d, want 0", got)
}
}
func TestStatsRecorderIsBoundedAndConcurrent(t *testing.T) {
recorder, err := NewStatsRecorder(1)
if err != nil {
t.Fatalf("NewStatsRecorder(): %v", err)
}
const workers = 100
var wait sync.WaitGroup
for range workers {
wait.Add(1)
go func() {
defer wait.Done()
recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchError})
}()
}
wait.Wait()
recorder.Record(Result{UpstreamID: "provider-b", Class: upstream.FetchError})
stats := recorder.ReadProviderStats([]string{"provider-a", "provider-b"})
if stats[0].FetchErrorCount != workers || stats[1].FetchErrorCount != 0 {
t.Fatalf("stats = %+v, want bounded provider-a errors", stats)
}
}
func TestStatsRecorderRetainsConfiguredProvidersAndReusesCapacity(t *testing.T) {
recorder, err := NewStatsRecorder(2)
if err != nil {
t.Fatalf("NewStatsRecorder(): %v", err)
}
recorder.Record(Result{UpstreamID: "removed-a", Class: upstream.FetchError})
recorder.Record(Result{UpstreamID: "provider-b", Class: upstream.FetchError})
const workers = 100
var wait sync.WaitGroup
for range workers {
wait.Add(2)
go func() {
defer wait.Done()
recorder.Record(Result{UpstreamID: "provider-b", Class: upstream.FetchError})
}()
go func() {
defer wait.Done()
recorder.RetainProviderStats([]string{"provider-a", "provider-b"})
}()
}
wait.Wait()
recorder.RetainProviderStats([]string{"provider-a", "provider-b"})
recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchError})
stats := recorder.ReadProviderStats([]string{"removed-a", "provider-a", "provider-b"})
if stats[0].FetchErrorCount != 0 || stats[1].FetchErrorCount != 1 || stats[2].FetchErrorCount == 0 {
t.Fatalf("stats after retention = %+v", stats)
}
}
func TestStatsRecorderObservesValidatedResultsOutsideBoundedStatusStorage(t *testing.T) {
recorder, err := NewStatsRecorder(1)
if err != nil {
t.Fatalf("NewStatsRecorder() = %v", err)
}
observer := &providerResultObserverStub{}
recorder.AddResultObserver(observer)
recorder.AddResultObserver(observer)
recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchEmpty})
recorder.Record(Result{UpstreamID: "provider-b", Class: upstream.FetchValid, ValidCount: 3, NewCount: 2})
recorder.Record(Result{UpstreamID: "provider-c", Class: "invalid"})
if len(observer.results) != 2 || observer.results[0].UpstreamID != "provider-a" || observer.results[1].UpstreamID != "provider-b" {
t.Fatalf("observed results = %+v", observer.results)
}
stats := recorder.ReadProviderStats([]string{"provider-a", "provider-b"})
if stats[0].ConsecutiveEmptyFetch != 1 || stats[1].ConsecutiveEmptyFetch != 0 || stats[1].FetchErrorCount != 0 {
t.Fatalf("bounded status stats = %+v", stats)
}
}
type providerResultObserverStub struct{ results []Result }
func (observer *providerResultObserverStub) ObserveProviderResult(result Result) {
observer.results = append(observer.results, result)
}