111 lines
3.7 KiB
Go
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)
|
|
}
|