134 lines
5.9 KiB
Go
134 lines
5.9 KiB
Go
package worker
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
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", WaitTimeout: config.Duration(25 * time.Millisecond)},
|
|
},
|
|
},
|
|
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}) ||
|
|
fallback.GetWaitTimeout().AsDuration() != 25*time.Millisecond {
|
|
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
|
|
}
|