package worker import ( "context" "errors" "reflect" "testing" controlplanev1 "proxy-pool/gen/controlplane/v1" "proxy-pool/internal/config" "proxy-pool/internal/domain/adminstate" ) func TestGatewayRoutingSourceBuildsOrderedRulesFromConfigurationAndAdminState(t *testing.T) { configuration := &config.Config{ Routing: []config.Routing{ {Name: "extract", Enabled: true, Purpose: "extract", Upstreams: []string{"provider-a"}, Strategy: config.Strategy{Type: "random"}, OnUnavailable: config.OnUnavailable{Action: "reject"}}, { Name: "checkout", Enabled: true, Purpose: "gateway", Match: config.RoutingMatch{HostRegex: "^api\\.example$", Methods: []string{"GET"}, Headers: map[string]string{"X-Tier": "gold"}}, Upstreams: []string{"provider-a", "provider-b"}, Strategy: config.Strategy{Type: "sequential", SwitchAfterEmptyFetch: 5}, OnUnavailable: config.OnUnavailable{Action: "reject"}, }, { Name: "fallback", Enabled: true, Purpose: "gateway", Upstreams: []string{"provider-a", "provider-c"}, Strategy: config.Strategy{Type: "weighted", Weights: map[string]int{"provider-a": 3, "provider-c": 7}}, OnUnavailable: config.OnUnavailable{Action: "wait"}, }, }, Upstreams: map[string]config.Upstream{ "provider-a": {Enabled: true}, "provider-b": {Enabled: false}, "provider-c": {Enabled: true}, }, } state := adminstate.Snapshot{ Config: &adminstate.ConfigRevision{Revision: 9}, Upstreams: []adminstate.UpstreamState{ {Name: "provider-a", Enabled: true}, {Name: "provider-b", Enabled: false}, {Name: "provider-c", Enabled: true}, }, Routings: []adminstate.RoutingState{ {Name: "checkout", Enabled: true, Upstreams: []string{"provider-a", "provider-b"}, CurrentUpstream: "provider-a"}, {Name: "fallback", Enabled: true, Upstreams: []string{"provider-a", "provider-c"}, CurrentUpstream: "provider-a"}, }, } source, err := NewGatewayRoutingSource(staticGatewayRoutingConfiguration{configuration: configuration, revision: 9}, staticGatewayRoutingState{snapshot: state}) if err != nil { t.Fatalf("NewGatewayRoutingSource(): %v", err) } rules, err := source.Read(context.Background()) if err != nil { t.Fatalf("Read(): %v", err) } if len(rules) != 2 || rules[0].GetName() != "checkout" || rules[1].GetName() != "fallback" { t.Fatalf("rules = %+v, want ordered gateway rules", rules) } checkout := rules[0] if !checkout.GetEnabled() || checkout.GetStrategy().GetType() != controlplanev1.StrategyType_STRATEGY_TYPE_SEQUENTIAL || checkout.GetStrategy().GetCurrentUpstream() != "provider-a" || !reflect.DeepEqual(checkout.GetUpstreams(), []string{"provider-a"}) || checkout.GetHostRegex() != "^api\\.example$" || checkout.GetHeaders()["X-Tier"] != "gold" { t.Fatalf("checkout rule = %+v", checkout) } fallback := rules[1] if !fallback.GetEnabled() || fallback.GetOnUnavailable() != controlplanev1.UnavailableAction_UNAVAILABLE_ACTION_WAIT || !reflect.DeepEqual(fallback.GetUpstreams(), []string{"provider-a", "provider-c"}) || !reflect.DeepEqual(fallback.GetStrategy().GetWeights(), map[string]uint32{"provider-a": 3, "provider-c": 7}) { t.Fatalf("fallback rule = %+v", fallback) } } func TestGatewayRoutingSourceFailsClosedWhenAdminStateDoesNotMatchConfiguration(t *testing.T) { configuration := &config.Config{Routing: []config.Routing{{ Name: "gateway", Enabled: true, Purpose: "gateway", Upstreams: []string{"provider-a"}, Strategy: config.Strategy{Type: "random"}, OnUnavailable: config.OnUnavailable{Action: "reject"}, }}, Upstreams: map[string]config.Upstream{"provider-a": {Enabled: true}}} source, err := NewGatewayRoutingSource( staticGatewayRoutingConfiguration{configuration: configuration, revision: 5}, staticGatewayRoutingState{snapshot: adminstate.Snapshot{Config: &adminstate.ConfigRevision{Revision: 4}}}, ) if err != nil { t.Fatalf("NewGatewayRoutingSource(): %v", err) } if _, err := source.Read(context.Background()); !errors.Is(err, ErrRoutingUnavailable) { t.Fatalf("Read() error = %v, want ErrRoutingUnavailable", err) } } func TestGatewayRoutingSourceDisablesSequentialRuleWhenCurrentUpstreamIsUnavailable(t *testing.T) { configuration := &config.Config{Routing: []config.Routing{{ Name: "gateway", Enabled: true, Purpose: "gateway", Upstreams: []string{"provider-a", "provider-b"}, Strategy: config.Strategy{Type: "sequential"}, OnUnavailable: config.OnUnavailable{Action: "reject"}, }}, Upstreams: map[string]config.Upstream{ "provider-a": {Enabled: true}, "provider-b": {Enabled: true}, }} source, err := NewGatewayRoutingSource( staticGatewayRoutingConfiguration{configuration: configuration, revision: 5}, staticGatewayRoutingState{snapshot: adminstate.Snapshot{ Config: &adminstate.ConfigRevision{Revision: 5}, Upstreams: []adminstate.UpstreamState{{Name: "provider-a", Enabled: false}, {Name: "provider-b", Enabled: true}}, Routings: []adminstate.RoutingState{{Name: "gateway", Enabled: true, CurrentUpstream: "provider-a"}}, }}, ) if err != nil { t.Fatalf("NewGatewayRoutingSource(): %v", err) } rules, err := source.Read(context.Background()) if err != nil { t.Fatalf("Read(): %v", err) } if len(rules) != 1 || rules[0].GetEnabled() || rules[0].GetStrategy().GetCurrentUpstream() != "provider-a" || !reflect.DeepEqual(rules[0].GetUpstreams(), []string{"provider-b"}) { t.Fatalf("rules = %+v, want disabled sequential rule with provider-b remaining", rules) } } type staticGatewayRoutingConfiguration struct { configuration *config.Config revision uint64 } func (reader staticGatewayRoutingConfiguration) Snapshot() (*config.Config, uint64) { return reader.configuration, reader.revision } type staticGatewayRoutingState struct { snapshot adminstate.Snapshot err error } func (reader staticGatewayRoutingState) Snapshot(context.Context) (adminstate.Snapshot, error) { return reader.snapshot, reader.err }