Pulse/internal/cloudcp/config_test.go

388 lines
11 KiB
Go

package cloudcp
import (
"strings"
"testing"
)
func setRequiredCPEnv(t *testing.T) {
t.Helper()
t.Setenv("CP_ADMIN_KEY", "test-key")
t.Setenv("CP_BASE_URL", "https://cloud.example.com")
t.Setenv("STRIPE_WEBHOOK_SECRET", "whsec_test")
t.Setenv("CP_ENV", "development")
t.Setenv("CP_REQUIRE_EMAIL_PROVIDER", "false")
}
func setTrialSigningEnv(t *testing.T) {
t.Helper()
t.Setenv("CP_TRIAL_ACTIVATION_PRIVATE_KEY", "A8medgdNdm12GXfTXWo6+TMZ2BeHPCLg2kd0znn6ZUk=")
}
func TestLoadConfig_MissingRequired(t *testing.T) {
// Clear relevant env vars
for _, key := range []string{
"CP_ADMIN_KEY", "CP_BASE_URL", "STRIPE_WEBHOOK_SECRET",
"CP_DATA_DIR", "CP_BIND_ADDRESS", "CP_PORT",
} {
t.Setenv(key, "")
}
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for missing required vars")
}
}
func TestLoadConfig_AllRequired(t *testing.T) {
setRequiredCPEnv(t)
// Clear optional vars to use defaults
for _, key := range []string{
"CP_DATA_DIR", "CP_BIND_ADDRESS", "CP_PORT",
"CP_PULSE_IMAGE", "CP_DOCKER_NETWORK",
"CP_TENANT_MEMORY_LIMIT", "CP_TENANT_CPU_SHARES",
"STRIPE_API_KEY",
} {
t.Setenv(key, "")
}
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.AdminKey != "test-key" {
t.Errorf("AdminKey = %q, want %q", cfg.AdminKey, "test-key")
}
if cfg.BaseURL != "https://cloud.example.com" {
t.Errorf("BaseURL = %q", cfg.BaseURL)
}
if cfg.Port != 8443 {
t.Errorf("Port = %d, want 8443", cfg.Port)
}
if cfg.DataDir != "/data" {
t.Errorf("DataDir = %q, want /data", cfg.DataDir)
}
if cfg.BindAddress != "0.0.0.0" {
t.Errorf("BindAddress = %q, want 0.0.0.0", cfg.BindAddress)
}
}
func TestLoadConfig_CustomValues(t *testing.T) {
t.Setenv("CP_ADMIN_KEY", "key")
t.Setenv("CP_BASE_URL", "https://test.example.com")
t.Setenv("STRIPE_WEBHOOK_SECRET", "whsec_x")
t.Setenv("CP_ENV", "development")
t.Setenv("CP_REQUIRE_EMAIL_PROVIDER", "false")
t.Setenv("CP_PORT", "9000")
t.Setenv("CP_DATA_DIR", "/custom/data")
t.Setenv("CP_BIND_ADDRESS", "127.0.0.1")
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.Port != 9000 {
t.Errorf("Port = %d, want 9000", cfg.Port)
}
if cfg.DataDir != "/custom/data" {
t.Errorf("DataDir = %q", cfg.DataDir)
}
if cfg.BindAddress != "127.0.0.1" {
t.Errorf("BindAddress = %q", cfg.BindAddress)
}
}
func TestLoadConfig_DerivesTrialActivationPublicKey(t *testing.T) {
setRequiredCPEnv(t)
setTrialSigningEnv(t)
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if strings.TrimSpace(cfg.TrialActivationPublicKey) == "" {
t.Fatal("TrialActivationPublicKey = empty, want derived public key")
}
}
func TestLoadConfig_TrustedProxyCIDRs(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_TRUSTED_PROXY_CIDRS", "172.18.0.0/16, 127.0.0.1/32")
t.Setenv("PULSE_TRUSTED_PROXY_CIDRS", "192.168.0.0/16")
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
want := []string{"172.18.0.0/16", "127.0.0.1/32", "192.168.0.0/16"}
if len(cfg.TrustedProxyCIDRs) != len(want) {
t.Fatalf("len(TrustedProxyCIDRs) = %d, want %d (%v)", len(cfg.TrustedProxyCIDRs), len(want), cfg.TrustedProxyCIDRs)
}
for i, expected := range want {
if cfg.TrustedProxyCIDRs[i] != expected {
t.Fatalf("TrustedProxyCIDRs[%d] = %q, want %q", i, cfg.TrustedProxyCIDRs[i], expected)
}
}
}
func TestLoadConfig_InvalidPortParse(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_PORT", "not-a-number")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for invalid CP_PORT")
}
if !strings.Contains(err.Error(), "CP_PORT must be a valid integer") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_InvalidPortRange(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_PORT", "0")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for invalid CP_PORT range")
}
if !strings.Contains(err.Error(), "CP_PORT must be between 1 and 65535") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_InvalidTenantLimits(t *testing.T) {
t.Run("memory limit", func(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_TENANT_MEMORY_LIMIT", "0")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for invalid CP_TENANT_MEMORY_LIMIT")
}
if !strings.Contains(err.Error(), "CP_TENANT_MEMORY_LIMIT must be greater than 0") {
t.Fatalf("unexpected error: %v", err)
}
})
t.Run("cpu shares", func(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_TENANT_CPU_SHARES", "-10")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for invalid CP_TENANT_CPU_SHARES")
}
if !strings.Contains(err.Error(), "CP_TENANT_CPU_SHARES must be greater than 0") {
t.Fatalf("unexpected error: %v", err)
}
})
}
func TestLoadConfig_InvalidRateLimits(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_RL_ADMIN_PER_MINUTE", "0")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for invalid CP_RL_ADMIN_PER_MINUTE")
}
if !strings.Contains(err.Error(), "CP_RL_ADMIN_PER_MINUTE must be greater than 0") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_InvalidBaseURL(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_BASE_URL", "cloud.example.com")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for invalid CP_BASE_URL")
}
if !strings.Contains(err.Error(), "CP_BASE_URL must use http or https scheme") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_EmailProviderRequired(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_REQUIRE_EMAIL_PROVIDER", "true")
t.Setenv("RESEND_API_KEY", "")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error when email provider is required but RESEND_API_KEY is missing")
}
if !strings.Contains(err.Error(), "RESEND_API_KEY is required") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_RejectsNonCloudTrialSignupPriceID(t *testing.T) {
setRequiredCPEnv(t)
setTrialSigningEnv(t)
t.Setenv("STRIPE_API_KEY", "sk_test_123")
t.Setenv("CP_TRIAL_SIGNUP_PRICE_ID", "price_1T47OVBrHBocJIGHg4sMHMV7")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error for non-cloud CP_TRIAL_SIGNUP_PRICE_ID")
}
if !strings.Contains(err.Error(), "CP_TRIAL_SIGNUP_PRICE_ID must map to the canonical cloud_starter Stripe price") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_AcceptsCanonicalCloudSignupPriceIDs(t *testing.T) {
setRequiredCPEnv(t)
setTrialSigningEnv(t)
t.Setenv("STRIPE_API_KEY", "sk_test_123")
t.Setenv("CP_PUBLIC_CLOUD_SIGNUP_ENABLED", "true")
t.Setenv("CP_TRIAL_SIGNUP_PRICE_ID", "price_1T5kflBrHBocJIGHUqPv1dzV")
t.Setenv("CP_CLOUD_POWER_PRICE_ID", "price_1T5kg2BrHBocJIGHmkoF0zXY")
t.Setenv("CP_CLOUD_MAX_PRICE_ID", "price_1T5kg4BrHBocJIGHHa8Ecqho")
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.TrialSignupPriceID != "price_1T5kflBrHBocJIGHUqPv1dzV" {
t.Fatalf("TrialSignupPriceID=%q want canonical cloud starter price", cfg.TrialSignupPriceID)
}
}
func TestLoadConfig_AllowsMissingTrialSignupPriceWhenPublicCloudSignupDisabled(t *testing.T) {
setRequiredCPEnv(t)
setTrialSigningEnv(t)
t.Setenv("STRIPE_API_KEY", "sk_test_123")
t.Setenv("CP_TRIAL_SIGNUP_PRICE_ID", "")
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.PublicCloudSignupEnabled {
t.Fatal("PublicCloudSignupEnabled = true, want false by default")
}
}
func TestLoadConfig_RequiresTrialSignupPriceWhenPublicCloudSignupEnabled(t *testing.T) {
setRequiredCPEnv(t)
setTrialSigningEnv(t)
t.Setenv("STRIPE_API_KEY", "sk_test_123")
t.Setenv("CP_PUBLIC_CLOUD_SIGNUP_ENABLED", "true")
t.Setenv("CP_TRIAL_SIGNUP_PRICE_ID", "")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error when public cloud signup is enabled but CP_TRIAL_SIGNUP_PRICE_ID is missing")
}
if !strings.Contains(err.Error(), "CP_TRIAL_SIGNUP_PRICE_ID is required") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_RequiresLiveStripeKeyInProduction(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_ENV", "production")
t.Setenv("CP_REQUIRE_EMAIL_PROVIDER", "false")
t.Setenv("STRIPE_API_KEY", "sk_test_123")
t.Setenv("CP_TRIAL_SIGNUP_PRICE_ID", "price_1T5kflBrHBocJIGHUqPv1dzV")
setTrialSigningEnv(t)
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error when CP_ENV=production and STRIPE_API_KEY is test mode")
}
if !strings.Contains(err.Error(), "must be a live key") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_RequiresTestStripeKeyInStaging(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_ENV", "staging")
t.Setenv("CP_REQUIRE_EMAIL_PROVIDER", "false")
t.Setenv("STRIPE_API_KEY", "sk_live_123")
t.Setenv("CP_TRIAL_SIGNUP_PRICE_ID", "price_1T5kflBrHBocJIGHUqPv1dzV")
setTrialSigningEnv(t)
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error when CP_ENV=staging and STRIPE_API_KEY is live mode")
}
if !strings.Contains(err.Error(), "must be a test key") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestLoadConfig_RejectsDockerlessProvisioningInProduction(t *testing.T) {
setRequiredCPEnv(t)
t.Setenv("CP_ENV", "production")
t.Setenv("CP_ALLOW_DOCKERLESS_PROVISIONING", "true")
t.Setenv("CP_REQUIRE_EMAIL_PROVIDER", "true")
t.Setenv("RESEND_API_KEY", "re_test_key")
_, err := LoadConfig()
if err == nil {
t.Fatal("expected error when dockerless provisioning is enabled in production")
}
if !strings.Contains(err.Error(), "CP_ALLOW_DOCKERLESS_PROVISIONING must be false in production") {
t.Fatalf("unexpected error: %v", err)
}
}
func TestTenantsDir(t *testing.T) {
cfg := &CPConfig{DataDir: "/data"}
if got := cfg.TenantsDir(); got != "/data/tenants" {
t.Errorf("TenantsDir = %q, want /data/tenants", got)
}
}
func TestControlPlaneDir(t *testing.T) {
cfg := &CPConfig{DataDir: "/data"}
if got := cfg.ControlPlaneDir(); got != "/data/control-plane" {
t.Errorf("ControlPlaneDir = %q, want /data/control-plane", got)
}
}
func TestEnvOrDefaultInt64(t *testing.T) {
const key = "TEST_CP_ENV_OR_DEFAULT_INT64"
t.Setenv(key, "")
if got, _ := envOrDefaultInt64(key, 99); got != 99 {
t.Fatalf("envOrDefaultInt64 unset = %d, want 99", got)
}
t.Setenv(key, " 1234 ")
if got, _ := envOrDefaultInt64(key, 99); got != 1234 {
t.Fatalf("envOrDefaultInt64 valid = %d, want 1234", got)
}
t.Setenv(key, "not-an-int")
if got, _ := envOrDefaultInt64(key, 99); got != 99 {
t.Fatalf("envOrDefaultInt64 invalid = %d, want 99", got)
}
}
func TestStripeSecretKeyMode(t *testing.T) {
tests := []struct {
name string
key string
want string
}{
{name: "test", key: "sk_test_123", want: "test"},
{name: "live", key: "sk_live_123", want: "live"},
{name: "unknown", key: "abc", want: "unknown"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := stripeSecretKeyMode(tt.key); got != tt.want {
t.Fatalf("stripeSecretKeyMode(%q) = %q, want %q", tt.key, got, tt.want)
}
})
}
}