package server import ( "errors" "net/http" "net/http/httptest" "reflect" "testing" "time" "proxy-pool/internal/domain/routing" "proxy-pool/internal/gateway/snapshot" ) func TestRulesRouterReturnsMatchedUpstreams(t *testing.T) { t.Parallel() rules, err := routing.Compile([]routing.Rule{{ Name: "api", Match: routing.Match{HostRegex: `^example\.test$`, Methods: []string{http.MethodGet}, PathRegex: `^/v1/`}, Upstreams: []string{"provider-a", "provider-b"}, Action: routing.ActionProxy, }}) if err != nil { t.Fatalf("routing.Compile() error = %v", err) } router := NewRulesRouter(rules) request := httptest.NewRequest(http.MethodGet, "http://example.test/v1/items", nil) result, err := router.Route(request) if err != nil { t.Fatalf("Route() error = %v", err) } if !reflect.DeepEqual(result.Upstreams, []string{"provider-a", "provider-b"}) { t.Fatalf("upstreams = %v", result.Upstreams) } } func TestRulesRouterReturnsStaticDirectRoute(t *testing.T) { t.Parallel() rules, err := routing.Compile([]routing.Rule{{ Name: "direct-api", Match: routing.Match{HostRegex: "^api\\.example\\.test$"}, Action: routing.ActionDirect, }}) if err != nil { t.Fatalf("routing.Compile() error = %v", err) } result, err := NewRulesRouter(rules).Route(httptest.NewRequest(http.MethodGet, "http://api.example.test/v1/items", nil)) if err != nil { t.Fatalf("Route() error = %v", err) } if result.RoutingName != "direct-api" || result.Action != routing.ActionDirect || len(result.Upstreams) != 0 { t.Fatalf("Route() = %+v", result) } } func TestRulesRouterRejectsExplicitRejectAndMissingRoute(t *testing.T) { t.Parallel() rules, err := routing.Compile([]routing.Rule{{ Name: "blocked", Match: routing.Match{HostRegex: `^blocked\.test$`}, Action: routing.ActionReject, }}) if err != nil { t.Fatalf("routing.Compile() error = %v", err) } router := NewRulesRouter(rules) if _, err := router.Route(httptest.NewRequest(http.MethodGet, "http://blocked.test/", nil)); !errors.Is(err, ErrRouteRejected) { t.Fatalf("blocked Route() error = %v", err) } if _, err := router.Route(httptest.NewRequest(http.MethodGet, "http://missing.test/", nil)); !errors.Is(err, ErrRouteNotFound) { t.Fatalf("missing Route() error = %v", err) } } func TestSnapshotRouterMatchesRulesFromCurrentSnapshot(t *testing.T) { store := snapshot.NewStore("cluster-a", "worker-a") envelope := snapshot.Envelope{ ClusterID: "cluster-a", WorkerID: "worker-a", Epoch: 1, Version: 1, Full: true, ValidUntil: time.Now().Add(time.Minute), Routing: []routing.Rule{{ Name: "gateway-api", Match: routing.Match{HostRegex: `^api\.example\.test$`}, Upstreams: []string{"provider-a"}, Action: routing.ActionProxy, Strategy: routing.Strategy{Type: routing.StrategyWeighted, Weights: map[string]uint32{"provider-a": 3}}, OnUnavailable: routing.OnUnavailableWait, WaitTimeout: 25 * time.Millisecond, }}, } envelope.Checksum = snapshot.ChecksumWithRouting(envelope.Proxies, envelope.Routing) if err := store.Apply(envelope); err != nil { t.Fatalf("Apply(): %v", err) } router := NewSnapshotRouter(store) result, err := router.Route(httptest.NewRequest(http.MethodGet, "http://api.example.test/items", nil)) if err != nil { t.Fatalf("Route(): %v", err) } if result.RoutingName != "gateway-api" || result.Strategy.Type != routing.StrategyWeighted || !reflect.DeepEqual(result.Strategy.Weights, map[string]uint32{"provider-a": 3}) || result.OnUnavailable != routing.OnUnavailableWait || result.WaitTimeout != 25*time.Millisecond { t.Fatalf("Route() = %+v", result) } }