proxy-pool/internal/controller/worker/routing_snapshot_source_test.go
youfak bc94f4dbf1
Some checks are pending
ci / proto (push) Waiting to run
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
ci / integration (push) Waiting to run
feat: publish gateway routing snapshots
2026-07-31 15:29:10 +08:00

132 lines
5.8 KiB
Go

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
}