234 lines
7.1 KiB
Go
234 lines
7.1 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"galaxy/redisconn"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var configEnvMu sync.Mutex
|
|
|
|
const (
|
|
gatewayRedisMasterAddrEnvVar = "GATEWAY_REDIS_MASTER_ADDR"
|
|
gatewayRedisPasswordEnvVar = "GATEWAY_REDIS_PASSWORD"
|
|
)
|
|
|
|
const (
|
|
defaultTestRedisMasterAddrValue = "127.0.0.1:6379"
|
|
defaultTestRedisPasswordValue = "secret"
|
|
defaultTestBackendHTTPURL = "http://127.0.0.1:8080"
|
|
defaultTestBackendGRPCPushURL = "127.0.0.1:8081"
|
|
defaultTestBackendClientID = "gw-test"
|
|
)
|
|
|
|
func TestLoadFromEnvAppliesBackendDefaults(t *testing.T) {
|
|
configEnvMu.Lock()
|
|
defer configEnvMu.Unlock()
|
|
|
|
resetEnv(t)
|
|
setBaseRequiredEnv(t)
|
|
|
|
cfg, err := LoadFromEnv()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, defaultShutdownTimeout, cfg.ShutdownTimeout)
|
|
assert.Equal(t, defaultLogLevel, cfg.Logging.Level)
|
|
|
|
assert.Equal(t, defaultTestBackendHTTPURL, cfg.Backend.HTTPBaseURL)
|
|
assert.Equal(t, defaultTestBackendGRPCPushURL, cfg.Backend.GRPCPushURL)
|
|
assert.Equal(t, defaultTestBackendClientID, cfg.Backend.GatewayClientID)
|
|
assert.Equal(t, defaultBackendHTTPTimeout, cfg.Backend.HTTPTimeout)
|
|
assert.Equal(t, defaultBackendPushReconnectBaseBackoff, cfg.Backend.PushReconnectBaseBackoff)
|
|
assert.Equal(t, defaultBackendPushReconnectMaxBackoff, cfg.Backend.PushReconnectMaxBackoff)
|
|
|
|
expectedRedis := redisconn.DefaultConfig()
|
|
expectedRedis.MasterAddr = defaultTestRedisMasterAddrValue
|
|
expectedRedis.Password = defaultTestRedisPasswordValue
|
|
assert.Equal(t, expectedRedis, cfg.Redis)
|
|
|
|
assert.Equal(t, defaultReplayRedisKeyPrefix, cfg.ReplayRedis.KeyPrefix)
|
|
assert.Equal(t, defaultReplayRedisReserveTimeout, cfg.ReplayRedis.ReserveTimeout)
|
|
}
|
|
|
|
func TestLoadFromEnvBackendOverrides(t *testing.T) {
|
|
configEnvMu.Lock()
|
|
defer configEnvMu.Unlock()
|
|
|
|
resetEnv(t)
|
|
setBaseRequiredEnv(t)
|
|
|
|
t.Setenv(backendHTTPURLEnvVar, " http://backend.internal:9080/ ")
|
|
t.Setenv(backendGRPCPushURLEnvVar, "backend.internal:9081")
|
|
t.Setenv(backendGatewayClientIDEnvVar, "gw-prod-1")
|
|
t.Setenv(backendHTTPTimeoutEnvVar, "7s")
|
|
t.Setenv(backendPushReconnectBaseBackoffEnvVar, "750ms")
|
|
t.Setenv(backendPushReconnectMaxBackoffEnvVar, "60s")
|
|
|
|
cfg, err := LoadFromEnv()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "http://backend.internal:9080", cfg.Backend.HTTPBaseURL)
|
|
assert.Equal(t, "backend.internal:9081", cfg.Backend.GRPCPushURL)
|
|
assert.Equal(t, "gw-prod-1", cfg.Backend.GatewayClientID)
|
|
assert.Equal(t, 7*time.Second, cfg.Backend.HTTPTimeout)
|
|
assert.Equal(t, 750*time.Millisecond, cfg.Backend.PushReconnectBaseBackoff)
|
|
assert.Equal(t, time.Minute, cfg.Backend.PushReconnectMaxBackoff)
|
|
}
|
|
|
|
func TestLoadFromEnvRejectsMissingBackendValues(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
mutate func(t *testing.T)
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "http url missing",
|
|
mutate: func(t *testing.T) { os.Unsetenv(backendHTTPURLEnvVar) },
|
|
wantErr: backendHTTPURLEnvVar,
|
|
},
|
|
{
|
|
name: "grpc url missing",
|
|
mutate: func(t *testing.T) { os.Unsetenv(backendGRPCPushURLEnvVar) },
|
|
wantErr: backendGRPCPushURLEnvVar,
|
|
},
|
|
{
|
|
name: "gateway client id missing",
|
|
mutate: func(t *testing.T) { os.Unsetenv(backendGatewayClientIDEnvVar) },
|
|
wantErr: backendGatewayClientIDEnvVar,
|
|
},
|
|
{
|
|
name: "http url not absolute",
|
|
mutate: func(t *testing.T) { t.Setenv(backendHTTPURLEnvVar, "/relative") },
|
|
wantErr: backendHTTPURLEnvVar,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
configEnvMu.Lock()
|
|
defer configEnvMu.Unlock()
|
|
|
|
resetEnv(t)
|
|
setBaseRequiredEnv(t)
|
|
tc.mutate(t)
|
|
|
|
_, err := LoadFromEnv()
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), tc.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadFromEnvRejectsInvalidPushBackoff(t *testing.T) {
|
|
configEnvMu.Lock()
|
|
defer configEnvMu.Unlock()
|
|
|
|
resetEnv(t)
|
|
setBaseRequiredEnv(t)
|
|
t.Setenv(backendPushReconnectBaseBackoffEnvVar, "1s")
|
|
t.Setenv(backendPushReconnectMaxBackoffEnvVar, "500ms")
|
|
|
|
_, err := LoadFromEnv()
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), backendPushReconnectMaxBackoffEnvVar)
|
|
}
|
|
|
|
func TestLoadFromEnvAppliesPublicAndAuthGRPCDefaults(t *testing.T) {
|
|
configEnvMu.Lock()
|
|
defer configEnvMu.Unlock()
|
|
|
|
resetEnv(t)
|
|
setBaseRequiredEnv(t)
|
|
|
|
cfg, err := LoadFromEnv()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, defaultPublicHTTPAddr, cfg.PublicHTTP.Addr)
|
|
assert.Equal(t, defaultPublicHTTPReadHeaderTimeout, cfg.PublicHTTP.ReadHeaderTimeout)
|
|
assert.Equal(t, defaultPublicHTTPReadTimeout, cfg.PublicHTTP.ReadTimeout)
|
|
assert.Equal(t, defaultPublicHTTPIdleTimeout, cfg.PublicHTTP.IdleTimeout)
|
|
assert.Equal(t, defaultPublicAuthUpstreamTimeout, cfg.PublicHTTP.AuthUpstreamTimeout)
|
|
|
|
assert.Equal(t, defaultAuthenticatedGRPCAddr, cfg.AuthenticatedGRPC.Addr)
|
|
assert.Equal(t, defaultAuthenticatedGRPCConnectionTimeout, cfg.AuthenticatedGRPC.ConnectionTimeout)
|
|
assert.Equal(t, defaultAuthenticatedGRPCDownstreamTimeout, cfg.AuthenticatedGRPC.DownstreamTimeout)
|
|
assert.Equal(t, defaultAuthenticatedGRPCFreshnessWindow, cfg.AuthenticatedGRPC.FreshnessWindow)
|
|
}
|
|
|
|
// resetEnv clears every env var the gateway config might read so that
|
|
// individual tests can build the exact environment they need without
|
|
// leakage from a previous test.
|
|
func resetEnv(t *testing.T) {
|
|
t.Helper()
|
|
|
|
for _, name := range []string{
|
|
shutdownTimeoutEnvVar,
|
|
logLevelEnvVar,
|
|
publicHTTPAddrEnvVar,
|
|
publicHTTPReadHeaderTimeoutEnvVar,
|
|
publicHTTPReadTimeoutEnvVar,
|
|
publicHTTPIdleTimeoutEnvVar,
|
|
publicAuthUpstreamTimeoutEnvVar,
|
|
backendHTTPURLEnvVar,
|
|
backendGRPCPushURLEnvVar,
|
|
backendGatewayClientIDEnvVar,
|
|
backendHTTPTimeoutEnvVar,
|
|
backendPushReconnectBaseBackoffEnvVar,
|
|
backendPushReconnectMaxBackoffEnvVar,
|
|
adminHTTPAddrEnvVar,
|
|
adminHTTPReadHeaderTimeoutEnvVar,
|
|
adminHTTPReadTimeoutEnvVar,
|
|
adminHTTPIdleTimeoutEnvVar,
|
|
authenticatedGRPCAddrEnvVar,
|
|
authenticatedGRPCConnectionTimeoutEnvVar,
|
|
authenticatedGRPCDownstreamTimeoutEnvVar,
|
|
authenticatedGRPCFreshnessWindowEnvVar,
|
|
gatewayRedisMasterAddrEnvVar,
|
|
gatewayRedisPasswordEnvVar,
|
|
replayRedisKeyPrefixEnvVar,
|
|
replayRedisReserveTimeoutEnvVar,
|
|
responseSignerPrivateKeyPEMPathEnvVar,
|
|
} {
|
|
os.Unsetenv(name)
|
|
}
|
|
}
|
|
|
|
func setBaseRequiredEnv(t *testing.T) {
|
|
t.Helper()
|
|
|
|
t.Setenv(gatewayRedisMasterAddrEnvVar, defaultTestRedisMasterAddrValue)
|
|
t.Setenv(gatewayRedisPasswordEnvVar, defaultTestRedisPasswordValue)
|
|
t.Setenv(backendHTTPURLEnvVar, defaultTestBackendHTTPURL)
|
|
t.Setenv(backendGRPCPushURLEnvVar, defaultTestBackendGRPCPushURL)
|
|
t.Setenv(backendGatewayClientIDEnvVar, defaultTestBackendClientID)
|
|
t.Setenv(responseSignerPrivateKeyPEMPathEnvVar, writeTestResponseSignerPEMFile(t))
|
|
}
|
|
|
|
func writeTestResponseSignerPEMFile(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
seed := sha256.Sum256([]byte("gateway-config-test-response-signer"))
|
|
privateKey := ed25519.NewKeyFromSeed(seed[:])
|
|
|
|
encodedPrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey)
|
|
require.NoError(t, err)
|
|
|
|
path := filepath.Join(t.TempDir(), "response-signer.pem")
|
|
require.NoError(t, os.WriteFile(path, pem.EncodeToMemory(&pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: encodedPrivateKey,
|
|
}), 0o600))
|
|
|
|
return path
|
|
}
|