131 lines
4.9 KiB
Go
131 lines
4.9 KiB
Go
package operations
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"proxy-pool/internal/config"
|
|
"proxy-pool/internal/controller/provider"
|
|
"proxy-pool/internal/domain/activitypool"
|
|
)
|
|
|
|
func TestReaderMapsCurrentUpstreamsToAdminOperationalStatus(t *testing.T) {
|
|
t.Parallel()
|
|
now := time.Date(2026, 7, 30, 10, 0, 0, 0, time.UTC)
|
|
inventory := &recordingStateInventoryReader{result: []activitypool.StateInventory{
|
|
{UpstreamID: "provider-a", Fetched: 3, Checking: 2, Available: 11, Suspect: 1, Draining: 4, Extracted: 8},
|
|
{UpstreamID: "provider-b", Available: 7},
|
|
}}
|
|
reader, err := NewReader(staticConfigurationReader{configuration: &config.Config{
|
|
Upstreams: map[string]config.Upstream{"provider-b": {}, "provider-a": {}},
|
|
}}, inventory, func() time.Time { return now }, staticProviderStatsReader{result: []provider.Stats{
|
|
{UpstreamID: "provider-a", ConsecutiveEmptyFetch: 3, FetchErrorCount: 4},
|
|
{UpstreamID: "provider-b", FetchErrorCount: 1},
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("NewReader() error = %v", err)
|
|
}
|
|
|
|
status, err := reader.ReadOperationalStatus(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("ReadOperationalStatus() error = %v", err)
|
|
}
|
|
if len(inventory.upstreamIDs) != 2 || inventory.upstreamIDs[0] != "provider-a" || inventory.upstreamIDs[1] != "provider-b" ||
|
|
!inventory.now.Equal(now) {
|
|
t.Fatalf("ReadStateInventory() input = %+v at %v", inventory.upstreamIDs, inventory.now)
|
|
}
|
|
if status.SnapshotVersion != 0 || len(status.Workers) != 0 || len(status.Upstreams) != 2 {
|
|
t.Fatalf("operational status shape = %+v", status)
|
|
}
|
|
first := status.Upstreams[0]
|
|
if first.Name != "provider-a" || first.Available != 11 || first.Checking != 2 || first.Suspect != 1 ||
|
|
first.Draining != 4 || first.Extracted != 8 || first.ConsecutiveEmptyFetch != 3 || first.FetchErrorCount != 4 {
|
|
t.Fatalf("first upstream = %+v", first)
|
|
}
|
|
if status.Upstreams[1].Name != "provider-b" || status.Upstreams[1].Available != 7 {
|
|
t.Fatalf("second upstream = %+v", status.Upstreams[1])
|
|
}
|
|
}
|
|
|
|
func TestReaderRejectsMissingOrMalformedDependencies(t *testing.T) {
|
|
t.Parallel()
|
|
now := func() time.Time { return time.Now().UTC() }
|
|
validConfig := staticConfigurationReader{configuration: &config.Config{
|
|
Upstreams: map[string]config.Upstream{"provider-a": {}},
|
|
}}
|
|
validInventory := &recordingStateInventoryReader{result: []activitypool.StateInventory{{UpstreamID: "provider-a"}}}
|
|
|
|
if _, err := NewReader(nil, validInventory, now); !errors.Is(err, ErrInvalidReader) {
|
|
t.Fatalf("NewReader(nil config) error = %v", err)
|
|
}
|
|
if _, err := NewReader(validConfig, nil, now); !errors.Is(err, ErrInvalidReader) {
|
|
t.Fatalf("NewReader(nil inventory) error = %v", err)
|
|
}
|
|
if _, err := NewReader(validConfig, validInventory, nil); !errors.Is(err, ErrInvalidReader) {
|
|
t.Fatalf("NewReader(nil clock) error = %v", err)
|
|
}
|
|
|
|
malformed := &recordingStateInventoryReader{result: []activitypool.StateInventory{{UpstreamID: "wrong"}}}
|
|
reader, err := NewReader(validConfig, malformed, now)
|
|
if err != nil {
|
|
t.Fatalf("NewReader() error = %v", err)
|
|
}
|
|
if _, err := reader.ReadOperationalStatus(context.Background()); !errors.Is(err, ErrUnavailable) {
|
|
t.Fatalf("ReadOperationalStatus(malformed) error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReaderPreservesCancellationAndClassifiesDependencyFailures(t *testing.T) {
|
|
t.Parallel()
|
|
dependencyError := errors.New("redis down")
|
|
inventory := &recordingStateInventoryReader{err: dependencyError}
|
|
reader, err := NewReader(staticConfigurationReader{configuration: &config.Config{
|
|
Upstreams: map[string]config.Upstream{"provider-a": {}},
|
|
}}, inventory, time.Now)
|
|
if err != nil {
|
|
t.Fatalf("NewReader() error = %v", err)
|
|
}
|
|
if _, err := reader.ReadOperationalStatus(context.Background()); !errors.Is(err, ErrUnavailable) || !errors.Is(err, dependencyError) {
|
|
t.Fatalf("ReadOperationalStatus(dependency) error = %v", err)
|
|
}
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if _, err := reader.ReadOperationalStatus(ctx); !errors.Is(err, context.Canceled) || errors.Is(err, ErrUnavailable) {
|
|
t.Fatalf("ReadOperationalStatus(canceled) error = %v", err)
|
|
}
|
|
}
|
|
|
|
type staticConfigurationReader struct {
|
|
configuration *config.Config
|
|
}
|
|
|
|
func (reader staticConfigurationReader) Current() *config.Config { return reader.configuration }
|
|
|
|
type recordingStateInventoryReader struct {
|
|
result []activitypool.StateInventory
|
|
err error
|
|
upstreamIDs []string
|
|
now time.Time
|
|
}
|
|
|
|
type staticProviderStatsReader struct {
|
|
result []provider.Stats
|
|
}
|
|
|
|
func (reader staticProviderStatsReader) ReadProviderStats([]string) []provider.Stats {
|
|
return append([]provider.Stats(nil), reader.result...)
|
|
}
|
|
|
|
func (reader *recordingStateInventoryReader) ReadStateInventory(
|
|
_ context.Context,
|
|
upstreamIDs []string,
|
|
now time.Time,
|
|
) ([]activitypool.StateInventory, error) {
|
|
reader.upstreamIDs = append([]string(nil), upstreamIDs...)
|
|
reader.now = now
|
|
return append([]activitypool.StateInventory(nil), reader.result...), reader.err
|
|
}
|