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 TestStatsRecorderAssignsOneGenerationToEachEmptyEpisode(t *testing.T) { recorder, err := NewStatsRecorder(1) if err != nil { t.Fatalf("NewStatsRecorder() = %v", err) } recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchEmpty}) recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchEmpty}) first := recorder.ReadProviderStats([]string{"provider-a"})[0] if first.ConsecutiveEmptyFetch != 2 || first.EmptyGeneration == 0 { t.Fatalf("first empty episode = %+v, want count=2 and generation", first) } recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchValid}) recorder.Record(Result{UpstreamID: "provider-a", Class: upstream.FetchEmpty}) second := recorder.ReadProviderStats([]string{"provider-a"})[0] if second.ConsecutiveEmptyFetch != 1 || second.EmptyGeneration <= first.EmptyGeneration { t.Fatalf("second empty episode = %+v after %+v, want reset count and newer generation", second, first) } } 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) }