Files
geniusrun/backend/internal/config/config_test.go
Christophe Vila 00605f950b config: default GarminTokenStoreRoot instead of leaving it empty
Per-user profile isolation namespaces each user's mcp-garmin session
cache under {GarminTokenStoreRoot}/{userID} (api.Server.garminFor), but
Load() left GarminTokenStoreRoot ("" when GARMIN_TOKENSTORE is unset)
with no required-var check and no safe default. In that state
garminFor's `if cfg.TokenStorePath != ""` guard skips the per-user
join entirely, so every user's subprocess would fall back to the same
default token cache -- a cross-user Garmin-session collision risk in
any deployment that forgets to set GARMIN_TOKENSTORE.

Default it to a "garmin-tokenstores" directory next to DBPath when
unset, so every deployment gets per-user isolation automatically,
while GARMIN_TOKENSTORE can still override it explicitly. Log the
derived default. Update the field's doc comment (no longer "if set")
and add config_test.go cases covering the default derivation and the
explicit-override precedence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 18:55:53 +02:00

129 lines
3.6 KiB
Go

package config
import (
"path/filepath"
"testing"
"time"
)
func setRequiredEnv(t *testing.T) {
t.Helper()
t.Setenv("MCP_GARMIN_PYTHON", "/usr/bin/python3")
t.Setenv("MCP_GARMIN_SERVER", "/opt/mcp-garmin/server.py")
t.Setenv("GENIUSRUN_PUBLIC_BASE_URL", "https://geniusrun.example.com")
t.Setenv("GENIUSRUN_OIDC_ISSUER_URL", "https://keycloak.example.com/realms/myrealm")
t.Setenv("GENIUSRUN_OIDC_CLIENT_ID", "geniusrun")
t.Setenv("GENIUSRUN_OIDC_CLIENT_SECRET", "client-secret")
t.Setenv("GENIUSRUN_SESSION_SECRET", "a-session-secret-that-is-at-least-32-bytes-long")
}
func TestLoad_DerivesOIDCSettingsFromPublicBaseURL(t *testing.T) {
setRequiredEnv(t)
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.OIDCRedirectURL != "https://geniusrun.example.com/api/session/callback" {
t.Errorf("OIDCRedirectURL = %q", cfg.OIDCRedirectURL)
}
if !cfg.SessionSecure {
t.Error("SessionSecure = false, want true for an https base URL")
}
if cfg.OIDCRequiredRole != "geniusrun-user" {
t.Errorf("OIDCRequiredRole = %q, want default", cfg.OIDCRequiredRole)
}
if cfg.SessionDuration != 720*time.Hour {
t.Errorf("SessionDuration = %v, want default 720h", cfg.SessionDuration)
}
}
func TestLoad_HTTPBaseURLYieldsInsecureCookies(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GENIUSRUN_PUBLIC_BASE_URL", "http://localhost:8080")
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.SessionSecure {
t.Error("SessionSecure = true, want false for an http base URL")
}
if cfg.OIDCRedirectURL != "http://localhost:8080/api/session/callback" {
t.Errorf("OIDCRedirectURL = %q", cfg.OIDCRedirectURL)
}
}
func TestLoad_MissingRequiredOIDCVars(t *testing.T) {
cases := []string{
"GENIUSRUN_PUBLIC_BASE_URL",
"GENIUSRUN_OIDC_ISSUER_URL",
"GENIUSRUN_OIDC_CLIENT_ID",
"GENIUSRUN_OIDC_CLIENT_SECRET",
}
for _, missing := range cases {
t.Run(missing, func(t *testing.T) {
setRequiredEnv(t)
t.Setenv(missing, "")
if _, err := Load(); err == nil {
t.Fatalf("expected error when %s is unset", missing)
}
})
}
}
func TestLoad_SessionSecretTooShort(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GENIUSRUN_SESSION_SECRET", "too-short")
if _, err := Load(); err == nil {
t.Fatal("expected error for a session secret under 32 characters")
}
}
func TestLoad_GarminTokenStoreRootDefaultsNextToDBPath(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GARMIN_TOKENSTORE", "")
t.Setenv("GENIUSRUN_DB_PATH", "/var/lib/geniusrun/geniusrun.db")
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
want := filepath.Join("/var/lib/geniusrun", "garmin-tokenstores")
if cfg.GarminTokenStoreRoot != want {
t.Errorf("GarminTokenStoreRoot = %q, want %q", cfg.GarminTokenStoreRoot, want)
}
}
func TestLoad_GarminTokenStoreRootExplicitOverridesDefault(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GARMIN_TOKENSTORE", "/custom/tokenstores")
t.Setenv("GENIUSRUN_DB_PATH", "/var/lib/geniusrun/geniusrun.db")
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.GarminTokenStoreRoot != "/custom/tokenstores" {
t.Errorf("GarminTokenStoreRoot = %q, want explicit override to take precedence", cfg.GarminTokenStoreRoot)
}
}
func TestLoad_CustomRoleAndDuration(t *testing.T) {
setRequiredEnv(t)
t.Setenv("GENIUSRUN_OIDC_REQUIRED_ROLE", "admin")
t.Setenv("GENIUSRUN_SESSION_DURATION", "24h")
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.OIDCRequiredRole != "admin" {
t.Errorf("OIDCRequiredRole = %q", cfg.OIDCRequiredRole)
}
if cfg.SessionDuration != 24*time.Hour {
t.Errorf("SessionDuration = %v", cfg.SessionDuration)
}
}