154 lines
4.6 KiB
Go
154 lines
4.6 KiB
Go
package routing
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestRuleSetUsesFirstMatchingRule(t *testing.T) {
|
|
rules, err := Compile([]Rule{
|
|
{Name: "specific", Match: Match{HostRegex: `(^|\.)jd\.com$`}, Upstreams: []string{"jd"}},
|
|
{Name: "default", Match: Match{HostRegex: `.*`}, Action: ActionReject},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Compile(): %v", err)
|
|
}
|
|
|
|
got, ok := rules.Match(Request{Host: "api.jd.com", Method: "GET", Path: "/"})
|
|
if !ok || got.Name != "specific" {
|
|
t.Fatalf("Match() = %q, %v; want specific, true", got.Name, ok)
|
|
}
|
|
}
|
|
|
|
func TestRuleSetIsDetachedFromInputAndReturnedRules(t *testing.T) {
|
|
input := []Rule{{
|
|
Name: "route-a",
|
|
Match: Match{
|
|
HostRegex: `.*`,
|
|
Methods: []string{"GET"},
|
|
Headers: map[string]string{"X-Tenant": "a"},
|
|
},
|
|
Upstreams: []string{"provider-a"},
|
|
}}
|
|
rules, err := Compile(input)
|
|
if err != nil {
|
|
t.Fatalf("Compile(): %v", err)
|
|
}
|
|
input[0].Match.Methods[0] = "POST"
|
|
input[0].Match.Headers["X-Tenant"] = "changed"
|
|
input[0].Upstreams[0] = "changed"
|
|
|
|
matched, ok := rules.Match(Request{
|
|
Host: "example.com", Method: "GET", Headers: map[string]string{"X-Tenant": "a"},
|
|
})
|
|
if !ok || matched.Upstreams[0] != "provider-a" {
|
|
t.Fatalf("Match() after input mutation = %+v, %v", matched, ok)
|
|
}
|
|
matched.Match.Methods[0] = "DELETE"
|
|
matched.Match.Headers["X-Tenant"] = "returned-change"
|
|
matched.Upstreams[0] = "returned-change"
|
|
second, ok := rules.Match(Request{
|
|
Host: "example.com", Method: "GET", Headers: map[string]string{"X-Tenant": "a"},
|
|
})
|
|
if !ok || second.Upstreams[0] != "provider-a" || second.Match.Methods[0] != "GET" {
|
|
t.Fatalf("Match() after returned-rule mutation = %+v, %v", second, ok)
|
|
}
|
|
}
|
|
|
|
func TestSequentialSwitchesOnceAtThreshold(t *testing.T) {
|
|
sequence, err := NewSequential([]string{"a", "b", "c"}, 5)
|
|
if err != nil {
|
|
t.Fatalf("NewSequential(): %v", err)
|
|
}
|
|
for range 4 {
|
|
sequence.ObserveEmpty("a")
|
|
}
|
|
if got := sequence.Current(); got != "a" {
|
|
t.Fatalf("Current() = %q before threshold, want a", got)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for range 100 {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
sequence.ObserveEmpty("a")
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
|
|
if got := sequence.Current(); got != "b" {
|
|
t.Fatalf("Current() = %q after concurrent threshold, want b", got)
|
|
}
|
|
}
|
|
|
|
func TestSequentialValidFetchResetsEmptyCount(t *testing.T) {
|
|
sequence, _ := NewSequential([]string{"a", "b"}, 5)
|
|
for range 4 {
|
|
sequence.ObserveEmpty("a")
|
|
}
|
|
sequence.ObserveValid("a")
|
|
for range 4 {
|
|
sequence.ObserveEmpty("a")
|
|
}
|
|
if got := sequence.Current(); got != "a" {
|
|
t.Fatalf("Current() = %q, want a after reset", got)
|
|
}
|
|
}
|
|
|
|
func TestSequentialSharesUpstreamEmptyStateAcrossRoutingCursors(t *testing.T) {
|
|
empty := NewUpstreamEmptyState()
|
|
first, err := NewSequentialWithState([]string{"a", "b"}, 5, EndStayLast, empty)
|
|
if err != nil {
|
|
t.Fatalf("NewSequentialWithState(first): %v", err)
|
|
}
|
|
second, err := NewSequentialWithState([]string{"a", "c"}, 5, EndStayLast, empty)
|
|
if err != nil {
|
|
t.Fatalf("NewSequentialWithState(second): %v", err)
|
|
}
|
|
|
|
for range 5 {
|
|
first.ObserveEmpty("a")
|
|
}
|
|
if got := first.Current(); got != "b" {
|
|
t.Fatalf("first.Current() = %q, want b", got)
|
|
}
|
|
if got := second.EmptyCount("a"); got != 5 {
|
|
t.Fatalf("second.EmptyCount(a) = %d, want shared count 5", got)
|
|
}
|
|
if !second.ObserveEmpty("a") || second.Current() != "c" {
|
|
t.Fatalf("second did not advance from shared empty state: current=%q", second.Current())
|
|
}
|
|
}
|
|
|
|
func TestSequentialStopEndBehaviorHasNoCurrentSelection(t *testing.T) {
|
|
sequence, err := NewSequentialWithState([]string{"a"}, 1, EndStop, NewUpstreamEmptyState())
|
|
if err != nil {
|
|
t.Fatalf("NewSequentialWithState(): %v", err)
|
|
}
|
|
if !sequence.ObserveEmpty("a") {
|
|
t.Fatal("ObserveEmpty() = false, want transition to stopped")
|
|
}
|
|
if current, ok, version := sequence.CurrentSelection(); ok || current != "" || version != 2 {
|
|
t.Fatalf("CurrentSelection() = %q, %v, %d; want stopped version 2", current, ok, version)
|
|
}
|
|
}
|
|
|
|
func TestSequentialLoopDoesNotReuseSameEmptyEpisode(t *testing.T) {
|
|
empty := NewUpstreamEmptyState()
|
|
sequence, err := NewSequentialWithState([]string{"a", "b"}, 1, EndLoop, empty)
|
|
if err != nil {
|
|
t.Fatalf("NewSequentialWithState(): %v", err)
|
|
}
|
|
if !sequence.ObserveEmpty("a") || !sequence.ObserveEmpty("b") || sequence.Current() != "a" {
|
|
t.Fatalf("sequence did not loop to a: current=%q", sequence.Current())
|
|
}
|
|
if sequence.ObserveEmpty("a") || sequence.Current() != "a" {
|
|
t.Fatal("sequence reused the same a empty episode")
|
|
}
|
|
sequence.ObserveValid("a")
|
|
if !sequence.ObserveEmpty("a") || sequence.Current() != "b" {
|
|
t.Fatal("sequence did not advance after a new empty episode")
|
|
}
|
|
}
|