Files

304 lines
12 KiB
Go

package config
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLoadFileUsesYAMLAndEnvironmentOverrides(t *testing.T) {
path := writeSettings(t, `schema_version: 1
server:
listen_address: 127.0.0.1:9080
trusted_proxy_cidrs: [127.0.0.0/8, 10.0.0.0/8]
registration:
attempts: 5
window_seconds: 900
provider:
http_timeout_seconds: 120
max_response_bytes: 1048576
allowed_ports: [80, 443, 8080, 8080]
worker:
lease_seconds: 150
poll_milliseconds: 500
`)
cfg, err := LoadFileFromLookup(path, lookup(map[string]string{"CHORUS_DSN": "test-dsn"}))
if err != nil {
t.Fatal(err)
}
if cfg.ProviderHTTPTimeout != 120*time.Second || cfg.ProviderMaxResponseBytes != 1048576 || cfg.WorkerLeaseDuration != 150*time.Second || cfg.WorkerPollInterval != 500*time.Millisecond {
t.Fatalf("YAML settings were not loaded: %#v", cfg)
}
if cfg.ListenAddress != "127.0.0.1:9080" {
t.Fatalf("listen address = %q", cfg.ListenAddress)
}
if cfg.RegistrationAttempts != 5 || cfg.RegistrationWindow != 15*time.Minute || len(cfg.TrustedProxyCIDRs) != 2 {
t.Fatalf("registration settings were not loaded: %#v", cfg)
}
if got := cfg.ProviderAllowedPorts; len(got) != 3 || got[2] != 8080 {
t.Fatalf("provider ports = %v", got)
}
overrides := map[string]string{
"CHORUS_DSN": "test-dsn",
"CHORUS_LISTEN_ADDRESS": "127.0.0.1:9081",
"CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS": "60",
"CHORUS_PROVIDER_MAX_RESPONSE_BYTES": "2097152",
"CHORUS_PROVIDER_ALLOWED_PORTS": "80,443,8443",
"CHORUS_WORKER_LEASE_SECONDS": "90",
"CHORUS_WORKER_POLL_MILLISECONDS": "100",
"CHORUS_TRUSTED_PROXY_CIDRS": "192.0.2.0/24",
"CHORUS_REGISTRATION_ATTEMPTS": "7",
"CHORUS_REGISTRATION_WINDOW_SECONDS": "600",
}
cfg, err = LoadFileFromLookup(path, lookup(overrides))
if err != nil {
t.Fatal(err)
}
if cfg.ProviderHTTPTimeout != 60*time.Second || cfg.ProviderMaxResponseBytes != 2097152 || cfg.WorkerLeaseDuration != 90*time.Second || cfg.WorkerPollInterval != 100*time.Millisecond || cfg.ProviderAllowedPorts[2] != 8443 {
t.Fatalf("environment overrides were not applied: %#v", cfg)
}
if cfg.ListenAddress != "127.0.0.1:9081" {
t.Fatalf("environment listen address override = %q", cfg.ListenAddress)
}
if cfg.RegistrationAttempts != 7 || cfg.RegistrationWindow != 10*time.Minute || len(cfg.TrustedProxyCIDRs) != 1 {
t.Fatalf("registration overrides were not applied: %#v", cfg)
}
}
func TestLoadFileRejectsInvalidSettings(t *testing.T) {
tests := map[string]string{
"unknown field": `schema_version: 1
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
worker: {lease_seconds: 60, poll_milliseconds: 250}
unknown: true
`,
"schema": `schema_version: 2
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
worker: {lease_seconds: 60, poll_milliseconds: 250}
`,
"empty ports": `schema_version: 1
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: []}
worker: {lease_seconds: 60, poll_milliseconds: 250}
`,
"unsafe lease": `schema_version: 1
provider: {http_timeout_seconds: 60, max_response_bytes: 1024, allowed_ports: [80]}
worker: {lease_seconds: 65, poll_milliseconds: 250}
`,
"empty listen address": `schema_version: 1
server: {listen_address: ""}
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
worker: {lease_seconds: 60, poll_milliseconds: 250}
`,
"invalid listen address": `schema_version: 1
server: {listen_address: "http://127.0.0.1:8080"}
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
worker: {lease_seconds: 60, poll_milliseconds: 250}
`,
"partial registration": `schema_version: 1
registration: {attempts: 5}
provider: {http_timeout_seconds: 45, max_response_bytes: 1024, allowed_ports: [80]}
worker: {lease_seconds: 60, poll_milliseconds: 250}
`,
}
for name, body := range tests {
t.Run(name, func(t *testing.T) {
if _, err := LoadFileFromLookup(writeSettings(t, body), lookup(map[string]string{"CHORUS_DSN": "test-dsn"})); err == nil {
t.Fatal("invalid settings were accepted")
}
})
}
}
func TestLoadRejectsInvalidListenAddressEnvironmentOverride(t *testing.T) {
for _, value := range []string{"127.0.0.1", ":8080", "127.0.0.1:0", "127.0.0.1:65536"} {
_, err := LoadFromLookup(lookup(map[string]string{
"CHORUS_DSN": "test-dsn", "CHORUS_LISTEN_ADDRESS": value,
}))
if err == nil || !strings.Contains(err.Error(), "listen address") {
t.Fatalf("invalid listen address %q was accepted: %v", value, err)
}
}
}
func writeSettings(t *testing.T, body string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "settings.yml")
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
return path
}
func lookup(values map[string]string) func(string) (string, bool) {
return func(key string) (string, bool) {
value, ok := values[key]
return value, ok
}
}
func TestLoadDevelopmentRequiresDSNWithoutLeakingIt(t *testing.T) {
secret := "user:secret@tcp(localhost:3308)/chorus"
cfg, err := LoadFromLookup(lookup(map[string]string{
"CHORUS_ENV": "development",
"CHORUS_DSN": secret,
}))
if err != nil {
t.Fatalf("LoadFromLookup() error = %v", err)
}
if cfg.DBDSN != secret || cfg.ListenAddress != "127.0.0.1:8080" {
t.Fatalf("unexpected config: %#v", cfg)
}
if cfg.WorkerLeaseDuration != 60*time.Second || cfg.WorkerPollInterval != 250*time.Millisecond || cfg.ProviderHTTPTimeout != 45*time.Second || cfg.ProviderMaxResponseBytes != 32<<20 || cfg.UserRateLimitCapacity != 60 || cfg.APIKeyRateLimitCapacity != 120 || cfg.ProviderRateLimitCapacity != 60 {
t.Fatalf("unexpected worker defaults: %#v", cfg)
}
if len(cfg.ProviderAllowedPorts) != 2 || cfg.ProviderAllowedPorts[0] != 80 || cfg.ProviderAllowedPorts[1] != 443 {
t.Fatalf("unexpected provider port defaults: %v", cfg.ProviderAllowedPorts)
}
}
func TestLoadProviderAllowedPorts(t *testing.T) {
cfg, err := LoadFromLookup(lookup(map[string]string{
"CHORUS_DSN": "test-dsn", "CHORUS_PROVIDER_ALLOWED_PORTS": "80, 443,8080,80",
}))
if err != nil {
t.Fatal(err)
}
want := []uint16{80, 443, 8080}
if len(cfg.ProviderAllowedPorts) != len(want) {
t.Fatalf("provider ports = %v, want %v", cfg.ProviderAllowedPorts, want)
}
for index := range want {
if cfg.ProviderAllowedPorts[index] != want[index] {
t.Fatalf("provider ports = %v, want %v", cfg.ProviderAllowedPorts, want)
}
}
}
func TestLoadRejectsInvalidProviderAllowedPorts(t *testing.T) {
for _, value := range []string{"0", "65536", "80,,443", "https", "-1"} {
_, err := LoadFromLookup(lookup(map[string]string{
"CHORUS_DSN": "test-dsn", "CHORUS_PROVIDER_ALLOWED_PORTS": value,
}))
if err == nil || !strings.Contains(err.Error(), "CHORUS_PROVIDER_ALLOWED_PORTS") {
t.Fatalf("invalid provider ports %q were accepted: %v", value, err)
}
}
}
func TestLoadProductionFailsClosed(t *testing.T) {
_, err := LoadFromLookup(lookup(map[string]string{
"CHORUS_ENV": "production",
"CHORUS_DSN": "sensitive-dsn",
}))
if err == nil {
t.Fatal("LoadFromLookup() expected an error")
}
for _, name := range []string{"CHORUS_SESSION_KEY", "CHORUS_STORAGE_ROOT"} {
if !strings.Contains(err.Error(), name) {
t.Errorf("error %q does not identify %s", err, name)
}
}
if strings.Contains(err.Error(), "sensitive-dsn") {
t.Fatalf("error leaked DSN: %v", err)
}
}
func TestLoadRejectsUnknownEnvironment(t *testing.T) {
_, err := LoadFromLookup(lookup(map[string]string{"CHORUS_ENV": "prod"}))
if err == nil {
t.Fatal("LoadFromLookup() expected an error")
}
}
func TestLoadProductionPortalLimits(t *testing.T) {
values := completeProductionValues()
cfg, err := LoadFromLookup(lookup(values))
if err != nil {
t.Fatal(err)
}
if err := cfg.ValidateProduction(); err != nil {
t.Fatal(err)
}
values["CHORUS_MAX_UPLOAD_BYTES"] = "100"
if _, err := LoadFromLookup(lookup(values)); err == nil {
t.Fatal("inconsistent upload limits accepted")
}
}
func completeProductionValues() map[string]string {
return map[string]string{
"CHORUS_ENV": "production", "CHORUS_DSN": "sensitive-dsn",
"CHORUS_SESSION_KEY": strings.Repeat("s", 32), "CHORUS_STORAGE_ROOT": "/var/lib/chorus",
"CHORUS_SESSION_TTL_MINUTES": "480", "CHORUS_LOGIN_ATTEMPTS": "5", "CHORUS_LOGIN_WINDOW_SECONDS": "60",
"CHORUS_MAX_PROMPT_BYTES": "8000", "CHORUS_MAX_IMAGES": "8", "CHORUS_MAX_IMAGE_BYTES": "10485760",
"CHORUS_MAX_UPLOAD_BYTES": "33554432", "CHORUS_MAX_IMAGE_PIXELS": "40000000", "CHORUS_HISTORY_LIMIT": "50",
"CHORUS_WORKER_LEASE_SECONDS": "60", "CHORUS_WORKER_POLL_MILLISECONDS": "250",
"CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS": "45", "CHORUS_PROVIDER_MAX_RESPONSE_BYTES": "33554432",
"CHORUS_USER_RATE_LIMIT_CAPACITY": "60", "CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS": "60",
"CHORUS_API_KEY_RATE_LIMIT_CAPACITY": "120", "CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS": "60",
"CHORUS_PROVIDER_RATE_LIMIT_CAPACITY": "60", "CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS": "60",
"CHORUS_REGISTRATION_ATTEMPTS": "5", "CHORUS_REGISTRATION_WINDOW_SECONDS": "900",
}
}
func TestProductionRequiresEveryRateLimitValue(t *testing.T) {
for _, name := range []string{
"CHORUS_USER_RATE_LIMIT_CAPACITY", "CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS",
"CHORUS_API_KEY_RATE_LIMIT_CAPACITY", "CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS",
"CHORUS_PROVIDER_RATE_LIMIT_CAPACITY", "CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS",
"CHORUS_REGISTRATION_ATTEMPTS", "CHORUS_REGISTRATION_WINDOW_SECONDS",
} {
values := completeProductionValues()
delete(values, name)
if _, err := LoadFromLookup(lookup(values)); err == nil || !strings.Contains(err.Error(), name) {
t.Fatalf("missing %s was accepted: %v", name, err)
}
}
}
func TestLoadRejectsInvalidRateLimits(t *testing.T) {
for _, name := range []string{
"CHORUS_USER_RATE_LIMIT_CAPACITY", "CHORUS_USER_RATE_LIMIT_WINDOW_SECONDS",
"CHORUS_API_KEY_RATE_LIMIT_CAPACITY", "CHORUS_API_KEY_RATE_LIMIT_WINDOW_SECONDS",
"CHORUS_PROVIDER_RATE_LIMIT_CAPACITY", "CHORUS_PROVIDER_RATE_LIMIT_WINDOW_SECONDS",
"CHORUS_REGISTRATION_ATTEMPTS", "CHORUS_REGISTRATION_WINDOW_SECONDS",
} {
values := map[string]string{"CHORUS_DSN": "test-dsn", name: "0"}
if _, err := LoadFromLookup(lookup(values)); err == nil || !strings.Contains(err.Error(), name) {
t.Fatalf("invalid %s was accepted: %v", name, err)
}
}
}
func TestLoadTrustedProxyCIDRs(t *testing.T) {
cfg, err := LoadFromLookup(lookup(map[string]string{"CHORUS_DSN": "test-dsn", "CHORUS_TRUSTED_PROXY_CIDRS": "127.0.0.0/8, 10.0.0.0/8,127.0.0.0/8"}))
if err != nil || len(cfg.TrustedProxyCIDRs) != 2 {
t.Fatalf("trusted proxy CIDRs = %v, %v", cfg.TrustedProxyCIDRs, err)
}
if _, err := LoadFromLookup(lookup(map[string]string{"CHORUS_DSN": "test-dsn", "CHORUS_TRUSTED_PROXY_CIDRS": "not-a-cidr"})); err == nil {
t.Fatal("invalid trusted proxy CIDR accepted")
}
}
func TestLoadRejectsUnsafeWorkerLease(t *testing.T) {
_, err := LoadFromLookup(lookup(map[string]string{
"CHORUS_DSN": "test-dsn", "CHORUS_WORKER_LEASE_SECONDS": "50", "CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS": "45",
}))
if err == nil || !strings.Contains(err.Error(), "worker lease") {
t.Fatalf("expected worker lease error, got %v", err)
}
}
func TestWorkerCanOnlyBeDisabledInTest(t *testing.T) {
if _, err := LoadFromLookup(lookup(map[string]string{"CHORUS_DSN": "test-dsn", "CHORUS_TEST_DISABLE_WORKER": "true"})); err == nil {
t.Fatal("development accepted disabled worker")
}
cfg, err := LoadFromLookup(lookup(map[string]string{"CHORUS_ENV": "test", "CHORUS_DSN": "test-dsn", "CHORUS_TEST_DISABLE_WORKER": "true"}))
if err != nil || !cfg.TestDisableWorker {
t.Fatalf("test worker fixture was rejected: %#v %v", cfg, err)
}
}