package health import ( "context" "errors" "testing" "time" "proxy-pool/internal/config" "proxy-pool/internal/domain/activitypool" healthDomain "proxy-pool/internal/domain/health" ) func TestConfiguredFailureThresholdResolverUsesCurrentProxyUpstream(t *testing.T) { now := time.Date(2026, 7, 31, 16, 0, 0, 0, time.UTC) source := &configurationSourceStub{configuration: &config.Config{ Defaults: config.Defaults{Check: config.Check{MaxConsecutiveFailures: 2}}, Upstreams: map[string]config.Upstream{ "provider-a": {Enabled: true, Check: config.Check{MaxConsecutiveFailures: 4}}, "provider-b": {Enabled: true}, }, }} proxies := &proxyUpstreamReaderStub{upstreams: map[string]string{"proxy-a": "provider-a", "proxy-b": "provider-b"}} resolver, err := NewConfiguredFailureThresholdResolver(source, proxies, func() time.Time { return now }) if err != nil { t.Fatalf("NewConfiguredFailureThresholdResolver(): %v", err) } if threshold, err := resolver(context.Background(), reducerObservation(healthDomain.LevelBasic, now)); err != nil || threshold != 4 { t.Fatalf("resolver(provider-a) = (%d, %v)", threshold, err) } fact := reducerObservation(healthDomain.LevelTarget, now) fact.ProxyID = "proxy-b" if threshold, err := resolver(context.Background(), fact); err != nil || threshold != 2 { t.Fatalf("resolver(provider-b default) = (%d, %v)", threshold, err) } if proxies.now != now { t.Fatalf("lookup time = %v, want %v", proxies.now, now) } } func TestConfiguredFailureThresholdResolverRejectsUnknownAndInvalidInputs(t *testing.T) { now := time.Date(2026, 7, 31, 16, 0, 0, 0, time.UTC) source := &configurationSourceStub{configuration: &config.Config{Upstreams: map[string]config.Upstream{}}} proxies := &proxyUpstreamReaderStub{upstreams: map[string]string{"proxy-a": "missing"}} resolver, err := NewConfiguredFailureThresholdResolver(source, proxies, func() time.Time { return now }) if err != nil { t.Fatalf("NewConfiguredFailureThresholdResolver(): %v", err) } if _, err := resolver(context.Background(), reducerObservation(healthDomain.LevelBasic, now)); !errors.Is(err, ErrUnconfiguredUpstream) { t.Fatalf("resolver(unknown) error = %v, want ErrUnconfiguredUpstream", err) } if resolver, err := NewConfiguredFailureThresholdResolver(nil, proxies, func() time.Time { return now }); err == nil || resolver != nil { t.Fatalf("NewConfiguredFailureThresholdResolver(nil configuration) = (%v, %v)", resolver, err) } var typedNilReader *proxyUpstreamReaderStub if resolver, err := NewConfiguredFailureThresholdResolver(source, typedNilReader, func() time.Time { return now }); err == nil || resolver != nil { t.Fatalf("NewConfiguredFailureThresholdResolver(typed nil reader) = (%v, %v)", resolver, err) } } type configurationSourceStub struct { configuration *config.Config } func (source *configurationSourceStub) Current() *config.Config { return source.configuration } type proxyUpstreamReaderStub struct { upstreams map[string]string now time.Time err error } func (reader *proxyUpstreamReaderStub) UpstreamForProxy(_ context.Context, proxyID string, now time.Time) (string, error) { reader.now = now if reader.err != nil { return "", reader.err } upstream, exists := reader.upstreams[proxyID] if !exists { return "", activitypool.ErrActivityNotFound } return upstream, nil }