package api import ( "os" "path/filepath" "runtime" "strings" "testing" "time" ) func writePrivateConfig(t *testing.T, dir, content string) { t.Helper() path := filepath.Join(dir, ConfigFileName) if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatal(err) } } func TestLoadConfigFromBinaryDirFirstLaunchCreatesExampleOnly(t *testing.T) { dir := t.TempDir() cfg := DefaultConfig() if err := LoadConfigFromBinaryDir(dir, &cfg); err != nil { t.Fatal(err) } if _, err := os.Stat(filepath.Join(dir, ConfigExampleName)); err != nil { t.Fatalf("config.toml.example was not created: %v", err) } if _, err := os.Stat(filepath.Join(dir, ".env")); !os.IsNotExist(err) { t.Fatalf("unexpected .env after first launch, err=%v", err) } if cfg.Listen != DefaultListen || cfg.RPCTimeout != 30*time.Second { t.Fatalf("first launch changed defaults: %+v", cfg) } } func TestCheckedInBatAPIConfigExampleMatchesRuntimeTemplate(t *testing.T) { _, sourceFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller failed") } path := filepath.Join(filepath.Dir(sourceFile), "..", "..", "cmd", "bat-api", ConfigExampleName) data, err := os.ReadFile(path) if err != nil { t.Fatal(err) } if string(data) != ConfigTOMLTemplate { t.Fatal("cmd/bat-api/config.toml.example differs from runtime template") } } func TestLoadConfigFromBinaryDirDoesNotReadDotEnv(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, ".env"), []byte("BAT_API_LISTEN=':19999'\n"), 0o600); err != nil { t.Fatal(err) } cfg := DefaultConfig() if err := LoadConfigFromBinaryDir(dir, &cfg); err != nil { t.Fatal(err) } if cfg.Listen != DefaultListen { t.Fatalf("dotenv value was applied: %q", cfg.Listen) } if _, err := os.Stat(filepath.Join(dir, ConfigExampleName)); err != nil { t.Fatalf("config.toml.example was not created: %v", err) } } func TestLoadConfigFromBinaryDirAppliesAPISectionOnly(t *testing.T) { dir := t.TempDir() writePrivateConfig(t, dir, ` [runtime] state_dir = '/rust-state' this_key_belongs_to_rust = 'ignored-by-go' [translation.worker] provider = 'mock' [api] listen = '127.0.0.1:19080' public_base_url = 'https://assets.example.test' state_dir = '/api-state' socket_path = '/api-state/bat.sock' resource_root = '/srv/releases/current' server_info_file = '/srv/server-info.json' require_indexed = false verify_size = false rpc_timeout = '2s' refresh_interval = '0s' auth_token = 'secret' auth_query_param = 'token' auth_exempt_paths = ['/healthz', '/readyz'] trust_proxy_headers = true access_log = true rate_limit_rps = 12.5 rate_limit_burst = 30 max_resource_page_limit = 250 database_url = 'postgres://example' database_password = 'db-secret' redis_url = 'redis://example' redis_password = 'redis-secret' `) cfg := DefaultConfig() if err := LoadConfigFromBinaryDir(dir, &cfg); err != nil { t.Fatal(err) } if cfg.Listen != "127.0.0.1:19080" || cfg.PublicBaseURL != "https://assets.example.test" || cfg.StateDir != "/api-state" || cfg.SocketPath != "/api-state/bat.sock" || cfg.ResourceRoot != "/srv/releases/current" || cfg.ServerInfoFile != "/srv/server-info.json" || cfg.RequireIndexed || cfg.VerifySize || cfg.RPCTimeout != 2*time.Second || cfg.RefreshInterval != 0 || cfg.AuthToken != "secret" || cfg.AuthQueryParam != "token" || cfg.TrustProxyHeaders != true || cfg.AccessLog != true || cfg.RateLimitRPS != 12.5 || cfg.RateLimitBurst != 30 || cfg.MaxResourcePageLimit != 250 || cfg.DatabaseURL != "postgres://example" || cfg.DatabasePassword != "db-secret" || cfg.RedisURL != "redis://example" || cfg.RedisPassword != "redis-secret" { t.Fatalf("unexpected config: %+v", cfg) } if strings.Join(cfg.AuthExemptPaths, ",") != "/healthz,/readyz" { t.Fatalf("auth exempt paths=%v", cfg.AuthExemptPaths) } } func TestLoadConfigFromBinaryDirRejectsUnknownAPIKey(t *testing.T) { dir := t.TempDir() writePrivateConfig(t, dir, "[api]\nunknown_key = 'value'\n") cfg := DefaultConfig() err := LoadConfigFromBinaryDir(dir, &cfg) if err == nil || !strings.Contains(err.Error(), "unsupported [api] key") { t.Fatalf("unexpected error: %v", err) } } func TestConfigPrecedenceEnvironmentOverConfig(t *testing.T) { dir := t.TempDir() writePrivateConfig(t, dir, "[api]\nlisten = ':19080'\nauth_token = 'config-token'\n") t.Setenv("BAT_API_LISTEN", ":19081") t.Setenv("BAT_API_AUTH_TOKEN", "env-token") cfg := DefaultConfig() if err := LoadConfigFromBinaryDir(dir, &cfg); err != nil { t.Fatal(err) } ApplyEnv(&cfg) if cfg.Listen != ":19081" || cfg.AuthToken != "env-token" { t.Fatalf("environment did not override config: %+v", cfg) } }