proxy-pool/internal/gateway/server/bootstrap_test.go
youfak 4de3ffb85f
Some checks are pending
ci / test (ubuntu-latest) (push) Waiting to run
ci / test (windows-latest) (push) Waiting to run
ci / race (push) Waiting to run
feat: add ephemeral proxy activity pool
2026-07-29 12:51:18 +08:00

178 lines
6.2 KiB
Go

package server
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"proxy-pool/internal/config"
"proxy-pool/internal/gateway/policy"
"proxy-pool/internal/platform/httpsecurity"
)
func TestBuildProtectionFromListenerConfig(t *testing.T) {
t.Parallel()
protection, err := BuildProtection(config.Listener{
Access: config.Access{AllowCIDRs: []string{"198.51.100.0/24"}},
Auth: config.Auth{
Mode: "usernamePassword",
Username: "client",
Password: "secret",
},
Limits: config.Limits{RequestsPerMinute: 10, RequestsPerMinutePerClient: 2},
})
if err != nil {
t.Fatalf("BuildProtection() error = %v", err)
}
request := httptest.NewRequest(http.MethodGet, "http://example.test", nil)
request.RemoteAddr = "198.51.100.8:1234"
request.Header.Set("Proxy-Authorization", "Basic Y2xpZW50OnNlY3JldA==")
for name, guard := range map[string]Guard{
"auth": protection.Auth, "access": protection.Access, "admission": protection.Admission,
} {
if err := guard.Check(context.Background(), request); err != nil {
t.Fatalf("%s guard error = %v", name, err)
}
}
}
func TestBuildProtectionSupportsBearerProxyAuthentication(t *testing.T) {
t.Parallel()
protection, err := BuildProtection(config.Listener{Auth: config.Auth{Mode: "bearer", Token: "proxy-token"}})
if err != nil {
t.Fatalf("BuildProtection() error = %v", err)
}
request := httptest.NewRequest(http.MethodGet, "http://example.test", nil)
request.RemoteAddr = "198.51.100.8:1234"
request.Header.Set("Proxy-Authorization", "Bearer proxy-token")
if err := protection.Auth.Check(context.Background(), request); err != nil {
t.Fatalf("auth.Check() error = %v", err)
}
request.Header.Set("Proxy-Authorization", "Bearer wrong")
err = protection.Auth.Check(context.Background(), request)
var securityError *httpsecurity.HTTPError
if !errors.As(err, &securityError) || securityError.StatusCode != http.StatusProxyAuthRequired {
t.Fatalf("auth.Check(wrong) error = %T %v", err, err)
}
recorder := httptest.NewRecorder()
writeGatewayError(recorder, err)
if recorder.Code != http.StatusProxyAuthRequired || recorder.Header().Get("Proxy-Authenticate") == "" {
t.Fatalf("gateway response = status %d headers %v", recorder.Code, recorder.Header())
}
}
func TestBuildProtectionAnyPreservesIPWhitelistRejectionIndependentOfOrder(t *testing.T) {
t.Parallel()
methods := [][]config.AuthMethod{
{
{Mode: "ipWhitelist", CIDRs: []string{"10.0.0.0/8"}},
{Mode: "apiKey", Header: "X-Proxy-Key", Value: "secret"},
},
{
{Mode: "apiKey", Header: "X-Proxy-Key", Value: "secret"},
{Mode: "ipWhitelist", CIDRs: []string{"10.0.0.0/8"}},
},
}
for index, configuredMethods := range methods {
protection, err := BuildProtection(config.Listener{
Auth: config.Auth{Mode: "any", Methods: configuredMethods},
})
if err != nil {
t.Fatalf("BuildProtection(%d) error = %v", index, err)
}
request := httptest.NewRequest(http.MethodGet, "http://example.test", nil)
request.RemoteAddr = "198.51.100.8:1234"
err = protection.Auth.Check(context.Background(), request)
recorder := httptest.NewRecorder()
writeGatewayError(recorder, err)
if recorder.Code != http.StatusForbidden {
t.Fatalf("method order %d status = %d, want 403", index, recorder.Code)
}
}
}
func TestTargetPolicyFromOmittedConfigDefaultsToDeny(t *testing.T) {
t.Parallel()
targets, err := TargetPolicyFromListener(config.Listener{})
if err != nil {
t.Fatalf("TargetPolicyFromListener() error = %v", err)
}
if _, err := targets.EvaluateURL(context.Background(), "http://127.0.0.1/"); !errors.Is(err, policy.ErrTargetDenied) {
t.Fatalf("EvaluateURL(loopback) error = %v, want ErrTargetDenied", err)
}
if _, err := targets.EvaluateConnectAuthority(context.Background(), "8.8.8.8:22"); !errors.Is(err, policy.ErrTargetDenied) {
t.Fatalf("EvaluateConnectAuthority(port 22) error = %v, want ErrTargetDenied", err)
}
}
func TestTargetPolicyFromListenerMapsAllowedPorts(t *testing.T) {
t.Parallel()
targets, err := TargetPolicyFromListener(config.Listener{
DestinationPolicy: config.DestinationPolicy{AllowedPorts: []uint16{8443}},
})
if err != nil {
t.Fatalf("TargetPolicyFromListener() error = %v", err)
}
if _, err := targets.EvaluateConnectAuthority(context.Background(), "8.8.8.8:8443"); err != nil {
t.Fatalf("EvaluateConnectAuthority(port 8443) error = %v", err)
}
if _, err := targets.EvaluateConnectAuthority(context.Background(), "8.8.8.8:443"); !errors.Is(err, policy.ErrTargetDenied) {
t.Fatalf("EvaluateConnectAuthority(port 443) error = %v, want ErrTargetDenied", err)
}
}
func TestTargetPolicyFromPartialConfigKeepsOmittedCategoriesDenied(t *testing.T) {
t.Parallel()
deny := true
targets, err := TargetPolicyFromListener(config.Listener{
DestinationPolicy: config.DestinationPolicy{DenyPrivateNetworks: &deny},
})
if err != nil {
t.Fatalf("TargetPolicyFromListener() error = %v", err)
}
for _, target := range []string{"http://127.0.0.1/", "http://169.254.10.20/"} {
if _, err := targets.EvaluateURL(context.Background(), target); !errors.Is(err, policy.ErrTargetDenied) {
t.Fatalf("EvaluateURL(%q) error = %v, want ErrTargetDenied", target, err)
}
}
}
func TestTargetPolicyFromListenerAllowsOnlyExplicitlyFalseCategory(t *testing.T) {
t.Parallel()
allow := false
targets, err := TargetPolicyFromListener(config.Listener{
DestinationPolicy: config.DestinationPolicy{DenyLoopback: &allow},
})
if err != nil {
t.Fatalf("TargetPolicyFromListener() error = %v", err)
}
if _, err := targets.EvaluateURL(context.Background(), "http://127.0.0.1/"); err != nil {
t.Fatalf("EvaluateURL(loopback) error = %v", err)
}
for _, target := range []string{"http://10.0.0.1/", "http://169.254.10.20/"} {
if _, err := targets.EvaluateURL(context.Background(), target); !errors.Is(err, policy.ErrTargetDenied) {
t.Fatalf("EvaluateURL(%q) error = %v, want ErrTargetDenied", target, err)
}
}
}
func TestConfigFromListenerMapsRetryAndConcurrency(t *testing.T) {
t.Parallel()
result := ConfigFromListener(config.Listener{
Limits: config.Limits{MaxConcurrentConnections: 123},
Retry: config.Retry{MaxAttempts: 2, RetryMethods: []string{"GET", "HEAD"}},
})
if result.MaxConcurrentRequests != 123 || result.MaxAttempts != 2 || len(result.RetryMethods) != 2 {
t.Fatalf("handler config = %+v", result)
}
}